mirror of
https://github.com/pi-hole/FTL.git
synced 2024-10-26 16:52:18 +02:00
Merge branch 'development' into update/dnsmasq
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "FTL x86_64 Build Env",
|
||||
"image": "ghcr.io/pi-hole/ftl-build:v2.5",
|
||||
"image": "ghcr.io/pi-hole/ftl-build:nightly",
|
||||
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
@@ -8,13 +8,15 @@
|
||||
"jetmartin.bats",
|
||||
"ms-vscode.cpptools",
|
||||
"ms-vscode.cmake-tools",
|
||||
"eamodio.gitlens"
|
||||
"eamodio.gitlens",
|
||||
"github.copilot",
|
||||
"ms-python.python"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mounts": [
|
||||
"type=bind,source=/home/${localEnv:USER}/.ssh,target=/root/.ssh,readonly",
|
||||
"type=bind,source=/var/www/html,target=/var/www/html,readonly"
|
||||
"type=bind,source=/var/www/html/admin,target=/var/www/html/admin,readonly"
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
@@ -7,3 +7,9 @@ requestor
|
||||
requestors
|
||||
punycode
|
||||
bitap
|
||||
mmapped
|
||||
dnsmasq
|
||||
iif
|
||||
prefered
|
||||
padd
|
||||
rabit
|
||||
|
||||
@@ -1 +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).
|
||||
|
||||
+16
-10
@@ -1,25 +1,31 @@
|
||||
FROM ghcr.io/pi-hole/ftl-build:v2.5 AS builder
|
||||
FROM ghcr.io/pi-hole/ftl-build:v2.8 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . /app
|
||||
|
||||
ARG CI_ARCH="linux/amd64"
|
||||
ENV CI_ARCH ${CI_ARCH}
|
||||
ENV CI_ARCH=${CI_ARCH}
|
||||
ARG GIT_BRANCH="test"
|
||||
ENV GIT_BRANCH ${GIT_BRANCH}
|
||||
ENV GIT_BRANCH=${GIT_BRANCH}
|
||||
ARG GIT_TAG="test"
|
||||
ENV GIT_TAG ${GIT_TAG}
|
||||
ENV GIT_TAG=${GIT_TAG}
|
||||
ARG BUILD_OPTS=""
|
||||
ENV BUILD_OPTS=${BUILD_OPTS}
|
||||
|
||||
# Setting TERM is needed for pretty output in BATS tests
|
||||
ENV TERM=xterm
|
||||
|
||||
# Monkeypatch BATS to remove duplicate output of starting and finished test
|
||||
# BATS uses ANSI escape codes to overwrite the line after the test has finished
|
||||
# This is not supported by Github Actions as it does not provide a TTY to the docker build container
|
||||
RUN sed -i '/buffer_with_truncation /d' /bats-core/libexec/bats-core/bats-format-pretty
|
||||
|
||||
# Build FTL
|
||||
# Remove possible old build files
|
||||
RUN rm -rf cmake && \
|
||||
# Build FTL
|
||||
bash build.sh "-DSTATIC=${STATIC}" && \
|
||||
# Run binary architecture tests
|
||||
bash test/arch_test.sh && \
|
||||
# Run full test suite
|
||||
bash test/run.sh && \
|
||||
# Build and test FTL
|
||||
bash build.sh "-DSTATIC=${STATIC}" test ${BUILD_OPTS} && \
|
||||
# Move FTL binary to root directory
|
||||
cd / &&\
|
||||
mv /app/pihole-FTL . && \
|
||||
|
||||
@@ -5,6 +5,9 @@ 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
|
||||
@@ -76,6 +79,7 @@ runs:
|
||||
"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
|
||||
@@ -98,22 +102,34 @@ runs:
|
||||
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'
|
||||
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'
|
||||
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
|
||||
if: inputs.event_name != 'pull_request'
|
||||
# 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
|
||||
|
||||
@@ -17,23 +17,3 @@ updates:
|
||||
github_action-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
# As above, but for development-v6
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
time: "10:00"
|
||||
open-pull-requests-limit: 10
|
||||
target-branch: development-v6
|
||||
reviewers:
|
||||
- "pi-hole/ftl-maintainers"
|
||||
pull-request-branch-name:
|
||||
# Separate sections of the branch name with a hyphen
|
||||
separator: "-"
|
||||
groups:
|
||||
github_action-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
name: Build, Test, Deploy
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -26,7 +31,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout code
|
||||
uses: actions/checkout@v4.1.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
-
|
||||
name: "Calculate required variables"
|
||||
id: variables
|
||||
@@ -59,10 +64,16 @@ jobs:
|
||||
include:
|
||||
- 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.platform }}
|
||||
GIT_BRANCH: ${{ needs.smoke-tests.outputs.GIT_BRANCH }}
|
||||
@@ -70,13 +81,14 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout code
|
||||
uses: actions/checkout@v4.1.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
-
|
||||
name: Build and test and deploy FTL
|
||||
uses: ./.github/actions/build-and-test
|
||||
with:
|
||||
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 }}
|
||||
@@ -108,7 +120,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout code
|
||||
uses: actions/checkout@v4.1.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
-
|
||||
name: Build and test and deploy FTL
|
||||
uses: ./.github/actions/build-and-test
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL Advanced"
|
||||
env:
|
||||
nettleversion: 3.9.1
|
||||
mbedtlsversion: 3.6.1
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master", "development", "special/CI*", "update/dnsmasq" ]
|
||||
pull_request:
|
||||
branches: [ "master", "development", "special/CI*", "update/dnsmasq" ]
|
||||
schedule:
|
||||
- cron: '45 10 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: c-cpp
|
||||
build-mode: manual
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libidn2-0 libidn2-0-dev libunistring-dev
|
||||
|
||||
- name: Install nettle
|
||||
run: |
|
||||
curl -sSL https://ftl.pi-hole.net/libraries/nettle-${nettleversion}.tar.gz | tar -xz
|
||||
cd nettle-${nettleversion}
|
||||
./configure --enable-static --disable-shared --disable-openssl --disable-mini-gmp -disable-gcov --disable-documentation
|
||||
sudo make -j $(nproc) install
|
||||
|
||||
- name: Install mbedTLS
|
||||
# Build static mbedTLS with pthread support
|
||||
# Disable AESNI on linux/386 asit would possibly result in an incompatible
|
||||
# binary in processors lacking the AESNI and SSE2 instruction sets
|
||||
run: |
|
||||
curl -sSL https://ftl.pi-hole.net/libraries/mbedtls-${mbedtlsversion}.tar.bz2 | tar -xj
|
||||
cd mbedtls-${mbedtlsversion}
|
||||
sed -i '/#define MBEDTLS_THREADING_C/s*^//**g' include/mbedtls/mbedtls_config.h
|
||||
sed -i '/#define MBEDTLS_THREADING_PTHREAD/s*^//**g' include/mbedtls/mbedtls_config.h
|
||||
sudo make -j $(nproc) install
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
queries: security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
./build.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
upload: failure-only # upload only in case of failure, otherwise upload later after filtering
|
||||
output: codeql-results
|
||||
|
||||
- name: Filter SARIF
|
||||
uses: advanced-security/filter-sarif@v1
|
||||
with:
|
||||
# filter out third-party dependencies
|
||||
patterns: |
|
||||
-src/dnsmasq/*
|
||||
-src/webserver/civetweb/*
|
||||
-src/webserver/cJSON/*
|
||||
-src/tre-regex/*
|
||||
-src/config/tomlc99/*
|
||||
-src/database/shell.c
|
||||
-src/database/sqlite3.c
|
||||
-src/database/sqlite3.h
|
||||
-src/zip/miniz/*
|
||||
-src/lua/*
|
||||
+src/lua/ftl_*
|
||||
input: codeql-results/cpp.sarif
|
||||
output: codeql-results/cpp.sarif
|
||||
|
||||
- name: Upload SARIF
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: codeql-results/cpp.sarif
|
||||
|
||||
- name: Upload CodeQL results as an artifact
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: codeql-results
|
||||
path: codeql-results
|
||||
retention-days: 5
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout repository
|
||||
uses: actions/checkout@v4.1.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
-
|
||||
name: Spell-Checking
|
||||
uses: codespell-project/actions-codespell@master
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
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 }}"
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4.1.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Set Node.js version
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.2
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Remove 'stale' label
|
||||
run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }}
|
||||
env:
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Syncing branches
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.2
|
||||
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:
|
||||
|
||||
+2
-3
@@ -14,9 +14,8 @@ version~
|
||||
# IDE files
|
||||
.idea/
|
||||
*.sw*
|
||||
/.vscode
|
||||
.vscode/
|
||||
/.vscode/
|
||||
.vscode/*
|
||||
!.vscode/c_cpp_properties.json
|
||||
/build/
|
||||
|
||||
# __pycache__ files (API tests)
|
||||
|
||||
Vendored
+18
@@ -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
|
||||
}
|
||||
+6
-2
@@ -8,10 +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)
|
||||
|
||||
@@ -23,6 +23,7 @@ do
|
||||
"-C" | "CLEAN" ) clean=1 && nobuild=1;;
|
||||
"-i" | "install" ) install=1;;
|
||||
"-t" | "test" ) test=1;;
|
||||
"clang" ) clang=1;;
|
||||
"ci" ) builddir="cmake_ci/";;
|
||||
esac
|
||||
done
|
||||
@@ -60,6 +61,13 @@ for scriptname in src/lua/scripts/*.lua; do
|
||||
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
|
||||
@@ -88,5 +96,6 @@ 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
|
||||
|
||||
+12
-2
@@ -1,13 +1,23 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "Applying patches for civetweb"
|
||||
echo "Applying patch 0001-add-pihole-mods.patch"
|
||||
patch -p1 < patch/civetweb/0001-add-pihole-mods.patch
|
||||
patch -p1 < patch/civetweb/0001-Add-NO_DLOPEN-option-to-civetweb-s-LUA-routines.patch
|
||||
|
||||
echo "Applying patch 0001-Always-Kepler-syntax-for-Lua-server-pages.patch"
|
||||
patch -p1 < patch/civetweb/0001-Always-Kepler-syntax-for-Lua-server-pages.patch
|
||||
|
||||
echo "Applying patch 0001-Add-FTL-URI-rewriting-changes-to-CivetWeb.patch"
|
||||
patch -p1 < patch/civetweb/0001-Add-FTL-URI-rewriting-changes-to-CivetWeb.patch
|
||||
|
||||
echo "Applying patch 0001-Add-mbedTLS-debug-logging-hook.patch"
|
||||
patch -p1 < patch/civetweb/0001-Add-mbedTLS-debug-logging-hook.patch
|
||||
|
||||
echo "Applying patch 0001-Add-Register-CSRF-token-in-conn-request_info.patch"
|
||||
patch -p1 < patch/civetweb/0001-Register-CSRF-token-in-conn-request_info.patch
|
||||
|
||||
echo "Applying patch 0001-Log-debug-messages-to-webserver.log-when-debug.webse.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"
|
||||
|
||||
@@ -14,14 +14,14 @@ 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);
|
||||
roots[i],
|
||||
uri);
|
||||
|
||||
+ FTL_rewrite_pattern(filename, filename_buf_len - 1, root, uri);
|
||||
+ FTL_rewrite_pattern(filename, filename_buf_len - 1);
|
||||
+
|
||||
if (truncated) {
|
||||
goto interpret_cleanup;
|
||||
}
|
||||
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
|
||||
@@ -30,8 +30,8 @@ index e71dfedc..2ad76693 100644
|
||||
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);
|
||||
+void FTL_rewrite_pattern(char *filename, unsigned long filename_buf_len);
|
||||
+
|
||||
+
|
||||
// Buffer used for additional "Set-Cookie" headers
|
||||
#define PIHOLE_HEADERS_MAXLEN 1024
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
From 1b81285fed48df6939d4b2569bba9e572f4c1137 Mon Sep 17 00:00:00 2001
|
||||
From: DL6ER <dl6er@dl6er.de>
|
||||
Date: Fri, 13 Jan 2023 21:37:31 +0100
|
||||
Subject: [PATCH] Add NO_DLOPEN option to civetweb's LUA routines
|
||||
|
||||
Signed-off-by: DL6ER <dl6er@dl6er.de>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -14,8 +14,8 @@ 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);
|
||||
void FTL_rewrite_pattern(char *filename, size_t filename_buf_len);
|
||||
|
||||
|
||||
+#define MG_CONFIG_MBEDTLS_DEBUG 3
|
||||
+void FTL_mbed_debug(void *user_param, int level, const char *file,
|
||||
@@ -36,9 +36,9 @@ index e72685f4..00b9280a 100644
|
||||
+ 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) {
|
||||
/* Initialize TLS key and cert */
|
||||
mbedtls_pk_init(&ctx->pkey);
|
||||
mbedtls_ctr_drbg_init(&ctx->ctr);
|
||||
--
|
||||
2.34.1
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
From ebb27741b10ed2eac51ac356708800ae96cdd17a Mon Sep 17 00:00:00 2001
|
||||
From: DL6ER <dl6er@dl6er.de>
|
||||
Date: Tue, 31 Oct 2023 08:35:31 +0100
|
||||
Subject: [PATCH] Allow extended ASCII characters in URIs
|
||||
|
||||
Signed-off-by: DL6ER <dl6er@dl6er.de>
|
||||
---
|
||||
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
|
||||
|
||||
@@ -27,28 +27,6 @@ index 3df8eab9..9b0c6308 100644
|
||||
#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
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ Subject: [PATCH] Register CSRF token and is_authenticated boolean in conn->reque
|
||||
Signed-off-by: DL6ER <dl6er@dl6er.de>
|
||||
---
|
||||
src/webserver/civetweb/civetweb.c | 3 +++
|
||||
src/webserver/civetweb/civetweb.h | 2 ++
|
||||
src/webserver/civetweb/mod_lua.inl | 3 +++
|
||||
3 files changed, 8 insertions(+)
|
||||
src/webserver/civetweb/civetweb.h | 3 +++
|
||||
src/webserver/civetweb/mod_lua.inl | 4 ++++
|
||||
3 files changed, 10 insertions(+)
|
||||
|
||||
diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c
|
||||
index 233b342a..f44b17ba 100644
|
||||
@@ -20,7 +20,6 @@ index 233b342a..f44b17ba 100644
|
||||
|
||||
+ /* 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;
|
||||
@@ -29,7 +28,7 @@ diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb
|
||||
index 5b3d596b..291ef683 100644
|
||||
--- a/src/webserver/civetweb/civetweb.h
|
||||
+++ b/src/webserver/civetweb/civetweb.h
|
||||
@@ -183,6 +183,8 @@ struct mg_request_info {
|
||||
@@ -183,6 +183,9 @@ struct mg_request_info {
|
||||
|
||||
const char *acceptedWebSocketSubprotocol; /* websocket subprotocol,
|
||||
* accepted during handshake */
|
||||
@@ -43,13 +42,14 @@ diff --git a/src/webserver/civetweb/mod_lua.inl b/src/webserver/civetweb/mod_lua
|
||||
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)
|
||||
@@ -2603,6 +2603,10 @@ 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);
|
||||
+ reg_boolean(L, "is_authenticated", conn->request_info.is_authenticated != 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
set -e
|
||||
|
||||
patch -p1 < patch/lua/0001-add-pihole-library.patch
|
||||
patch -p1 < patch/lua/0001-Increase-LUA_IDSIZE-so-that-long-script-filenames-as.patch
|
||||
patch -p1 < patch/lua/0001-Add-bundled-script-loading-into-luaL_openlibs-to-mak.patch
|
||||
|
||||
echo "ALL PATCHES APPLIED OKAY"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
From 0ff00e1c838ec91a31970c2b51a7651954cba3d6 Mon Sep 17 00:00:00 2001
|
||||
From: DL6ER <dl6er@dl6er.de>
|
||||
Date: Mon, 23 Sep 2024 21:42:21 +0200
|
||||
Subject: [PATCH] Add bundled script loading into luaL_openlibs to make them
|
||||
available globally (also in the webserver)
|
||||
|
||||
Signed-off-by: DL6ER <dl6er@dl6er.de>
|
||||
---
|
||||
src/lua/ftl_lua.h | 2 --
|
||||
src/lua/linit.c | 6 ++++++
|
||||
src/lua/lua.c | 13 +------------
|
||||
3 files changed, 7 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/src/lua/ftl_lua.h b/src/lua/ftl_lua.h
|
||||
index d986498a..30bad1f9 100644
|
||||
--- a/src/lua/ftl_lua.h
|
||||
+++ b/src/lua/ftl_lua.h
|
||||
@@ -21,8 +21,6 @@ int run_luac(const int argc, char **argv);
|
||||
int lua_main (int argc, char **argv);
|
||||
int luac_main (int argc, char **argv);
|
||||
|
||||
-extern int dolibrary (lua_State *L, char *name);
|
||||
-
|
||||
void print_embedded_scripts(void);
|
||||
void ftl_lua_init(lua_State *L);
|
||||
|
||||
diff --git a/src/lua/linit.c b/src/lua/linit.c
|
||||
index 9a5bcfdc..787865c0 100644
|
||||
--- a/src/lua/linit.c
|
||||
+++ b/src/lua/linit.c
|
||||
@@ -8,6 +8,10 @@
|
||||
#define linit_c
|
||||
#define LUA_LIB
|
||||
|
||||
+/** Pi-hole modification **/
|
||||
+#include "ftl_lua.h"
|
||||
+/**************************/
|
||||
+
|
||||
/*
|
||||
** If you embed Lua in your program and need to open the standard
|
||||
** libraries, call luaL_openlibs in your program. If you need a
|
||||
@@ -64,5 +68,7 @@ LUALIB_API void luaL_openlibs (lua_State *L) {
|
||||
luaL_requiref(L, lib->name, lib->func, 1);
|
||||
lua_pop(L, 1); /* remove lib */
|
||||
}
|
||||
+ // Load and enable libraries bundled with Pi-hole
|
||||
+ ftl_lua_init(L);
|
||||
}
|
||||
|
||||
diff --git a/src/lua/lua.c b/src/lua/lua.c
|
||||
index 35fb281d..111a1b2b 100644
|
||||
--- a/src/lua/lua.c
|
||||
+++ b/src/lua/lua.c
|
||||
@@ -20,10 +20,6 @@
|
||||
#include "lauxlib.h"
|
||||
#include "lualib.h"
|
||||
|
||||
-/** Pi-hole modification **/
|
||||
-#include "ftl_lua.h"
|
||||
-/**************************/
|
||||
-
|
||||
|
||||
#if !defined(LUA_PROGNAME)
|
||||
#define LUA_PROGNAME "lua"
|
||||
@@ -218,9 +214,7 @@ static int dostring (lua_State *L, const char *s, const char *name) {
|
||||
** If there is no explicit modname and globname contains a '-', cut
|
||||
** the suffix after '-' (the "version") to make the global name.
|
||||
*/
|
||||
-/************** Pi-hole modification ***************/
|
||||
-int dolibrary (lua_State *L, char *globname) {
|
||||
-/***************************************************/
|
||||
+static int dolibrary (lua_State *L, char *globname) {
|
||||
int status;
|
||||
char *suffix = NULL;
|
||||
char *modname = strchr(globname, '=');
|
||||
@@ -655,11 +649,6 @@ static int pmain (lua_State *L) {
|
||||
return 0; /* error running LUA_INIT */
|
||||
}
|
||||
|
||||
- /************** Pi-hole modification ***************/
|
||||
- // Load and enable libraries bundled with Pi-hole
|
||||
- ftl_lua_init(L);
|
||||
- /***************************************************/
|
||||
-
|
||||
if (!runargs(L, argv, optlim)) /* execute arguments -e and -l */
|
||||
return 0; /* something failed */
|
||||
if (script > 0) { /* execute main script (if there is one) */
|
||||
--
|
||||
2.34.1
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
From 835933f8501e517a781b380b8cfafa656adc6fa7 Mon Sep 17 00:00:00 2001
|
||||
From: DL6ER <dl6er@dl6er.de>
|
||||
Date: Mon, 23 Sep 2024 13:25:34 +0200
|
||||
Subject: [PATCH] Increase LUA_IDSIZE so that long script filenames as well as
|
||||
long script lines fit into the error logging buffer
|
||||
|
||||
Signed-off-by: DL6ER <dl6er@dl6er.de>
|
||||
---
|
||||
src/lua/luaconf.h | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/lua/luaconf.h b/src/lua/luaconf.h
|
||||
index 33bb580d..dacc5221 100644
|
||||
--- a/src/lua/luaconf.h
|
||||
+++ b/src/lua/luaconf.h
|
||||
@@ -765,7 +765,7 @@
|
||||
** of a function in debug information.
|
||||
** CHANGE it if you want a different size.
|
||||
*/
|
||||
-#define LUA_IDSIZE 60
|
||||
+#define LUA_IDSIZE 256
|
||||
|
||||
|
||||
/*
|
||||
--
|
||||
2.34.1
|
||||
|
||||
+95
-75
@@ -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)
|
||||
@@ -29,7 +27,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
|
||||
# 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_TEMP_STORE=2: Store temporary tables in memory for reduced IO and higher performance (can be overwritten by the user at runtime).
|
||||
# HAVE_READLINE: Enable readline support to allow easy editing, history and auto-completion
|
||||
# SQLITE_DEFAULT_CACHE_SIZE=-16384: Allow up to 16 MiB of cache to be used by SQLite3 (default is 2000 kiB)
|
||||
# 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.
|
||||
@@ -37,7 +34,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
|
||||
# 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 -DHAVE_READLINE -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")
|
||||
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
|
||||
@@ -53,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.
|
||||
@@ -155,11 +154,27 @@ else()
|
||||
set(EXTRAWARN_GCC13 "")
|
||||
endif()
|
||||
|
||||
set(EXTRAWARN "${EXTRAWARN_GCC6} \
|
||||
${EXTRAWARN_GCC7} \
|
||||
${EXTRAWARN_GCC8} \
|
||||
${EXTRAWARN_GCC12} \
|
||||
${EXTRAWARN_GCC13}")
|
||||
# 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}")
|
||||
@@ -185,11 +200,14 @@ 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")
|
||||
@@ -250,15 +268,14 @@ 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
|
||||
$<TARGET_OBJECTS:FTL>
|
||||
$<TARGET_OBJECTS:core>
|
||||
$<TARGET_OBJECTS:api>
|
||||
$<TARGET_OBJECTS:api_docs>
|
||||
$<TARGET_OBJECTS:webserver>
|
||||
@@ -270,32 +287,36 @@ add_executable(pihole-FTL
|
||||
$<TARGET_OBJECTS:dnsmasq>
|
||||
$<TARGET_OBJECTS:sqlite3>
|
||||
$<TARGET_OBJECTS:lua>
|
||||
$<TARGET_OBJECTS:ftl_lua>
|
||||
$<TARGET_OBJECTS:tre-regex>
|
||||
$<TARGET_OBJECTS:syscalls>
|
||||
$<TARGET_OBJECTS:tomlc99>
|
||||
$<TARGET_OBJECTS:config>
|
||||
$<TARGET_OBJECTS:tools>
|
||||
$<TARGET_OBJECTS:ntp>
|
||||
)
|
||||
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 HINTS /usr/local/lib64)
|
||||
find_library(LIBGMP NAMES libgmp${CMAKE_STATIC_LIBRARY_SUFFIX} gmp)
|
||||
find_library(LIBNETTLE NAMES libnettle${CMAKE_STATIC_LIBRARY_SUFFIX} nettle HINTS /usr/local/lib64)
|
||||
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)
|
||||
|
||||
# for IDN2 we need the idn2 library which in turn depends on the unistring library
|
||||
find_library(LIBIDN2 NAMES libidn2${CMAKE_STATIC_LIBRARY_SUFFIX} idn2)
|
||||
find_library(LIBUNISTRING NAMES libunistring${CMAKE_STATIC_LIBRARY_SUFFIX} unistring)
|
||||
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})
|
||||
|
||||
@@ -304,55 +325,6 @@ if(LUA_DL STREQUAL "true")
|
||||
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)
|
||||
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_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${CMAKE_STATIC_LIBRARY_SUFFIX} mbedcrypto)
|
||||
find_library(LIBMBEDX509 NAMES lmbedx509${CMAKE_STATIC_LIBRARY_SUFFIX} mbedx509)
|
||||
find_library(LIBMBEDTLS NAMES lmbedtls${CMAKE_STATIC_LIBRARY_SUFFIX} mbedtls)
|
||||
if(LIBMBEDCRYPTO AND LIBMBEDX509 AND LIBMBEDTLS)
|
||||
# Link against the mbedTLS libraries, the order is important (!)
|
||||
target_compile_definitions(FTL PRIVATE HAVE_MBEDTLS)
|
||||
target_link_libraries(pihole-FTL ${LIBMBEDTLS} ${LIBMBEDX509} ${LIBMBEDCRYPTO})
|
||||
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,CAP_CHOWN+eip \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)")
|
||||
|
||||
add_subdirectory(api)
|
||||
add_subdirectory(webserver)
|
||||
add_subdirectory(zip)
|
||||
@@ -364,3 +336,51 @@ add_subdirectory(tre-regex)
|
||||
add_subdirectory(syscalls)
|
||||
add_subdirectory(config)
|
||||
add_subdirectory(tools)
|
||||
add_subdirectory(ntp)
|
||||
|
||||
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(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()
|
||||
|
||||
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()
|
||||
|
||||
# After finishing building the FTL binary, we append the sha256sum of the binary in raw form to itself
|
||||
add_custom_command(TARGET pihole-FTL POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:pihole-FTL> $<TARGET_FILE_DIR:pihole-FTL>/pihole-FTL.tmp
|
||||
COMMAND sha256sum $<TARGET_FILE:pihole-FTL>.tmp | cut -d ' ' -f 1 | xxd -r -p >> $<TARGET_FILE:pihole-FTL>.tmp
|
||||
COMMAND mv $<TARGET_FILE:pihole-FTL>.tmp $<TARGET_FILE:pihole-FTL>
|
||||
)
|
||||
|
||||
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,CAP_CHOWN,CAP_SYS_TIME+eip \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)")
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/prctl.h>
|
||||
//#include <math.h>
|
||||
#include <pwd.h>
|
||||
// syslog
|
||||
#include <syslog.h>
|
||||
@@ -48,7 +47,7 @@
|
||||
// MIN(x,y) is already defined in dnsmasq.h
|
||||
|
||||
// Number of elements in an array
|
||||
#define ArraySize(X) (sizeof(X)/sizeof(X[0]))
|
||||
#define ArraySize(X) (sizeof(X)/sizeof(*X))
|
||||
|
||||
// Constant socket buffer length
|
||||
#define SOCKETBUFFERLEN 1024
|
||||
@@ -124,12 +123,13 @@
|
||||
// Default: 180 [seconds]
|
||||
#define DELAY_UPTIME 180
|
||||
|
||||
// DB_QUERY_MAX_ITER defines how many queries we check periodically for updates to be added
|
||||
// to the in-memory database. This value may need to be increased on *very* busy systems.
|
||||
// However, there is an algorithm in place that tries to ensure we are not missing queries
|
||||
// on systems with > 100 queries per second
|
||||
// Default: 100 (per second)
|
||||
#define DB_QUERY_MAX_ITER 100
|
||||
// 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
|
||||
@@ -142,12 +142,16 @@
|
||||
// Default: 2592000 (once per month)
|
||||
#define DATABASE_MACVENDOR_INTERVAL 2592000
|
||||
|
||||
// Over how many seconds should the query-per-second (QPS) value be averaged?
|
||||
// Default: 30 (seconds)
|
||||
#define QPS_AVGLEN 30
|
||||
|
||||
// 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((void**)&ptr, __FILE__, __FUNCTION__, __LINE__)
|
||||
#define free(ptr) { FTLfree(ptr, __FILE__, __FUNCTION__, __LINE__); ptr = NULL; }
|
||||
#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__)
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@
|
||||
#include "config/config.h"
|
||||
// getrandom()
|
||||
#include "daemon.h"
|
||||
// generate_app_password()
|
||||
// generate_password()
|
||||
#include "config/password.h"
|
||||
|
||||
// TOTP+HMAC
|
||||
@@ -313,7 +313,7 @@ int generateAppPw(struct ftl_conn *api)
|
||||
{
|
||||
// Generate and set app password
|
||||
char *password = NULL, *pwhash = NULL;
|
||||
if(!generate_app_password(&password, &pwhash))
|
||||
if(!generate_password(&password, &pwhash))
|
||||
{
|
||||
return send_json_error(api,
|
||||
500,
|
||||
|
||||
@@ -20,6 +20,7 @@ set(sources
|
||||
dhcp.c
|
||||
dns.c
|
||||
network.c
|
||||
padd.c
|
||||
history.c
|
||||
info.c
|
||||
list.c
|
||||
|
||||
+1
-4
@@ -128,10 +128,7 @@ int api_action_restartDNS(struct ftl_conn *api)
|
||||
"Restarting DNS is not allowed",
|
||||
"Check setting webserver.api.allow_destructive");
|
||||
|
||||
log_info("Restarting FTL due to API action request");
|
||||
exit_code = RESTART_FTL_CODE;
|
||||
// Send SIGTERM to FTL
|
||||
kill(main_pid(), SIGTERM);
|
||||
restart_ftl("API action request");
|
||||
|
||||
return send_json_success(api);
|
||||
}
|
||||
|
||||
+6
-22
@@ -90,6 +90,7 @@ static struct {
|
||||
{ "/api/config", "/{element}", api_config, { API_PARSE_JSON, 0 }, true, HTTP_GET },
|
||||
{ "/api/config", "/{element}/{value}", api_config, { API_PARSE_JSON, 0 }, true, HTTP_DELETE | HTTP_PUT },
|
||||
{ "/api/network/gateway", "", api_network_gateway, { API_PARSE_JSON, 0 }, true, HTTP_GET },
|
||||
{ "/api/network/routes", "", api_network_routes, { API_PARSE_JSON, 0 }, true, HTTP_GET },
|
||||
{ "/api/network/interfaces", "", api_network_interfaces, { API_PARSE_JSON, 0 }, true, HTTP_GET },
|
||||
{ "/api/network/devices", "", api_network_devices, { API_PARSE_JSON, 0 }, true, HTTP_GET },
|
||||
{ "/api/network/devices", "/{device_id}", api_network_devices, { API_PARSE_JSON, 0 }, true, HTTP_DELETE },
|
||||
@@ -101,6 +102,7 @@ static struct {
|
||||
{ "/api/action/restartdns", "", api_action_restartDNS, { API_PARSE_JSON, 0 }, true, HTTP_POST },
|
||||
{ "/api/action/flush/logs", "", api_action_flush_logs, { API_PARSE_JSON, 0 }, true, HTTP_POST },
|
||||
{ "/api/action/flush/arp", "", api_action_flush_arp, { API_PARSE_JSON, 0 }, true, HTTP_POST },
|
||||
{ "/api/padd", "", api_padd, { API_PARSE_JSON, 0 }, true, HTTP_GET },
|
||||
{ "/api/docs", "", api_docs, { API_PARSE_JSON, 0 }, false, HTTP_GET },
|
||||
};
|
||||
|
||||
@@ -113,10 +115,12 @@ int api_handler(struct mg_connection *conn, void *ignored)
|
||||
http_method(conn),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
API_AUTH_UNAUTHORIZED,
|
||||
double_time(),
|
||||
{ false, NULL, NULL, NULL, 0u },
|
||||
{ false },
|
||||
NULL,
|
||||
{ API_FLAG_NONE, 0 }
|
||||
};
|
||||
|
||||
@@ -170,22 +174,7 @@ int api_handler(struct mg_connection *conn, void *ignored)
|
||||
}
|
||||
|
||||
// Verify requesting client is allowed to see this resource
|
||||
if(api_request[i].func == api_search)
|
||||
{
|
||||
// Handle /api/search special as it may be allowed for local users due to webserver.api.searchAPIauth
|
||||
if(!config.webserver.api.searchAPIauth.v.b && is_local_api_user(api.request->remote_addr))
|
||||
{
|
||||
// Local users does not need to authenticate when searchAPIauth is false
|
||||
;
|
||||
}
|
||||
else if(api_request[i].require_auth && check_client_auth(&api, true) == API_AUTH_UNAUTHORIZED)
|
||||
{
|
||||
// Users need to authenticate but authentication failed
|
||||
unauthorized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(api_request[i].require_auth && check_client_auth(&api, true) == API_AUTH_UNAUTHORIZED)
|
||||
if(api_request[i].require_auth && check_client_auth(&api, true) == API_AUTH_UNAUTHORIZED)
|
||||
{
|
||||
unauthorized = true;
|
||||
break;
|
||||
@@ -270,12 +259,7 @@ int api_handler(struct mg_connection *conn, void *ignored)
|
||||
|
||||
// Restart FTL if requested
|
||||
if(api.ftl.restart)
|
||||
{
|
||||
log_info("Restarting FTL due to API config change");
|
||||
exit_code = RESTART_FTL_CODE;
|
||||
// Send SIGTERM to FTL
|
||||
kill(main_pid(), SIGTERM);
|
||||
}
|
||||
restart_ftl(api.ftl.restart_reason);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "webserver/http-common.h"
|
||||
// regex_t
|
||||
#include "regex_r.h"
|
||||
// enum conf_type
|
||||
#include "config/config.h"
|
||||
|
||||
// Common definitions
|
||||
#define LOCALHOSTv4 "127.0.0.1"
|
||||
@@ -27,12 +29,19 @@ int api_handler(struct mg_connection *conn, void *ignored);
|
||||
|
||||
// Statistic methods
|
||||
int __attribute__((pure)) cmpdesc(const void *a, const void *b);
|
||||
unsigned int get_active_clients(void);
|
||||
int api_stats_summary(struct ftl_conn *api);
|
||||
int api_stats_query_types(struct ftl_conn *api);
|
||||
int api_stats_upstreams(struct ftl_conn *api);
|
||||
int api_stats_top_domains(struct ftl_conn *api);
|
||||
int api_stats_top_clients(struct ftl_conn *api);
|
||||
int api_stats_recentblocked(struct ftl_conn *api);
|
||||
cJSON *get_top_domains(struct ftl_conn *api, const int count,
|
||||
const bool blocked, const bool domains_only);
|
||||
cJSON *get_top_clients(struct ftl_conn *api, const int count,
|
||||
const bool blocked, const bool clients_only,
|
||||
const bool names_only, const bool ip_if_no_name);
|
||||
cJSON *get_top_upstreams(struct ftl_conn *api, const bool upstreams_only);
|
||||
|
||||
// History methods
|
||||
int api_history(struct ftl_conn *api);
|
||||
@@ -65,18 +74,26 @@ int api_info_messages_count(struct ftl_conn *api);
|
||||
int api_info_messages(struct ftl_conn *api);
|
||||
int api_info_metrics(struct ftl_conn *api);
|
||||
int api_info_login(struct ftl_conn *api);
|
||||
cJSON *read_sys_property(const char *path);
|
||||
int get_system_obj(struct ftl_conn *api, cJSON *system);
|
||||
int get_sensors_obj(struct ftl_conn *api, cJSON *sensors, const bool add_list);
|
||||
int get_version_obj(struct ftl_conn *api, cJSON *version);
|
||||
|
||||
// Config methods
|
||||
int api_config(struct ftl_conn *api);
|
||||
int get_json_config(struct ftl_conn *api, cJSON *json, const bool detailed);
|
||||
cJSON *addJSONConfValue(const enum conf_type conf_type, union conf_value *val);
|
||||
|
||||
// Log methods
|
||||
int api_logs(struct ftl_conn *api);
|
||||
|
||||
// Network methods
|
||||
int api_network_gateway(struct ftl_conn *api);
|
||||
int api_network_routes(struct ftl_conn *api);
|
||||
int api_network_interfaces(struct ftl_conn *api);
|
||||
int api_network_devices(struct ftl_conn *api);
|
||||
int api_client_suggestions(struct ftl_conn *api);
|
||||
int get_gateway(struct ftl_conn *api, cJSON * json, const bool detailed);
|
||||
|
||||
// DNS methods
|
||||
int api_dns_blocking(struct ftl_conn *api);
|
||||
@@ -125,4 +142,7 @@ int api_search(struct ftl_conn *api);
|
||||
int api_dhcp_leases_GET(struct ftl_conn *api);
|
||||
int api_dhcp_leases_DELETE(struct ftl_conn *api);
|
||||
|
||||
// PADD methods
|
||||
int api_padd(struct ftl_conn *api);
|
||||
|
||||
#endif // ROUTES_H
|
||||
|
||||
+57
-42
@@ -78,17 +78,10 @@ bool __attribute__((pure)) is_local_api_user(const char *remote_addr)
|
||||
// Returns >= 0 for any valid authentication
|
||||
int check_client_auth(struct ftl_conn *api, const bool is_api)
|
||||
{
|
||||
// Is the user requesting from localhost?
|
||||
// This may be allowed without authentication depending on the configuration
|
||||
if(!config.webserver.api.localAPIauth.v.b && is_local_api_user(api->request->remote_addr))
|
||||
{
|
||||
add_request_info(api, NULL);
|
||||
return API_AUTH_LOCALHOST;
|
||||
}
|
||||
|
||||
// When the pwhash is unset, authentication is disabled
|
||||
if(config.webserver.api.pwhash.v.s[0] == '\0')
|
||||
{
|
||||
api->message = "no password set";
|
||||
add_request_info(api, NULL);
|
||||
return API_AUTH_EMPTYPASS;
|
||||
}
|
||||
@@ -186,7 +179,8 @@ int check_client_auth(struct ftl_conn *api, const bool is_api)
|
||||
|
||||
if(!sid_avail)
|
||||
{
|
||||
log_debug(DEBUG_API, "API Authentication: FAIL (no SID provided)");
|
||||
api->message = "no SID provided";
|
||||
log_debug(DEBUG_API, "API Authentication: FAIL (%s)", api->message);
|
||||
return API_AUTH_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
@@ -212,21 +206,28 @@ int check_client_auth(struct ftl_conn *api, const bool is_api)
|
||||
}
|
||||
else
|
||||
{
|
||||
log_debug(DEBUG_API, "API Authentication: FAIL (Cookie authentication without CSRF token)");
|
||||
api->message = "Cookie authentication without CSRF token";
|
||||
log_debug(DEBUG_API, "API Authentication: FAIL (%s)", api->message);
|
||||
return API_AUTH_UNAUTHORIZED;
|
||||
}
|
||||
}
|
||||
|
||||
bool expired = false;
|
||||
for(unsigned int i = 0; i < max_sessions; i++)
|
||||
{
|
||||
if(auth_data[i].used &&
|
||||
auth_data[i].valid_until >= now &&
|
||||
strcmp(auth_data[i].sid, sid) == 0)
|
||||
{
|
||||
// Check if session is known but expired
|
||||
if(auth_data[i].valid_until < now)
|
||||
expired = true;
|
||||
|
||||
// Check CSRF if authentiating via cookie
|
||||
if(need_csrf && strcmp(auth_data[i].csrf, csrf) != 0)
|
||||
{
|
||||
log_debug(DEBUG_API, "API Authentication: FAIL (CSRF token mismatch, received \"%s\", expected \"%s\")",
|
||||
csrf, auth_data[i].csrf);
|
||||
api->message = "CSRF token mismatch";
|
||||
log_debug(DEBUG_API, "API Authentication: FAIL (%s, received \"%s\", expected \"%s\")",
|
||||
api->message, csrf, auth_data[i].csrf);
|
||||
return API_AUTH_UNAUTHORIZED;
|
||||
}
|
||||
user_id = i;
|
||||
@@ -258,7 +259,7 @@ int check_client_auth(struct ftl_conn *api, const bool is_api)
|
||||
// Debug logging
|
||||
if(config.debug.api.v.b)
|
||||
{
|
||||
char timestr[128];
|
||||
char timestr[TIMESTR_SIZE];
|
||||
get_timestr(timestr, auth_data[user_id].valid_until, false, false);
|
||||
log_debug(DEBUG_API, "Recognized known user: user_id %i, valid_until: %s, remote_addr %s (%s at login)",
|
||||
user_id, timestr, api->request->remote_addr, auth_data[user_id].remote_addr);
|
||||
@@ -266,12 +267,15 @@ int check_client_auth(struct ftl_conn *api, const bool is_api)
|
||||
}
|
||||
else
|
||||
{
|
||||
log_debug(DEBUG_API, "API Authentication: FAIL (SID invalid/expired)");
|
||||
api->message = expired ? "session expired" : "session unknown";
|
||||
log_debug(DEBUG_API, "API Authentication: FAIL (%s)", api->message);
|
||||
return API_AUTH_UNAUTHORIZED;
|
||||
}
|
||||
|
||||
api->user_id = user_id;
|
||||
api->session = &auth_data[user_id];
|
||||
|
||||
api->message = "correct password";
|
||||
return user_id;
|
||||
}
|
||||
|
||||
@@ -295,8 +299,16 @@ static int get_all_sessions(struct ftl_conn *api, cJSON *json)
|
||||
JSON_ADD_NUMBER_TO_OBJECT(session, "last_active", auth_data[i].valid_until - config.webserver.session.timeout.v.ui);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(session, "valid_until", auth_data[i].valid_until);
|
||||
JSON_REF_STR_IN_OBJECT(session, "remote_addr", auth_data[i].remote_addr);
|
||||
JSON_REF_STR_IN_OBJECT(session, "user_agent", auth_data[i].user_agent);
|
||||
if(auth_data[i].user_agent[0] != '\0')
|
||||
JSON_REF_STR_IN_OBJECT(session, "user_agent", auth_data[i].user_agent);
|
||||
else
|
||||
JSON_ADD_NULL_TO_OBJECT(session, "user_agent");
|
||||
if(auth_data[i].x_forwarded_for[0] != '\0')
|
||||
JSON_REF_STR_IN_OBJECT(session, "x_forwarded_for", auth_data[i].x_forwarded_for);
|
||||
else
|
||||
JSON_ADD_NULL_TO_OBJECT(session, "x_forwarded_for");
|
||||
JSON_ADD_BOOL_TO_OBJECT(session, "app", auth_data[i].app);
|
||||
JSON_ADD_BOOL_TO_OBJECT(session, "cli", auth_data[i].cli);
|
||||
JSON_ADD_ITEM_TO_ARRAY(sessions, session);
|
||||
}
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "sessions", sessions);
|
||||
@@ -308,12 +320,13 @@ static int get_session_object(struct ftl_conn *api, cJSON *json, const int user_
|
||||
cJSON *session = JSON_NEW_OBJECT();
|
||||
|
||||
// Authentication not needed
|
||||
if(user_id == API_AUTH_LOCALHOST || user_id == API_AUTH_EMPTYPASS)
|
||||
if(user_id == API_AUTH_EMPTYPASS)
|
||||
{
|
||||
JSON_ADD_BOOL_TO_OBJECT(session, "valid", true);
|
||||
JSON_ADD_BOOL_TO_OBJECT(session, "totp", strlen(config.webserver.api.totp_secret.v.s) > 0);
|
||||
JSON_ADD_NULL_TO_OBJECT(session, "sid");
|
||||
JSON_ADD_NUMBER_TO_OBJECT(session, "validity", -1);
|
||||
JSON_REF_STR_IN_OBJECT(session, "message", api->message);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "session", session);
|
||||
return 0;
|
||||
}
|
||||
@@ -326,6 +339,7 @@ static int get_session_object(struct ftl_conn *api, cJSON *json, const int user_
|
||||
JSON_REF_STR_IN_OBJECT(session, "sid", auth_data[user_id].sid);
|
||||
JSON_REF_STR_IN_OBJECT(session, "csrf", auth_data[user_id].csrf);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(session, "validity", auth_data[user_id].valid_until - now);
|
||||
JSON_REF_STR_IN_OBJECT(session, "message", api->message);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "session", session);
|
||||
return 0;
|
||||
}
|
||||
@@ -335,6 +349,7 @@ static int get_session_object(struct ftl_conn *api, cJSON *json, const int user_
|
||||
JSON_ADD_BOOL_TO_OBJECT(session, "totp", strlen(config.webserver.api.totp_secret.v.s) > 0);
|
||||
JSON_ADD_NULL_TO_OBJECT(session, "sid");
|
||||
JSON_ADD_NUMBER_TO_OBJECT(session, "validity", -1);
|
||||
JSON_REF_STR_IN_OBJECT(session, "message", api->message);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "session", session);
|
||||
return 0;
|
||||
}
|
||||
@@ -401,14 +416,6 @@ static int send_api_auth_status(struct ftl_conn *api, const int user_id, const t
|
||||
JSON_SEND_OBJECT_CODE(json, 401); // 401 Unauthorized
|
||||
}
|
||||
}
|
||||
else if(user_id == API_AUTH_LOCALHOST)
|
||||
{
|
||||
log_debug(DEBUG_API, "API Auth status: OK (localhost does not need auth)");
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
get_session_object(api, json, user_id, now);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
else if(user_id == API_AUTH_EMPTYPASS)
|
||||
{
|
||||
log_debug(DEBUG_API, "API Auth status: OK (empty password)");
|
||||
@@ -461,19 +468,9 @@ int api_auth(struct ftl_conn *api)
|
||||
if(api->method == HTTP_POST)
|
||||
{
|
||||
// Try to extract response from payload
|
||||
if (api->payload.json == NULL)
|
||||
{
|
||||
if (api->payload.json_error == NULL)
|
||||
return send_json_error(api, 400,
|
||||
"bad_request",
|
||||
"No request body data",
|
||||
NULL);
|
||||
else
|
||||
return send_json_error(api, 400,
|
||||
"bad_request",
|
||||
"Invalid request body data (no valid JSON), error before hint",
|
||||
api->payload.json_error);
|
||||
}
|
||||
const int ret = check_json_payload(api);
|
||||
if(ret != 0)
|
||||
return ret;
|
||||
|
||||
// Check if password is available
|
||||
cJSON *json_password;
|
||||
@@ -531,7 +528,9 @@ int api_auth(struct ftl_conn *api)
|
||||
else
|
||||
result = verify_login(password);
|
||||
|
||||
if(result == PASSWORD_CORRECT || result == APPPASSWORD_CORRECT)
|
||||
if(result == PASSWORD_CORRECT ||
|
||||
result == APPPASSWORD_CORRECT ||
|
||||
result == CLIPASSWORD_CORRECT)
|
||||
{
|
||||
// Accepted
|
||||
|
||||
@@ -542,7 +541,7 @@ int api_auth(struct ftl_conn *api)
|
||||
|
||||
// Check possible 2FA token
|
||||
// Successful login with empty password does not require 2FA
|
||||
if(strlen(config.webserver.api.totp_secret.v.s) > 0 && result != APPPASSWORD_CORRECT)
|
||||
if(strlen(config.webserver.api.totp_secret.v.s) > 0 && result == PASSWORD_CORRECT)
|
||||
{
|
||||
// Get 2FA token from payload
|
||||
cJSON *json_totp;
|
||||
@@ -583,7 +582,7 @@ int api_auth(struct ftl_conn *api)
|
||||
auth_data[i].valid_until < now)
|
||||
{
|
||||
log_debug(DEBUG_API, "API: Session of client %u (%s) expired, freeing...",
|
||||
i, auth_data[i].remote_addr);
|
||||
i, auth_data[i].remote_addr);
|
||||
delete_session(i);
|
||||
}
|
||||
|
||||
@@ -609,10 +608,22 @@ int api_auth(struct ftl_conn *api)
|
||||
{
|
||||
auth_data[i].user_agent[0] = '\0';
|
||||
}
|
||||
// Store X-Forwarded-For (if available)
|
||||
const char *x_forwarded_for = mg_get_header(api->conn, "X-Forwarded-For");
|
||||
if(x_forwarded_for != NULL)
|
||||
{
|
||||
strncpy(auth_data[i].x_forwarded_for, x_forwarded_for, sizeof(auth_data[i].x_forwarded_for));
|
||||
auth_data[i].x_forwarded_for[sizeof(auth_data[i].x_forwarded_for)-1] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
auth_data[i].x_forwarded_for[0] = '\0';
|
||||
}
|
||||
|
||||
auth_data[i].tls.login = api->request->is_ssl;
|
||||
auth_data[i].tls.mixed = false;
|
||||
auth_data[i].app = result == APPPASSWORD_CORRECT;
|
||||
auth_data[i].cli = result == CLIPASSWORD_CORRECT;
|
||||
|
||||
// Generate new SID and CSRF token
|
||||
generateSID(auth_data[i].sid);
|
||||
@@ -626,7 +637,7 @@ int api_auth(struct ftl_conn *api)
|
||||
// Debug logging
|
||||
if(config.debug.api.v.b && user_id > API_AUTH_UNAUTHORIZED)
|
||||
{
|
||||
char timestr[128];
|
||||
char timestr[TIMESTR_SIZE];
|
||||
get_timestr(timestr, auth_data[user_id].valid_until, false, false);
|
||||
log_debug(DEBUG_API, "API: Registered new user: user_id %i valid_until: %s remote_addr %s (accepted due to %s)",
|
||||
user_id, timestr, auth_data[user_id].remote_addr,
|
||||
@@ -642,6 +653,8 @@ int api_auth(struct ftl_conn *api)
|
||||
"API seats exceeded",
|
||||
"increase webserver.api.max_sessions");
|
||||
}
|
||||
|
||||
api->message = result == APPPASSWORD_CORRECT ? "app-password correct" : "password correct";
|
||||
}
|
||||
else if(result == PASSWORD_RATE_LIMITED)
|
||||
{
|
||||
@@ -654,10 +667,12 @@ int api_auth(struct ftl_conn *api)
|
||||
else if(result == NO_PASSWORD_SET)
|
||||
{
|
||||
// No password set
|
||||
api->message = "password incorrect";
|
||||
log_debug(DEBUG_API, "API: Trying to auth with password but none set: '%s'", password);
|
||||
}
|
||||
else
|
||||
{
|
||||
api->message = "password incorrect";
|
||||
log_debug(DEBUG_API, "API: Password incorrect: '%s'", password);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -48,6 +48,7 @@
|
||||
struct session {
|
||||
bool used;
|
||||
bool app;
|
||||
bool cli;
|
||||
struct {
|
||||
bool login;
|
||||
bool mixed;
|
||||
@@ -56,8 +57,9 @@ struct session {
|
||||
time_t valid_until;
|
||||
char remote_addr[48]; // Large enough for IPv4 and IPv6 addresses, hard-coded in civetweb.h as mg_request_info.remote_addr
|
||||
char user_agent[128];
|
||||
char x_forwarded_for[48]; // see remote_addr note
|
||||
char sid[SID_SIZE];
|
||||
char csrf[SID_SIZE];
|
||||
};
|
||||
|
||||
#endif // AUTH_H
|
||||
#endif // AUTH_H
|
||||
|
||||
+99
-37
@@ -37,6 +37,7 @@ static struct {
|
||||
{
|
||||
{ "dns", "DNS", "DNS server settings" },
|
||||
{ "dhcp", "DHCP", "DHCP server settings" },
|
||||
{ "ntp", "NTP", "Network Time Sync settings" },
|
||||
{ "resolver", "Resolver", "Resolver settings" },
|
||||
{ "database", "Database", "Database settings" },
|
||||
{ "webserver", "HTTP/API", "Webserver and API settings" },
|
||||
@@ -91,7 +92,7 @@ static cJSON *get_or_create_object(cJSON *parent, const char *path_element)
|
||||
|
||||
// This function is used to add a property to the JSON output using the
|
||||
// appropriate type of the config item to add.
|
||||
static cJSON *addJSONvalue(const enum conf_type conf_type, union conf_value *val)
|
||||
cJSON *addJSONConfValue(const enum conf_type conf_type, union conf_value *val)
|
||||
{
|
||||
switch(conf_type)
|
||||
{
|
||||
@@ -128,6 +129,8 @@ static cJSON *addJSONvalue(const enum conf_type conf_type, union conf_value *val
|
||||
return cJSON_CreateStringReference(get_web_theme_str(val->web_theme));
|
||||
case CONF_ENUM_TEMP_UNIT:
|
||||
return cJSON_CreateStringReference(get_temp_unit_str(val->temp_unit));
|
||||
case CONF_ENUM_BLOCKING_EDNS_MODE:
|
||||
return cJSON_CreateStringReference(get_edns_mode_str(val->edns_mode));
|
||||
case CONF_STRUCT_IN_ADDR:
|
||||
{
|
||||
// Special case 0.0.0.0 -> return empty string
|
||||
@@ -238,7 +241,7 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem, struct
|
||||
// 1. Check it is a number
|
||||
// 2. Check the number is within the allowed range for the given data type
|
||||
if(!cJSON_IsNumber(elem) ||
|
||||
elem->valuedouble < LONG_MIN || elem->valuedouble > LONG_MAX)
|
||||
elem->valuedouble < (double)LONG_MIN || elem->valuedouble > (double)LONG_MAX)
|
||||
return "not of type long";
|
||||
// Set item
|
||||
conf_item->v.l = elem->valuedouble;
|
||||
@@ -250,7 +253,7 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem, struct
|
||||
// 1. Check it is a number
|
||||
// 2. Check the number is within the allowed range for the given data type
|
||||
if(!cJSON_IsNumber(elem) ||
|
||||
elem->valuedouble < 0 || elem->valuedouble > ULONG_MAX)
|
||||
elem->valuedouble < 0 || elem->valuedouble > (double)ULONG_MAX)
|
||||
return "not of type unsigned long";
|
||||
// Set item
|
||||
conf_item->v.ul = elem->valuedouble;
|
||||
@@ -278,6 +281,7 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem, struct
|
||||
free(conf_item->v.s);
|
||||
// Set item
|
||||
conf_item->v.s = strdup(elem->valuestring);
|
||||
conf_item->t = CONF_STRING_ALLOCATED; // allocated now
|
||||
log_debug(DEBUG_CONFIG, "%s = \"%s\"", conf_item->k, conf_item->v.s);
|
||||
break;
|
||||
}
|
||||
@@ -389,6 +393,19 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem, struct
|
||||
log_debug(DEBUG_CONFIG, "%s = %d", conf_item->k, conf_item->v.temp_unit);
|
||||
break;
|
||||
}
|
||||
case CONF_ENUM_BLOCKING_EDNS_MODE:
|
||||
{
|
||||
// Check type
|
||||
if(!cJSON_IsString(elem))
|
||||
return "not of type string";
|
||||
const int edns_mode = get_edns_mode_val(elem->valuestring);
|
||||
if(edns_mode == -1)
|
||||
return "invalid option";
|
||||
// Set item
|
||||
conf_item->v.edns_mode = edns_mode;
|
||||
log_debug(DEBUG_CONFIG, "%s = %d", conf_item->k, conf_item->v.edns_mode);
|
||||
break;
|
||||
}
|
||||
case CONF_ENUM_PRIVACY_LEVEL:
|
||||
{
|
||||
// Check type
|
||||
@@ -463,17 +480,9 @@ static const char *getJSONvalue(struct conf_item *conf_item, cJSON *elem, struct
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int api_config_get(struct ftl_conn *api)
|
||||
int get_json_config(struct ftl_conn *api, cJSON *json, const bool detailed)
|
||||
{
|
||||
// Parse query string parameters
|
||||
bool detailed = false;
|
||||
if(api->request->query_string != NULL)
|
||||
{
|
||||
// Check if we should return detailed config information
|
||||
get_bool_var(api->request->query_string, "detailed", &detailed);
|
||||
}
|
||||
|
||||
// Create root JSON object
|
||||
// Create root config object
|
||||
cJSON *config_j = JSON_NEW_OBJECT();
|
||||
|
||||
// Does the user request only a subset of /config?
|
||||
@@ -551,7 +560,7 @@ static int api_config_get(struct ftl_conn *api)
|
||||
else
|
||||
{
|
||||
// Add current value
|
||||
cJSON *val = addJSONvalue(conf_item->t, &conf_item->v);
|
||||
cJSON *val = addJSONConfValue(conf_item->t, &conf_item->v);
|
||||
if(val == NULL)
|
||||
{
|
||||
log_warn("Cannot format config item type %s of type %i",
|
||||
@@ -562,7 +571,7 @@ static int api_config_get(struct ftl_conn *api)
|
||||
}
|
||||
|
||||
// Add default value
|
||||
cJSON *dval = addJSONvalue(conf_item->t, &conf_item->d);
|
||||
cJSON *dval = addJSONConfValue(conf_item->t, &conf_item->d);
|
||||
if(dval == NULL)
|
||||
{
|
||||
log_warn("Cannot format config item type %s of type %i",
|
||||
@@ -576,7 +585,6 @@ static int api_config_get(struct ftl_conn *api)
|
||||
// Add config item flags
|
||||
cJSON *flags = JSON_NEW_OBJECT();
|
||||
JSON_ADD_BOOL_TO_OBJECT(flags, "restart_dnsmasq", conf_item->f & FLAG_RESTART_FTL);
|
||||
JSON_ADD_BOOL_TO_OBJECT(flags, "advanced", conf_item->f & FLAG_ADVANCED_SETTING);
|
||||
JSON_ADD_BOOL_TO_OBJECT(flags, "session_reset", conf_item->f & FLAG_INVALIDATE_SESSIONS);
|
||||
JSON_ADD_BOOL_TO_OBJECT(flags, "env_var", conf_item->f & FLAG_ENV_VAR);
|
||||
JSON_ADD_ITEM_TO_OBJECT(leaf, "flags", flags);
|
||||
@@ -592,7 +600,7 @@ static int api_config_get(struct ftl_conn *api)
|
||||
else
|
||||
{
|
||||
// Create the config item leaf object
|
||||
cJSON *leaf = addJSONvalue(conf_item->t, &conf_item->v);
|
||||
cJSON *leaf = addJSONConfValue(conf_item->t, &conf_item->v);
|
||||
if(leaf == NULL)
|
||||
{
|
||||
log_warn("Cannot format config item type %s of type %i",
|
||||
@@ -607,8 +615,6 @@ static int api_config_get(struct ftl_conn *api)
|
||||
// Release allocated memory
|
||||
free_config_path(requested_path);
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
|
||||
// Add topics and DNS server suggestions if in detailed mode
|
||||
if(detailed)
|
||||
{
|
||||
@@ -650,25 +656,33 @@ static int api_config_get(struct ftl_conn *api)
|
||||
|
||||
// Build and return JSON response
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "config", config_j);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int api_config_get(struct ftl_conn *api)
|
||||
{
|
||||
// Parse query string parameters
|
||||
bool detailed = false;
|
||||
if(api->request->query_string != NULL)
|
||||
{
|
||||
// Check if we should return detailed config information
|
||||
get_bool_var(api->request->query_string, "detailed", &detailed);
|
||||
}
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
get_json_config(api, json, detailed);
|
||||
|
||||
// Build and return JSON response
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
static int api_config_patch(struct ftl_conn *api)
|
||||
{
|
||||
// Is there a payload with valid JSON data?
|
||||
if (api->payload.json == NULL)
|
||||
{
|
||||
if (api->payload.json_error == NULL)
|
||||
return send_json_error(api, 400,
|
||||
"bad_request",
|
||||
"No request body data",
|
||||
NULL);
|
||||
else
|
||||
return send_json_error(api, 400,
|
||||
"bad_request",
|
||||
"Invalid request body data (no valid JSON), error before hint",
|
||||
api->payload.json_error);
|
||||
}
|
||||
const int ret = check_json_payload(api);
|
||||
if(ret != 0)
|
||||
return ret;
|
||||
|
||||
// Is there a "config" object at the root of the received JSON payload?
|
||||
cJSON *conf = cJSON_GetObjectItem(api->payload.json, "config");
|
||||
@@ -680,6 +694,16 @@ static int api_config_patch(struct ftl_conn *api)
|
||||
NULL);
|
||||
}
|
||||
|
||||
// Return early if the user tries to change some settings but the config
|
||||
// is in read-only mode
|
||||
if(config.misc.readOnly.v.b)
|
||||
{
|
||||
return send_json_error(api, 403,
|
||||
"forbidden",
|
||||
"The config is currently in read-only mode",
|
||||
NULL);
|
||||
}
|
||||
|
||||
// Read all known config items
|
||||
bool config_changed = false;
|
||||
bool dnsmasq_changed = false;
|
||||
@@ -706,6 +730,16 @@ static int api_config_patch(struct ftl_conn *api)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(new_item->f & FLAG_READ_ONLY && cJSON_IsBool(elem) && elem->valueint == 1)
|
||||
{
|
||||
char *key = strdup(new_item->k);
|
||||
free_config(&newconf);
|
||||
return send_json_error_free(api, 400,
|
||||
"bad_request",
|
||||
"This config option can only be set in pihole.toml, not via the API",
|
||||
key, true, true);
|
||||
}
|
||||
|
||||
// Check if this is a write-only config item with the placeholder value
|
||||
if(new_item->f & FLAG_WRITE_ONLY && cJSON_IsString(elem) &&
|
||||
strcmp(elem->valuestring, PASSWORD_VALUE) == 0)
|
||||
@@ -734,7 +768,7 @@ static int api_config_patch(struct ftl_conn *api)
|
||||
return send_json_error_free(api, 400,
|
||||
"bad_request",
|
||||
"Config item is invalid",
|
||||
hint, true);
|
||||
hint, true, true);
|
||||
}
|
||||
|
||||
// Get pointer to memory location of this conf_item (global)
|
||||
@@ -749,7 +783,7 @@ static int api_config_patch(struct ftl_conn *api)
|
||||
return send_json_error_free(api, 400,
|
||||
"bad_request",
|
||||
"Config items set via environment variables cannot be changed via the API",
|
||||
key, true);
|
||||
key, true, true);
|
||||
}
|
||||
|
||||
// Skip processing if value didn't change compared to current value
|
||||
@@ -788,7 +822,10 @@ static int api_config_patch(struct ftl_conn *api)
|
||||
// If the privacy level was decreased, we need to restart
|
||||
if(new_item == &newconf.misc.privacylevel &&
|
||||
new_item->v.privacy_level < conf_item->v.privacy_level)
|
||||
{
|
||||
api->ftl.restart_reason = "Privacy level decreased";
|
||||
api->ftl.restart = true;
|
||||
}
|
||||
|
||||
// Check if this item changed the password, if so, we need to
|
||||
// invalidate all currently active sessions
|
||||
@@ -804,7 +841,10 @@ static int api_config_patch(struct ftl_conn *api)
|
||||
{
|
||||
char errbuf[ERRBUF_SIZE] = { 0 };
|
||||
if(write_dnsmasq_config(&newconf, true, errbuf))
|
||||
{
|
||||
api->ftl.restart_reason = "dnsmasq config changed";
|
||||
api->ftl.restart = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
free_config(&newconf);
|
||||
@@ -855,9 +895,9 @@ static int api_config_put_delete(struct ftl_conn *api)
|
||||
|
||||
const char *hint = NULL, *message = NULL;
|
||||
if(api->method == HTTP_PUT)
|
||||
hint = "Use, e.g., PUT /api/config/dnsmasq/upstreams/127.0.0.1 to add \"127.0.0.1\" to config.dns.upstreams";
|
||||
hint = "Use, e.g., PUT /api/config/dns/upstreams/127.0.0.1 to add \"127.0.0.1\" to config.dns.upstreams";
|
||||
else
|
||||
hint = "Use, e.g., DELETE /api/config/dnsmasq/upstreams/127.0.0.1 to remove \"127.0.0.1\" from config.dns.upstreams";
|
||||
hint = "Use, e.g., DELETE /api/config/dns/upstreams/127.0.0.1 to remove \"127.0.0.1\" from config.dns.upstreams";
|
||||
|
||||
if(min_level < 2)
|
||||
{
|
||||
@@ -912,7 +952,7 @@ static int api_config_put_delete(struct ftl_conn *api)
|
||||
return send_json_error_free(api, 400,
|
||||
"bad_request",
|
||||
"Config items set via environment variables cannot be changed via the API",
|
||||
key, true);
|
||||
key, true, true);
|
||||
}
|
||||
|
||||
// Check if this entry exists in the array
|
||||
@@ -1012,7 +1052,10 @@ static int api_config_put_delete(struct ftl_conn *api)
|
||||
char errbuf[ERRBUF_SIZE] = { 0 };
|
||||
// Request restart of FTL
|
||||
if(write_dnsmasq_config(&newconf, true, errbuf))
|
||||
{
|
||||
api->ftl.restart_reason = "dnsmasq config changed";
|
||||
api->ftl.restart = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The new config did not work
|
||||
@@ -1048,6 +1091,25 @@ int api_config(struct ftl_conn *api)
|
||||
if(api->method == HTTP_GET)
|
||||
return api_config_get(api);
|
||||
|
||||
// Check if this is an app session and reject the request if app sudo
|
||||
// mode is disabled
|
||||
if(api->session != NULL && api->session->app && !config.webserver.api.app_sudo.v.b)
|
||||
{
|
||||
return send_json_error(api, 403,
|
||||
"forbidden",
|
||||
"Unable to change configuration (read-only)",
|
||||
"The current app session is not allowed to modify Pi-hole config settings (webserver.api.app_sudo is false)");
|
||||
}
|
||||
|
||||
// Check if this is a CLI session and reject the request
|
||||
if(api->session != NULL && api->session->cli)
|
||||
{
|
||||
return send_json_error(api, 403,
|
||||
"forbidden",
|
||||
"Unable to change configuration (read-only)",
|
||||
"The current CLI session is not allowed to modify Pi-hole config settings");
|
||||
}
|
||||
|
||||
// POST: Create a new config (not supported)
|
||||
// PATCH: Replace parts of the the config with the provided one
|
||||
// PUT: Replaces the entire config with the provided one (not supported
|
||||
|
||||
+1
-1
@@ -110,4 +110,4 @@ int api_dhcp_leases_DELETE(struct ftl_conn *api)
|
||||
// - 404 Not Found (if no lease was found)
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_SEND_OBJECT_CODE(json, found ? 204 : 404);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-29
@@ -31,21 +31,8 @@ static int get_blocking(struct ftl_conn *api)
|
||||
// Return current status
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
const enum blocking_status blocking = get_blockingstatus();
|
||||
switch(blocking)
|
||||
{
|
||||
case BLOCKING_ENABLED:
|
||||
JSON_REF_STR_IN_OBJECT(json, "blocking", "enabled");
|
||||
break;
|
||||
case BLOCKING_DISABLED:
|
||||
JSON_REF_STR_IN_OBJECT(json, "blocking", "disabled");
|
||||
break;
|
||||
case DNS_FAILED:
|
||||
JSON_REF_STR_IN_OBJECT(json, "blocking", "failure");
|
||||
break;
|
||||
case BLOCKING_UNKNOWN:
|
||||
JSON_REF_STR_IN_OBJECT(json, "blocking", "unknown");
|
||||
break;
|
||||
}
|
||||
const char *status = get_blocking_status_str(blocking);
|
||||
JSON_REF_STR_IN_OBJECT(json, "blocking", status);
|
||||
|
||||
// Get timer information (if applicable)
|
||||
double delay;
|
||||
@@ -74,19 +61,10 @@ static int set_blocking(struct ftl_conn *api)
|
||||
NULL);
|
||||
}
|
||||
|
||||
if (api->payload.json == NULL)
|
||||
{
|
||||
if (api->payload.json_error == NULL)
|
||||
return send_json_error(api, 400,
|
||||
"bad_request",
|
||||
"No request body data",
|
||||
NULL);
|
||||
else
|
||||
return send_json_error(api, 400,
|
||||
"bad_request",
|
||||
"Invalid request body data (no valid JSON), error before hint",
|
||||
api->payload.json_error);
|
||||
}
|
||||
// Check if the payload is valid JSON
|
||||
const int ret = check_json_payload(api);
|
||||
if(ret != 0)
|
||||
return ret;
|
||||
|
||||
cJSON *elem = cJSON_GetObjectItemCaseSensitive(api->payload.json, "blocking");
|
||||
if (!cJSON_IsBool(elem))
|
||||
@@ -109,7 +87,7 @@ static int set_blocking(struct ftl_conn *api)
|
||||
// The blocking status does not need to be changed
|
||||
|
||||
// Delete a possibly running timer
|
||||
set_blockingmode_timer(-1.0, true);
|
||||
set_blockingmode_timer(timer, true);
|
||||
|
||||
log_debug(DEBUG_API, "No change in blocking mode, resetting timer");
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ set(sources
|
||||
hex/specs/logs.yaml
|
||||
hex/specs/main.yaml
|
||||
hex/specs/network.yaml
|
||||
hex/specs/padd.yaml
|
||||
hex/specs/queries.yaml
|
||||
hex/specs/search.yaml
|
||||
hex/specs/stats.yaml
|
||||
|
||||
@@ -280,6 +280,7 @@ components:
|
||||
- sid
|
||||
- csrf
|
||||
- validity
|
||||
- message
|
||||
- totp
|
||||
properties:
|
||||
valid:
|
||||
@@ -299,6 +300,10 @@ components:
|
||||
validity:
|
||||
type: integer
|
||||
description: Remaining lifetime of this session unless refreshed (seconds)
|
||||
message:
|
||||
type: string
|
||||
description: Human-readable message describing the session status
|
||||
nullable: true
|
||||
|
||||
password:
|
||||
type: object
|
||||
@@ -340,6 +345,9 @@ components:
|
||||
app:
|
||||
type: boolean
|
||||
description: Indicator if this session was initiated using an application password
|
||||
cli:
|
||||
type: boolean
|
||||
description: Indicator if this session was initiated using the command-line interface (CLI)
|
||||
login_at:
|
||||
type: integer
|
||||
description: Timestamp of login (seconds since epoch)
|
||||
@@ -354,7 +362,12 @@ components:
|
||||
description: IP address of the client
|
||||
user_agent:
|
||||
type: string
|
||||
description: User agent of the client
|
||||
nullable: true
|
||||
description: User agent of the client (optional)
|
||||
x_forwarded_for:
|
||||
type: string
|
||||
nullable: true
|
||||
description: IP address of the client (if behind a proxy, optional)
|
||||
example:
|
||||
- id: 1
|
||||
current_session: true
|
||||
@@ -363,11 +376,13 @@ components:
|
||||
login: true
|
||||
mixed: false
|
||||
app: false
|
||||
cli: false
|
||||
login_at: 1580000000
|
||||
last_active: 1580000000
|
||||
valid_until: 1580000300
|
||||
remote_addr: "192.168.0.34"
|
||||
user_agent: "Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0"
|
||||
x_forwarded_for: null
|
||||
totp:
|
||||
type: object
|
||||
description: TOTP secret suggestion
|
||||
@@ -431,7 +446,7 @@ components:
|
||||
|
||||
examples:
|
||||
auth_okay:
|
||||
summary: Authentication valid
|
||||
summary: Session valid
|
||||
value:
|
||||
session:
|
||||
valid: true
|
||||
@@ -439,6 +454,7 @@ components:
|
||||
sid: null
|
||||
csrf: null
|
||||
validity: 300
|
||||
message: null
|
||||
login_okay:
|
||||
summary: Login successful
|
||||
value:
|
||||
@@ -448,6 +464,7 @@ components:
|
||||
sid: "vFA+EP4MQ5JJvJg+3Q2Jnw="
|
||||
csrf: "Ux87YTIiMOf/GKCefVIOMw="
|
||||
validity: 300
|
||||
message: correct password
|
||||
no_login_required:
|
||||
summary: No login required for this client
|
||||
value:
|
||||
@@ -457,6 +474,7 @@ components:
|
||||
sid: null
|
||||
csrf: null
|
||||
validity: -1
|
||||
message: no auth for local user
|
||||
login_required:
|
||||
summary: Login required, 2FA disabled
|
||||
value:
|
||||
@@ -466,6 +484,7 @@ components:
|
||||
sid: null
|
||||
csrf: null
|
||||
validity: -1
|
||||
message: password incorrect
|
||||
login_required_2fa:
|
||||
summary: Login required, 2FA enabled
|
||||
value:
|
||||
@@ -475,6 +494,7 @@ components:
|
||||
sid: null
|
||||
csrf: null
|
||||
validity: -1
|
||||
message: password incorrect
|
||||
login_failed:
|
||||
summary: Login failed
|
||||
value:
|
||||
@@ -484,6 +504,7 @@ components:
|
||||
sid: null
|
||||
csrf: null
|
||||
validity: -1
|
||||
message: no SID provided
|
||||
errors:
|
||||
no_payload:
|
||||
summary: Bad request (no valid JSON payload)
|
||||
|
||||
@@ -11,7 +11,7 @@ components:
|
||||
- "Client management"
|
||||
operationId: "get_clients"
|
||||
description: |
|
||||
`{client}` is optional. Specifying it will result in only the requested client being returned.
|
||||
`{client}` is optional. If it is specified, it will result in only the requested client being returned. This parameter needs to be URI-encoded.
|
||||
|
||||
Valid combinations are:
|
||||
- `/api/clients` (all clients)
|
||||
@@ -42,7 +42,7 @@ components:
|
||||
- "Client management"
|
||||
operationId: "replace_client"
|
||||
description: |
|
||||
Items may be updated by replacing them. `{client}` is required.
|
||||
Items may be updated by replacing them. `{client}` is required and needs to be URI-encoded.
|
||||
|
||||
Ensure to send all the required parameters (such as `comment` or `groups`) to ensure these properties are retained.
|
||||
The read-only fields `id` and `date_added` are preserved, `date_modified` is automatically updated on success.
|
||||
@@ -91,7 +91,7 @@ components:
|
||||
- "Client management"
|
||||
operationId: "delete_client"
|
||||
description: |
|
||||
*Note:* There will be no content on success.
|
||||
*Note:* There will be no content on success. `{client}` is required and needs to be URI-encoded.
|
||||
responses:
|
||||
'204':
|
||||
description: Item deleted
|
||||
@@ -383,6 +383,12 @@ components:
|
||||
type: integer
|
||||
readOnly: true
|
||||
example: 1611239099
|
||||
name:
|
||||
description: hostname (only if available)
|
||||
type: string
|
||||
readOnly: true
|
||||
nullable: true
|
||||
example: localhost
|
||||
lists_processed:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -239,6 +239,8 @@ components:
|
||||
type: integer
|
||||
optimizer:
|
||||
type: integer
|
||||
upstreamBlockedTTL:
|
||||
type: integer
|
||||
revServers:
|
||||
type: array
|
||||
items:
|
||||
@@ -250,6 +252,8 @@ components:
|
||||
type: boolean
|
||||
mode:
|
||||
type: string
|
||||
edns:
|
||||
type: string
|
||||
specialDomains:
|
||||
type: object
|
||||
properties:
|
||||
@@ -320,10 +324,51 @@ components:
|
||||
type: boolean
|
||||
logging:
|
||||
type: boolean
|
||||
ignoreUnknownClients:
|
||||
type: boolean
|
||||
hosts:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
ntp:
|
||||
type: object
|
||||
properties:
|
||||
ipv4:
|
||||
type: object
|
||||
properties:
|
||||
active:
|
||||
type: boolean
|
||||
address:
|
||||
type: string
|
||||
x-format: ipv4
|
||||
ipv6:
|
||||
type: object
|
||||
properties:
|
||||
active:
|
||||
type: boolean
|
||||
address:
|
||||
type: string
|
||||
x-format: ipv6
|
||||
sync:
|
||||
type: object
|
||||
properties:
|
||||
active:
|
||||
type: boolean
|
||||
server:
|
||||
type: string
|
||||
interval:
|
||||
type: integer
|
||||
count:
|
||||
type: integer
|
||||
rtc:
|
||||
type: object
|
||||
properties:
|
||||
set:
|
||||
type: boolean
|
||||
device:
|
||||
type: string
|
||||
utc:
|
||||
type: boolean
|
||||
resolver:
|
||||
type: object
|
||||
properties:
|
||||
@@ -372,8 +417,6 @@ components:
|
||||
tls:
|
||||
type: object
|
||||
properties:
|
||||
rev_proxy:
|
||||
type: boolean
|
||||
cert:
|
||||
type: string
|
||||
paths:
|
||||
@@ -393,10 +436,6 @@ components:
|
||||
api:
|
||||
type: object
|
||||
properties:
|
||||
localAPIauth:
|
||||
type: boolean
|
||||
searchAPIauth:
|
||||
type: boolean
|
||||
max_sessions:
|
||||
type: integer
|
||||
prettyJSON:
|
||||
@@ -411,6 +450,10 @@ components:
|
||||
type: string
|
||||
app_pwhash:
|
||||
type: string
|
||||
app_sudo:
|
||||
type: boolean
|
||||
cli_pw:
|
||||
type: boolean
|
||||
excludeClients:
|
||||
type: array
|
||||
items:
|
||||
@@ -479,6 +522,8 @@ components:
|
||||
type: string
|
||||
extraLogging:
|
||||
type: boolean
|
||||
readOnly:
|
||||
type: boolean
|
||||
check:
|
||||
type: object
|
||||
properties:
|
||||
@@ -545,6 +590,8 @@ components:
|
||||
type: boolean
|
||||
reserved:
|
||||
type: boolean
|
||||
ntp:
|
||||
type: boolean
|
||||
all:
|
||||
type: boolean
|
||||
topics:
|
||||
@@ -620,11 +667,13 @@ components:
|
||||
cache:
|
||||
size: 10000
|
||||
optimizer: 3600
|
||||
upstreamBlockedTTL: 86400
|
||||
revServers:
|
||||
- "true,192.168.0.0/24,192.168.0.1,lan"
|
||||
blocking:
|
||||
active: true
|
||||
mode: 'NULL'
|
||||
edns: 'NONE'
|
||||
specialDomains:
|
||||
mozillaCanary: true
|
||||
iCloudPrivateRelay: true
|
||||
@@ -653,9 +702,26 @@ components:
|
||||
rapidCommit: false
|
||||
multiDNS: false
|
||||
logging: false
|
||||
ignoreUnknownClients: false
|
||||
hosts:
|
||||
- "11:22:33:44:55:66,192.168.1.123"
|
||||
- "11:22:33:44:55:67,192.168.1.124,hostname"
|
||||
ntp:
|
||||
ipv4:
|
||||
active: true
|
||||
address: ""
|
||||
ipv6:
|
||||
active: true
|
||||
address: ""
|
||||
sync:
|
||||
active: true
|
||||
server: "pool.ntp.org"
|
||||
interval: 3600
|
||||
count: 8
|
||||
rtc:
|
||||
set: true
|
||||
device: ""
|
||||
utc: true
|
||||
resolver:
|
||||
resolveIPv4: true
|
||||
resolveIPv6: true
|
||||
@@ -677,7 +743,6 @@ components:
|
||||
timeout: 300
|
||||
restore: true
|
||||
tls:
|
||||
rev_proxy: false
|
||||
cert: "/etc/pihole/tls.pem"
|
||||
paths:
|
||||
webroot: "/var/www/html"
|
||||
@@ -686,14 +751,14 @@ components:
|
||||
boxed: true
|
||||
theme: "default-darker"
|
||||
api:
|
||||
localAPIauth: false
|
||||
searchAPIauth: false
|
||||
max_sessions: 16
|
||||
prettyJSON: false
|
||||
password: "********"
|
||||
pwhash: ''
|
||||
totp_secret: ''
|
||||
app_pwhash: ''
|
||||
app_sudo: false
|
||||
cli_pw: true
|
||||
excludeClients: [ '1\.2\.3\.4', 'localhost', 'fe80::345' ]
|
||||
excludeDomains: [ 'google\\.de', 'pi-hole\.net' ]
|
||||
maxHistory: 86400
|
||||
@@ -723,6 +788,7 @@ components:
|
||||
etc_dnsmasq_d: false
|
||||
dnsmasq_lines: [ ]
|
||||
extraLogging: false
|
||||
readOnly: false
|
||||
check:
|
||||
load: true
|
||||
shmem: 90
|
||||
@@ -755,6 +821,7 @@ components:
|
||||
webserver: false
|
||||
extra: false
|
||||
reserved: false
|
||||
ntp: false
|
||||
all: false
|
||||
config_one:
|
||||
summary: One option
|
||||
|
||||
@@ -717,10 +717,25 @@ components:
|
||||
type: integer
|
||||
description: Number of denied domains
|
||||
example: 3
|
||||
regex:
|
||||
type: object
|
||||
properties:
|
||||
allowed:
|
||||
type: integer
|
||||
description: Number of allowed regex filters
|
||||
example: 4
|
||||
denied:
|
||||
type: integer
|
||||
description: Number of denied regex filters
|
||||
example: 2
|
||||
privacy_level:
|
||||
type: integer
|
||||
description: Currently used privacy level
|
||||
example: 0
|
||||
query_frequency:
|
||||
type: number
|
||||
description: Average number of queries per second
|
||||
example: 1.1
|
||||
clients:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -63,6 +63,8 @@ tags:
|
||||
description: Methods used to gather advanced information about your network
|
||||
- name: "Actions"
|
||||
description: Methods used to trigger certain actions on your Pi-hole
|
||||
- name: "PADD"
|
||||
description: Methods used to query Pi-hole from PADD
|
||||
|
||||
|
||||
|
||||
@@ -241,6 +243,9 @@ paths:
|
||||
/network/gateway:
|
||||
$ref: 'network.yaml#/components/paths/gateway'
|
||||
|
||||
/network/routes:
|
||||
$ref: 'network.yaml#/components/paths/routes'
|
||||
|
||||
/network/interfaces:
|
||||
$ref: 'network.yaml#/components/paths/interfaces'
|
||||
|
||||
@@ -271,6 +276,9 @@ paths:
|
||||
/docs:
|
||||
$ref: 'docs.yaml#/components/paths/docs'
|
||||
|
||||
/padd:
|
||||
$ref: 'padd.yaml#/components/paths/padd'
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
query_sid:
|
||||
|
||||
@@ -10,6 +10,10 @@ components:
|
||||
operationId: "get_gateway"
|
||||
description: |
|
||||
This API hook returns infos about the gateway of your Pi-hole.
|
||||
|
||||
If the optional parameter `detailed` is set to `true`, the response will include detailed information about the individual interfaces and routes. Note that the available information is dependent on the interface type and state.
|
||||
parameters:
|
||||
- $ref: 'network.yaml#/components/parameters/devices/detailed'
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
@@ -27,14 +31,47 @@ components:
|
||||
allOf:
|
||||
- $ref: 'common.yaml#/components/errors/unauthorized'
|
||||
- $ref: 'common.yaml#/components/schemas/took'
|
||||
routes:
|
||||
get:
|
||||
summary: Get info about the routes of your Pi-hole
|
||||
tags:
|
||||
- "Network information"
|
||||
operationId: "get_routes"
|
||||
parameters:
|
||||
- $ref: 'network.yaml#/components/parameters/devices/detailed'
|
||||
description: |
|
||||
This API hook returns infos about the networking routes of your Pi-hole. Note that not all described fields are applicable to any routing type. Users must not rely on the presence of any field without checking the route type first.
|
||||
|
||||
If the optional parameter `detailed` is set to `true`, the response will include more detailed information about the individual routes where the available information is dependent on the route type and state.
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'network.yaml#/components/schemas/routes'
|
||||
- $ref: 'common.yaml#/components/schemas/took'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'common.yaml#/components/errors/unauthorized'
|
||||
- $ref: 'common.yaml#/components/schemas/took'
|
||||
interfaces:
|
||||
get:
|
||||
summary: Get info about the interfaces of your Pi-hole
|
||||
tags:
|
||||
- "Network information"
|
||||
operationId: "get_interfaces"
|
||||
parameters:
|
||||
- $ref: 'network.yaml#/components/parameters/devices/detailed'
|
||||
description: |
|
||||
This API hook returns infos about the networking interfaces of your Pi-hole.
|
||||
This API hook returns infos about the networking interfaces of your Pi-hole. Note that not all described fields are applicable to any routing type. Users must not rely on the presence of any field without checking the route type first.
|
||||
|
||||
If the optional parameter `detailed` is set to `true`, the response will include more detailed information about the individual interfaces where the available information is dependent on the interface type and state.
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
@@ -119,14 +156,144 @@ components:
|
||||
gateway:
|
||||
type: object
|
||||
properties:
|
||||
address:
|
||||
type: string
|
||||
description: Address of the gateway
|
||||
example: "192.168.0.1"
|
||||
interface:
|
||||
type: string
|
||||
description: Interface of your Pi-hole connected to the gateway
|
||||
example: "eth0"
|
||||
gateway:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
family:
|
||||
type: string
|
||||
description: Address family
|
||||
interface:
|
||||
type: string
|
||||
description: Interface name
|
||||
address:
|
||||
type: string
|
||||
description: Gateway address
|
||||
local:
|
||||
type: array
|
||||
description: Local interface addresses
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
- family: "inet"
|
||||
interface: "eth0"
|
||||
address: "192.168.0.1"
|
||||
local:
|
||||
- "192.168.0.22"
|
||||
- family: "inet6"
|
||||
interface: "eth0"
|
||||
address: "fe80::3587:2fff:f11a:1"
|
||||
local:
|
||||
- "fe80::3587:2fff:f11a:4321"
|
||||
routes:
|
||||
type: object
|
||||
properties:
|
||||
routes:
|
||||
type: array
|
||||
description: Array of routes
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
gateway:
|
||||
type: string
|
||||
description: Gateway address
|
||||
family:
|
||||
type: string
|
||||
enum: [ "inet", "inet6", "link", "mpls", "bridge", "???" ]
|
||||
description: Address family
|
||||
table:
|
||||
type: integer
|
||||
description: Routing table ID (0 = unspecified, 253 = default, 254 = local, 255 = local, other = user-defined)
|
||||
protocol:
|
||||
type: string
|
||||
description: Routing protocol
|
||||
scope:
|
||||
type: string
|
||||
description: Routing scope
|
||||
type:
|
||||
type: string
|
||||
description: Routing type
|
||||
flags:
|
||||
type: array
|
||||
description: Array of route flags
|
||||
items:
|
||||
type: string
|
||||
oif:
|
||||
type: string
|
||||
description: Outgoing interface
|
||||
iif:
|
||||
type: string
|
||||
description: Incoming interface
|
||||
dst:
|
||||
type: string
|
||||
description: Destination address (or "default" for the default route)
|
||||
src:
|
||||
type: string
|
||||
description: Source address
|
||||
prefsrc:
|
||||
type: string
|
||||
description: Preferred source address
|
||||
priority:
|
||||
type: integer
|
||||
description: Route priority
|
||||
pref:
|
||||
type: integer
|
||||
description: Route preference
|
||||
|
||||
example:
|
||||
- family: "inet"
|
||||
table: 254
|
||||
protocol: "static"
|
||||
scope: "universe"
|
||||
type: "unicast"
|
||||
flags: []
|
||||
gateway: "192.168.0.1"
|
||||
oif: "eth0"
|
||||
- family: "inet"
|
||||
table: 254
|
||||
protocol: "boot"
|
||||
scope: "link"
|
||||
type: "unicast"
|
||||
flags: []
|
||||
dst: "10.1.0.0"
|
||||
oif: "wg0"
|
||||
- family: "inet"
|
||||
table: 255
|
||||
protocol: "kernel"
|
||||
scope: "host"
|
||||
type: "local"
|
||||
flags: []
|
||||
dst: "127.0.0.1"
|
||||
prefsrc: "127.0.0.1"
|
||||
oif: "lo"
|
||||
- family: "inet6"
|
||||
table: 255
|
||||
protocol: "kernel"
|
||||
scope: "universe"
|
||||
type: "local"
|
||||
flags: []
|
||||
dst: "::1"
|
||||
priority: 0
|
||||
oif: "eth0"
|
||||
- family: "inet6"
|
||||
table: 254
|
||||
protocol: "static"
|
||||
scope: "universe"
|
||||
type: "unicast"
|
||||
flags: []
|
||||
gateway: "fe80::3587:2fff:f11a:4321"
|
||||
oif: "eth0"
|
||||
- family: "inet6"
|
||||
table: 255
|
||||
protocol: "kernel"
|
||||
scope: "universe"
|
||||
type: "multicast"
|
||||
flags: []
|
||||
dst: "fd00:4711::"
|
||||
priority: 5
|
||||
oif: "wg0"
|
||||
|
||||
interfaces:
|
||||
type: object
|
||||
properties:
|
||||
@@ -138,84 +305,259 @@ components:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Interface name
|
||||
default:
|
||||
type: boolean
|
||||
description: If the interface is the default gateway
|
||||
carrier:
|
||||
type: boolean
|
||||
description: If the interface is connected
|
||||
speed:
|
||||
type: integer
|
||||
description: Speed of the interface in Mbit/s (-1 if not applicable)
|
||||
tx:
|
||||
type: object
|
||||
properties:
|
||||
num:
|
||||
type: number
|
||||
description: Number of transmitted data since boot
|
||||
unit:
|
||||
type: string
|
||||
description: Unit of transmitted data since boot
|
||||
rx:
|
||||
type: object
|
||||
properties:
|
||||
num:
|
||||
type: number
|
||||
description: Number of received data since boot
|
||||
unit:
|
||||
type: string
|
||||
description: Unit of received data since boot
|
||||
ipv4:
|
||||
type: array
|
||||
nullable: true
|
||||
description: Array of associated IPv4 addresses
|
||||
description: Speed of the interface in Mbit/s (`null` if not applicable)
|
||||
carrier:
|
||||
type: boolean
|
||||
description: Whether the interface is connected
|
||||
type:
|
||||
type: string
|
||||
description: Type of the interface
|
||||
flags:
|
||||
type: array
|
||||
description: Array of address flags
|
||||
items:
|
||||
type: string
|
||||
ipv6:
|
||||
state:
|
||||
type: string
|
||||
description: State of the interface
|
||||
proto_down:
|
||||
type: boolean
|
||||
description: Whether the interface is administratively down
|
||||
address:
|
||||
type: string
|
||||
description: Interface hardware address
|
||||
broadcast:
|
||||
type: string
|
||||
description: Interface broadcast address
|
||||
perm_address:
|
||||
type: string
|
||||
description: Interface permanent hardware address
|
||||
stats:
|
||||
type: object
|
||||
properties:
|
||||
rx_bytes:
|
||||
type: object
|
||||
description: Interface received bytes
|
||||
properties:
|
||||
value:
|
||||
type: number
|
||||
description: Number of received bytes
|
||||
unit:
|
||||
type: string
|
||||
description: Unit of the received bytes
|
||||
tx_bytes:
|
||||
type: object
|
||||
description: Interface transmitted bytes
|
||||
properties:
|
||||
value:
|
||||
type: number
|
||||
description: Number of transmitted bytes
|
||||
unit:
|
||||
type: string
|
||||
description: Unit of the transmitted bytes
|
||||
addresses:
|
||||
type: array
|
||||
nullable: true
|
||||
description: Array of associated IPv6 addresses
|
||||
description: Array of associated IPv addresses
|
||||
items:
|
||||
type: string
|
||||
type: object
|
||||
properties:
|
||||
address:
|
||||
type: string
|
||||
description: Interface address
|
||||
address_type:
|
||||
type: string
|
||||
description: Type of the interface address
|
||||
broadcast:
|
||||
type: string
|
||||
description: Interface broadcast address
|
||||
broadcast_type:
|
||||
type: string
|
||||
description: Type of the broadcast address
|
||||
local:
|
||||
type: string
|
||||
description: Local address
|
||||
local_type:
|
||||
type: string
|
||||
description: Type of the local address
|
||||
label:
|
||||
type: string
|
||||
description: Interface label
|
||||
family:
|
||||
type: string
|
||||
enum: [ "inet", "inet6", "link", "mpls", "bridge", "???" ]
|
||||
description: Address family
|
||||
flags:
|
||||
type: array
|
||||
description: Array of address flags
|
||||
items:
|
||||
type: string
|
||||
prefixlen:
|
||||
type: integer
|
||||
description: Prefix length of the interface address
|
||||
scope:
|
||||
type: string
|
||||
description: Address scope
|
||||
prefered:
|
||||
type: integer
|
||||
description: Preferred lifetime of the address (`4294967295` = forever)
|
||||
valid:
|
||||
type: integer
|
||||
description: Valid lifetime of the address (`4294967295` = forever)
|
||||
cstamp:
|
||||
type: number
|
||||
description: Creation timestamp of the address
|
||||
tstamp:
|
||||
type: number
|
||||
description: Updated timestamp of the address
|
||||
example:
|
||||
- name: "lo"
|
||||
speed: null
|
||||
type: "loopback"
|
||||
flags: [ "up", "loopback", "running", "lower_up" ]
|
||||
state: "unknown"
|
||||
carrier: true
|
||||
address: "00:00:00:00:00:00"
|
||||
broadcast: "00:00:00:00:00:00"
|
||||
stats:
|
||||
rx_bytes:
|
||||
value: 81.6571641
|
||||
unit: "MB"
|
||||
tx_bytes:
|
||||
value: 648.818
|
||||
unit: "MB"
|
||||
addresses:
|
||||
- address: "127.0.0.1"
|
||||
address_type: "loopback"
|
||||
local: "127.0.0.1"
|
||||
local_type: "loopback"
|
||||
family: "inet"
|
||||
scope: "host"
|
||||
flags: [ "permanent" ]
|
||||
prefixlen: 8
|
||||
label: "lo"
|
||||
prefered: 4294967295
|
||||
valid: 4294967295
|
||||
cstamp: 1720989931
|
||||
tstamp: 1720989931
|
||||
- address: "::1"
|
||||
address_type: "loopback"
|
||||
local: "::1"
|
||||
local_type: "loopback"
|
||||
family: "inet6"
|
||||
scope: "host"
|
||||
flags: [ "permanent" ]
|
||||
prefixlen: 128
|
||||
label: "lo"
|
||||
prefered: 4294967295
|
||||
valid: 4294967295
|
||||
cstamp: 1720989931.1
|
||||
tstamp: 1720989931.1
|
||||
- name: "eth0"
|
||||
default: true
|
||||
carrier: true
|
||||
speed: 1000
|
||||
tx:
|
||||
num: 10.4
|
||||
unit: "MB"
|
||||
rx:
|
||||
num: 8.1
|
||||
unit: "MB"
|
||||
ipv4: ["192.168.0.123"]
|
||||
ipv6: ["fe80::1234:5678:9abc:def0", "2001:db8::1234:5678:9abc:def0"]
|
||||
- name: "wlan0"
|
||||
default: false
|
||||
carrier: false
|
||||
speed: -1
|
||||
tx:
|
||||
num: 0
|
||||
unit: "B"
|
||||
rx:
|
||||
num: 0
|
||||
unit: "B"
|
||||
ipv4: []
|
||||
ipv6: []
|
||||
- name: "wg0"
|
||||
default: false
|
||||
type: "ether"
|
||||
flags: [ "up", "broadcast", "running", "multicast", "lower_up" ]
|
||||
state: "up"
|
||||
carrier: true
|
||||
speed: -1
|
||||
tx:
|
||||
num: 170.3
|
||||
unit: "kB"
|
||||
rx:
|
||||
num: 222.3
|
||||
unit: "kB"
|
||||
ipv4: ["10.1.0.1"]
|
||||
ipv6: ["fd00:4711::1"]
|
||||
address: "00:11:22:33:44:55"
|
||||
broadcast: "ff:ff:ff:ff:ff:ff"
|
||||
perm_address: "00:11:22:33:44:55"
|
||||
stats:
|
||||
rx_bytes:
|
||||
value: 15.5585
|
||||
unit: "GB"
|
||||
tx_bytes:
|
||||
value: 1.55858
|
||||
unit: "GB"
|
||||
addresses:
|
||||
- address: "192.168.0.123"
|
||||
address_type: "private"
|
||||
local: "192.168.0.123"
|
||||
local_type: "private"
|
||||
family: "inet"
|
||||
scope: "universe"
|
||||
flags: [ "permanent" ]
|
||||
prefixlen: 24
|
||||
label: "eth0"
|
||||
prefered: 4294967295
|
||||
valid: 4294967295
|
||||
cstamp: 1720989931.1
|
||||
tstamp: 1720989931.1
|
||||
- address: "2001:db8::1234:5678:9abc:def0"
|
||||
address_type: "global (GUA)"
|
||||
family: "inet6"
|
||||
scope: "universe"
|
||||
flags: []
|
||||
prefixlen: 64
|
||||
label: "eth0"
|
||||
prefered: 3461
|
||||
valid: 7061
|
||||
cstamp: 2789057.25
|
||||
tstamp: 2789057.25
|
||||
- address: "fd29:db8::1234:5678:9abc:def0"
|
||||
address_type: "site-local (ULA)"
|
||||
family: "inet6"
|
||||
scope: "universe"
|
||||
flags: []
|
||||
prefixlen: 64
|
||||
label: "eth0"
|
||||
prefered: 3461
|
||||
valid: 7061
|
||||
cstamp: 1720989931.1
|
||||
tstamp: 1720989931.1
|
||||
- address: "fe80::1234:5678:9abc:def0"
|
||||
address_type: "link-local (LL)"
|
||||
family: "inet6"
|
||||
scope: "link"
|
||||
flags: [ "permanent" ]
|
||||
prefixlen: 64
|
||||
label: "eth0"
|
||||
prefered: 4294967295
|
||||
valid: 4294967295
|
||||
cstamp: 1720989931.1
|
||||
tstamp: 1720989931.1
|
||||
- name: "wg0"
|
||||
speed: null
|
||||
type: "none"
|
||||
flags: [ "up", "pointopoint", "running", "noarp", "lower_up" ]
|
||||
state: "unknown"
|
||||
carrier: true
|
||||
stats:
|
||||
rx_bytes:
|
||||
value: 458.44598
|
||||
unit: "MB"
|
||||
tx_bytes:
|
||||
value: 5.5895
|
||||
unit: "MB"
|
||||
addresses:
|
||||
- address: "10.1.0.1"
|
||||
address_type: "private"
|
||||
local: "10.1.0.1"
|
||||
local_type: "private"
|
||||
family: "inet"
|
||||
scope: "universe"
|
||||
flags: [ "permanent" ]
|
||||
prefixlen: 24
|
||||
label: "wg0"
|
||||
prefered: 4294967295
|
||||
valid: 4294967295
|
||||
cstamp: 1720989931.1
|
||||
tstamp: 1720989931.1
|
||||
- address: "fd00:4711::1"
|
||||
address_type: "site-local (ULA)"
|
||||
family: "inet6"
|
||||
scope: "global"
|
||||
flags: [ "permanent" ]
|
||||
prefixlen: 64
|
||||
label: "wg0"
|
||||
prefered: 4294967295
|
||||
valid: 4294967295
|
||||
cstamp: 1720989931.1
|
||||
tstamp: 1720989931.1
|
||||
devices:
|
||||
type: object
|
||||
properties:
|
||||
@@ -304,3 +646,11 @@ components:
|
||||
type: integer
|
||||
required: true
|
||||
example: 1
|
||||
detailed:
|
||||
in: query
|
||||
description: (Optional) Detailed interface/routing information
|
||||
name: detailed
|
||||
schema:
|
||||
type: boolean
|
||||
required: false
|
||||
example: false
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
openapi: 3.0.2
|
||||
components:
|
||||
paths:
|
||||
padd:
|
||||
get:
|
||||
summary: Get summarized data for PADD
|
||||
tags:
|
||||
- "PADD"
|
||||
operationId: "get_padd"
|
||||
parameters:
|
||||
- in: query
|
||||
description: (Optional) Return full data
|
||||
name: full
|
||||
schema:
|
||||
type: boolean
|
||||
required: false
|
||||
example: true
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'padd.yaml#/components/schemas/padd'
|
||||
- $ref: 'info.yaml#/components/schemas/system'
|
||||
- $ref: 'info.yaml#/components/schemas/version'
|
||||
- $ref: 'common.yaml#/components/schemas/took'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: 'common.yaml#/components/errors/unauthorized'
|
||||
- $ref: 'common.yaml#/components/schemas/took'
|
||||
|
||||
schemas:
|
||||
padd:
|
||||
type: object
|
||||
properties:
|
||||
recent_blocked:
|
||||
type: string
|
||||
description: "Most recent blocked domain"
|
||||
nullable: true
|
||||
example: "bad.example.com"
|
||||
top_domain:
|
||||
type: string
|
||||
description: "Most requested domain"
|
||||
nullable: true
|
||||
example: "good.example.com"
|
||||
top_blocked:
|
||||
type: string
|
||||
description: "Most blocked domain"
|
||||
nullable: true
|
||||
example: "bad.example.com"
|
||||
top_client:
|
||||
type: string
|
||||
description: "Most active client"
|
||||
nullable: true
|
||||
example: "localhost"
|
||||
active_clients:
|
||||
type: integer
|
||||
description: "Number of active clients"
|
||||
example: 22
|
||||
gravity_size:
|
||||
type: integer
|
||||
description: "Gravity list size"
|
||||
example: 225382
|
||||
blocking:
|
||||
type: string
|
||||
description: "Blocking status"
|
||||
example: "enabled"
|
||||
queries:
|
||||
type: object
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
description: "Total number of queries within the last 24 hours"
|
||||
example: 92258
|
||||
blocked:
|
||||
type: integer
|
||||
description: "Number of blocked queries"
|
||||
example: 4784
|
||||
percent_blocked:
|
||||
type: number
|
||||
description: "Percentage of blocked queries"
|
||||
example: 5.18
|
||||
cache:
|
||||
type: object
|
||||
properties:
|
||||
size:
|
||||
type: integer
|
||||
description: "Total cache size"
|
||||
example: 10000
|
||||
inserted:
|
||||
type: integer
|
||||
description: "Number of inserted cache entries"
|
||||
example: 233
|
||||
evicted:
|
||||
type: integer
|
||||
description: "Number of evicted cache entries"
|
||||
example: 0
|
||||
iface:
|
||||
type: object
|
||||
description: "Default interfaces"
|
||||
properties:
|
||||
v4:
|
||||
type: object
|
||||
description: "IPv4 interface"
|
||||
properties:
|
||||
addr:
|
||||
type: string
|
||||
description: "Primary address"
|
||||
nullable: true # there may be no IPv4 address
|
||||
example: "192.168.2.11"
|
||||
rx_bytes:
|
||||
type: object
|
||||
description: "Received bytes"
|
||||
properties:
|
||||
value:
|
||||
type: number
|
||||
example: 76.46
|
||||
unit:
|
||||
type: string
|
||||
example: "G"
|
||||
tx_bytes:
|
||||
type: object
|
||||
description: "Transmitted bytes"
|
||||
properties:
|
||||
value:
|
||||
type: number
|
||||
example: 68.58
|
||||
unit:
|
||||
type: string
|
||||
example: "G"
|
||||
num_addrs:
|
||||
type: integer
|
||||
description: "Number of addresses on the interface"
|
||||
example: 1
|
||||
name:
|
||||
type: string
|
||||
description: "Interface name"
|
||||
example: "eth0"
|
||||
gw_addr:
|
||||
type: string
|
||||
description: "Gateway address"
|
||||
nullable: true # there may be no IPv4 gateway
|
||||
example: "192.168.2.1"
|
||||
v6:
|
||||
type: object
|
||||
description: "IPv6 interface"
|
||||
properties:
|
||||
addr:
|
||||
type: string
|
||||
description: "Primary address"
|
||||
nullable: true # there may be no IPv6 address
|
||||
example: "fe80::b0e4:1b1e:7b7d:5855"
|
||||
num_addrs:
|
||||
type: integer
|
||||
description: "Number of addresses on the interface"
|
||||
example: 3
|
||||
name:
|
||||
type: string
|
||||
description: "Interface name"
|
||||
example: "eth0"
|
||||
gw_addr:
|
||||
type: string
|
||||
description: "Gateway address"
|
||||
nullable: true # there may be no IPv6 gateway
|
||||
example: "fe80::b0e4:1b1e:7b7d:1b1e"
|
||||
node_name:
|
||||
type: string
|
||||
description: "Pi-hole host's name"
|
||||
example: "pihole"
|
||||
host_model:
|
||||
type: string
|
||||
description: "Host model"
|
||||
example: "Raspberry Pi 3 Model B Plus Rev 1.3"
|
||||
nullable: true
|
||||
config:
|
||||
type: object
|
||||
description: "Pi-hole configuration (excerpt)"
|
||||
properties:
|
||||
dhcp_active:
|
||||
type: boolean
|
||||
description: "DHCP server status"
|
||||
example: true
|
||||
dhcp_start:
|
||||
type: string
|
||||
description: "DHCP start address"
|
||||
example: "192.168.0.1"
|
||||
dhcp_end:
|
||||
type: string
|
||||
description: "DHCP end address"
|
||||
example: "192.168.0.254"
|
||||
dhcp_ipv6:
|
||||
type: boolean
|
||||
description: "DHCPv6 server status"
|
||||
example: false
|
||||
dns_domain:
|
||||
type: string
|
||||
description: "DNS domain"
|
||||
example: "lan"
|
||||
dns_port:
|
||||
type: integer
|
||||
description: "DNS port"
|
||||
example: 53
|
||||
dns_num_upstreams:
|
||||
type: integer
|
||||
description: "Number of upstream DNS servers"
|
||||
example: 1
|
||||
dns_dnssec:
|
||||
type: boolean
|
||||
description: "DNSSEC status"
|
||||
example: true
|
||||
dns_revServer_active:
|
||||
type: boolean
|
||||
description: "Reverse DNS server status"
|
||||
example: false
|
||||
"%cpu":
|
||||
type: number
|
||||
description: "CPU usage"
|
||||
example: 0.0
|
||||
"%mem":
|
||||
type: number
|
||||
description: "Memory usage"
|
||||
example: 1.5
|
||||
pid:
|
||||
type: integer
|
||||
description: "FTL's process ID"
|
||||
example: 1639
|
||||
sensors:
|
||||
type: object
|
||||
properties:
|
||||
cpu_temp:
|
||||
type: number
|
||||
description: "CPU temperature"
|
||||
nullable: true
|
||||
example: 45.0
|
||||
hot_limit:
|
||||
type: number
|
||||
description: "Temperature limit"
|
||||
example: 80.0
|
||||
unit:
|
||||
type: string
|
||||
description: "Temperature unit"
|
||||
example: "C"
|
||||
@@ -18,7 +18,6 @@ components:
|
||||
The optional parameters `N` and `partial` limit the maximum number of returned records and whether partial matches should be returned, respectively.
|
||||
There is a hard upper limit of `N` defined in FTL (currently set to 10,000) to ensure that the response is not too large.
|
||||
ABP matches are not returned when partial matching is requested.
|
||||
Depending on the value of the config option webserver.api.searchAPIauth, local clients may not need to authenticate for this endpoint.
|
||||
International domains names (IDNs) are internally converted to punycode before matching.
|
||||
responses:
|
||||
'200':
|
||||
|
||||
@@ -338,6 +338,10 @@ components:
|
||||
type: integer
|
||||
description: Number of queries replied to from cache or local configuration
|
||||
example: 9765
|
||||
frequency:
|
||||
type: number
|
||||
description: Average number of queries per second
|
||||
example: 1.1
|
||||
types:
|
||||
type: object
|
||||
description: Number of individual queries
|
||||
@@ -482,6 +486,10 @@ components:
|
||||
type: integer
|
||||
description: Type CACHE_STALE queries
|
||||
example: 0
|
||||
EXTERNAL_BLOCKED_EDE15:
|
||||
type: integer
|
||||
description: Type EXTERNAL_BLOCKED_EDE15 queries
|
||||
example: 0
|
||||
replies:
|
||||
type: object
|
||||
description: Number of individual replies
|
||||
@@ -560,6 +568,10 @@ components:
|
||||
type: integer
|
||||
description: Number of domain on your Pi-hole's gravity list
|
||||
example: 104756
|
||||
last_update:
|
||||
type: integer
|
||||
description: Unix timestamp of last gravity update (may be `0` if unknown)
|
||||
example: 1725194639
|
||||
upstreams:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -42,6 +42,50 @@ components:
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
import:
|
||||
type: object
|
||||
nullable: true
|
||||
properties:
|
||||
config:
|
||||
type: boolean
|
||||
description: "Import Pi-hole configuration"
|
||||
example: true
|
||||
dhcp_leases:
|
||||
type: boolean
|
||||
description: "Import Pi-hole DHCP leases"
|
||||
example: true
|
||||
gravity:
|
||||
type: object
|
||||
properties:
|
||||
group:
|
||||
type: boolean
|
||||
description: "Import Pi-hole's groups table"
|
||||
example: true
|
||||
adlist:
|
||||
type: boolean
|
||||
description: "Import Pi-hole's adlist table"
|
||||
example: true
|
||||
adlist_by_group:
|
||||
type: boolean
|
||||
description: "Import Pi-hole's table relating adlist entries to groups"
|
||||
example: true
|
||||
domainlist:
|
||||
type: boolean
|
||||
description: "Import Pi-hole's domainlist table"
|
||||
example: true
|
||||
domainlist_by_group:
|
||||
type: boolean
|
||||
description: "Import Pi-hole's table relating domainlist entries to groups"
|
||||
example: true
|
||||
client:
|
||||
type: boolean
|
||||
description: "Import Pi-hole's client table"
|
||||
example: true
|
||||
client_by_group:
|
||||
type: boolean
|
||||
description: "Import Pi-hole's table relating client entries to groups"
|
||||
example: true
|
||||
description: "A JSON object of files to import. If omitted, all files will be imported."
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
@@ -106,4 +150,6 @@ components:
|
||||
value:
|
||||
processed:
|
||||
- etc/pihole/pihole.toml
|
||||
- etc/pihole/gravity.db
|
||||
- etc/pihole/gravity.db->group
|
||||
- etc/pihole/gravity.db->adlist
|
||||
- etc/pihole/gravity.db->adlist_by_group
|
||||
|
||||
@@ -132,6 +132,10 @@ static const unsigned char specs_action_yaml[] = {
|
||||
#include "hex/specs/action.yaml"
|
||||
};
|
||||
|
||||
static const unsigned char specs_padd_yaml[] = {
|
||||
#include "hex/specs/padd.yaml"
|
||||
};
|
||||
|
||||
struct {
|
||||
const char *path;
|
||||
const char *mime_type;
|
||||
@@ -168,6 +172,7 @@ struct {
|
||||
{"specs/stats.yaml", "text/plain", (const char*)specs_stats_yaml, sizeof(specs_stats_yaml)},
|
||||
{"specs/teleporter.yaml", "text/plain", (const char*)specs_teleporter_yaml, sizeof(specs_teleporter_yaml)},
|
||||
{"specs/action.yaml", "text/plain", (const char*)specs_action_yaml, sizeof(specs_action_yaml)},
|
||||
{"specs/padd.yaml", "text/plain", (const char*)specs_padd_yaml, sizeof(specs_padd_yaml)},
|
||||
};
|
||||
|
||||
#endif // API_DOCS_H
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
#include "overTime.h"
|
||||
// config struct
|
||||
#include "config/config.h"
|
||||
// read_setupVarsconf()
|
||||
#include "config/setupVars.h"
|
||||
// get_aliasclient_list()
|
||||
#include "database/aliasclients.h"
|
||||
|
||||
|
||||
+45
-15
@@ -12,7 +12,7 @@
|
||||
#include "webserver/http-common.h"
|
||||
#include "webserver/json_macros.h"
|
||||
#include "api/api.h"
|
||||
// sysinfo()
|
||||
// sysinfo(), get_nprocs_conf()
|
||||
#include <sys/sysinfo.h>
|
||||
// get_blockingstatus()
|
||||
#include "config/setupVars.h"
|
||||
@@ -157,9 +157,13 @@ int api_info_database(struct ftl_conn *api)
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
static int get_system_obj(struct ftl_conn *api, cJSON *system)
|
||||
int get_system_obj(struct ftl_conn *api, cJSON *system)
|
||||
{
|
||||
const int nprocs = get_nprocs();
|
||||
// Use total number of processors
|
||||
// This difference is important for virtualized systems where the number
|
||||
// of available (= online) processors can be lower than the total number
|
||||
// (= configured) of processors
|
||||
const int nprocs = get_nprocs_conf();
|
||||
struct sysinfo info;
|
||||
if(sysinfo(&info) != 0)
|
||||
return send_json_error(api, 500, "error", strerror(errno), NULL);
|
||||
@@ -465,7 +469,7 @@ static int get_hwmon_sensors(struct ftl_conn *api, cJSON *sensors)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static cJSON *read_sys_property(const char *path)
|
||||
cJSON *read_sys_property(const char *path)
|
||||
{
|
||||
if(!file_exists(path))
|
||||
return cJSON_CreateNull();
|
||||
@@ -540,10 +544,13 @@ static int get_ftl_obj(struct ftl_conn *api, cJSON *ftl)
|
||||
const int db_groups = counters->database.groups;
|
||||
const int db_lists = counters->database.lists;
|
||||
const int db_clients = counters->database.clients;
|
||||
const int db_allowed = counters->database.domains.allowed;
|
||||
const int db_denied = counters->database.domains.denied;
|
||||
const int db_allowed_exact = counters->database.domains.allowed.exact;
|
||||
const int db_denied_exact = counters->database.domains.denied.exact;
|
||||
const int db_allowed_regex = counters->database.domains.allowed.regex;
|
||||
const int db_denied_regex = counters->database.domains.denied.regex;
|
||||
const int clients_total = counters->clients;
|
||||
const int privacylevel = config.misc.privacylevel.v.privacy_level;
|
||||
const double qps = get_qps();
|
||||
|
||||
// unique_clients: count only clients that have been active within the most recent 24 hours
|
||||
int activeclients = 0;
|
||||
@@ -565,12 +572,18 @@ static int get_ftl_obj(struct ftl_conn *api, cJSON *ftl)
|
||||
JSON_ADD_NUMBER_TO_OBJECT(database, "clients", db_clients);
|
||||
|
||||
cJSON *domains = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(domains, "allowed", db_allowed);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(domains, "denied", db_denied);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(domains, "allowed", db_allowed_exact);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(domains, "denied", db_denied_exact);
|
||||
JSON_ADD_ITEM_TO_OBJECT(database, "domains", domains);
|
||||
|
||||
cJSON *regex = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(regex, "allowed", db_allowed_regex);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(regex, "denied", db_denied_regex);
|
||||
JSON_ADD_ITEM_TO_OBJECT(database, "regex", regex);
|
||||
JSON_ADD_ITEM_TO_OBJECT(ftl, "database", database);
|
||||
|
||||
JSON_ADD_NUMBER_TO_OBJECT(ftl, "privacy_level", privacylevel);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(ftl, "query_frequency", qps);
|
||||
|
||||
cJSON *clients = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(clients, "total",clients_total);
|
||||
@@ -641,16 +654,15 @@ int api_info_host(struct ftl_conn *api)
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_info_sensors(struct ftl_conn *api)
|
||||
int get_sensors_obj(struct ftl_conn *api, cJSON *sensors, const bool add_list)
|
||||
{
|
||||
cJSON *sensors = JSON_NEW_OBJECT();
|
||||
|
||||
// Get sensors array
|
||||
cJSON *list = JSON_NEW_ARRAY();
|
||||
int ret = get_hwmon_sensors(api, list);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
JSON_ADD_ITEM_TO_OBJECT(sensors, "list", list);
|
||||
if(add_list)
|
||||
JSON_ADD_ITEM_TO_OBJECT(sensors, "list", list);
|
||||
|
||||
// Loop over available sensors and try to identify the most suitable CPU temperature sensor
|
||||
int cpu_temp_sensor = -1;
|
||||
@@ -708,12 +720,25 @@ int api_info_sensors(struct ftl_conn *api)
|
||||
unit = "K";
|
||||
JSON_REF_STR_IN_OBJECT(sensors, "unit", unit);
|
||||
|
||||
if(!add_list)
|
||||
cJSON_Delete(list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_info_sensors(struct ftl_conn *api)
|
||||
{
|
||||
cJSON *sensors = JSON_NEW_OBJECT();
|
||||
int ret = get_sensors_obj(api, sensors, true);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "sensors", sensors);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_info_version(struct ftl_conn *api)
|
||||
int get_version_obj(struct ftl_conn *api, cJSON *version)
|
||||
{
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
@@ -802,8 +827,6 @@ int api_info_version(struct ftl_conn *api)
|
||||
JSON_REF_STR_IN_OBJECT(ftl_local, "version", get_FTL_version());
|
||||
JSON_REF_STR_IN_OBJECT(ftl_local, "date", GIT_DATE);
|
||||
|
||||
cJSON *version = JSON_NEW_OBJECT();
|
||||
|
||||
cJSON *core = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NULL_IF_NOT_EXISTS(core_local, "branch");
|
||||
JSON_ADD_NULL_IF_NOT_EXISTS(core_local, "version");
|
||||
@@ -839,7 +862,14 @@ int api_info_version(struct ftl_conn *api)
|
||||
JSON_ADD_NULL_IF_NOT_EXISTS(docker, "remote");
|
||||
JSON_ADD_ITEM_TO_OBJECT(version, "docker", docker);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_info_version(struct ftl_conn *api)
|
||||
{
|
||||
// Send reply
|
||||
cJSON *version = JSON_NEW_OBJECT();
|
||||
get_version_obj(api, version);
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "version", version);
|
||||
JSON_SEND_OBJECT(json);
|
||||
|
||||
+13
-27
@@ -19,6 +19,8 @@
|
||||
#include "database/network-table.h"
|
||||
// valid_domain()
|
||||
#include "tools/gravity-parseList.h"
|
||||
// parse_groupIDs()
|
||||
#include "webserver/http-common.h"
|
||||
#include <idn2.h>
|
||||
|
||||
static int api_list_read(struct ftl_conn *api,
|
||||
@@ -96,19 +98,13 @@ static int api_list_read(struct ftl_conn *api,
|
||||
{
|
||||
if(table.group_ids != NULL)
|
||||
{
|
||||
// Black magic at work here: We build a JSON array from
|
||||
// the group_concat result delivered from the database,
|
||||
// parse it as valid array and append it as row to the
|
||||
// data
|
||||
const size_t buflen = strlen(table.group_ids)+3u;
|
||||
char *group_ids_str = calloc(buflen, sizeof(char));
|
||||
group_ids_str[0] = '[';
|
||||
strcpy(group_ids_str+1u , table.group_ids);
|
||||
group_ids_str[buflen-2u] = ']';
|
||||
group_ids_str[buflen-1u] = '\0';
|
||||
cJSON * group_ids = cJSON_Parse(group_ids_str);
|
||||
free(group_ids_str);
|
||||
JSON_ADD_ITEM_TO_OBJECT(row, "groups", group_ids);
|
||||
const int ret = parse_groupIDs(api, &table, row);
|
||||
if(ret != 0)
|
||||
{
|
||||
JSON_DELETE(rows);
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -184,19 +180,9 @@ static int api_list_write(struct ftl_conn *api,
|
||||
tablerow row = { 0 };
|
||||
|
||||
// Check if valid JSON payload is available
|
||||
if (api->payload.json == NULL)
|
||||
{
|
||||
if (api->payload.json_error == NULL)
|
||||
return send_json_error(api, 400,
|
||||
"bad_request",
|
||||
"No request body data",
|
||||
NULL);
|
||||
else
|
||||
return send_json_error(api, 400,
|
||||
"bad_request",
|
||||
"Invalid request body data (no valid JSON), error before hint",
|
||||
api->payload.json_error);
|
||||
}
|
||||
const int json_ret = check_json_payload(api);
|
||||
if(json_ret != 0)
|
||||
return json_ret;
|
||||
|
||||
bool spaces_allowed = false;
|
||||
bool allocated_json = false;
|
||||
@@ -475,7 +461,7 @@ static int api_list_write(struct ftl_conn *api,
|
||||
return send_json_error_free(api, 400, // 400 Bad Request
|
||||
"regex_error",
|
||||
"Regex validation failed",
|
||||
regex_msg, true);
|
||||
regex_msg, true, true);
|
||||
}
|
||||
|
||||
// Try to add item(s) to table
|
||||
|
||||
+106
-257
@@ -24,292 +24,141 @@
|
||||
#include "database/query-table.h"
|
||||
// config struct
|
||||
#include "config/config.h"
|
||||
// PRIx64
|
||||
#include <inttypes.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
// IFA_LINK and friends
|
||||
#include <linux/if_addr.h>
|
||||
// nlroutes(), nladdrs(), nllinks()
|
||||
#include "tools/netlink.h"
|
||||
|
||||
static bool getDefaultInterface(char iface[IF_NAMESIZE], in_addr_t *gw)
|
||||
int get_gateway(struct ftl_conn *api, cJSON * json, const bool detailed)
|
||||
{
|
||||
// Get IPv4 default route gateway and associated interface
|
||||
unsigned long dest_r = 0, gw_r = 0;
|
||||
unsigned int flags = 0u;
|
||||
int metric = 0, minmetric = __INT_MAX__;
|
||||
|
||||
FILE *file;
|
||||
if((file = fopen("/proc/net/route", "r")))
|
||||
// Get routing information
|
||||
cJSON *routes = JSON_NEW_ARRAY();
|
||||
nlroutes(routes, detailed);
|
||||
|
||||
// Get interface information ...
|
||||
cJSON *interfaces = JSON_NEW_ARRAY();
|
||||
nllinks(interfaces, detailed);
|
||||
// ... and enrich them with addresses
|
||||
nladdrs(interfaces, detailed);
|
||||
|
||||
cJSON *gateway = JSON_NEW_ARRAY();
|
||||
// Search through routes for the default gateway
|
||||
// They are the ones with "dst" == "default"
|
||||
cJSON *route = NULL;
|
||||
cJSON_ArrayForEach(route, routes)
|
||||
{
|
||||
// Parse /proc/net/route - the kernel's IPv4 routing table
|
||||
char buf[1024] = { 0 };
|
||||
while(fgets(buf, sizeof(buf), file))
|
||||
cJSON *dst = cJSON_GetObjectItem(route, "dst");
|
||||
if(dst != NULL &&
|
||||
cJSON_IsString(dst) &&
|
||||
strcmp(cJSON_GetStringValue(dst), "default") == 0)
|
||||
{
|
||||
char iface_r[IF_NAMESIZE] = { 0 };
|
||||
if(sscanf(buf, "%15s %lx %lx %x %*i %*i %i", iface_r, &dest_r, &gw_r, &flags, &metric) != 5)
|
||||
continue;
|
||||
cJSON *gwobj = JSON_NEW_OBJECT();
|
||||
|
||||
// Only analyze routes which are UP and whose
|
||||
// destinations are a gateway
|
||||
if(!(flags & RTF_UP) || !(flags & RTF_GATEWAY))
|
||||
continue;
|
||||
// Extract and add family
|
||||
const char *family = cJSON_GetStringValue(cJSON_GetObjectItem(route, "family"));
|
||||
JSON_REF_STR_IN_OBJECT(gwobj, "family", family);
|
||||
|
||||
// Only analyze "catch all" routes (destination 0.0.0.0)
|
||||
if(dest_r != 0)
|
||||
continue;
|
||||
// Extract and add interface name
|
||||
const char *iface_name = cJSON_GetStringValue(cJSON_GetObjectItem(route, "oif"));
|
||||
JSON_COPY_STR_TO_OBJECT(gwobj, "interface", iface_name);
|
||||
|
||||
// Store default gateway, overwrite if we find a route with
|
||||
// a lower metric
|
||||
if(metric < minmetric)
|
||||
// Extract and add gateway address
|
||||
const char *gw_addr = cJSON_GetStringValue(cJSON_GetObjectItem(route, "gateway"));
|
||||
JSON_COPY_STR_TO_OBJECT(gwobj, "address", gw_addr);
|
||||
|
||||
// Extract and add local interface address
|
||||
cJSON *local = JSON_NEW_ARRAY();
|
||||
cJSON *iface = NULL;
|
||||
cJSON_ArrayForEach(iface, interfaces)
|
||||
{
|
||||
minmetric = metric;
|
||||
*gw = gw_r;
|
||||
strcpy(iface, iface_r);
|
||||
const char *ifname = cJSON_GetStringValue(cJSON_GetObjectItem(iface, "name"));
|
||||
if(ifname != NULL && strcmp(ifname, iface_name) == 0)
|
||||
{
|
||||
cJSON *addr = NULL;
|
||||
cJSON *addrs = cJSON_GetObjectItem(iface, "addresses");
|
||||
cJSON_ArrayForEach(addr, addrs)
|
||||
{
|
||||
// Skip addresses belonging to another address family
|
||||
const char *ifamily = cJSON_GetStringValue(cJSON_GetObjectItem(addr, "family"));
|
||||
if(ifamily == NULL || strcmp(ifamily, family) != 0)
|
||||
continue;
|
||||
|
||||
log_debug(DEBUG_API, "Reading interfaces: flags: %u, addr: %s, iface: %s, metric: %i, minmetric: %i",
|
||||
flags, inet_ntoa(*(struct in_addr *) gw), iface, metric, minmetric);
|
||||
const char *addr_str = cJSON_GetStringValue(cJSON_GetObjectItem(addr, "address"));
|
||||
if(addr_str != NULL)
|
||||
JSON_COPY_STR_TO_ARRAY(local, addr_str);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add local addresses array to gateway object
|
||||
JSON_ADD_ITEM_TO_OBJECT(gwobj, "local", local);
|
||||
|
||||
cJSON_AddItemToArray(gateway, gwobj);
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
// Send gateway information
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "gateway", gateway);
|
||||
|
||||
if(detailed)
|
||||
{
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "routes", routes);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "interfaces", interfaces);
|
||||
}
|
||||
else
|
||||
log_err("Cannot read /proc/net/route: %s", strerror(errno));
|
||||
{
|
||||
// Free arrays
|
||||
cJSON_Delete(routes);
|
||||
cJSON_Delete(interfaces);
|
||||
}
|
||||
|
||||
// Return success based on having found the default gateway's address
|
||||
return gw != 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int api_network_gateway(struct ftl_conn *api)
|
||||
{
|
||||
in_addr_t gw = 0;
|
||||
char iface[IF_NAMESIZE] = { 0 };
|
||||
// Get ?detailed parameter
|
||||
bool detailed = false;
|
||||
get_bool_var(api->request->query_string, "detailed", &detailed);
|
||||
|
||||
// Get default interface
|
||||
getDefaultInterface(iface, &gw);
|
||||
|
||||
// Generate JSON response
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
const char *gwaddr = inet_ntoa(*(struct in_addr *) &gw);
|
||||
JSON_COPY_STR_TO_OBJECT(json, "address", gwaddr);
|
||||
JSON_REF_STR_IN_OBJECT(json, "interface", iface);
|
||||
get_gateway(api, json, detailed);
|
||||
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_network_routes(struct ftl_conn *api)
|
||||
{
|
||||
// Get ?detailed parameter
|
||||
bool detailed = false;
|
||||
get_bool_var(api->request->query_string, "detailed", &detailed);
|
||||
|
||||
// Add routing information
|
||||
cJSON *routes = JSON_NEW_ARRAY();
|
||||
nlroutes(routes, detailed);
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "routes", routes);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_network_interfaces(struct ftl_conn *api)
|
||||
{
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
|
||||
// Get interface with default route
|
||||
in_addr_t gw = 0;
|
||||
char default_iface[IF_NAMESIZE] = { 0 };
|
||||
getDefaultInterface(default_iface, &gw);
|
||||
|
||||
// Enumerate and list interfaces
|
||||
// Loop over interfaces and extract information
|
||||
DIR *dfd;
|
||||
FILE *f;
|
||||
struct dirent *dp;
|
||||
size_t tx_sum = 0, rx_sum = 0;
|
||||
char fname[64 + IF_NAMESIZE] = { 0 };
|
||||
char readbuffer[1024] = { 0 };
|
||||
|
||||
// Open /sys/class/net directory
|
||||
if ((dfd = opendir("/sys/class/net")) == NULL)
|
||||
{
|
||||
log_err("API: Cannot access /sys/class/net");
|
||||
return 500;
|
||||
}
|
||||
|
||||
// Get IP addresses of all interfaces on this machine
|
||||
struct ifaddrs *ifap = NULL;
|
||||
if(getifaddrs(&ifap) == -1)
|
||||
log_err("API: Cannot get interface addresses: %s", strerror(errno));
|
||||
// Get ?detailed parameter
|
||||
bool detailed = false;
|
||||
get_bool_var(api->request->query_string, "detailed", &detailed);
|
||||
|
||||
cJSON *interfaces = JSON_NEW_ARRAY();
|
||||
// Walk /sys/class/net directory
|
||||
while ((dp = readdir(dfd)) != NULL)
|
||||
{
|
||||
// Skip "." and ".."
|
||||
if(strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
|
||||
continue;
|
||||
// Get links ...
|
||||
nllinks(interfaces, detailed);
|
||||
// ... and enrich them with addresses
|
||||
nladdrs(interfaces, detailed);
|
||||
|
||||
// Create new interface record
|
||||
cJSON *iface = JSON_NEW_OBJECT();
|
||||
|
||||
// Extract interface name
|
||||
const char *iface_name = dp->d_name;
|
||||
JSON_COPY_STR_TO_OBJECT(iface, "name", iface_name);
|
||||
|
||||
// Is this the default interface?
|
||||
const bool is_default_iface = strcmp(iface_name, default_iface) == 0;
|
||||
JSON_ADD_BOOL_TO_OBJECT(iface, "default", is_default_iface);
|
||||
|
||||
// Extract carrier status
|
||||
bool carrier = false;
|
||||
snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/carrier", iface_name);
|
||||
if((f = fopen(fname, "r")) != NULL)
|
||||
{
|
||||
if(fgets(readbuffer, sizeof(readbuffer)-1, f) != NULL)
|
||||
carrier = readbuffer[0] == '1';
|
||||
fclose(f);
|
||||
}
|
||||
else
|
||||
log_err("Cannot read %s: %s", fname, strerror(errno));
|
||||
JSON_ADD_BOOL_TO_OBJECT(iface, "carrier", carrier);
|
||||
|
||||
// Extract link speed (may not be possible, e.g., for WiFi devices with dynamic link speeds)
|
||||
int speed = -1;
|
||||
snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/speed", iface_name);
|
||||
if((f = fopen(fname, "r")) != NULL)
|
||||
{
|
||||
if(fscanf(f, "%i", &(speed)) != 1)
|
||||
speed = -1;
|
||||
fclose(f);
|
||||
}
|
||||
else
|
||||
log_err("Cannot read %s: %s", fname, strerror(errno));
|
||||
JSON_ADD_NUMBER_TO_OBJECT(iface, "speed", speed);
|
||||
|
||||
// Get total transmitted bytes
|
||||
ssize_t tx_bytes = -1;
|
||||
snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/statistics/tx_bytes", iface_name);
|
||||
if((f = fopen(fname, "r")) != NULL)
|
||||
{
|
||||
if(fscanf(f, "%zi", &(tx_bytes)) != 1)
|
||||
tx_bytes = -1;
|
||||
fclose(f);
|
||||
}
|
||||
else
|
||||
log_err("Cannot read %s: %s", fname, strerror(errno));
|
||||
|
||||
// Format transmitted bytes
|
||||
double tx = 0.0;
|
||||
char tx_unit[3] = { 0 };
|
||||
format_memory_size(tx_unit, tx_bytes, &tx);
|
||||
if(tx_unit[0] != '\0')
|
||||
tx_unit[1] = 'B';
|
||||
|
||||
// Add transmitted bytes to interface record
|
||||
cJSON *tx_json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(tx_json, "num", tx);
|
||||
JSON_COPY_STR_TO_OBJECT(tx_json, "unit", tx_unit);
|
||||
JSON_ADD_ITEM_TO_OBJECT(iface, "tx", tx_json);
|
||||
|
||||
// Get total received bytes
|
||||
ssize_t rx_bytes = -1;
|
||||
snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/statistics/rx_bytes", iface_name);
|
||||
if((f = fopen(fname, "r")) != NULL)
|
||||
{
|
||||
if(fscanf(f, "%zi", &(rx_bytes)) != 1)
|
||||
rx_bytes = -1;
|
||||
fclose(f);
|
||||
}
|
||||
else
|
||||
log_err("Cannot read %s: %s", fname, strerror(errno));
|
||||
|
||||
// Format received bytes
|
||||
double rx = 0.0;
|
||||
char rx_unit[3] = { 0 };
|
||||
format_memory_size(rx_unit, rx_bytes, &rx);
|
||||
if(rx_unit[0] != '\0')
|
||||
rx_unit[1] = 'B';
|
||||
|
||||
// Add received bytes to JSON object
|
||||
cJSON *rx_json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(rx_json, "num", rx);
|
||||
JSON_COPY_STR_TO_OBJECT(rx_json, "unit", rx_unit);
|
||||
JSON_ADD_ITEM_TO_OBJECT(iface, "rx", rx_json);
|
||||
|
||||
// Get IP address(es) of this interface
|
||||
if(ifap)
|
||||
{
|
||||
// Walk through linked list of interface addresses
|
||||
cJSON *ipv4 = JSON_NEW_ARRAY();
|
||||
cJSON *ipv6 = JSON_NEW_ARRAY();
|
||||
for(struct ifaddrs *ifa = ifap; ifa != NULL; ifa = ifa->ifa_next)
|
||||
{
|
||||
// Skip interfaces without an address and those
|
||||
// not matching the current interface
|
||||
if(ifa->ifa_addr == NULL || strcmp(ifa->ifa_name, iface_name) != 0)
|
||||
continue;
|
||||
|
||||
// If we reach this point, we found the correct interface
|
||||
const sa_family_t family = ifa->ifa_addr->sa_family;
|
||||
char host[NI_MAXHOST] = { 0 };
|
||||
if(family == AF_INET || family == AF_INET6)
|
||||
{
|
||||
// Get IP address
|
||||
const int s = getnameinfo(ifa->ifa_addr,
|
||||
(family == AF_INET) ?
|
||||
sizeof(struct sockaddr_in) :
|
||||
sizeof(struct sockaddr_in6),
|
||||
host, NI_MAXHOST,
|
||||
NULL, 0, NI_NUMERICHOST);
|
||||
if (s != 0)
|
||||
{
|
||||
log_warn("API: getnameinfo() failed: %s\n", gai_strerror(s));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(family == AF_INET)
|
||||
{
|
||||
JSON_COPY_STR_TO_ARRAY(ipv4, host);
|
||||
}
|
||||
else if(family == AF_INET6)
|
||||
{
|
||||
JSON_COPY_STR_TO_ARRAY(ipv6, host);
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON_ADD_ITEM_TO_OBJECT(iface, "ipv4", ipv4);
|
||||
JSON_ADD_ITEM_TO_OBJECT(iface, "ipv6", ipv6);
|
||||
}
|
||||
|
||||
// Sum up transmitted and received bytes
|
||||
if(tx_bytes > 0)
|
||||
tx_sum += tx_bytes;
|
||||
if(rx_bytes > 0)
|
||||
rx_sum += rx_bytes;
|
||||
|
||||
// Add interface to array
|
||||
JSON_ADD_ITEM_TO_ARRAY(interfaces, iface);
|
||||
}
|
||||
|
||||
freeifaddrs(ifap);
|
||||
closedir(dfd);
|
||||
|
||||
cJSON *sum = JSON_NEW_OBJECT();
|
||||
JSON_COPY_STR_TO_OBJECT(sum, "name", "sum");
|
||||
JSON_ADD_BOOL_TO_OBJECT(sum, "carrier", true);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(sum, "speed", 0);
|
||||
|
||||
// Format transmitted bytes
|
||||
double tx = 0.0;
|
||||
char tx_unit[3] = { 0 };
|
||||
format_memory_size(tx_unit, tx_sum, &tx);
|
||||
if(tx_unit[0] != '\0')
|
||||
tx_unit[1] = 'B';
|
||||
|
||||
// Add transmitted bytes to interface record
|
||||
cJSON *tx_json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(tx_json, "num", tx);
|
||||
JSON_COPY_STR_TO_OBJECT(tx_json, "unit", tx_unit);
|
||||
JSON_ADD_ITEM_TO_OBJECT(sum, "tx", tx_json);
|
||||
|
||||
// Format received bytes
|
||||
double rx = 0.0;
|
||||
char rx_unit[3] = { 0 };
|
||||
format_memory_size(rx_unit, rx_sum, &rx);
|
||||
if(rx_unit[0] != '\0')
|
||||
rx_unit[1] = 'B';
|
||||
|
||||
// Add received bytes to JSON object
|
||||
cJSON *rx_json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(rx_json, "num", rx);
|
||||
JSON_COPY_STR_TO_OBJECT(rx_json, "unit", rx_unit);
|
||||
JSON_ADD_ITEM_TO_OBJECT(sum, "rx", rx_json);
|
||||
|
||||
cJSON *ipv4 = JSON_NEW_ARRAY();
|
||||
cJSON *ipv6 = JSON_NEW_ARRAY();
|
||||
JSON_ADD_ITEM_TO_OBJECT(sum, "ipv4", ipv4);
|
||||
JSON_ADD_ITEM_TO_OBJECT(sum, "ipv6", ipv6);
|
||||
|
||||
// Add interface to array
|
||||
JSON_ADD_ITEM_TO_ARRAY(interfaces, sum);
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "interfaces", interfaces);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
/* Pi-hole: A black hole for Internet advertisements
|
||||
* (c) 2019 Pi-hole, LLC (https://pi-hole.net)
|
||||
* Network-wide ad blocking via your own hardware.
|
||||
*
|
||||
* FTL Engine
|
||||
* API Implementation /api/dns
|
||||
*
|
||||
* 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 "webserver/http-common.h"
|
||||
#include "webserver/json_macros.h"
|
||||
#include "api.h"
|
||||
// lock_shm() and unlock_shm()
|
||||
#include "shmem.h"
|
||||
// counters
|
||||
#include "datastructure.h"
|
||||
// get_dnsmasq_metrics(&metrics)
|
||||
#include "metrics.h"
|
||||
// get_blockingstatus()
|
||||
#include "config/config.h"
|
||||
// uname()
|
||||
#include <sys/utsname.h>
|
||||
// nlroutes(), nladdrs(), nllinks()
|
||||
#include "tools/netlink.h"
|
||||
// struct proc_mem, getProcessMemory()
|
||||
#include "procps.h"
|
||||
// getcpu_percentage()
|
||||
#include "daemon.h"
|
||||
|
||||
int api_padd(struct ftl_conn *api)
|
||||
{
|
||||
// Parse parameters
|
||||
bool full = true;
|
||||
if(api->request->query_string != NULL)
|
||||
get_bool_var(api->request->query_string, "full", &full);
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
const int total = counters->queries;
|
||||
const int blocked = get_blocked_count();
|
||||
const unsigned int active_clients = get_active_clients();
|
||||
const int num_gravity = counters->database.gravity;
|
||||
|
||||
// If privacy level is set to hide domains, do not return the most
|
||||
// recent blocked domain
|
||||
if(config.misc.privacylevel.v.privacy_level < PRIVACY_HIDE_DOMAINS)
|
||||
{
|
||||
// Find most recently blocked query
|
||||
for(int queryID = counters->queries - 1; queryID > 0 ; queryID--)
|
||||
{
|
||||
const queriesData *query = getQuery(queryID, true);
|
||||
if(query == NULL)
|
||||
continue;
|
||||
|
||||
if(query->flags.blocked)
|
||||
{
|
||||
// Ask subroutine for domain. It may return "hidden" depending on
|
||||
// the privacy settings at the time the query was made
|
||||
const char *domain = getDomainString(query);
|
||||
if(domain == NULL)
|
||||
continue;
|
||||
|
||||
JSON_COPY_STR_TO_OBJECT(json, "recent_blocked", domain);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Add the number of active clients, the size of the gravity list
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "active_clients", active_clients);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "gravity_size", num_gravity);
|
||||
|
||||
cJSON *top_domains = get_top_domains(api, 1, false, true);
|
||||
if(cJSON_GetArraySize(top_domains) == 0)
|
||||
{
|
||||
JSON_ADD_NULL_TO_OBJECT(json, "top_domain");
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON *top_domain = cJSON_GetArrayItem(top_domains, 0);
|
||||
const char *domain = cJSON_GetStringValue(top_domain);
|
||||
JSON_COPY_STR_TO_OBJECT(json, "top_domain", domain);
|
||||
}
|
||||
cJSON_Delete(top_domains);
|
||||
cJSON *top_blocked = get_top_domains(api, 1, true, true);
|
||||
if(cJSON_GetArraySize(top_blocked) == 0)
|
||||
{
|
||||
JSON_ADD_NULL_TO_OBJECT(json, "top_blocked");
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON *top_block = cJSON_GetArrayItem(top_blocked, 0);
|
||||
const char *domain = cJSON_GetStringValue(top_block);
|
||||
JSON_COPY_STR_TO_OBJECT(json, "top_blocked", domain);
|
||||
}
|
||||
cJSON *top_clients = get_top_clients(api, 1, false, true, false, true);
|
||||
if(cJSON_GetArraySize(top_clients) == 0)
|
||||
{
|
||||
JSON_ADD_NULL_TO_OBJECT(json, "top_client");
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON *top_client = cJSON_GetArrayItem(top_clients, 0);
|
||||
const char *client = cJSON_GetStringValue(top_client);
|
||||
JSON_COPY_STR_TO_OBJECT(json, "top_client", client);
|
||||
}
|
||||
|
||||
// Add a null entry if the domain is hidden or there is no recent
|
||||
// blocked domain (e.g. when blocking is disabled)
|
||||
JSON_ADD_NULL_IF_NOT_EXISTS(json, "recent_blocked");
|
||||
|
||||
// Calculate percentage of blocked queries
|
||||
float percent_blocked = 0.0f;
|
||||
// Avoid 1/0 condition
|
||||
if(total > 0)
|
||||
percent_blocked = 1e2f*blocked/total;
|
||||
|
||||
// Add the blocking status
|
||||
const char *blocking = get_blocking_status_str(get_blockingstatus());
|
||||
JSON_REF_STR_IN_OBJECT(json, "blocking", blocking);
|
||||
|
||||
// Add query statistics
|
||||
cJSON *queries = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "total", total);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "blocked", blocked);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "percent_blocked", percent_blocked);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "queries", queries);
|
||||
|
||||
// Add cache statistics
|
||||
cJSON *cache = JSON_NEW_OBJECT();
|
||||
struct metrics metrics = { 0 };
|
||||
get_dnsmasq_metrics(&metrics);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(cache, "size", metrics.dns.cache.size);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(cache, "inserted", metrics.dns.cache.inserted);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(cache, "evicted", metrics.dns.cache.live_freed);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "cache", cache);
|
||||
|
||||
// info/system
|
||||
cJSON *system = JSON_NEW_OBJECT();
|
||||
get_system_obj(api, system);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "system", system);
|
||||
|
||||
// info/host
|
||||
struct utsname un = { 0 };
|
||||
uname(&un);
|
||||
JSON_COPY_STR_TO_OBJECT(json, "node_name", un.nodename);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "host_model", read_sys_property("/sys/firmware/devicetree/base/model"));
|
||||
|
||||
// Expensive calls, do only if full is requested
|
||||
if(full)
|
||||
{
|
||||
// network/gateway
|
||||
cJSON *gateway_ = JSON_NEW_OBJECT();
|
||||
get_gateway(api, gateway_, true);
|
||||
|
||||
cJSON *gateway = cJSON_GetObjectItemCaseSensitive(gateway_, "gateway");
|
||||
cJSON *interfaces = cJSON_GetObjectItemCaseSensitive(gateway_, "interfaces");
|
||||
|
||||
// Loop over gateway and find first entry with family == "inet"
|
||||
cJSON *entry = NULL;
|
||||
const char *gw_v4_name = NULL, *gw_v6_name = NULL;
|
||||
const char *gw_v4_addr = NULL, *gw_v6_addr = NULL;
|
||||
cJSON_ArrayForEach(entry, gateway)
|
||||
{
|
||||
cJSON *family = cJSON_GetObjectItemCaseSensitive(entry, "family");
|
||||
if(gw_v4_name == NULL && strcmp(cJSON_GetStringValue(family), "inet") == 0)
|
||||
{
|
||||
gw_v4_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "interface"));
|
||||
gw_v4_addr = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "address"));
|
||||
}
|
||||
if(gw_v6_name == NULL && strcmp(cJSON_GetStringValue(family), "inet6") == 0)
|
||||
{
|
||||
gw_v6_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "interface"));
|
||||
gw_v6_addr = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "address"));
|
||||
}
|
||||
|
||||
// Break if both addresses are found
|
||||
if(gw_v4_name && gw_v6_name)
|
||||
break;
|
||||
}
|
||||
|
||||
// If no IPv6 gateway is found, use the IPv4 gateway
|
||||
if(gw_v6_name == NULL)
|
||||
gw_v6_name = gw_v4_name;
|
||||
|
||||
// Iterate over all interfaces until we find the one associated
|
||||
// with the IPv4 gateway
|
||||
cJSON *iface_v4 = JSON_NEW_OBJECT();
|
||||
cJSON *iface_v6 = JSON_NEW_OBJECT();
|
||||
unsigned int v4_addrs = 0, v6_addrs = 0;
|
||||
cJSON_ArrayForEach(entry, interfaces)
|
||||
{
|
||||
if(strcmp(cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "name")), gw_v4_name) == 0)
|
||||
{
|
||||
// Add first interface address with family == inet
|
||||
cJSON *addr = NULL;
|
||||
cJSON *addrs = cJSON_GetObjectItemCaseSensitive(entry, "addresses");
|
||||
cJSON_ArrayForEach(addr, addrs)
|
||||
{
|
||||
cJSON *family = cJSON_GetObjectItemCaseSensitive(addr, "family");
|
||||
if(strcmp(cJSON_GetStringValue(family), "inet") == 0)
|
||||
{
|
||||
if(v4_addrs == 0)
|
||||
{
|
||||
cJSON *_addr = cJSON_GetObjectItemCaseSensitive(addr, "address");
|
||||
JSON_COPY_STR_TO_OBJECT(iface_v4, "addr", cJSON_GetStringValue(_addr));
|
||||
}
|
||||
v4_addrs++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add NULL if no IPv4 address is found
|
||||
if(v4_addrs == 0)
|
||||
JSON_ADD_NULL_TO_OBJECT(iface_v4, "addr");
|
||||
|
||||
// Also add IPv4 interface statistics
|
||||
cJSON *stats = cJSON_GetObjectItemCaseSensitive(entry, "stats");
|
||||
cJSON *rx_bytes = cJSON_GetObjectItemCaseSensitive(stats, "rx_bytes");
|
||||
JSON_ADD_ITEM_TO_OBJECT(iface_v4, "rx_bytes", cJSON_Duplicate(rx_bytes, true));
|
||||
cJSON *tx_bytes = cJSON_GetObjectItemCaseSensitive(stats, "tx_bytes");
|
||||
JSON_ADD_ITEM_TO_OBJECT(iface_v4, "tx_bytes", cJSON_Duplicate(tx_bytes, true));
|
||||
}
|
||||
if(strcmp(cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(entry, "name")), gw_v6_name) == 0)
|
||||
{
|
||||
// Add first interface address with family == inet
|
||||
cJSON *addr = NULL;
|
||||
cJSON *addrs = cJSON_GetObjectItemCaseSensitive(entry, "addresses");
|
||||
cJSON_ArrayForEach(addr, addrs)
|
||||
{
|
||||
cJSON *family = cJSON_GetObjectItemCaseSensitive(addr, "family");
|
||||
if(strcmp(cJSON_GetStringValue(family), "inet6") == 0)
|
||||
{
|
||||
if(v6_addrs == 0)
|
||||
{
|
||||
cJSON *_addr = cJSON_GetObjectItemCaseSensitive(addr, "address");
|
||||
JSON_COPY_STR_TO_OBJECT(iface_v6, "addr", cJSON_GetStringValue(_addr));
|
||||
}
|
||||
v6_addrs++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add NULL if no IPv6 address is found
|
||||
if(v6_addrs == 0)
|
||||
JSON_ADD_NULL_TO_OBJECT(iface_v6, "addr");
|
||||
}
|
||||
}
|
||||
|
||||
// Add the number of addresses found
|
||||
JSON_ADD_NUMBER_TO_OBJECT(iface_v4, "num_addrs", v4_addrs);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(iface_v6, "num_addrs", v6_addrs);
|
||||
|
||||
// Add the interfaces to the gateway object
|
||||
JSON_COPY_STR_TO_OBJECT(iface_v4, "name", gw_v4_name);
|
||||
JSON_COPY_STR_TO_OBJECT(iface_v4, "gw_addr", gw_v4_addr);
|
||||
JSON_COPY_STR_TO_OBJECT(iface_v6, "name", gw_v6_name);
|
||||
JSON_COPY_STR_TO_OBJECT(iface_v6, "gw_addr", gw_v6_addr);
|
||||
|
||||
// Create interface object
|
||||
cJSON *iface = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(iface, "v4", iface_v4);
|
||||
JSON_ADD_ITEM_TO_OBJECT(iface, "v6", iface_v6);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "iface", iface);
|
||||
|
||||
// Free memory
|
||||
cJSON_Delete(gateway_);
|
||||
|
||||
// info/version
|
||||
cJSON *version = JSON_NEW_OBJECT();
|
||||
get_version_obj(api, version);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "version", version);
|
||||
}
|
||||
|
||||
// subset of config
|
||||
cJSON *jconfig = JSON_NEW_OBJECT();
|
||||
JSON_ADD_BOOL_TO_OBJECT(jconfig, "dhcp_active", config.dhcp.active.v.b);
|
||||
JSON_ADD_ITEM_TO_OBJECT(jconfig, "dhcp_start", addJSONConfValue(config.dhcp.start.t, &config.dhcp.start.v));
|
||||
JSON_ADD_ITEM_TO_OBJECT(jconfig, "dhcp_end", addJSONConfValue(config.dhcp.end.t, &config.dhcp.end.v));
|
||||
JSON_ADD_BOOL_TO_OBJECT(jconfig, "dhcp_ipv6", config.dhcp.ipv6.v.b);
|
||||
JSON_COPY_STR_TO_OBJECT(jconfig, "dns_domain", config.dns.domain.v.s);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(jconfig, "dns_port", config.dns.port.v.u16);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(jconfig, "dns_num_upstreams", cJSON_GetArraySize(config.dns.upstreams.v.json));
|
||||
JSON_ADD_BOOL_TO_OBJECT(jconfig, "dns_dnssec", config.dns.dnssec.v.b);
|
||||
JSON_ADD_BOOL_TO_OBJECT(jconfig, "dns_revServer_active", cJSON_GetArraySize(config.dns.revServers.v.json) > 0);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "config", jconfig);
|
||||
|
||||
// subset of info/ftl
|
||||
struct proc_mem pmem = { 0 };
|
||||
struct proc_meminfo mem = { 0 };
|
||||
parse_proc_meminfo(&mem);
|
||||
getProcessMemory(&pmem, mem.total);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "%mem", pmem.VmRSS_percent);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "%cpu", get_cpu_percentage());
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "pid", getpid());
|
||||
|
||||
// info/sensors -> CPU temp sensor
|
||||
cJSON *sensors = JSON_NEW_OBJECT();
|
||||
get_sensors_obj(api, sensors, false);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "sensors", sensors);
|
||||
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
+61
-49
@@ -22,7 +22,8 @@
|
||||
// dbopen(false, ), dbclose()
|
||||
#include "database/common.h"
|
||||
|
||||
static int add_strings_to_array(struct ftl_conn *api, cJSON *array, const char *querystr, const int max_count)
|
||||
#if 0
|
||||
static int add_strings_to_array(struct ftl_conn *api, cJSON *array1, cJSON *array2, const char *querystr, const int max_count)
|
||||
{
|
||||
|
||||
sqlite3 *memdb = get_memdb();
|
||||
@@ -44,11 +45,24 @@ static int add_strings_to_array(struct ftl_conn *api, cJSON *array, const char *
|
||||
sqlite3_errstr(rc));
|
||||
}
|
||||
|
||||
// Loop through returned rows
|
||||
// Loop through returned rows and add them to the array
|
||||
int counter = 0;
|
||||
while((rc = sqlite3_step(stmt)) == SQLITE_ROW &&
|
||||
(max_count < 0 || ++counter < max_count))
|
||||
JSON_COPY_STR_TO_ARRAY(array, (const char*)sqlite3_column_text(stmt, 0));
|
||||
(max_count < 0 || ++counter <= max_count))
|
||||
{
|
||||
const char *array1_str = (const char*)sqlite3_column_text(stmt, 0);
|
||||
if(array1_str != NULL && array1_str[0] != '\0')
|
||||
// Only add non-empty strings
|
||||
JSON_COPY_STR_TO_ARRAY(array1, array1_str);
|
||||
if(array2 != NULL)
|
||||
{
|
||||
// We have a second array to fill (second column in the query)
|
||||
const char *array2_str = (const char*)sqlite3_column_text(stmt, 1);
|
||||
if(array2_str != NULL && array2_str[0] != '\0')
|
||||
// Only add non-empty strings
|
||||
JSON_COPY_STR_TO_ARRAY(array2, array2_str);
|
||||
}
|
||||
}
|
||||
|
||||
// Acceptable return codes are either
|
||||
// - SQLITE_DONE: We read all lines, or
|
||||
@@ -67,60 +81,47 @@ static int add_strings_to_array(struct ftl_conn *api, cJSON *array, const char *
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int api_queries_suggestions(struct ftl_conn *api)
|
||||
{
|
||||
int rc;
|
||||
// Does the user request a custom number of records to be included?
|
||||
int count = 30;
|
||||
get_int_var(api->request->query_string, "count", &count);
|
||||
|
||||
// Get domains
|
||||
cJSON *domain = JSON_NEW_ARRAY();
|
||||
rc = add_strings_to_array(api, domain, "SELECT domain FROM domain_by_id", count);
|
||||
if(rc != 0)
|
||||
cJSON *domain = get_top_domains(api, count, false, true);
|
||||
cJSON *blocked = get_top_domains(api, count, true, true);
|
||||
// Add domains from both arrays, avoiding duplicates
|
||||
cJSON *entry = NULL;
|
||||
cJSON_ArrayForEach(entry, blocked)
|
||||
{
|
||||
log_err("Cannot read domains from database");
|
||||
cJSON_Delete(domain);
|
||||
return rc;
|
||||
// Check if the domain is already in the list
|
||||
bool found = false;
|
||||
cJSON *entry2 = NULL;
|
||||
cJSON_ArrayForEach(entry2, domain)
|
||||
{
|
||||
if(strcmp(cJSON_GetStringValue(entry), cJSON_GetStringValue(entry2)) == 0)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found)
|
||||
JSON_ADD_ITEM_TO_ARRAY(domain, cJSON_Duplicate(entry, true));
|
||||
}
|
||||
// Free the blocked list
|
||||
cJSON_Delete(blocked);
|
||||
|
||||
// Get clients, both by IP and names
|
||||
// We have to call DISTINCT() here as multiple IPs can map to and name and
|
||||
// vice versa
|
||||
cJSON *client_ip = JSON_NEW_ARRAY();
|
||||
rc = add_strings_to_array(api, client_ip, "SELECT DISTINCT(ip) FROM client_by_id", count);
|
||||
if(rc != 0)
|
||||
{
|
||||
log_err("Cannot read client IPs from database");
|
||||
cJSON_Delete(domain);
|
||||
cJSON_Delete(client_ip);
|
||||
return rc;
|
||||
}
|
||||
cJSON *client_name = JSON_NEW_ARRAY();
|
||||
rc = add_strings_to_array(api, client_name, "SELECT DISTINCT(name) FROM client_by_id", count);
|
||||
if(rc != 0)
|
||||
{
|
||||
log_err("Cannot read client names from database");
|
||||
cJSON_Delete(domain);
|
||||
cJSON_Delete(client_ip);
|
||||
cJSON_Delete(client_name);
|
||||
return rc;
|
||||
}
|
||||
cJSON *client_ip = get_top_clients(api, count, false, true, false, false);
|
||||
cJSON *client_name = get_top_clients(api, count, false, true, true, false);
|
||||
|
||||
// Delete duplicate entries from client_name
|
||||
cJSON_unique_array(client_name);
|
||||
|
||||
// Get upstreams
|
||||
cJSON *upstream = JSON_NEW_ARRAY();
|
||||
rc = add_strings_to_array(api, upstream, "SELECT forward FROM forward_by_id", count);
|
||||
if(rc != 0)
|
||||
{
|
||||
log_err("Cannot read forward from database");
|
||||
cJSON_Delete(domain);
|
||||
cJSON_Delete(client_ip);
|
||||
cJSON_Delete(client_name);
|
||||
cJSON_Delete(upstream);
|
||||
return rc;
|
||||
}
|
||||
|
||||
cJSON *upstream = get_top_upstreams(api, true);
|
||||
// Get types
|
||||
cJSON *type = JSON_NEW_ARRAY();
|
||||
queriesData query = { 0 };
|
||||
@@ -306,6 +307,11 @@ int api_queries(struct ftl_conn *api)
|
||||
bool cursor_set = false, where = false;
|
||||
double timestamp_from = 0.0, timestamp_until = 0.0;
|
||||
|
||||
// We use this boolean to memorize if we are filtering at all. It is used
|
||||
// later to decide if we can short-circuit the query counting for
|
||||
// performance reasons.
|
||||
bool filtering = false;
|
||||
|
||||
// Filter-/sorting based on GET parameters?
|
||||
if(api->request->query_string != NULL)
|
||||
{
|
||||
@@ -330,11 +336,17 @@ int api_queries(struct ftl_conn *api)
|
||||
if(GET_STR("upstream", upstreamname, api->request->query_string) > 0)
|
||||
{
|
||||
if(strcmp(upstreamname, "blocklist") == 0)
|
||||
{
|
||||
// Pseudo-upstream for blocked queries
|
||||
add_querystr_string(api, querystr, "q.status IN ", get_blocked_statuslist(), &where);
|
||||
filtering = true;
|
||||
}
|
||||
else if(strcmp(upstreamname, "cache") == 0)
|
||||
{
|
||||
// Pseudo-upstream for cached queries
|
||||
add_querystr_string(api, querystr, "q.status IN ", get_cached_statuslist(), &where);
|
||||
filtering = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(is_wildcard(upstreamname))
|
||||
@@ -432,10 +444,14 @@ int api_queries(struct ftl_conn *api)
|
||||
|
||||
// Encoded URI string: %5B = [ and %5D = ]
|
||||
if(GET_VAR(sort_col_id, sort_col, api->request->query_string) > 0)
|
||||
{
|
||||
log_debug(DEBUG_API, "Sorting by column %s (%s)", sort_col, sort_dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
log_warn("Sorting by column %d (%s) requested, but column name not found",
|
||||
sort_column, sort_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// Column searching?
|
||||
@@ -505,11 +521,6 @@ int api_queries(struct ftl_conn *api)
|
||||
}
|
||||
}
|
||||
|
||||
// We use this boolean to memorize if we are filtering at all. It is used
|
||||
// later to decide if we can short-circuit the query counting for
|
||||
// performance reasons.
|
||||
bool filtering = false;
|
||||
|
||||
// Regex filtering?
|
||||
regex_t *regex_domains = NULL;
|
||||
unsigned int N_regex_domains = 0;
|
||||
@@ -1010,6 +1021,7 @@ int api_queries(struct ftl_conn *api)
|
||||
case QUERY_EXTERNAL_BLOCKED_IP:
|
||||
case QUERY_EXTERNAL_BLOCKED_NULL:
|
||||
case QUERY_EXTERNAL_BLOCKED_NXRA:
|
||||
case QUERY_EXTERNAL_BLOCKED_EDE15:
|
||||
case QUERY_RETRIED:
|
||||
case QUERY_RETRIED_DNSSEC:
|
||||
case QUERY_IN_PROGRESS:
|
||||
|
||||
+5
-13
@@ -15,6 +15,8 @@
|
||||
#include "database/gravity-db.h"
|
||||
// match_regex()
|
||||
#include "regex_r.h"
|
||||
// parse_groupIDs()
|
||||
#include "webserver/http-common.h"
|
||||
#include <idn2.h>
|
||||
|
||||
#define MAX_SEARCH_RESULTS 10000u
|
||||
@@ -77,19 +79,9 @@ static int search_table(struct ftl_conn *api, const char *item,
|
||||
|
||||
if(table.group_ids != NULL)
|
||||
{
|
||||
// Black magic at work here: We build a JSON array from
|
||||
// the group_concat result delivered from the database,
|
||||
// parse it as valid array and append it as row to the
|
||||
// data
|
||||
const size_t buflen = strlen(table.group_ids)+3u;
|
||||
char *group_ids_str = calloc(buflen, sizeof(char));
|
||||
group_ids_str[0] = '[';
|
||||
strcpy(group_ids_str+1u , table.group_ids);
|
||||
group_ids_str[buflen-2u] = ']';
|
||||
group_ids_str[buflen-1u] = '\0';
|
||||
cJSON * group_ids = cJSON_Parse(group_ids_str);
|
||||
free(group_ids_str);
|
||||
JSON_ADD_ITEM_TO_OBJECT(row, "groups", group_ids);
|
||||
const int ret = parse_groupIDs(api, &table, row);
|
||||
if(ret != 0)
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+404
-271
@@ -14,8 +14,6 @@
|
||||
#include "api/api.h"
|
||||
#include "shmem.h"
|
||||
#include "datastructure.h"
|
||||
// read_setupVarsconf()
|
||||
#include "config/setupVars.h"
|
||||
// logging routines
|
||||
#include "log.h"
|
||||
// config struct
|
||||
@@ -27,6 +25,17 @@
|
||||
// sqrt()
|
||||
#include <math.h>
|
||||
|
||||
struct top_entries {
|
||||
int count;
|
||||
unsigned int responses;
|
||||
in_port_t port;
|
||||
size_t namepos;
|
||||
size_t ippos;
|
||||
double rtime;
|
||||
double rtuncertainty;
|
||||
|
||||
};
|
||||
|
||||
/* qsort comparison function (count field), sort ASC
|
||||
static int __attribute__((pure)) cmpasc(const void *a, const void *b)
|
||||
{
|
||||
@@ -55,6 +64,20 @@ int __attribute__((pure)) cmpdesc(const void *a, const void *b)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// qsort subroutine, sort DESC
|
||||
static int __attribute__((pure)) cmpdesc_te(const void *a, const void *b)
|
||||
{
|
||||
const struct top_entries *elem1 = (struct top_entries*)a;
|
||||
const struct top_entries *elem2 = (struct top_entries*)b;
|
||||
|
||||
if (elem1->count > elem2->count)
|
||||
return -1;
|
||||
else if (elem1->count < elem2->count)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_query_types_obj(struct ftl_conn *api, cJSON *types)
|
||||
{
|
||||
for(unsigned int i = TYPE_A; i < TYPE_MAX; i++)
|
||||
@@ -69,29 +92,59 @@ static int get_query_types_obj(struct ftl_conn *api, cJSON *types)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// shmem needs to be locked while calling this function
|
||||
unsigned int get_active_clients(void)
|
||||
{
|
||||
unsigned int activeclients = 0;
|
||||
for(int clientID=0; clientID < counters->clients; clientID++)
|
||||
{
|
||||
// Get client pointer
|
||||
const clientsData* client = getClient(clientID, true);
|
||||
if(client == NULL)
|
||||
continue;
|
||||
|
||||
if(client->count > 0)
|
||||
activeclients++;
|
||||
}
|
||||
|
||||
return activeclients;
|
||||
}
|
||||
|
||||
int api_stats_summary(struct ftl_conn *api)
|
||||
{
|
||||
const int blocked = get_blocked_count();
|
||||
const int forwarded = get_forwarded_count();
|
||||
const int cached = get_cached_count();
|
||||
const int total = counters->queries;
|
||||
float percent_blocked = 0.0f;
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
const int blocked = get_blocked_count();
|
||||
const int forwarded = get_forwarded_count();
|
||||
const int cached = get_cached_count();
|
||||
const int total = counters->queries;
|
||||
const int num_gravity = counters->database.gravity;
|
||||
const int num_clients = counters->clients;
|
||||
const int num_domains = counters->domains;
|
||||
|
||||
// Count clients that have been active within the most recent 24 hours
|
||||
unsigned int activeclients = get_active_clients();
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Calculate percentage of blocked queries
|
||||
float percent_blocked = 0.0f;
|
||||
// Avoid 1/0 condition
|
||||
if(total > 0)
|
||||
percent_blocked = 1e2f*blocked/total;
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
cJSON *queries = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "total", total);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "blocked", blocked);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "percent_blocked", percent_blocked);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "unique_domains", counters->domains);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "unique_domains", num_domains);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "forwarded", forwarded);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "cached", cached);
|
||||
|
||||
JSON_ADD_NUMBER_TO_OBJECT(queries, "frequency", get_qps());
|
||||
|
||||
cJSON *types = JSON_NEW_OBJECT();
|
||||
int ret = get_query_types_obj(api, types);
|
||||
if(ret != 0)
|
||||
@@ -108,34 +161,23 @@ int api_stats_summary(struct ftl_conn *api)
|
||||
JSON_ADD_NUMBER_TO_OBJECT(replies, get_query_reply_str(reply), counters->reply[reply]);
|
||||
JSON_ADD_ITEM_TO_OBJECT(queries, "replies", replies);
|
||||
|
||||
// Count clients that have been active within the most recent 24 hours
|
||||
unsigned int activeclients = 0;
|
||||
for(int clientID=0; clientID < counters->clients; clientID++)
|
||||
{
|
||||
// Get client pointer
|
||||
const clientsData* client = getClient(clientID, true);
|
||||
if(client == NULL)
|
||||
continue;
|
||||
|
||||
if(client->count > 0)
|
||||
activeclients++;
|
||||
}
|
||||
|
||||
cJSON *clients = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(clients, "active", activeclients);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(clients, "total", counters->clients);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(clients, "total", num_clients);
|
||||
|
||||
cJSON *gravity = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(gravity, "domains_being_blocked", counters->database.gravity);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(gravity, "domains_being_blocked", num_gravity);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(gravity, "last_update", gravity_last_updated());
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "queries", queries);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "clients", clients);
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "gravity", gravity);
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_stats_top_domains(struct ftl_conn *api)
|
||||
cJSON *get_top_domains(struct ftl_conn *api, const int count,
|
||||
const bool blocked, const bool domains_only)
|
||||
{
|
||||
// Exit before processing any data if requested via config setting
|
||||
if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS)
|
||||
@@ -145,24 +187,150 @@ int api_stats_top_domains(struct ftl_conn *api)
|
||||
|
||||
// Minimum structure is
|
||||
// {"top_domains":[]}
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
cJSON *top_domains = JSON_NEW_ARRAY();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "top_domains", top_domains);
|
||||
JSON_SEND_OBJECT(json);
|
||||
if(domains_only)
|
||||
return cJSON_CreateArray();
|
||||
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "domains", cJSON_CreateArray());
|
||||
cJSON_AddNumberToObject(json, "total_queries", -1);
|
||||
cJSON_AddNumberToObject(json, "blocked_queries", -1);
|
||||
return json;
|
||||
}
|
||||
|
||||
// Get domains which the user doesn't want to see
|
||||
regex_t *regex_domains = NULL;
|
||||
unsigned int N_regex_domains = 0;
|
||||
compile_filter_regex(api, "webserver.api.excludeDomains",
|
||||
config.webserver.api.excludeDomains.v.json,
|
||||
®ex_domains, &N_regex_domains);
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
// Allocate memory
|
||||
const int domains = counters->domains;
|
||||
int *temparray = calloc(2*domains, sizeof(int));
|
||||
if(temparray == NULL)
|
||||
const int total_queries = counters->queries;
|
||||
const int blocked_count = get_blocked_count();
|
||||
struct top_entries *top_domains = calloc(domains, sizeof(struct top_entries));
|
||||
if(top_domains == NULL)
|
||||
{
|
||||
log_err("Memory allocation failed in %s()", __FUNCTION__);
|
||||
return 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned int added_domains = 0u;
|
||||
for(int domainID = 0; domainID < domains; domainID++)
|
||||
{
|
||||
// Get domain pointer
|
||||
const domainsData* domain = getDomain(domainID, true);
|
||||
if(domain == NULL)
|
||||
continue;
|
||||
|
||||
const char *domain_name = getstr(domain->domainpos);
|
||||
|
||||
// Hidden domain, probably due to privacy level. Skip this in the top lists
|
||||
if(strcmp(domain_name, HIDDEN_DOMAIN) == 0)
|
||||
continue;
|
||||
|
||||
// Use either blocked or total count based on request string
|
||||
top_domains[added_domains].count = blocked ? domain->blockedcount : domain->count - domain->blockedcount;
|
||||
|
||||
// Get domain name
|
||||
top_domains[added_domains].namepos = domain->domainpos;
|
||||
|
||||
// Increment counter
|
||||
added_domains++;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Sort temporary array
|
||||
qsort(top_domains, added_domains, sizeof(*top_domains), cmpdesc_te);
|
||||
|
||||
int n = 0;
|
||||
cJSON *jtop_domains = cJSON_CreateArray();
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
for(unsigned int i = 0; i < added_domains; i++)
|
||||
{
|
||||
// Skip e.g. recycled domains
|
||||
if(top_domains[i].namepos == 0)
|
||||
continue;
|
||||
|
||||
const char *domain = getstr(top_domains[i].namepos);
|
||||
|
||||
// Skip this client if there is a filter on it
|
||||
bool skip_domain = false;
|
||||
if(N_regex_domains > 0)
|
||||
{
|
||||
// Iterate over all regex filters
|
||||
for(unsigned int j = 0; j < N_regex_domains; j++)
|
||||
{
|
||||
// Check if the domain matches the regex
|
||||
if(regexec(®ex_domains[j], domain, 0, NULL, 0) == 0)
|
||||
{
|
||||
// Domain matches
|
||||
skip_domain = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(skip_domain || top_domains[i].count < 1)
|
||||
continue;
|
||||
|
||||
if(domains_only)
|
||||
{
|
||||
cJSON_AddStringToArray(jtop_domains, domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON *domain_item = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(domain_item, "domain", domain);
|
||||
cJSON_AddNumberToObject(domain_item, "count", top_domains[i].count);
|
||||
cJSON_AddItemToArray(jtop_domains, domain_item);
|
||||
}
|
||||
|
||||
// Only count entries that are actually sent and return when we have send enough data
|
||||
if(++n >= count)
|
||||
break;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Free temporary array
|
||||
free(top_domains);
|
||||
|
||||
// Free regexes
|
||||
if(N_regex_domains > 0)
|
||||
{
|
||||
// Free individual regexes
|
||||
for(unsigned int i = 0; i < N_regex_domains; i++)
|
||||
regfree(®ex_domains[i]);
|
||||
|
||||
// Free array of regex pointers
|
||||
free(regex_domains);
|
||||
}
|
||||
|
||||
if(domains_only)
|
||||
{
|
||||
// Return the array of domains only
|
||||
return jtop_domains;
|
||||
}
|
||||
|
||||
// else: Build and return full object
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "domains", jtop_domains);
|
||||
cJSON_AddNumberToObject(json, "total_queries", total_queries);
|
||||
cJSON_AddNumberToObject(json, "blocked_queries", blocked_count);
|
||||
return json;
|
||||
}
|
||||
|
||||
int api_stats_top_domains(struct ftl_conn *api)
|
||||
{
|
||||
bool blocked = false; // Can be overwritten by query string
|
||||
int count = 10;
|
||||
// /api/stats/top_domains?blocked=true
|
||||
@@ -176,138 +344,14 @@ int api_stats_top_domains(struct ftl_conn *api)
|
||||
get_int_var(api->request->query_string, "count", &count);
|
||||
}
|
||||
|
||||
unsigned int added_domains = 0u;
|
||||
for(int domainID = 0; domainID < domains; domainID++)
|
||||
{
|
||||
// Get domain pointer
|
||||
const domainsData* domain = getDomain(domainID, true);
|
||||
if(domain == NULL)
|
||||
continue;
|
||||
|
||||
// Add domain ID
|
||||
temparray[2*added_domains + 0] = domainID;
|
||||
|
||||
// Use either blocked or total count based on request string
|
||||
temparray[2*added_domains + 1] = blocked ? domain->blockedcount : domain->count - domain->blockedcount;
|
||||
|
||||
added_domains++;
|
||||
}
|
||||
|
||||
// Sort temporary array
|
||||
qsort(temparray, added_domains, sizeof(int[2]), cmpdesc);
|
||||
|
||||
// Get filter
|
||||
const char* log_show = read_setupVarsconf("API_QUERY_LOG_SHOW");
|
||||
bool showpermitted = true, showblocked = true;
|
||||
if(log_show != NULL)
|
||||
{
|
||||
if((strcmp(log_show, "permittedonly")) == 0)
|
||||
showblocked = false;
|
||||
else if((strcmp(log_show, "blockedonly")) == 0)
|
||||
showpermitted = false;
|
||||
else if((strcmp(log_show, "nothing")) == 0)
|
||||
{
|
||||
showpermitted = false;
|
||||
showblocked = false;
|
||||
}
|
||||
}
|
||||
clearSetupVarsArray();
|
||||
|
||||
// Get domains which the user doesn't want to see
|
||||
regex_t *regex_domains = NULL;
|
||||
unsigned int N_regex_domains = 0;
|
||||
compile_filter_regex(api, "webserver.api.excludeDomains",
|
||||
config.webserver.api.excludeDomains.v.json,
|
||||
®ex_domains, &N_regex_domains);
|
||||
|
||||
int n = 0;
|
||||
cJSON *top_domains = JSON_NEW_ARRAY();
|
||||
for(unsigned int i = 0; i < added_domains; i++)
|
||||
{
|
||||
// Get sorted index
|
||||
const int domainID = temparray[2*i + 0];
|
||||
// Get domain pointer
|
||||
const domainsData* domain = getDomain(domainID, true);
|
||||
if(domain == NULL)
|
||||
continue;
|
||||
|
||||
// Get domain name
|
||||
const char *domain_name = getstr(domain->domainpos);
|
||||
|
||||
// Hidden domain, probably due to privacy level. Skip this in the top lists
|
||||
if(strcmp(domain_name, HIDDEN_DOMAIN) == 0)
|
||||
continue;
|
||||
|
||||
// Skip this client if there is a filter on it
|
||||
bool skip_domain = false;
|
||||
if(N_regex_domains > 0)
|
||||
{
|
||||
// Iterate over all regex filters
|
||||
for(unsigned int j = 0; j < N_regex_domains; j++)
|
||||
{
|
||||
// Check if the domain matches the regex
|
||||
if(regexec(®ex_domains[j], domain_name, 0, NULL, 0) == 0)
|
||||
{
|
||||
// Domain matches
|
||||
skip_domain = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(skip_domain)
|
||||
continue;
|
||||
|
||||
int domain_count = -1;
|
||||
if(blocked && showblocked && domain->blockedcount > 0)
|
||||
{
|
||||
domain_count = domain->blockedcount;
|
||||
n++;
|
||||
}
|
||||
else if(!blocked && showpermitted && (domain->count - domain->blockedcount) > 0)
|
||||
{
|
||||
domain_count = domain->count - domain->blockedcount;
|
||||
n++;
|
||||
}
|
||||
if(domain_count > -1)
|
||||
{
|
||||
cJSON *domain_item = JSON_NEW_OBJECT();
|
||||
JSON_REF_STR_IN_OBJECT(domain_item, "domain", domain_name);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(domain_item, "count", domain_count);
|
||||
JSON_ADD_ITEM_TO_ARRAY(top_domains, domain_item);
|
||||
}
|
||||
|
||||
// Only count entries that are actually sent and return when we have send enough data
|
||||
if(n >= count)
|
||||
break;
|
||||
}
|
||||
free(temparray);
|
||||
|
||||
// Free regexes
|
||||
if(N_regex_domains > 0)
|
||||
{
|
||||
// Free individual regexes
|
||||
for(unsigned int i = 0; i < N_regex_domains; i++)
|
||||
regfree(®ex_domains[i]);
|
||||
|
||||
// Free array of regex pointers
|
||||
free(regex_domains);
|
||||
}
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "domains", top_domains);
|
||||
|
||||
const int blocked_count = get_blocked_count();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", counters->queries);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "blocked_queries", blocked_count);
|
||||
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
cJSON *json = get_top_domains(api, count, blocked, false);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_stats_top_clients(struct ftl_conn *api)
|
||||
cJSON *get_top_clients(struct ftl_conn *api, const int count,
|
||||
const bool blocked, const bool clients_only,
|
||||
const bool names_only, const bool ip_if_no_name)
|
||||
{
|
||||
int count = 10;
|
||||
|
||||
// Exit before processing any data if requested via config setting
|
||||
if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS_CLIENTS)
|
||||
{
|
||||
@@ -316,31 +360,26 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
|
||||
// Minimum structure is
|
||||
// {"top_clients":[]}
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
cJSON *top_clients = JSON_NEW_ARRAY();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "top_clients", top_clients);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
if(clients_only)
|
||||
return cJSON_CreateArray();
|
||||
|
||||
bool blocked = false; // /api/stats/top_clients?blocked=true
|
||||
if(api->request->query_string != NULL)
|
||||
{
|
||||
// Should blocked clients be shown?
|
||||
get_bool_var(api->request->query_string, "blocked", &blocked);
|
||||
|
||||
// Does the user request a non-default number of replies?
|
||||
// Note: We do not accept zero query requests here
|
||||
get_int_var(api->request->query_string, "count", &count);
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "clients", cJSON_CreateArray());
|
||||
cJSON_AddNumberToObject(json, "total_queries", -1);
|
||||
cJSON_AddNumberToObject(json, "blocked_queries", -1);
|
||||
return json;
|
||||
}
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
int clients = counters->clients;
|
||||
int *temparray = calloc(2*clients, sizeof(int));
|
||||
if(temparray == NULL)
|
||||
const int total_queries = counters->queries;
|
||||
const int blocked_count = get_blocked_count();
|
||||
struct top_entries *top_clients = calloc(clients, sizeof(struct top_entries));
|
||||
if(top_clients == NULL)
|
||||
{
|
||||
log_err("Memory allocation failed in api_stats_top_clients()");
|
||||
log_err("Memory allocation failed in %s()", __FUNCTION__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -352,17 +391,44 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
|
||||
// Skip invalid clients and also those managed by alias clients
|
||||
if(client == NULL || (!client->flags.aliasclient && client->aliasclient_id >= 0))
|
||||
{
|
||||
log_debug(DEBUG_API, "Skipping client %i because %s", clientID,
|
||||
client == NULL ? "it is invalid" : "it is an alias client");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip recycled clients
|
||||
if(client->ippos == 0)
|
||||
{
|
||||
log_debug(DEBUG_API, "Skipping client %i because it is recycled", clientID);
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *client_ip = getstr(client->ippos);
|
||||
// Hidden client, probably due to privacy level. Skip this in the top lists
|
||||
if(strcmp(client_ip, HIDDEN_CLIENT) == 0)
|
||||
{
|
||||
log_debug(DEBUG_API, "Skipping client %i because it is hidden", clientID);
|
||||
continue;
|
||||
}
|
||||
|
||||
temparray[2*added_clients + 0] = clientID;
|
||||
// Use either blocked or total count based on request string
|
||||
temparray[2*added_clients + 1] = blocked ? client->blockedcount : client->count;
|
||||
top_clients[added_clients].count = blocked ? client->blockedcount : client->count;
|
||||
|
||||
// Get client name and IP
|
||||
top_clients[added_clients].ippos = client->ippos;
|
||||
top_clients[added_clients].namepos = client->namepos;
|
||||
|
||||
added_clients++;
|
||||
}
|
||||
|
||||
log_debug(DEBUG_API, "Found %u clients", added_clients);
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Sort temporary array
|
||||
qsort(temparray, added_clients, sizeof(int[2]), cmpdesc);
|
||||
qsort(top_clients, added_clients, sizeof(*top_clients), cmpdesc_te);
|
||||
|
||||
// Get clients which the user doesn't want to see
|
||||
regex_t *regex_clients = NULL;
|
||||
@@ -372,24 +438,15 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
®ex_clients, &N_regex_clients);
|
||||
|
||||
int n = 0;
|
||||
cJSON *top_clients = JSON_NEW_ARRAY();
|
||||
cJSON *jtop_clients = JSON_NEW_ARRAY();
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
for(unsigned int i = 0; i < added_clients; i++)
|
||||
{
|
||||
// Get sorted indices and counter values (may be either total or blocked count)
|
||||
const int clientID = temparray[2*i + 0];
|
||||
const int client_count = temparray[2*i + 1];
|
||||
// Get client pointer
|
||||
const clientsData* client = getClient(clientID, true);
|
||||
if(client == NULL)
|
||||
continue;
|
||||
|
||||
// Get IP and host name of client
|
||||
const char *client_ip = getstr(client->ippos);
|
||||
const char *client_name = getstr(client->namepos);
|
||||
|
||||
// Hidden client, probably due to privacy level. Skip this in the top lists
|
||||
if(strcmp(client_ip, HIDDEN_CLIENT) == 0)
|
||||
continue;
|
||||
const char *client_ip = getstr(top_clients[i].ippos);
|
||||
const char *client_name = getstr(top_clients[i].namepos);
|
||||
|
||||
// Skip this client if there is a filter on it
|
||||
bool skip_client = false;
|
||||
@@ -414,26 +471,48 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
}
|
||||
}
|
||||
|
||||
if(skip_client)
|
||||
continue;
|
||||
|
||||
// Return this client if the client made at least one query
|
||||
// within the most recent 24 hours
|
||||
if(client_count > 0)
|
||||
if(skip_client || top_clients[i].count < 1)
|
||||
{
|
||||
cJSON *client_item = JSON_NEW_OBJECT();
|
||||
JSON_REF_STR_IN_OBJECT(client_item, "name", client_name);
|
||||
JSON_REF_STR_IN_OBJECT(client_item, "ip", client_ip);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(client_item, "count", client_count);
|
||||
JSON_ADD_ITEM_TO_ARRAY(top_clients, client_item);
|
||||
n++;
|
||||
log_debug(DEBUG_API, "Skipping client %s because it %s", client_ip,
|
||||
skip_client ? "matches a filter" : "has no queries");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(n == count)
|
||||
if(clients_only)
|
||||
{
|
||||
if(ip_if_no_name)
|
||||
{
|
||||
if(strlen(client_name) > 0)
|
||||
cJSON_AddStringToArray(jtop_clients, client_name);
|
||||
else
|
||||
cJSON_AddStringToArray(jtop_clients, client_ip);
|
||||
}
|
||||
else if(names_only)
|
||||
{
|
||||
if(strlen(client_name) > 0)
|
||||
cJSON_AddStringToArray(jtop_clients, client_name);
|
||||
}
|
||||
else
|
||||
cJSON_AddStringToArray(jtop_clients, client_ip);
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON *client_item = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(client_item, "name", client_name);
|
||||
cJSON_AddStringToObject(client_item, "ip", client_ip);
|
||||
cJSON_AddNumberToObject(client_item, "count", top_clients[i].count);
|
||||
cJSON_AddItemToArray(jtop_clients, client_item);
|
||||
}
|
||||
|
||||
if(++n == count)
|
||||
break;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Free temporary array
|
||||
free(temparray);
|
||||
free(top_clients);
|
||||
|
||||
// Free regexes
|
||||
if(N_regex_clients > 0)
|
||||
@@ -446,22 +525,46 @@ int api_stats_top_clients(struct ftl_conn *api)
|
||||
free(regex_clients);
|
||||
}
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "clients", top_clients);
|
||||
if(clients_only)
|
||||
{
|
||||
// Return the array of clients only
|
||||
return jtop_clients;
|
||||
}
|
||||
|
||||
const int blocked_count = get_blocked_count();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "blocked_queries", blocked_count);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", counters->queries);
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
// else: Build and return full object
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "clients", jtop_clients);
|
||||
cJSON_AddNumberToObject(json, "total_queries", total_queries);
|
||||
cJSON_AddNumberToObject(json, "blocked_queries", blocked_count);
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
int api_stats_upstreams(struct ftl_conn *api)
|
||||
int api_stats_top_clients(struct ftl_conn *api)
|
||||
{
|
||||
bool blocked = false; // Can be overwritten by query string
|
||||
int count = 10;
|
||||
// /api/stats/top_clients?blocked=true
|
||||
if(api->request->query_string != NULL)
|
||||
{
|
||||
// Should blocked clients be shown?
|
||||
get_bool_var(api->request->query_string, "blocked", &blocked);
|
||||
|
||||
// Does the user request a non-default number of replies?
|
||||
// Note: We do not accept zero query requests here
|
||||
get_int_var(api->request->query_string, "count", &count);
|
||||
}
|
||||
|
||||
cJSON *json = get_top_clients(api, count, blocked, false, false, false);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
cJSON *get_top_upstreams(struct ftl_conn *api, const bool upstreams_only)
|
||||
{
|
||||
unsigned int totalcount = 0;
|
||||
const int upstreams = counters->upstreams;
|
||||
int *temparray = calloc(2*upstreams, sizeof(int));
|
||||
if(temparray == NULL)
|
||||
const int forwarded_count = get_forwarded_count();
|
||||
const int total_queries = counters->queries;
|
||||
struct top_entries *top_upstreams = calloc(upstreams, sizeof(struct top_entries));
|
||||
if(top_upstreams == NULL)
|
||||
{
|
||||
log_err("Memory allocation failed in api_stats_upstreams()");
|
||||
return 0;
|
||||
@@ -478,23 +581,34 @@ int api_stats_upstreams(struct ftl_conn *api)
|
||||
if(upstream == NULL)
|
||||
continue;
|
||||
|
||||
temparray[2*added_upstreams + 0] = upstreamID;
|
||||
temparray[2*added_upstreams + 1] = upstream->count;
|
||||
totalcount += upstream->count;
|
||||
top_upstreams[added_upstreams].count = upstream->count;
|
||||
top_upstreams[added_upstreams].ippos = upstream->ippos;
|
||||
top_upstreams[added_upstreams].namepos = upstream->namepos;
|
||||
top_upstreams[added_upstreams].port = upstream->port;
|
||||
top_upstreams[added_upstreams].responses = upstream->responses;
|
||||
top_upstreams[added_upstreams].rtime = upstream->rtime;
|
||||
top_upstreams[added_upstreams].rtuncertainty = upstream->rtuncertainty;
|
||||
|
||||
added_upstreams++;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
// Sort temporary array in descending order
|
||||
qsort(temparray, upstreams, sizeof(int[2]), cmpdesc);
|
||||
qsort(top_upstreams, added_upstreams, sizeof(*top_upstreams), cmpdesc);
|
||||
|
||||
// Loop over available forward destinations
|
||||
cJSON *top_upstreams = JSON_NEW_ARRAY();
|
||||
cJSON *jtop_upstreams = JSON_NEW_ARRAY();
|
||||
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
for(int i = -2; i < (int)added_upstreams; i++)
|
||||
{
|
||||
int count = 0;
|
||||
const char* ip, *name;
|
||||
int port = -1;
|
||||
int port = -1; // Need signed data type here as -1 means: no port applicable
|
||||
double responsetime = 0.0, uncertainty = 0.0;
|
||||
|
||||
if(i == -2)
|
||||
@@ -514,67 +628,80 @@ int api_stats_upstreams(struct ftl_conn *api)
|
||||
else
|
||||
{
|
||||
// Regular upstream destination
|
||||
// Get sorted indices
|
||||
const int upstreamID = temparray[2*i + 0];
|
||||
|
||||
// Get upstream pointer
|
||||
const upstreamsData *upstream = getUpstream(upstreamID, true);
|
||||
if(upstream == NULL)
|
||||
continue;
|
||||
|
||||
// Get IP and host name of upstream destination if available
|
||||
ip = getstr(upstream->ippos);
|
||||
name = getstr(upstream->namepos);
|
||||
port = upstream->port;
|
||||
|
||||
// Get percentage
|
||||
count = upstream->count;
|
||||
ip = getstr(top_upstreams[i].ippos);
|
||||
name = getstr(top_upstreams[i].namepos);
|
||||
port = top_upstreams[i].port;
|
||||
count = top_upstreams[i].count;
|
||||
|
||||
// Compute average response time and uncertainty (unit: seconds)
|
||||
if(upstream->responses > 0)
|
||||
if(top_upstreams[i].responses > 0)
|
||||
{
|
||||
// Simple average of the response times
|
||||
responsetime = upstream->rtime / upstream->responses;
|
||||
responsetime = top_upstreams[i].rtime / top_upstreams[i].responses;
|
||||
}
|
||||
if(upstream->responses > 1)
|
||||
if(top_upstreams[i].responses > 1)
|
||||
{
|
||||
// The actual value will be somewhere in a neighborhood around the mean value.
|
||||
// This neighborhood of values is the uncertainty in the mean.
|
||||
uncertainty = sqrt(upstream->rtuncertainty / upstream->responses / (upstream->responses-1));
|
||||
uncertainty = sqrt(top_upstreams[i].rtuncertainty / top_upstreams[i].responses / (top_upstreams[i].responses-1));
|
||||
}
|
||||
}
|
||||
|
||||
// Send data:
|
||||
// - always if i < 0 (special upstreams: blocklist and cache)
|
||||
// - only if there are any queries for all others (i > 0)
|
||||
if(count > 0 || i < 0)
|
||||
if(count < 1 && i >= 0)
|
||||
continue;
|
||||
|
||||
if(upstreams_only)
|
||||
{
|
||||
cJSON_AddStringToArray(jtop_upstreams, name);
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON *upstream = JSON_NEW_OBJECT();
|
||||
JSON_REF_STR_IN_OBJECT(upstream, "ip", ip);
|
||||
JSON_REF_STR_IN_OBJECT(upstream, "name", name);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(upstream, "port", port);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(upstream, "count", count);
|
||||
cJSON_AddStringToObject(upstream, "ip", ip);
|
||||
cJSON_AddStringToObject(upstream, "name", name);
|
||||
cJSON_AddNumberToObject(upstream, "port", port);
|
||||
cJSON_AddNumberToObject(upstream, "count", count);
|
||||
cJSON *statistics = JSON_NEW_OBJECT();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(statistics, "response", responsetime);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(statistics, "variance", uncertainty);
|
||||
JSON_ADD_ITEM_TO_OBJECT(upstream, "statistics", statistics);
|
||||
JSON_ADD_ITEM_TO_ARRAY(top_upstreams, upstream);
|
||||
cJSON_AddNumberToObject(statistics, "response", responsetime);
|
||||
cJSON_AddNumberToObject(statistics, "variance", uncertainty);
|
||||
cJSON_AddItemToObject(upstream, "statistics", statistics);
|
||||
cJSON_AddItemToArray(jtop_upstreams, upstream);
|
||||
}
|
||||
}
|
||||
|
||||
// Free temporary array
|
||||
free(temparray);
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "upstreams", top_upstreams);
|
||||
const int forwarded_count = get_forwarded_count();
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "forwarded_queries", forwarded_count);
|
||||
JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", counters->queries);
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
// Free temporary array
|
||||
free(top_upstreams);
|
||||
|
||||
if(upstreams_only)
|
||||
{
|
||||
// Return the array of upstreams only
|
||||
return jtop_upstreams;
|
||||
}
|
||||
|
||||
// else: Build and return full object
|
||||
cJSON *json = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(json, "upstreams", jtop_upstreams);
|
||||
cJSON_AddNumberToObject(json, "total_queries", total_queries);
|
||||
cJSON_AddNumberToObject(json, "forwarded_queries", forwarded_count);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
int api_stats_upstreams(struct ftl_conn *api)
|
||||
{
|
||||
cJSON *json = get_top_upstreams(api, false);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_stats_query_types(struct ftl_conn *api)
|
||||
{
|
||||
// Lock shared memory
|
||||
lock_shm();
|
||||
|
||||
cJSON *types = JSON_NEW_OBJECT();
|
||||
@@ -585,11 +712,14 @@ int api_stats_query_types(struct ftl_conn *api)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "types", types);
|
||||
|
||||
// Send response
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
int api_stats_recentblocked(struct ftl_conn *api)
|
||||
@@ -621,7 +751,7 @@ int api_stats_recentblocked(struct ftl_conn *api)
|
||||
cJSON *blocked = JSON_NEW_ARRAY();
|
||||
for(int queryID = counters->queries - 1; queryID > 0 ; queryID--)
|
||||
{
|
||||
const queriesData* query = getQuery(queryID, true);
|
||||
const queriesData *query = getQuery(queryID, true);
|
||||
if(query == NULL)
|
||||
continue;
|
||||
|
||||
@@ -643,7 +773,10 @@ int api_stats_recentblocked(struct ftl_conn *api)
|
||||
break;
|
||||
}
|
||||
|
||||
// Unlock shared memory
|
||||
unlock_shm();
|
||||
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "blocked", blocked);
|
||||
JSON_SEND_OBJECT_UNLOCK(json);
|
||||
JSON_SEND_OBJECT(json);
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
* 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 "../webserver/http-common.h"
|
||||
#include "../webserver/json_macros.h"
|
||||
#include "FTL.h"
|
||||
#include "webserver/http-common.h"
|
||||
#include "webserver/json_macros.h"
|
||||
#include "api.h"
|
||||
// querytypes[]
|
||||
#include "../datastructure.h"
|
||||
#include "datastructure.h"
|
||||
// logging routines
|
||||
#include "log.h"
|
||||
// db
|
||||
#include "../database/common.h"
|
||||
#include "database/common.h"
|
||||
|
||||
// SQL Query type filters for the database
|
||||
#define FILTER_STATUS_NOT_BLOCKED "status IN (0,2,3,12,13,14,17)"
|
||||
@@ -514,13 +514,11 @@ int api_history_database_clients(struct ftl_conn *api)
|
||||
|
||||
// Loop over clients and accumulate results
|
||||
cJSON *clients = JSON_NEW_OBJECT();
|
||||
unsigned int num_clients = 0;
|
||||
while((rc = sqlite3_step(stmt)) == SQLITE_ROW)
|
||||
{
|
||||
cJSON *item = JSON_NEW_OBJECT();
|
||||
JSON_COPY_STR_TO_OBJECT(item, "name", sqlite3_column_text(stmt, 2));
|
||||
JSON_ADD_ITEM_TO_OBJECT(clients, (const char*)sqlite3_column_text(stmt, 1), item);
|
||||
num_clients++;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
|
||||
+129
-14
@@ -25,6 +25,12 @@
|
||||
#include "database/common.h"
|
||||
// MAX_ROTATIONS
|
||||
#include "files.h"
|
||||
//basename()
|
||||
#include <libgen.h>
|
||||
// restart_ftl()
|
||||
#include "signals.h"
|
||||
// create_migration_target_v6()
|
||||
#include "config/config.h"
|
||||
|
||||
#define MAXFILESIZE (50u*1024*1024)
|
||||
|
||||
@@ -68,14 +74,18 @@ static int api_teleporter_GET(struct ftl_conn *api)
|
||||
struct upload_data {
|
||||
bool too_large;
|
||||
char *sid;
|
||||
cJSON *import;
|
||||
uint8_t *data;
|
||||
char *filename;
|
||||
size_t filesize;
|
||||
struct {
|
||||
bool file;
|
||||
bool sid;
|
||||
bool import;
|
||||
} field;
|
||||
};
|
||||
|
||||
// Callback function for CivetWeb to determine which fields we want to receive
|
||||
static bool is_file = false;
|
||||
static bool is_sid = false;
|
||||
static int field_found(const char *key,
|
||||
const char *filename,
|
||||
char *path,
|
||||
@@ -85,17 +95,22 @@ static int field_found(const char *key,
|
||||
struct upload_data *data = (struct upload_data *)user_data;
|
||||
log_debug(DEBUG_API, "Found field: \"%s\", filename: \"%s\"", key, filename);
|
||||
|
||||
is_file = false;
|
||||
is_sid = false;
|
||||
// Set all fields to false
|
||||
memset(&data->field, false, sizeof(data->field));
|
||||
if(strcasecmp(key, "file") == 0 && filename && *filename)
|
||||
{
|
||||
data->filename = strdup(filename);
|
||||
is_file = true;
|
||||
data->field.file = true;
|
||||
return MG_FORM_FIELD_STORAGE_GET;
|
||||
}
|
||||
else if(strcasecmp(key, "sid") == 0)
|
||||
{
|
||||
is_sid = true;
|
||||
data->field.sid = true;
|
||||
return MG_FORM_FIELD_STORAGE_GET;
|
||||
}
|
||||
else if(strcasecmp(key, "import") == 0)
|
||||
{
|
||||
data->field.import = true;
|
||||
return MG_FORM_FIELD_STORAGE_GET;
|
||||
}
|
||||
|
||||
@@ -111,7 +126,7 @@ static int field_get(const char *key, const char *value, size_t valuelen, void *
|
||||
struct upload_data *data = (struct upload_data *)user_data;
|
||||
log_debug(DEBUG_API, "Received field: \"%s\" (length %zu bytes)", key, valuelen);
|
||||
|
||||
if(is_file)
|
||||
if(data->field.file)
|
||||
{
|
||||
if(data->filesize + valuelen > MAXFILESIZE)
|
||||
{
|
||||
@@ -129,7 +144,7 @@ static int field_get(const char *key, const char *value, size_t valuelen, void *
|
||||
log_debug(DEBUG_API, "Received file (%zu bytes, buffer is now %zu bytes)",
|
||||
valuelen, data->filesize);
|
||||
}
|
||||
else if(is_sid)
|
||||
else if(data->field.sid)
|
||||
{
|
||||
// Allocate memory for the SID
|
||||
data->sid = calloc(valuelen + 1, sizeof(char));
|
||||
@@ -138,6 +153,28 @@ static int field_get(const char *key, const char *value, size_t valuelen, void *
|
||||
// Add terminating NULL byte (memcpy does not do this)
|
||||
data->sid[valuelen] = '\0';
|
||||
}
|
||||
else if(data->field.import)
|
||||
{
|
||||
// Try to parse the JSON data
|
||||
const char *json_error = NULL;
|
||||
cJSON *json = cJSON_ParseWithLengthOpts(value, valuelen, &json_error, false);
|
||||
if(json == NULL)
|
||||
{
|
||||
log_err("Unable to parse JSON data in API request, error at: %.20s", json_error);
|
||||
return MG_FORM_FIELD_HANDLE_ABORT;
|
||||
}
|
||||
|
||||
// Check if the JSON data is an object
|
||||
if(!cJSON_IsObject(json))
|
||||
{
|
||||
log_err("JSON data in API request is not an object");
|
||||
cJSON_Delete(json);
|
||||
return MG_FORM_FIELD_HANDLE_ABORT;
|
||||
}
|
||||
|
||||
// Store the parsed JSON data
|
||||
data->import = json;
|
||||
}
|
||||
|
||||
// If there is more data in this field, get the next chunk.
|
||||
// Otherwise: handle the next field.
|
||||
@@ -168,6 +205,11 @@ static int free_upload_data(struct upload_data *data)
|
||||
free(data->data);
|
||||
data->data = NULL;
|
||||
}
|
||||
if(data->import)
|
||||
{
|
||||
cJSON_Delete(data->import);
|
||||
data->import = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -224,6 +266,9 @@ static int api_teleporter_POST(struct ftl_conn *api)
|
||||
NULL);
|
||||
}
|
||||
|
||||
// Ensure v6 migration directory exists
|
||||
create_migration_target_v6();
|
||||
|
||||
// Check if we received something that claims to be a ZIP archive
|
||||
// - filename should end in ".zip"
|
||||
// - the data itself
|
||||
@@ -262,7 +307,7 @@ static int process_received_zip(struct ftl_conn *api, struct upload_data *data)
|
||||
char hint[ERRBUF_SIZE];
|
||||
memset(hint, 0, sizeof(hint));
|
||||
cJSON *json_files = JSON_NEW_ARRAY();
|
||||
const char *error = read_teleporter_zip(data->data, data->filesize, hint, json_files);
|
||||
const char *error = read_teleporter_zip(data->data, data->filesize, hint, data->import, json_files);
|
||||
if(error != NULL)
|
||||
{
|
||||
const size_t msglen = strlen(error) + strlen(hint) + 4;
|
||||
@@ -277,13 +322,17 @@ static int process_received_zip(struct ftl_conn *api, struct upload_data *data)
|
||||
free_upload_data(data);
|
||||
return send_json_error_free(api, 400,
|
||||
"bad_request",
|
||||
"Invalid ZIP archive",
|
||||
msg, true);
|
||||
"Invalid request",
|
||||
msg, true, true);
|
||||
}
|
||||
|
||||
// Free allocated memory
|
||||
free_upload_data(data);
|
||||
|
||||
// Signal FTL we want to restart for re-import
|
||||
api->ftl.restart_reason = "Teleporter (ZIP) import";
|
||||
api->ftl.restart = true;
|
||||
|
||||
// Send response
|
||||
cJSON *json = JSON_NEW_OBJECT();
|
||||
JSON_ADD_ITEM_TO_OBJECT(json, "files", json_files);
|
||||
@@ -632,14 +681,49 @@ static int process_received_tar_gz(struct ftl_conn *api, struct upload_data *dat
|
||||
|
||||
// Parse JSON files in the TAR archive
|
||||
cJSON *imported_files = JSON_NEW_ARRAY();
|
||||
|
||||
// Check if the archive contains gravity tables
|
||||
cJSON *gravity = data->import != NULL ? cJSON_GetObjectItemCaseSensitive(data->import, "gravity") : NULL;
|
||||
for(size_t i = 0; i < sizeof(teleporter_v5_files) / sizeof(struct teleporter_files); i++)
|
||||
{
|
||||
// - if import is non-NULL we may skip some tables
|
||||
if(data->import != NULL)
|
||||
{
|
||||
// - if import is non-NULL, but gravity is NULL we skip
|
||||
// the import of gravity tables altogether
|
||||
// - if import is non-NULL, and gravity is non-NULL, we
|
||||
// import the file/table if it is in the object, a
|
||||
// boolean and true
|
||||
if(gravity == NULL || !JSON_KEY_TRUE(gravity, teleporter_v5_files[i].table_name))
|
||||
{
|
||||
log_info("Skipping import of \"%s\" as it was not requested for import (JSON: %s, gravity: %s)",
|
||||
teleporter_v5_files[i].filename,
|
||||
data->import != NULL ? "yes" : "no",
|
||||
gravity != NULL ? "yes" : "no");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Import the JSON file
|
||||
size_t fileSize = 0u;
|
||||
cJSON *json = NULL;
|
||||
const char *file = find_file_in_tar(archive, archive_size, teleporter_v5_files[i].filename, &fileSize);
|
||||
if(file != NULL && fileSize > 0u && (json = cJSON_ParseWithLength(file, fileSize)) != NULL)
|
||||
const char *json_error = NULL;
|
||||
if(file != NULL && fileSize > 0u && (json = cJSON_ParseWithLengthOpts(file, fileSize, &json_error, false)) != NULL)
|
||||
{
|
||||
if(import_json_table(json, &teleporter_v5_files[i]))
|
||||
JSON_COPY_STR_TO_ARRAY(imported_files, teleporter_v5_files[i].filename);
|
||||
}
|
||||
else if(json_error != NULL)
|
||||
{
|
||||
log_err("Unable to parse JSON file \"%s\", error at: %.20s",
|
||||
teleporter_v5_files[i].filename, json_error);
|
||||
}
|
||||
else
|
||||
{
|
||||
log_debug(DEBUG_CONFIG, "Unable to find file \"%s\" in TAR archive",
|
||||
teleporter_v5_files[i].filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Temporarily write further files to to disk so we can import them on restart
|
||||
@@ -648,23 +732,45 @@ static int process_received_tar_gz(struct ftl_conn *api, struct upload_data *dat
|
||||
const char *destination;
|
||||
} extract_files[] = {
|
||||
{
|
||||
// i = 0
|
||||
.archive_name = "custom.list",
|
||||
.destination = DNSMASQ_CUSTOM_LIST_LEGACY
|
||||
},{
|
||||
// i = 1
|
||||
.archive_name = "dhcp.leases",
|
||||
.destination = DHCPLEASESFILE
|
||||
},{
|
||||
// i = 2
|
||||
.archive_name = "pihole-FTL.conf",
|
||||
.destination = GLOBALCONFFILE_LEGACY
|
||||
},{
|
||||
// i = 3
|
||||
.archive_name = "setupVars.conf",
|
||||
.destination = config.files.setupVars.v.s
|
||||
},{
|
||||
.archive_name = "dnsmasq.d/05-pihole-custom-cname.conf",
|
||||
.destination = DNSMASQ_CNAMES
|
||||
}
|
||||
};
|
||||
for(size_t i = 0; i < sizeof(extract_files) / sizeof(*extract_files); i++)
|
||||
{
|
||||
size_t fileSize = 0u;
|
||||
const char *file = find_file_in_tar(archive, archive_size, extract_files[i].archive_name, &fileSize);
|
||||
|
||||
if(data->import != NULL && i == 1 && !JSON_KEY_TRUE(data->import, "dhcp_leases"))
|
||||
{
|
||||
log_info("Skipping import of \"%s\" as it was not requested for import",
|
||||
extract_files[i].archive_name);
|
||||
continue;
|
||||
}
|
||||
// all other values of i belong to config files
|
||||
else if(data->import != NULL && !JSON_KEY_TRUE(data->import, "config"))
|
||||
{
|
||||
log_info("Skipping import of \"%s\" as it was not requested for import",
|
||||
extract_files[i].archive_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(file != NULL && fileSize > 0u)
|
||||
{
|
||||
// Write file to disk
|
||||
@@ -676,6 +782,12 @@ static int process_received_tar_gz(struct ftl_conn *api, struct upload_data *dat
|
||||
log_err("Unable to open file \"%s\" for writing: %s", extract_files[i].destination, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Restrict permissions to owner read/write only
|
||||
if(fchmod(fileno(fp), S_IRUSR | S_IWUSR) != 0)
|
||||
log_warn("Unable to set permissions on file \"%s\": %s", extract_files[i].destination, strerror(errno));
|
||||
|
||||
// Write file to disk
|
||||
if(fwrite(file, fileSize, 1, fp) != 1)
|
||||
{
|
||||
log_err("Unable to write file \"%s\": %s", extract_files[i].destination, strerror(errno));
|
||||
@@ -705,8 +817,8 @@ static int process_received_tar_gz(struct ftl_conn *api, struct upload_data *dat
|
||||
// restore on restart
|
||||
for(unsigned int i = MAX_ROTATIONS; i > 0; i--)
|
||||
{
|
||||
const char *fname = GLOBALTOMLPATH;
|
||||
const char *filename = basename(fname);
|
||||
char *fname = strdup(GLOBALTOMLPATH);
|
||||
char *filename = basename(fname);
|
||||
// extra 6 bytes is enough space for up to 999 rotations ("/", ".", "\0", "999")
|
||||
const size_t buflen = strlen(filename) + strlen(BACKUP_DIR) + 6;
|
||||
char *path = calloc(buflen, sizeof(char));
|
||||
@@ -715,12 +827,15 @@ static int process_received_tar_gz(struct ftl_conn *api, struct upload_data *dat
|
||||
// Remove file (if it exists)
|
||||
if(remove(path) != 0 && errno != ENOENT)
|
||||
log_err("Unable to remove file \"%s\": %s", path, strerror(errno));
|
||||
|
||||
free(fname);
|
||||
}
|
||||
|
||||
// Free allocated memory
|
||||
free_upload_data(data);
|
||||
|
||||
// Signal FTL we want to restart for re-import
|
||||
api->ftl.restart_reason = "Teleporter (TAR.GZ) import";
|
||||
api->ftl.restart = true;
|
||||
|
||||
// Send response
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
// NULL
|
||||
#include <stddef.h>
|
||||
// strcasecmp()
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "theme.h"
|
||||
|
||||
|
||||
+118
-16
@@ -66,6 +66,10 @@
|
||||
#include "files.h"
|
||||
// resolveHostname()
|
||||
#include "resolve.h"
|
||||
// ntp_client()
|
||||
#include "ntp/ntp.h"
|
||||
// check_capability()
|
||||
#include "capabilities.h"
|
||||
|
||||
// defined in dnsmasq.c
|
||||
extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal);
|
||||
@@ -102,6 +106,7 @@ const char** argv_dnsmasq = NULL;
|
||||
#define COL_BLUE "\x1b[94m" // bright foreground color
|
||||
#define COL_PURPLE "\x1b[95m" // bright foreground color
|
||||
#define COL_CYAN "\x1b[96m" // bright foreground color
|
||||
#define CLI_OVER "\r\x1b[K" // go back to beginning of line and erase to end of line
|
||||
|
||||
static bool __attribute__ ((pure)) is_term(void)
|
||||
{
|
||||
@@ -145,6 +150,16 @@ const char __attribute__ ((pure)) *cli_bold(void)
|
||||
return is_term() ? COL_BOLD : "";
|
||||
}
|
||||
|
||||
const char __attribute__ ((pure)) *cli_underline(void)
|
||||
{
|
||||
return is_term() ? COL_ULINE : "";
|
||||
}
|
||||
|
||||
const char __attribute__ ((pure)) *cli_italics(void)
|
||||
{
|
||||
return is_term() ? COL_ITALIC : "";
|
||||
}
|
||||
|
||||
// Resets font to normal
|
||||
const char __attribute__ ((pure)) *cli_normal(void)
|
||||
{
|
||||
@@ -161,7 +176,7 @@ static const char __attribute__ ((pure)) *cli_color(const char *color)
|
||||
const char __attribute__ ((pure)) *cli_over(void)
|
||||
{
|
||||
// \x1b[K is the ANSI escape sequence for "erase to end of line"
|
||||
return is_term() ? "\r\x1b[K" : "\r";
|
||||
return is_term() ? CLI_OVER : "\r";
|
||||
}
|
||||
|
||||
static inline bool strEndsWith(const char *input, const char *end)
|
||||
@@ -197,6 +212,10 @@ void parse_args(int argc, char* argv[])
|
||||
if(strEndsWith(argv[0], "luac"))
|
||||
exit(run_luac(argc, argv));
|
||||
|
||||
// Special (undocumented) mode to test kernel signal handling
|
||||
if(argc == 2 && strcmp(argv[1], "sigtest") == 0)
|
||||
exit(sigtest());
|
||||
|
||||
// If the binary name is "sqlite3" (e.g., symlink /usr/bin/sqlite3 -> /usr/bin/pihole-FTL),
|
||||
// we operate in drop-in mode and consume all arguments for the embedded SQLite3 engine
|
||||
// Also, we do this if the first argument is a file with ".db" ending
|
||||
@@ -305,6 +324,38 @@ void parse_args(int argc, char* argv[])
|
||||
exit(write_teleporter_zip_to_disk() ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Create test NTP client
|
||||
if((argc > 1 && argc < 5) && strcmp(argv[1], "ntp") == 0)
|
||||
{
|
||||
// Parse arguments
|
||||
const bool update = (argc > 2 && strcmp(argv[2], "--update") == 0) ||
|
||||
(argc > 3 && strcmp(argv[3], "--update") == 0);
|
||||
const char *server = "127.0.0.1";
|
||||
if(argc > 2 && strcmp(argv[2], "--update") != 0)
|
||||
server = argv[2];
|
||||
|
||||
// Ensure we have the necessary capabilities
|
||||
if(update && !check_capability(CAP_SYS_TIME))
|
||||
{
|
||||
puts("Insufficient capabilities to run NTP client");
|
||||
const char *bold = cli_bold();
|
||||
const char *normal = cli_normal();
|
||||
printf("Try: %ssudo%s ", bold, normal);
|
||||
for(int i = 0; i < argc; i++)
|
||||
printf("%s ", argv[i]);
|
||||
puts("");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("Using NTP server: %s\n", server);
|
||||
|
||||
// Enable stdout printing
|
||||
cli_mode = true;
|
||||
log_ctrl(false, true);
|
||||
readFTLconf(&config, false);
|
||||
exit(ntp_client(server, update, true) ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Import teleporter archive through CLI
|
||||
if(argc == 3 && strcmp(argv[1], "--teleporter") == 0)
|
||||
{
|
||||
@@ -486,7 +537,7 @@ void parse_args(int argc, char* argv[])
|
||||
// Enable stdout printing
|
||||
cli_mode = true;
|
||||
uint8_t checksum[SHA256_DIGEST_SIZE];
|
||||
if(!sha256sum(argv[2], checksum))
|
||||
if(!sha256sum(argv[2], checksum, false))
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
// Convert checksum to hex string
|
||||
@@ -498,8 +549,20 @@ void parse_args(int argc, char* argv[])
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
// Checksum verification mode
|
||||
if(argc == 2 && strcmp(argv[1], "verify") == 0)
|
||||
{
|
||||
// Enable stdout printing
|
||||
cli_mode = true;
|
||||
const bool match = verify_FTL(true);
|
||||
printf("%s Binary integrity check: %s\n",
|
||||
match ? cli_tick() : cli_cross() ,
|
||||
match ? "OK" : "FAILED");
|
||||
exit(match ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Local reverse name resolver
|
||||
if(argc == 3 && strcasecmp(argv[1], "ptr") == 0)
|
||||
if((argc == 3 || argc == 4) && strcasecmp(argv[1], "ptr") == 0)
|
||||
{
|
||||
// Enable stdout printing
|
||||
cli_mode = true;
|
||||
@@ -507,7 +570,18 @@ void parse_args(int argc, char* argv[])
|
||||
// Need to get dns.port and the resolver settings
|
||||
readFTLconf(&config, false);
|
||||
|
||||
char *name = resolveHostname(argv[2], true);
|
||||
// TCP or UDP (default)?
|
||||
const bool tcp = argc == 4 && strcasecmp(argv[3], "tcp") == 0;
|
||||
|
||||
// Create a socket
|
||||
struct sockaddr_in dest;
|
||||
const int sock = create_socket(tcp, &dest);
|
||||
char *name = resolveHostname(sock, tcp, &dest, argv[2], true, NULL);
|
||||
|
||||
// Close the socket
|
||||
close(sock);
|
||||
|
||||
// Exit early if no name was found
|
||||
if(name == NULL)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
@@ -715,7 +789,12 @@ void parse_args(int argc, char* argv[])
|
||||
printf("Branch: " GIT_BRANCH "\n");
|
||||
printf("Commit: " GIT_HASH " (" GIT_DATE ")\n");
|
||||
printf("Architecture: " FTL_ARCH "\n");
|
||||
printf("Compiler: " FTL_CC "\n\n");
|
||||
printf("Compiler: " FTL_CC "\n");
|
||||
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
|
||||
printf("GLIBC version: %d.%d\n\n", __GLIBC__, __GLIBC_MINOR__);
|
||||
#else
|
||||
printf("GLIBC version: -\n\n");
|
||||
#endif
|
||||
|
||||
// Print dnsmasq version and compile time options
|
||||
print_dnsmasq_version(yellow, green, bold, normal);
|
||||
@@ -750,11 +829,12 @@ void parse_args(int argc, char* argv[])
|
||||
printf("\n");
|
||||
printf("****************************** %s%sCivetWeb%s *****************************\n",
|
||||
yellow, bold, normal);
|
||||
#ifdef MBEDTLS_VERSION_STRING_FULL
|
||||
printf("Version: %s%s%s%s with %smbed TLS %s%s"MBEDTLS_VERSION_STRING"%s\n",
|
||||
#ifdef HAVE_MBEDTLS
|
||||
printf("Version: %s%s%s%s (modified by Pi-hole) with %smbed TLS %s%s"MBEDTLS_VERSION_STRING"%s\n",
|
||||
green, bold, mg_version(), normal, yellow, green, bold, normal);
|
||||
#else
|
||||
printf("Version: %s%s%s%s\n", green, bold, mg_version(), normal);
|
||||
printf("Version: %s%s%s%s%s (modified by Pi-hole) without %smbed TLS%s\n",
|
||||
green, bold, mg_version(), normal, red, yellow, normal);
|
||||
#endif
|
||||
printf("Features: ");
|
||||
if(mg_check_feature(MG_FEATURES_FILES))
|
||||
@@ -868,6 +948,7 @@ void parse_args(int argc, char* argv[])
|
||||
if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "help") == 0 || strcmp(argv[i], "--help") == 0)
|
||||
{
|
||||
const char *bold = cli_bold();
|
||||
const char *uline = cli_underline();
|
||||
const char *normal = cli_normal();
|
||||
const char *blue = cli_color(COL_BLUE);
|
||||
const char *cyan = cli_color(COL_CYAN);
|
||||
@@ -958,12 +1039,17 @@ void parse_args(int argc, char* argv[])
|
||||
|
||||
printf("%sEmbedded GZIP un-/compressor:%s\n", yellow, normal);
|
||||
printf(" A simple but fast in-memory gzip compressor\n\n");
|
||||
printf(" Usage: %spihole-FTL --compress %sinfile %s[outfile]%s\n", green, cyan, purple, normal);
|
||||
printf(" Usage: %spihole-FTL --uncompress %sinfile %s[outfile]%s\n\n", green, cyan, purple, normal);
|
||||
printf(" - %sinfile%s is the file to be compressed.\n", cyan, normal);
|
||||
printf(" - %s[outfile]%s is the optional target. If omitted, FTL will\n", purple, normal);
|
||||
printf(" %s--compress%s: use the %sinfile%s and append %s.gz%s at the end\n", green, normal, cyan, normal, cyan, normal);
|
||||
printf(" %s--uncompress%s: use the %sinfile%s and remove %s.gz%s at the end\n\n", green, normal, cyan, normal, cyan, normal);
|
||||
printf(" Usage: %spihole-FTL --gzip %sinfile %s[outfile]%s\n\n", green, cyan, purple, normal);
|
||||
printf(" - %sinfile%s is the file to be processed. If the filename ends\n", cyan, normal);
|
||||
printf(" in %s.gz%s, FTL will uncompress, otherwise it will compress\n\n", yellow, normal);
|
||||
printf(" - %s[outfile]%s is the optional target file.\n", purple, normal);
|
||||
printf(" If omitted, FTL will try to derive the target file from\n");
|
||||
printf(" the source file.\n\n");
|
||||
printf(" Examples:\n");
|
||||
printf(" - %spihole-FTL --gzip %sfile.txt%s\n", green, cyan, normal);
|
||||
printf(" compresses %sfile.txt%s to %sfile.txt%s.gz%s\n\n", cyan, normal, cyan, yellow, normal);
|
||||
printf(" - %spihole-FTL --gzip %sfile.txt%s.gz%s\n", green, cyan, yellow, normal);
|
||||
printf(" %sun%scompresses %sfile.txt%s.gz%s to %sfile.txt%s\n\n", uline, normal, cyan, yellow, normal, cyan, normal);
|
||||
|
||||
printf("%sTeleporter:%s\n", yellow, normal);
|
||||
printf("\t%s--teleporter%s Create a Teleporter archive in the\n", green, normal);
|
||||
@@ -1000,9 +1086,25 @@ void parse_args(int argc, char* argv[])
|
||||
printf(" Encoding: %spihole-FTL idn2 %sdomain%s\n", green, cyan, normal);
|
||||
printf(" Decoding: %spihole-FTL idn2 -d %spunycode%s\n\n", green, cyan, normal);
|
||||
|
||||
printf("%sNTP client:%s\n", yellow, normal);
|
||||
printf(" Query an NTP server for the current time and print the\n");
|
||||
printf(" result in human-readable format. An optional %sserver%s may be\n", cyan, normal);
|
||||
printf(" as argument. If the server is omitted, 127.0.0.1 is used.\n\n");
|
||||
printf(" The system time is updated on the system when the optional\n");
|
||||
printf(" %s--update%s flag is given.\n\n", purple, normal);
|
||||
printf(" Usage: %spihole-FTL ntp %s[server]%s %s[--update]%s\n\n", green, cyan, normal, purple, normal);
|
||||
|
||||
printf("%sSHA256 checksum tools:%s\n", yellow, normal);
|
||||
printf(" Calculates the SHA256 checksum of a file. The checksum is\n");
|
||||
printf(" computed as described in FIPS-180-2 and uses streaming\n");
|
||||
printf(" to allow processing arbitrary large files with a small\n");
|
||||
printf(" memory footprint.\n\n");
|
||||
printf(" Usage: %spihole-FTL sha256sum %sfile%s\n\n", green, cyan, normal);
|
||||
|
||||
printf("%sOther:%s\n", yellow, normal);
|
||||
printf("\t%sptr %sIP%s Resolve IP address to hostname\n", green, cyan, normal);
|
||||
printf("\t%ssha256sum %sfile%s Calculate SHA256 checksum of a file\n", green, cyan, normal);
|
||||
printf("\t%sverify%s Verify the integrity of the FTL binary\n", green, normal);
|
||||
printf("\t%sptr %sIP%s %s[tcp]%s Resolve IP address to hostname\n", green, cyan, normal, purple, normal);
|
||||
printf("\t Append %stcp%s to use TCP instead of UDP\n", purple, normal);
|
||||
printf("\t%sdhcp-discover%s Discover DHCP servers in the local\n", green, normal);
|
||||
printf("\t network\n");
|
||||
printf("\t%sarp-scan %s[-a/-x]%s Use ARP to scan local network for\n", green, cyan, normal);
|
||||
|
||||
@@ -24,6 +24,8 @@ const char *cli_done(void) __attribute__ ((pure));
|
||||
const char *cli_bold(void) __attribute__ ((pure));
|
||||
const char *cli_normal(void) __attribute__ ((pure));
|
||||
const char *cli_over(void) __attribute__ ((pure));
|
||||
const char *cli_underline(void) __attribute__ ((pure));
|
||||
const char *cli_italics(void) __attribute__ ((pure));
|
||||
|
||||
void test_dnsmasq_options(int argc, const char *argv[]);
|
||||
|
||||
|
||||
@@ -141,6 +141,13 @@ bool check_capabilities(void)
|
||||
log_warn("Required Linux capability CAP_CHOWN not available");
|
||||
capabilities_okay = false;
|
||||
}
|
||||
if (!(data->permitted & (1 << CAP_SYS_TIME)) ||
|
||||
!(data->effective & (1 << CAP_SYS_TIME)))
|
||||
{
|
||||
// Necessary for setting the system time in the NTP client
|
||||
log_warn("Required Linux capability CAP_SYS_TIME not available");
|
||||
capabilities_okay = false;
|
||||
}
|
||||
|
||||
// Free allocated memory
|
||||
free(hdr);
|
||||
|
||||
+36
-3
@@ -182,7 +182,7 @@ static bool readStringValue(struct conf_item *conf_item, const char *value, stru
|
||||
|
||||
// Free old password hash if it was allocated
|
||||
if(conf_item->t == CONF_STRING_ALLOCATED)
|
||||
free(conf_item->v.s);
|
||||
free(conf_item->v.s);
|
||||
|
||||
// Store new password hash
|
||||
conf_item->v.s = pwhash;
|
||||
@@ -306,6 +306,21 @@ static bool readStringValue(struct conf_item *conf_item, const char *value, stru
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONF_ENUM_BLOCKING_EDNS_MODE:
|
||||
{
|
||||
const int edns_mode = get_edns_mode_val(value);
|
||||
if(edns_mode != -1)
|
||||
conf_item->v.edns_mode = edns_mode;
|
||||
else
|
||||
{
|
||||
char *allowed = NULL;
|
||||
CONFIG_ITEM_ARRAY(conf_item->a, allowed);
|
||||
log_err("Config setting %s is invalid, allowed options are: %s", conf_item->k, allowed);
|
||||
free(allowed);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONF_STRUCT_IN_ADDR:
|
||||
{
|
||||
struct in_addr addr4 = { 0 };
|
||||
@@ -342,10 +357,11 @@ static bool readStringValue(struct conf_item *conf_item, const char *value, stru
|
||||
}
|
||||
case CONF_JSON_STRING_ARRAY:
|
||||
{
|
||||
cJSON *elem = cJSON_Parse(value);
|
||||
const char *json_error = NULL;
|
||||
cJSON *elem = cJSON_ParseWithOpts(value, &json_error, 0);
|
||||
if(elem == NULL)
|
||||
{
|
||||
log_err("Config setting %s is invalid: not valid JSON, error before: %s", conf_item->k, cJSON_GetErrorPtr());
|
||||
log_err("Config setting %s is invalid: not valid JSON, error at: %.20s", conf_item->k, json_error);
|
||||
return false;
|
||||
}
|
||||
if(!cJSON_IsArray(elem))
|
||||
@@ -396,6 +412,14 @@ int set_config_from_CLI(const char *key, const char *value)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Return early if the user tries to change some settings but the config
|
||||
// is in read-only mode
|
||||
if(config.misc.readOnly.v.b)
|
||||
{
|
||||
printf("Config is in read-only mode, changes are not allowed (misc.readOnly = true)\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Identify config option
|
||||
struct config newconf;
|
||||
duplicate_config(&newconf, &config);
|
||||
@@ -409,6 +433,7 @@ int set_config_from_CLI(const char *key, const char *value)
|
||||
if(strcmp(item->k, key) != 0)
|
||||
continue;
|
||||
|
||||
// Check if this is a read-only config option (forced by env var)
|
||||
if(item->f & FLAG_ENV_VAR)
|
||||
{
|
||||
log_err("Config option %s is read-only (set via environmental variable)", key);
|
||||
@@ -416,6 +441,14 @@ int set_config_from_CLI(const char *key, const char *value)
|
||||
return ENV_VAR_FORCED;
|
||||
}
|
||||
|
||||
// Check if this the special read-only config option
|
||||
if(item->f & FLAG_READ_ONLY)
|
||||
{
|
||||
log_err("Config option %s can only be set in pihole.toml, not via the CLI", key);
|
||||
free_config(&newconf);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// This is the config option we are looking for
|
||||
new_item = item;
|
||||
|
||||
|
||||
+1
-1
@@ -13,4 +13,4 @@
|
||||
int set_config_from_CLI(const char *key, const char *value);
|
||||
int get_config_from_CLI(const char *key, const bool quiet);
|
||||
|
||||
#endif //CONFIG_CLI_H
|
||||
#endif //CONFIG_CLI_H
|
||||
|
||||
+228
-120
File diff suppressed because it is too large
Load Diff
+42
-12
@@ -11,7 +11,7 @@
|
||||
#define CONFIG_H
|
||||
|
||||
// enum privacy_level
|
||||
#include "../enums.h"
|
||||
#include "enums.h"
|
||||
#include <stdbool.h>
|
||||
// typedef int16_t
|
||||
#include <sys/types.h>
|
||||
@@ -39,6 +39,9 @@
|
||||
// Location of the legacy (pre-v6.0) config file
|
||||
#define GLOBALCONFFILE_LEGACY "/etc/pihole/pihole-FTL.conf"
|
||||
|
||||
// Migration target for the legacy (pre-v6.0) config file
|
||||
#define MIGRATION_TARGET_V6 "/etc/pihole/migration_backup_v6"
|
||||
|
||||
union conf_value {
|
||||
bool b; // boolean value
|
||||
int i; // integer value
|
||||
@@ -57,6 +60,7 @@ union conf_value {
|
||||
enum listening_mode listeningMode; // enum listening_mode value
|
||||
enum web_theme web_theme; // enum web_theme value
|
||||
enum temp_unit temp_unit; // enum temp_unit value
|
||||
enum edns_mode edns_mode; // enum edns_mode value
|
||||
struct in_addr in_addr; // struct in_addr value
|
||||
struct in6_addr in6_addr; // struct in6_addr value
|
||||
cJSON *json; // cJSON * value
|
||||
@@ -80,6 +84,7 @@ enum conf_type {
|
||||
CONF_ENUM_PRIVACY_LEVEL,
|
||||
CONF_ENUM_LISTENING_MODE,
|
||||
CONF_ENUM_WEB_THEME,
|
||||
CONF_ENUM_BLOCKING_EDNS_MODE,
|
||||
CONF_ENUM_TEMP_UNIT,
|
||||
CONF_STRUCT_IN_ADDR,
|
||||
CONF_STRUCT_IN6_ADDR,
|
||||
@@ -90,12 +95,12 @@ enum conf_type {
|
||||
#define MAX_CONFIG_PATH_DEPTH 6
|
||||
|
||||
#define FLAG_RESTART_FTL (1 << 0)
|
||||
#define FLAG_ADVANCED_SETTING (1 << 1)
|
||||
#define FLAG_PSEUDO_ITEM (1 << 2)
|
||||
#define FLAG_INVALIDATE_SESSIONS (1 << 3)
|
||||
#define FLAG_WRITE_ONLY (1 << 4)
|
||||
#define FLAG_ENV_VAR (1 << 5)
|
||||
#define FLAG_CONF_IMPORTED (1 << 6)
|
||||
#define FLAG_PSEUDO_ITEM (1 << 1)
|
||||
#define FLAG_INVALIDATE_SESSIONS (1 << 2)
|
||||
#define FLAG_WRITE_ONLY (1 << 3)
|
||||
#define FLAG_ENV_VAR (1 << 4)
|
||||
#define FLAG_CONF_IMPORTED (1 << 5)
|
||||
#define FLAG_READ_ONLY (1 << 6)
|
||||
|
||||
struct conf_item {
|
||||
const char *k; // item Key
|
||||
@@ -147,10 +152,12 @@ struct config {
|
||||
struct {
|
||||
struct conf_item size;
|
||||
struct conf_item optimizer;
|
||||
struct conf_item upstreamBlockedTTL;
|
||||
} cache;
|
||||
struct {
|
||||
struct conf_item active;
|
||||
struct conf_item mode;
|
||||
struct conf_item edns;
|
||||
} blocking;
|
||||
struct {
|
||||
struct conf_item mozillaCanary;
|
||||
@@ -187,9 +194,32 @@ struct config {
|
||||
struct conf_item rapidCommit;
|
||||
struct conf_item multiDNS;
|
||||
struct conf_item logging;
|
||||
struct conf_item ignoreUnknownClients;
|
||||
struct conf_item hosts;
|
||||
} dhcp;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
struct conf_item active;
|
||||
struct conf_item address;
|
||||
} ipv4;
|
||||
struct {
|
||||
struct conf_item active;
|
||||
struct conf_item address;
|
||||
} ipv6;
|
||||
struct {
|
||||
struct conf_item active;
|
||||
struct conf_item server;
|
||||
struct conf_item interval;
|
||||
struct conf_item count;
|
||||
struct {
|
||||
struct conf_item set;
|
||||
struct conf_item device;
|
||||
struct conf_item utc;
|
||||
} rtc;
|
||||
} sync;
|
||||
} ntp;
|
||||
|
||||
struct {
|
||||
struct conf_item resolveIPv4;
|
||||
struct conf_item resolveIPv6;
|
||||
@@ -217,7 +247,6 @@ struct config {
|
||||
struct conf_item restore;
|
||||
} session;
|
||||
struct {
|
||||
struct conf_item rev_proxy;
|
||||
struct conf_item cert;
|
||||
} tls;
|
||||
struct {
|
||||
@@ -229,14 +258,14 @@ struct config {
|
||||
struct conf_item theme;
|
||||
} interface;
|
||||
struct {
|
||||
struct conf_item localAPIauth;
|
||||
struct conf_item searchAPIauth;
|
||||
struct conf_item max_sessions;
|
||||
struct conf_item prettyJSON;
|
||||
struct conf_item pwhash;
|
||||
struct conf_item password; // This is a pseudo-item
|
||||
struct conf_item totp_secret; // This is a write-only item
|
||||
struct conf_item app_pwhash;
|
||||
struct conf_item app_sudo;
|
||||
struct conf_item cli_pw;
|
||||
struct conf_item excludeClients;
|
||||
struct conf_item excludeDomains;
|
||||
struct conf_item maxHistory;
|
||||
@@ -273,6 +302,7 @@ struct config {
|
||||
struct conf_item etc_dnsmasq_d;
|
||||
struct conf_item dnsmasq_lines;
|
||||
struct conf_item extraLogging;
|
||||
struct conf_item readOnly;
|
||||
struct {
|
||||
struct conf_item load;
|
||||
struct conf_item shmem;
|
||||
@@ -311,6 +341,7 @@ struct config {
|
||||
struct conf_item webserver;
|
||||
struct conf_item extra;
|
||||
struct conf_item reserved;
|
||||
struct conf_item ntp;
|
||||
// all must be the last item in this struct
|
||||
struct conf_item all;
|
||||
} debug;
|
||||
@@ -324,8 +355,6 @@ extern struct config config;
|
||||
// Defined in config.c
|
||||
void set_debug_flags(struct config *conf);
|
||||
void set_all_debug(struct config *conf, const bool status);
|
||||
void initConfig(struct config *conf);
|
||||
void reset_config(struct conf_item *conf_item);
|
||||
bool readFTLconf(struct config *conf, const bool rewrite);
|
||||
bool getLogFilePath(void);
|
||||
struct conf_item *get_conf_item(struct config *conf, const unsigned int n);
|
||||
@@ -340,6 +369,7 @@ bool check_paths_equal(char **paths1, char **paths2, unsigned int max_level) __a
|
||||
const char *get_conf_type_str(const enum conf_type type) __attribute__ ((const));
|
||||
void replace_config(struct config *newconf);
|
||||
void reread_config(void);
|
||||
bool create_migration_target_v6(void);
|
||||
|
||||
// Defined in toml_reader.c
|
||||
bool readDebugSettings(void);
|
||||
|
||||
+56
-24
@@ -191,7 +191,7 @@ char *get_dnsmasq_line(const unsigned int lineno)
|
||||
static void write_config_header(FILE *fp, const char *description)
|
||||
{
|
||||
const time_t now = time(NULL);
|
||||
char timestring[TIMESTR_SIZE] = "";
|
||||
char timestring[TIMESTR_SIZE];
|
||||
get_timestr(timestring, now, false, false);
|
||||
fputs("# Pi-hole: A black hole for Internet advertisements\n", fp);
|
||||
fprintf(fp, "# (c) %u Pi-hole, LLC (https://pi-hole.net)\n", get_year(now));
|
||||
@@ -398,8 +398,13 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_
|
||||
fputs("# Use DNNSEC\n", pihole_conf);
|
||||
fputs("dnssec\n", pihole_conf);
|
||||
fputs("# 2017-02-02 root zone trust anchor\n", pihole_conf);
|
||||
fputs("# https://www.iana.org/reports/2017/root-ksk-2017.pdf\n", pihole_conf);
|
||||
fputs("trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D\n",
|
||||
pihole_conf);
|
||||
fputs("# 2024-07-26 root zone trust anchor\n", pihole_conf);
|
||||
fputs("# https://www.iana.org/reports/2024/root-ksk-2024.pdf\n", pihole_conf);
|
||||
fputs("trust-anchor=.,38696,8,2,683D2D0ACB8C9B712A1948B27F741219298D0A450D612C483AF444A4C0FB2B16\n",
|
||||
pihole_conf);
|
||||
fputs("\n", pihole_conf);
|
||||
}
|
||||
|
||||
@@ -449,6 +454,8 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_
|
||||
}
|
||||
fputs("\n", pihole_conf);
|
||||
|
||||
// Add upstream DNS servers for reverse lookups
|
||||
bool domain_revServer = false;
|
||||
const unsigned int revServers = cJSON_GetArraySize(conf->dns.revServers.v.json);
|
||||
for(unsigned int i = 0; i < revServers; i++)
|
||||
{
|
||||
@@ -485,8 +492,15 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_
|
||||
// If we have a reverse domain, we forward all queries to this domain to
|
||||
// the same destination
|
||||
if(strlen(domain) > 0)
|
||||
{
|
||||
fprintf(pihole_conf, "server=/%s/%s\n", domain, target);
|
||||
|
||||
// Check if the configured domain is the same as the main domain
|
||||
if(strlen(config.dns.domain.v.s) > 0 &&
|
||||
strcasecmp(domain, config.dns.domain.v.s) == 0)
|
||||
domain_revServer = true;
|
||||
}
|
||||
|
||||
// Forward unqualified names to the target only when the "never forward
|
||||
// non-FQDN" option is NOT ticked
|
||||
if(!conf->dns.domainNeeded.v.b)
|
||||
@@ -497,19 +511,14 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_
|
||||
free(copy);
|
||||
}
|
||||
|
||||
// When there is a Pi-hole domain set and "Never forward non-FQDNs" is
|
||||
// ticked, we add `local=/domain/` to signal that this domain is purely
|
||||
// local and FTL may answer queries from /etc/hosts or DHCP but should
|
||||
// never forward queries on that domain to any upstream servers
|
||||
// When "Never forward non-FQDNs" is ticked, we add `local=//` to signal
|
||||
// that non-FQDNs queries should never be sent to any upstream servers
|
||||
if(conf->dns.domainNeeded.v.b)
|
||||
{
|
||||
fputs("# Never forward A or AAAA queries for plain names, without\n",pihole_conf);
|
||||
fputs("# dots or domain parts, to upstream nameservers. If the name\n", pihole_conf);
|
||||
fputs("# is not known from /etc/hosts or DHCP a NXDOMAIN is returned\n", pihole_conf);
|
||||
if(strlen(conf->dns.domain.v.s))
|
||||
fprintf(pihole_conf, "local=/%s/\n\n", conf->dns.domain.v.s);
|
||||
else
|
||||
fputs("\n", pihole_conf);
|
||||
fputs("# is not known from /etc/hosts or DHCP, NXDOMAIN is returned\n", pihole_conf);
|
||||
fputs("local=//\n\n", pihole_conf);
|
||||
}
|
||||
|
||||
// Add domain to DNS server. It will also be used for DHCP if the DHCP
|
||||
@@ -517,7 +526,20 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_
|
||||
if(strlen(conf->dns.domain.v.s) > 0)
|
||||
{
|
||||
fputs("# DNS domain for both the DNS and DHCP server\n", pihole_conf);
|
||||
fprintf(pihole_conf, "domain=%s\n\n", conf->dns.domain.v.s);
|
||||
if(!domain_revServer)
|
||||
{
|
||||
fputs("# This DNS domain in purely local. FTL may answer queries from\n", pihole_conf);
|
||||
fputs("# /etc/hosts or DHCP but should never forward queries on that\n", pihole_conf);
|
||||
fputs("# domain to any upstream servers\n", pihole_conf);
|
||||
fprintf(pihole_conf, "domain=%s\n", conf->dns.domain.v.s);
|
||||
fprintf(pihole_conf, "local=/%s/\n\n", conf->dns.domain.v.s);
|
||||
}
|
||||
else
|
||||
{
|
||||
fputs("# This DNS domain is also used for reverse lookups\n", pihole_conf);
|
||||
fputs("# (see server=/<domain>/target above)\n", pihole_conf);
|
||||
fprintf(pihole_conf, "domain=%s\n\n", conf->dns.domain.v.s);
|
||||
}
|
||||
}
|
||||
|
||||
if(conf->dhcp.active.v.b)
|
||||
@@ -582,6 +604,22 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_
|
||||
fputs("log-dhcp\n\n", pihole_conf);
|
||||
}
|
||||
|
||||
// Check if IPv4 NTP server is active and broadcast it as DHCP option
|
||||
if(conf->ntp.ipv4.active.v.b)
|
||||
{
|
||||
fputs("# Add NTP server to DHCP\n", pihole_conf);
|
||||
// The special address 0.0.0.0 is taken to mean "the
|
||||
// address of the machine running the DHCP server"
|
||||
fputs("dhcp-option=option:ntp-server,0.0.0.0\n\n", pihole_conf);
|
||||
}
|
||||
|
||||
// Add option to ignore unknown clients if enabled
|
||||
if(conf->dhcp.ignoreUnknownClients.v.b)
|
||||
{
|
||||
fputs("# Ignore clients not configured below\n", pihole_conf);
|
||||
fputs("dhcp-ignore=tag:!known\n\n", pihole_conf);
|
||||
}
|
||||
|
||||
// Add per-host parameters
|
||||
if(cJSON_GetArraySize(conf->dhcp.hosts.v.json) > 0)
|
||||
{
|
||||
@@ -716,6 +754,12 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_
|
||||
{
|
||||
log_warn("New dnsmasq configuration is not valid (%s), config remains unchanged", errbuf);
|
||||
|
||||
if(debug_flags[DEBUG_ANY])
|
||||
{
|
||||
log_debug(DEBUG_ANY, "Temporary dnsmasq config file left in place for debugging purposes");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove temporary config file
|
||||
if(remove(DNSMASQ_TEMP_CONF) != 0)
|
||||
{
|
||||
@@ -760,7 +804,6 @@ bool read_legacy_dhcp_static_config(void)
|
||||
{
|
||||
// Check if file exists, if not, there is nothing to do
|
||||
const char *path = DNSMASQ_STATIC_LEASES;
|
||||
const char *target = DNSMASQ_STATIC_LEASES".bck";
|
||||
if(!file_exists(path))
|
||||
return true;
|
||||
|
||||
@@ -810,11 +853,6 @@ bool read_legacy_dhcp_static_config(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move file to backup location
|
||||
log_info("Moving %s to %s", path, target);
|
||||
if(rename(path, target) != 0)
|
||||
log_warn("Unable to move %s to %s: %s", path, target, strerror(errno));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -823,7 +861,6 @@ bool read_legacy_cnames_config(void)
|
||||
{
|
||||
// Check if file exists, if not, there is nothing to do
|
||||
const char *path = DNSMASQ_CNAMES;
|
||||
const char *target = DNSMASQ_CNAMES".bck";
|
||||
if(!file_exists(path))
|
||||
return true;
|
||||
|
||||
@@ -873,11 +910,6 @@ bool read_legacy_cnames_config(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move file to backup location
|
||||
log_info("Moving %s to %s", path, target);
|
||||
if(rename(path, target) != 0)
|
||||
log_warn("Unable to move %s to %s: %s", path, target, strerror(errno));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -885,7 +917,7 @@ bool read_legacy_custom_hosts_config(void)
|
||||
{
|
||||
// Check if file exists, if not, there is nothing to do
|
||||
const char *path = DNSMASQ_CUSTOM_LIST_LEGACY;
|
||||
const char *target = DNSMASQ_CUSTOM_LIST_LEGACY".bck";
|
||||
const char *target = DNSMASQ_CUSTOM_LIST_LEGACY_TARGET;
|
||||
if(!file_exists(path))
|
||||
return true;
|
||||
|
||||
|
||||
@@ -24,11 +24,12 @@ bool write_custom_list(void);
|
||||
|
||||
#define DNSMASQ_PH_CONFIG "/etc/pihole/dnsmasq.conf"
|
||||
#define DNSMASQ_TEMP_CONF "/etc/pihole/dnsmasq.conf.temp"
|
||||
#define DNSMASQ_STATIC_LEASES "/etc/pihole/04-pihole-static-dhcp.conf"
|
||||
#define DNSMASQ_CNAMES "/etc/pihole/05-pihole-custom-cname.conf"
|
||||
#define DNSMASQ_STATIC_LEASES MIGRATION_TARGET_V6"/04-pihole-static-dhcp.conf"
|
||||
#define DNSMASQ_CNAMES MIGRATION_TARGET_V6"/05-pihole-custom-cname.conf"
|
||||
#define DNSMASQ_HOSTSDIR "/etc/pihole/hosts"
|
||||
#define DNSMASQ_CUSTOM_LIST DNSMASQ_HOSTSDIR"/custom.list"
|
||||
#define DNSMASQ_CUSTOM_LIST_LEGACY "/etc/pihole/custom.list"
|
||||
#define DNSMASQ_CUSTOM_LIST_LEGACY_TARGET MIGRATION_TARGET_V6"/custom.list"
|
||||
#define DHCPLEASESFILE "/etc/pihole/dhcp.leases"
|
||||
|
||||
#endif //DNSMASQ_CONFIG_H
|
||||
|
||||
+112
-3
@@ -19,6 +19,10 @@
|
||||
#include "args.h"
|
||||
// suggest_closest()
|
||||
#include "config/suggest.h"
|
||||
// LINE_MAX
|
||||
#include <limits.h>
|
||||
// openFTLtoml()
|
||||
#include "config/toml_helper.h"
|
||||
struct env_item
|
||||
{
|
||||
bool used;
|
||||
@@ -161,16 +165,51 @@ void freeEnvVars(void)
|
||||
}
|
||||
}
|
||||
|
||||
bool readEnvValue(struct conf_item *conf_item, struct config *newconf)
|
||||
bool __attribute__((nonnull(1,2,3))) readEnvValue(struct conf_item *conf_item, struct config *newconf, cJSON *forced_vars, bool *reset)
|
||||
{
|
||||
// First check if a environmental variable with the given key exists by
|
||||
// iterating over the list of FTLCONF_ variables
|
||||
struct env_item *item = getFTLenv(conf_item->e);
|
||||
|
||||
// Return early if this environment variable does not exist
|
||||
if(item == NULL)
|
||||
return false;
|
||||
{
|
||||
// Environment variable does not exist
|
||||
|
||||
// Check if this was a forced setting before
|
||||
// If so, we revert the config option to default
|
||||
for(int i = 0; i < cJSON_GetArraySize(forced_vars); i++)
|
||||
{
|
||||
const char *forced_var = cJSON_GetArrayItem(forced_vars, i)->valuestring;
|
||||
if(strcmp(forced_var, conf_item->k) == 0)
|
||||
{
|
||||
log_info("Resetting %s to default (not forced anymore)", conf_item->k);
|
||||
|
||||
// Revert to default
|
||||
if(conf_item->t == CONF_STRING_ALLOCATED)
|
||||
{
|
||||
// Free previously allocated string
|
||||
free(conf_item->v.s);
|
||||
// Make a duplicate of the default value
|
||||
conf_item->v.s = strdup(conf_item->d.s);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Revert to default value
|
||||
memcpy(&conf_item->v, &conf_item->d, sizeof(conf_item->v));
|
||||
}
|
||||
|
||||
// Mark this environment variable as reset to
|
||||
// default
|
||||
if(reset != NULL)
|
||||
*reset = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Return false as this setting is not forced by an environment
|
||||
// variable
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark this environment variable as used
|
||||
item->used = true;
|
||||
@@ -460,6 +499,25 @@ bool readEnvValue(struct conf_item *conf_item, struct config *newconf)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONF_ENUM_BLOCKING_EDNS_MODE:
|
||||
{
|
||||
const int edns_mode = get_edns_mode_val(envvar);
|
||||
if(edns_mode != -1)
|
||||
{
|
||||
conf_item->v.edns_mode = edns_mode;
|
||||
item->valid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
item->error = "not an allowed option";
|
||||
item->allowed = conf_item->h;
|
||||
log_warn("ENV %s is %s, allowed options are: %s",
|
||||
conf_item->e, item->error, item->allowed);
|
||||
item->valid = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONF_ENUM_PRIVACY_LEVEL:
|
||||
{
|
||||
int val = 0;
|
||||
@@ -561,3 +619,54 @@ bool readEnvValue(struct conf_item *conf_item, struct config *newconf)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
cJSON *read_forced_vars(const unsigned int version)
|
||||
{
|
||||
// Create cJSON array to store forced variables
|
||||
cJSON *env_vars = cJSON_CreateArray();
|
||||
|
||||
// Try to open default config file. Use fallback if not found
|
||||
FILE *fp;
|
||||
if((fp = openFTLtoml("r", version)) == NULL)
|
||||
{
|
||||
// Return empty cJSON array
|
||||
return env_vars;
|
||||
}
|
||||
|
||||
// Read file line by line until we get to the end of the file where the
|
||||
// statistics are stored, specifically, the line starting with
|
||||
// "# X entr{y is,ies are} forced through environment"
|
||||
char line[LINE_MAX] = { 0 };
|
||||
while(fgets(line, sizeof(line), fp) != NULL)
|
||||
{
|
||||
// Check if this is the line we are looking for
|
||||
if(strncmp(line, "# ", 2) == 0)
|
||||
{
|
||||
// Check if this is the line we are looking for
|
||||
if(strstr(line, "forced through environment:") != NULL)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the next lines to extract the variables
|
||||
while(fgets(line, sizeof(line), fp) != NULL)
|
||||
{
|
||||
// Check if this is the line we are looking for
|
||||
if(strncmp(line, "# - ", 6) != 0)
|
||||
{
|
||||
// We are done, break out of the loop
|
||||
break;
|
||||
}
|
||||
|
||||
// else: Add the variable to the cJSON array
|
||||
// Trim the string (remove leading "# - " and trailing newline)
|
||||
line[strcspn(line, "\n")] = '\0';
|
||||
cJSON_AddItemToArray(env_vars, cJSON_CreateString(line + 6));
|
||||
}
|
||||
|
||||
// Close file and release exclusive lock
|
||||
closeFTLtoml(fp);
|
||||
|
||||
// Return cJSON array
|
||||
return env_vars;
|
||||
}
|
||||
|
||||
+2
-1
@@ -23,6 +23,7 @@ int dist(const char *str);
|
||||
void getEnvVars(void);
|
||||
void freeEnvVars(void);
|
||||
void printFTLenv(void);
|
||||
bool readEnvValue(struct conf_item *conf_item, struct config *newconf);
|
||||
bool readEnvValue(struct conf_item *conf_item, struct config *newconf, cJSON *forced_vars, bool *reset) __attribute__((nonnull(1,2,3)));
|
||||
cJSON *read_forced_vars(const unsigned int version);
|
||||
|
||||
#endif //CONFIG_ENV_H
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "log.h"
|
||||
#include <sys/inotify.h>
|
||||
// NAME_MAX
|
||||
#include <limits.h>
|
||||
#include <linux/limits.h>
|
||||
|
||||
#define WATCHDIR "/etc/pihole"
|
||||
|
||||
|
||||
+33
-23
@@ -28,7 +28,7 @@ static pthread_mutex_t lock;
|
||||
// Private prototypes
|
||||
static char *parseFTLconf(FILE *fp, const char *key);
|
||||
static void releaseConfigMemory(void);
|
||||
static char *getPath(FILE* fp, const char *option, char *ptr);
|
||||
static char *__attribute__((nonnull(1,2,3), malloc, warn_unused_result)) getPath(FILE* fp, const char *option, char *ptr);
|
||||
static bool parseBool(const char *option, bool *ptr);
|
||||
static void readDebugingSettingsLegacy(FILE *fp);
|
||||
static void getBlockingModeLegacy(FILE *fp);
|
||||
@@ -66,6 +66,10 @@ bool getLogFilePathLegacy(struct config *conf, FILE *fp)
|
||||
// No option set => use default log location
|
||||
if(buffer == NULL)
|
||||
{
|
||||
// Free previously allocated memory (if any)
|
||||
if(conf->files.log.ftl.t == CONF_STRING_ALLOCATED)
|
||||
free(conf->files.log.ftl.v.s);
|
||||
|
||||
// Use standard path if no custom path was obtained from the config file
|
||||
conf->files.log.ftl.v.s = strdup("/var/log/pihole/FTL.log");
|
||||
conf->files.log.ftl.t = CONF_STRING_ALLOCATED;
|
||||
@@ -77,9 +81,12 @@ bool getLogFilePathLegacy(struct config *conf, FILE *fp)
|
||||
strerror(errno), errno);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
// Use sscanf() to obtain filename from config file parameter only if buffer != NULL
|
||||
else if(sscanf(buffer, "%127ms", &val_buffer) == 0)
|
||||
else if((val_buffer = calloc(128, sizeof(char))) == NULL || sscanf(buffer, "%127s", val_buffer) == 0)
|
||||
{
|
||||
// Free previously allocated memory (if any)
|
||||
if(conf->files.log.ftl.t == CONF_STRING_ALLOCATED)
|
||||
@@ -89,9 +96,14 @@ bool getLogFilePathLegacy(struct config *conf, FILE *fp)
|
||||
conf->files.log.ftl.v.s = NULL;
|
||||
conf->files.log.ftl.t = CONF_STRING;
|
||||
log_info("Using syslog facility");
|
||||
|
||||
// Free buffer
|
||||
if(val_buffer != NULL)
|
||||
free(val_buffer);
|
||||
}
|
||||
|
||||
if(val_buffer)
|
||||
// Set string if memory allocation was successful and a value was read
|
||||
if(val_buffer != NULL && strlen(val_buffer) > 0)
|
||||
{
|
||||
// Free previously allocated memory (if any)
|
||||
if(conf->files.log.ftl.t == CONF_STRING_ALLOCATED)
|
||||
@@ -256,7 +268,7 @@ const char *readFTLlegacy(struct config *conf)
|
||||
buffer = parseFTLconf(fp, "DELAY_STARTUP");
|
||||
|
||||
unsigned int unum;
|
||||
if(buffer != NULL && sscanf(buffer, "%u", &unum) && unum > 0 && unum <= 300)
|
||||
if(buffer != NULL && sscanf(buffer, "%u", &unum) == 1 && unum > 0 && unum <= 300)
|
||||
conf->misc.delay_startup.v.ui = unum;
|
||||
|
||||
// BLOCK_ESNI
|
||||
@@ -305,11 +317,6 @@ const char *readFTLlegacy(struct config *conf)
|
||||
if(buffer != NULL)
|
||||
conf->webserver.acl.v.s = strdup(buffer);
|
||||
|
||||
// API_AUTH_FOR_LOCALHOST
|
||||
// defaults to: true
|
||||
buffer = parseFTLconf(fp, "API_AUTH_FOR_LOCALHOST");
|
||||
parseBool(buffer, &conf->webserver.api.localAPIauth.v.b);
|
||||
|
||||
// API_SESSION_TIMEOUT
|
||||
// How long should a session be considered valid after login?
|
||||
// defaults to: 300 seconds
|
||||
@@ -589,35 +596,38 @@ const char *readFTLlegacy(struct config *conf)
|
||||
return path;
|
||||
}
|
||||
|
||||
static char* getPath(FILE* fp, const char *option, char *ptr)
|
||||
static char *__attribute__((nonnull(1,2,3), malloc, warn_unused_result)) getPath(FILE* fp, const char *option, char *path_default)
|
||||
{
|
||||
// This subroutine is used to read paths from pihole-FTL.conf
|
||||
// fp: File ptr to opened and readable config file
|
||||
// option: Option string ("key") to try to read
|
||||
// ptr: Location where read (or default) parameter is stored
|
||||
// fp: File path to opened and readable config file
|
||||
// option: Option string ("key") to try to read
|
||||
// path_default: Location where read (or default) parameter is stored
|
||||
char *buffer = parseFTLconf(fp, option);
|
||||
|
||||
errno = 0;
|
||||
// Use sscanf() to obtain filename from config file parameter only if buffer != NULL
|
||||
if(buffer == NULL || sscanf(buffer, "%127ms", &ptr) != 1)
|
||||
{
|
||||
// Use standard path if no custom path was obtained from the config file
|
||||
return ptr;
|
||||
}
|
||||
char *val_ptr = calloc(128, sizeof(char));
|
||||
|
||||
// Test if memory allocation was successful
|
||||
if(ptr == NULL)
|
||||
if(val_ptr == NULL)
|
||||
{
|
||||
log_crit("Allocating memory for %s failed (%s, %i). Exiting.", option, strerror(errno), errno);
|
||||
log_crit("Allocating memory for %s failed (%s, %i). Exiting.",
|
||||
option, strerror(errno), errno);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
else if(strlen(ptr) == 0)
|
||||
|
||||
if(buffer == NULL || sscanf(buffer, "%127s", val_ptr) != 1 || strlen(val_ptr) == 0)
|
||||
{
|
||||
// Use standard path if no custom path was obtained from the config file
|
||||
log_info(" %s: Empty path is not possible, using default",
|
||||
option);
|
||||
|
||||
strncpy(val_ptr, path_default, 127);
|
||||
val_ptr[127] = '\0';
|
||||
return val_ptr;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
return val_ptr;
|
||||
}
|
||||
|
||||
static char *parseFTLconf(FILE *fp, const char * key)
|
||||
@@ -703,7 +713,7 @@ void releaseConfigMemory(void)
|
||||
void init_config_mutex(void)
|
||||
{
|
||||
// Initialize the lock attributes
|
||||
pthread_mutexattr_t lock_attr = {};
|
||||
pthread_mutexattr_t lock_attr;
|
||||
pthread_mutexattr_init(&lock_attr);
|
||||
|
||||
// Initialize the lock
|
||||
|
||||
+154
-21
@@ -39,6 +39,20 @@
|
||||
// 2023, using 128 bits should be sufficient for the foreseeable future.
|
||||
#define SALT_LEN 16 // 16 bytes = 128 bits
|
||||
|
||||
// App password length
|
||||
// The app password is a 256 bit password. This is a good balance between
|
||||
// security and usability. It is long enough to be secure.
|
||||
#define APPPW_LEN 32 // 32 bytes = 256 bits
|
||||
|
||||
// CLI password file and memory
|
||||
// We store the password in plain memory. This is not a security issue as the
|
||||
// memory is only accessible to the user running the FTL process. Anyone with
|
||||
// sufficient access to the memory (ptrace, swapfile) would also have access to
|
||||
// the password file. Leaking the password after exit is not a concern as a new
|
||||
// password is generated on every start.
|
||||
#define CLI_PW_FILE "/etc/pihole/cli_pw"
|
||||
static char *cli_password = NULL;
|
||||
|
||||
// Convert RAW data into hex representation
|
||||
// Two hexadecimal digits are generated for each input byte.
|
||||
void sha256_raw_to_hex(uint8_t *data, char *buffer)
|
||||
@@ -185,6 +199,14 @@ static char * __attribute__((malloc)) balloon_password(const char *password,
|
||||
|
||||
// Build PHC string-like output (output string is 101 bytes long (measured))
|
||||
char *output = calloc(128, sizeof(char));
|
||||
|
||||
if(output == NULL || salt_base64 == NULL || scratch_base64 == NULL)
|
||||
{
|
||||
log_err("Error while allocating memory for PHC string: %s", strerror(errno));
|
||||
goto clean_and_exit;
|
||||
}
|
||||
|
||||
// Generate PHC string
|
||||
int size = snprintf(output, 128, "$BALLOON-SHA256$v=1$s=%zu,t=%zu$%s$%s",
|
||||
s_cost,
|
||||
t_cost,
|
||||
@@ -199,11 +221,15 @@ static char * __attribute__((malloc)) balloon_password(const char *password,
|
||||
}
|
||||
|
||||
clean_and_exit:
|
||||
free(scratch);
|
||||
free(salt_base64);
|
||||
free(scratch_base64);
|
||||
// Clean up
|
||||
if(scratch != NULL)
|
||||
free(scratch);
|
||||
if(salt_base64 != NULL)
|
||||
free(salt_base64);
|
||||
if(scratch_base64 != NULL)
|
||||
free(scratch_base64);
|
||||
|
||||
return output;
|
||||
return output; // may be NULL on failure (unlikely)
|
||||
}
|
||||
|
||||
// Parse a PHC string and return the parameters and hash
|
||||
@@ -314,11 +340,15 @@ char * __attribute__((malloc)) create_password(const char *password)
|
||||
|
||||
enum password_result verify_login(const char *password)
|
||||
{
|
||||
// Check if this is the CLI password
|
||||
if(config.webserver.api.cli_pw.v.b && cli_password != NULL)
|
||||
{
|
||||
if(strcmp(cli_password, password) == 0)
|
||||
return CLIPASSWORD_CORRECT;
|
||||
}
|
||||
|
||||
enum password_result pw = verify_password(password, config.webserver.api.pwhash.v.s, true);
|
||||
if(pw == PASSWORD_CORRECT)
|
||||
log_debug(DEBUG_API, "Password correct");
|
||||
else
|
||||
log_debug(DEBUG_API, "Password incorrect");
|
||||
log_debug(DEBUG_API, "Password %s correct", pw == PASSWORD_CORRECT ? "" : "not");
|
||||
|
||||
// Check if an application password is set and if it matches
|
||||
if(pw == PASSWORD_INCORRECT &&
|
||||
@@ -410,7 +440,6 @@ enum password_result verify_password(const char *password, const char *pwhash, c
|
||||
config.webserver.api.pwhash.v.s = new_hash;
|
||||
config.webserver.api.pwhash.t = CONF_STRING_ALLOCATED;
|
||||
writeFTLtoml(true);
|
||||
free(new_hash);
|
||||
}
|
||||
|
||||
// Successful logins do not count against rate-limiting
|
||||
@@ -604,6 +633,16 @@ int run_performance_test(void)
|
||||
|
||||
bool set_and_check_password(struct conf_item *conf_item, const char *password)
|
||||
{
|
||||
// Check if the user wants to set an empty password but the password is
|
||||
// already empty, or if the newly set password is the same as the old
|
||||
// one
|
||||
if((strlen(password) == 0 && strlen(config.webserver.api.pwhash.v.s) == 0) ||
|
||||
verify_password(password, config.webserver.api.pwhash.v.s, false) == PASSWORD_CORRECT)
|
||||
{
|
||||
log_debug(DEBUG_CONFIG, "Password unchanged, not updating");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get password hash as allocated string (an empty string is hashed to an empty string)
|
||||
char *pwhash = strlen(password) > 0 ? create_password(password) : strdup("");
|
||||
|
||||
@@ -625,33 +664,46 @@ bool set_and_check_password(struct conf_item *conf_item, const char *password)
|
||||
|
||||
// Set item
|
||||
conf_item->v.s = pwhash;
|
||||
conf_item->t = CONF_STRING_ALLOCATED;
|
||||
log_debug(DEBUG_CONFIG, "Set %s to \"%s\"", conf_item->k, conf_item->v.s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool generate_app_password(char **password, char **pwhash)
|
||||
bool generate_password(char **password, char **pwhash)
|
||||
{
|
||||
// Generate a 128 bit random salt
|
||||
// genrandom() returns cryptographically secure random data
|
||||
uint8_t salt[SALT_LEN] = { 0 };
|
||||
if(getrandom(salt, sizeof(salt), 0) < 0)
|
||||
{
|
||||
log_err("getrandom() failed in generate_app_password()");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate a 256 bit random password
|
||||
uint8_t password_raw[256/8] = { 0 };
|
||||
// genrandom() returns cryptographically secure random data
|
||||
uint8_t password_raw[APPPW_LEN] = { 0 };
|
||||
if(getrandom(password_raw, sizeof(password_raw), 0) < 0)
|
||||
{
|
||||
log_err("getrandom() failed in generate_app_password()");
|
||||
log_err("getrandom() failed in generate_password()");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Encode password as base64
|
||||
*password = base64_encode(password_raw, sizeof(password_raw));
|
||||
|
||||
if(*password == NULL)
|
||||
{
|
||||
log_err("Error while encoding password as base64");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(pwhash == NULL)
|
||||
{
|
||||
// No password hash requested
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate a 128 bit random salt
|
||||
uint8_t salt[SALT_LEN] = { 0 };
|
||||
if(getrandom(salt, sizeof(salt), 0) < 0)
|
||||
{
|
||||
log_err("getrandom() failed in generate_password()");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate balloon PHC-encoded password hash
|
||||
*pwhash = balloon_password(*password, salt, true);
|
||||
|
||||
@@ -666,3 +718,84 @@ bool generate_app_password(char **password, char **pwhash)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool create_cli_password(void)
|
||||
{
|
||||
// Check if the CLI password is enabled
|
||||
if(!config.webserver.api.cli_pw.v.b)
|
||||
{
|
||||
log_debug(DEBUG_API, "CLI password is not set");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate a new CLI password hash
|
||||
if(!generate_password(&cli_password, NULL))
|
||||
{
|
||||
log_err("Failed to generate CLI password hash!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store the CLI password in the corresponding file
|
||||
FILE *file = fopen(CLI_PW_FILE, "w");
|
||||
if(file == NULL)
|
||||
{
|
||||
log_err("Failed to open CLI password file for writing: %s", strerror(errno));
|
||||
free(cli_password);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write password
|
||||
if(fputs(cli_password, file) == EOF)
|
||||
{
|
||||
log_err("Failed to write CLI password to file: %s", strerror(errno));
|
||||
fclose(file);
|
||||
free(cli_password);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Close file
|
||||
fclose(file);
|
||||
|
||||
// Set file permissions to 0640
|
||||
if(chmod(CLI_PW_FILE, S_IRUSR | S_IWUSR | S_IRGRP) < 0)
|
||||
{
|
||||
log_err("Failed to set permissions on CLI password file: %s", strerror(errno));
|
||||
free(cli_password);
|
||||
return false;
|
||||
}
|
||||
|
||||
log_debug(DEBUG_API, "CLI password set and stored in file");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool remove_cli_password(void)
|
||||
{
|
||||
// Remove the CLI password from memory (if allocated)
|
||||
if(cli_password != NULL)
|
||||
{
|
||||
free(cli_password);
|
||||
cli_password = NULL;
|
||||
}
|
||||
|
||||
// Empty the CLI password file
|
||||
FILE *file = fopen(CLI_PW_FILE, "w");
|
||||
if(file == NULL)
|
||||
{
|
||||
log_err("Failed to open CLI password file for writing: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Close file
|
||||
fclose(file);
|
||||
|
||||
// Remove the CLI password file from disk
|
||||
// If the file does not exist, we returned above already
|
||||
if(unlink(CLI_PW_FILE) < 0)
|
||||
{
|
||||
log_err("Failed to remove CLI password file: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
log_debug(DEBUG_API, "CLI password removed");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -20,13 +20,16 @@ enum password_result verify_login(const char *password);
|
||||
enum password_result verify_password(const char *password, const char *pwhash, const bool rate_limiting);
|
||||
int run_performance_test(void);
|
||||
bool set_and_check_password(struct conf_item *conf_item, const char *password);
|
||||
bool generate_app_password(char **password, char **pwhash);
|
||||
bool generate_password(char **password, char **pwhash);
|
||||
bool create_cli_password(void);
|
||||
bool remove_cli_password(void);
|
||||
|
||||
enum password_result {
|
||||
PASSWORD_INCORRECT = 0,
|
||||
PASSWORD_CORRECT = 1,
|
||||
APPPASSWORD_CORRECT = 2,
|
||||
NO_PASSWORD_SET = 3,
|
||||
CLIPASSWORD_CORRECT = 3,
|
||||
NO_PASSWORD_SET = 4,
|
||||
PASSWORD_RATE_LIMITED = -1
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
+69
-60
@@ -30,7 +30,7 @@ static void get_conf_string_from_setupVars(const char *key, struct conf_item *co
|
||||
if(setupVarsValue == NULL)
|
||||
{
|
||||
// Do not change default value, this value is not set in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Not set", key);
|
||||
log_info("setupVars.conf:%s -> Not set", key);
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -48,7 +48,7 @@ static void get_conf_string_from_setupVars(const char *key, struct conf_item *co
|
||||
clearSetupVarsArray();
|
||||
|
||||
// Parameter present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Setting %s to %s", key, conf_item->k, conf_item->v.s);
|
||||
log_info("setupVars.conf:%s -> Setting %s to %s", key, conf_item->k, conf_item->v.s);
|
||||
}
|
||||
|
||||
static void get_conf_ipv4_from_setupVars(const char *key, struct conf_item *conf_item)
|
||||
@@ -64,7 +64,7 @@ static void get_conf_ipv4_from_setupVars(const char *key, struct conf_item *conf
|
||||
if(setupVarsValue == NULL)
|
||||
{
|
||||
// Do not change default value, this value is not set in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Not set", key);
|
||||
log_info("setupVars.conf:%s -> Not set", key);
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -75,7 +75,7 @@ static void get_conf_ipv4_from_setupVars(const char *key, struct conf_item *conf
|
||||
memset(&conf_item->v.in_addr, 0, sizeof(struct in_addr));
|
||||
else if(inet_pton(AF_INET, setupVarsValue, &conf_item->v.in_addr) != 1)
|
||||
{
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Invalid IPv4 address: %s", key, setupVarsValue);
|
||||
log_info("setupVars.conf:%s -> Invalid IPv4 address: %s", key, setupVarsValue);
|
||||
memset(&conf_item->v.in_addr, 0, sizeof(struct in_addr));
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ static void get_conf_ipv4_from_setupVars(const char *key, struct conf_item *conf
|
||||
clearSetupVarsArray();
|
||||
|
||||
// Parameter present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Setting %s to %s", key, conf_item->k, inet_ntoa(conf_item->v.in_addr));
|
||||
log_info("setupVars.conf:%s -> Setting %s to %s", key, conf_item->k, inet_ntoa(conf_item->v.in_addr));
|
||||
}
|
||||
|
||||
static void get_conf_bool_from_setupVars(const char *key, struct conf_item *conf_item)
|
||||
@@ -100,7 +100,7 @@ static void get_conf_bool_from_setupVars(const char *key, struct conf_item *conf
|
||||
if(boolean == NULL)
|
||||
{
|
||||
// Do not change default value, this value is not set in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Not set", key);
|
||||
log_info("setupVars.conf:%s -> Not set", key);
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -116,13 +116,12 @@ static void get_conf_bool_from_setupVars(const char *key, struct conf_item *conf
|
||||
clearSetupVarsArray();
|
||||
|
||||
// Parameter present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Setting %s to %s",
|
||||
key, conf_item->k, conf_item->v.b ? "true" : "false");
|
||||
log_info("setupVars.conf:%s -> Setting %s to %s",
|
||||
key, conf_item->k, conf_item->v.b ? "true" : "false");
|
||||
}
|
||||
|
||||
static void get_revServer_from_setupVars(void)
|
||||
{
|
||||
bool active = false;
|
||||
char *cidr = NULL;
|
||||
char *target = NULL;
|
||||
char *domain = NULL;
|
||||
@@ -130,17 +129,21 @@ static void get_revServer_from_setupVars(void)
|
||||
if(active_str == NULL)
|
||||
{
|
||||
// Do not change default value, this value is not set in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:REV_SERVER -> Not set");
|
||||
log_info("setupVars.conf:REV_SERVER -> Not set");
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
return;
|
||||
}
|
||||
else
|
||||
// Parameter present in setupVars.conf, check if either "true" or "false"
|
||||
if(strcasecmp(active_str, "true") != 0 && strcasecmp(active_str, "false") != 0)
|
||||
{
|
||||
// Parameter present in setupVars.conf
|
||||
active = getSetupVarsBool(active_str);
|
||||
log_info("setupVars.conf:REV_SERVER -> Invalid value: %s", active_str);
|
||||
|
||||
clearSetupVarsArray();
|
||||
return;
|
||||
}
|
||||
bool active = strcasecmp(active_str, "true") == 0;
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -151,6 +154,8 @@ static void get_revServer_from_setupVars(void)
|
||||
cidr = strdup(cidr_str);
|
||||
trim_whitespace(cidr);
|
||||
}
|
||||
else
|
||||
log_info("setupVars.conf:REV_SERVER_CIDR -> Not set");
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -161,6 +166,8 @@ static void get_revServer_from_setupVars(void)
|
||||
target = strdup(target_str);
|
||||
trim_whitespace(target);
|
||||
}
|
||||
else
|
||||
log_info("setupVars.conf:REV_SERVER_TARGET -> Not set");
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -171,25 +178,36 @@ static void get_revServer_from_setupVars(void)
|
||||
domain = strdup(domain_str);
|
||||
trim_whitespace(domain);
|
||||
}
|
||||
else
|
||||
log_info("setupVars.conf:REV_SERVER_DOMAIN -> Not set");
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
|
||||
// Only add the entry if all values are present and active
|
||||
if(active && cidr != NULL && target != NULL && domain != NULL)
|
||||
if(cidr != NULL && target != NULL && domain != NULL)
|
||||
{
|
||||
// Build comma-separated string of all values
|
||||
// 8 = 3 commas, "true", and null terminator
|
||||
char *old = calloc(strlen(cidr) + strlen(target) + strlen(domain) + 8, sizeof(char));
|
||||
if(old)
|
||||
// 9 = 3 commas, "true/false", and null terminator
|
||||
char *old = calloc(strlen(cidr) + strlen(target) + strlen(domain) + 9, sizeof(char));
|
||||
if(old != NULL)
|
||||
{
|
||||
// Add to new config
|
||||
// active is always true as we only add active entries
|
||||
sprintf(old, "true,%s,%s,%s", cidr, target, domain);
|
||||
sprintf(old, "%s,%s,%s,%s", active ? "true" : "false", cidr, target, domain);
|
||||
cJSON_AddItemToArray(config.dns.revServers.v.json, cJSON_CreateString(old));
|
||||
|
||||
// Parameter present in setupVars.conf
|
||||
log_info("setupVars.conf:REV_SERVER -> Setting %s to %s",
|
||||
config.dns.revServers.k, old);
|
||||
free(old);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parameter not present in setupVars.conf
|
||||
log_info("setupVars.conf:REV_SERVER_* -> Not set (found invalid/incomplete parameters)");
|
||||
}
|
||||
|
||||
// Free memory
|
||||
if(cidr != NULL)
|
||||
@@ -262,8 +280,8 @@ static void get_conf_string_array_from_setupVars_regex(const char *key, struct c
|
||||
cJSON *item = cJSON_CreateString(regex2);
|
||||
cJSON_AddItemToArray(conf_item->v.json, item);
|
||||
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:%s -> Setting %s[%u] = %s\n",
|
||||
key, conf_item->k, i, item->valuestring);
|
||||
log_info("setupVars.conf:%s -> Setting %s[%u] = %s\n",
|
||||
key, conf_item->k, i, item->valuestring);
|
||||
|
||||
// Free memory
|
||||
free(regex2);
|
||||
@@ -295,13 +313,12 @@ static void get_conf_upstream_servers_from_setupVars(struct conf_item *conf_item
|
||||
|
||||
if(value != NULL)
|
||||
{
|
||||
log_debug(DEBUG_CONFIG, "%s = %s\n", server_key, value);
|
||||
// Add string to our JSON array
|
||||
cJSON *item = cJSON_CreateString(value);
|
||||
cJSON_AddItemToArray(conf_item->v.json, item);
|
||||
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:PIHOLE_DNS_%u -> Setting %s[%u] = %s\n",
|
||||
j, conf_item->k, j, item->valuestring);
|
||||
log_info("setupVars.conf:PIHOLE_DNS_%u -> Setting %s[%u] = %s",
|
||||
j, conf_item->k, j, item->valuestring);
|
||||
}
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
@@ -317,7 +334,7 @@ static void get_conf_temp_limit_from_setupVars(void)
|
||||
if(temp_limit == NULL)
|
||||
{
|
||||
// Do not change default value, this value is not set in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:TEMPERATURE_LIMIT -> Not set");
|
||||
log_info("setupVars.conf:TEMPERATURE_LIMIT -> Not set");
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -339,13 +356,13 @@ static void get_conf_temp_limit_from_setupVars(void)
|
||||
if(set)
|
||||
{
|
||||
// Parameter present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:TEMPERATURE_LIMIT -> Setting %s to %f",
|
||||
log_info("setupVars.conf:TEMPERATURE_LIMIT -> Setting %s to %f",
|
||||
config.webserver.api.temp.limit.k, config.webserver.api.temp.limit.v.d);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parameter not present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:TEMPERATURE_LIMIT -> Not set (found invalid value)");
|
||||
log_info("setupVars.conf:TEMPERATURE_LIMIT -> Not set (found invalid value)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +374,7 @@ static void get_conf_weblayout_from_setupVars(void)
|
||||
if(web_layout == NULL)
|
||||
{
|
||||
// Do not change default value, this value is not set in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:WEBUIBOXEDLAYOUT -> Not set");
|
||||
log_info("setupVars.conf:WEBUIBOXEDLAYOUT -> Not set");
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -365,16 +382,14 @@ static void get_conf_weblayout_from_setupVars(void)
|
||||
}
|
||||
|
||||
// If the property is set to false and different than "boxed", the property
|
||||
// is disabled. This is consistent with the code in AdminLTE when writing
|
||||
// this code
|
||||
if(strcasecmp(web_layout, "boxed") != 0)
|
||||
config.webserver.interface.boxed.v.b = false;
|
||||
// is disabled
|
||||
config.webserver.interface.boxed.v.b = strcasecmp(web_layout, "boxed") == 0;
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
|
||||
// Parameter present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:WEBUIBOXEDLAYOUT -> Setting %s to %s",
|
||||
log_info("setupVars.conf:WEBUIBOXEDLAYOUT -> Setting %s to %s",
|
||||
config.webserver.interface.boxed.k,config.webserver.interface.boxed.v.b ? "true" : "false");
|
||||
}
|
||||
|
||||
@@ -386,7 +401,7 @@ static void get_conf_webtheme_from_setupVars(void)
|
||||
if(webTheme == NULL)
|
||||
{
|
||||
// Do not change default value, this value is not set in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:WEBTHEME -> Not set");
|
||||
log_info("setupVars.conf:WEBTHEME -> Not set");
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -407,14 +422,14 @@ static void get_conf_webtheme_from_setupVars(void)
|
||||
if(set)
|
||||
{
|
||||
// Parameter present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:WEBTHEME -> Setting %s to %s",
|
||||
config.webserver.interface.theme.k,
|
||||
get_web_theme_str(config.webserver.interface.theme.v.web_theme));
|
||||
log_info("setupVars.conf:WEBTHEME -> Setting %s to %s",
|
||||
config.webserver.interface.theme.k,
|
||||
get_web_theme_str(config.webserver.interface.theme.v.web_theme));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parameter not present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:WEBTHEME -> Not set (found invalid value)");
|
||||
log_info("setupVars.conf:WEBTHEME -> Not set (found invalid value)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,7 +441,7 @@ static void get_conf_temp_unit_from_setupVars(void)
|
||||
if(temp_unit == NULL)
|
||||
{
|
||||
// Do not change default value, this value is not set in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:TEMPERATURE_UNIT -> Not set");
|
||||
log_info("setupVars.conf:TEMPERATURE_UNIT -> Not set");
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -447,14 +462,14 @@ static void get_conf_temp_unit_from_setupVars(void)
|
||||
if(set)
|
||||
{
|
||||
// Parameter present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:TEMPERATURE_UNIT -> Setting %s to %s",
|
||||
config.webserver.interface.theme.k,
|
||||
get_temp_unit_str(config.webserver.api.temp.unit.v.temp_unit));
|
||||
log_info("setupVars.conf:TEMPERATURE_UNIT -> Setting %s to %s",
|
||||
config.webserver.interface.theme.k,
|
||||
get_temp_unit_str(config.webserver.api.temp.unit.v.temp_unit));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parameter not present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:TEMPERATURE_UNIT -> Not set (found invalid value)");
|
||||
log_info("setupVars.conf:TEMPERATURE_UNIT -> Not set (found invalid value)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,7 +481,7 @@ static void get_conf_listeningMode_from_setupVars(void)
|
||||
if(listeningMode == NULL)
|
||||
{
|
||||
// Do not change default value, this value is not set in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:DNSMASQ_LISTENING -> Not set");
|
||||
log_info("setupVars.conf:DNSMASQ_LISTENING -> Not set");
|
||||
|
||||
// Free memory, harmless to call if read_setupVarsconf() didn't return a result
|
||||
clearSetupVarsArray();
|
||||
@@ -487,13 +502,13 @@ static void get_conf_listeningMode_from_setupVars(void)
|
||||
if(set)
|
||||
{
|
||||
// Parameter present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:DNSMASQ_LISTENING -> Setting %s to %s",
|
||||
config.dns.listeningMode.k, get_listeningMode_str(config.dns.listeningMode.v.listeningMode));
|
||||
log_info("setupVars.conf:DNSMASQ_LISTENING -> Setting %s to %s",
|
||||
config.dns.listeningMode.k, get_listeningMode_str(config.dns.listeningMode.v.listeningMode));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parameter not present in setupVars.conf
|
||||
log_debug(DEBUG_CONFIG, "setupVars.conf:DNSMASQ_LISTENING -> Not set (found invalid value)");
|
||||
log_info("setupVars.conf:DNSMASQ_LISTENING -> Not set (found invalid value)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,7 +568,7 @@ void importsetupVarsConf(void)
|
||||
get_conf_string_from_setupVars("DHCP_LEASETIME", &config.dhcp.leaseTime);
|
||||
|
||||
// If the DHCP lease time is set to "24", it is interpreted as "24h".
|
||||
// This is some relic from the past that may still be present in some
|
||||
// This is some relict from the past that may still be present in some
|
||||
// setups
|
||||
if(strcmp(config.dhcp.leaseTime.v.s, "24") == 0)
|
||||
{
|
||||
@@ -573,20 +588,14 @@ void importsetupVarsConf(void)
|
||||
// Ports may be temporarily stored when importing a legacy Teleporter v5 file
|
||||
get_conf_string_from_setupVars("WEB_PORTS", &config.webserver.port);
|
||||
|
||||
// Move the setupVars.conf file to setupVars.conf.old
|
||||
char *old_setupVars = calloc(strlen(config.files.setupVars.v.s) + 5, sizeof(char));
|
||||
if(old_setupVars == NULL)
|
||||
{
|
||||
log_warn("Could not allocate memory for old_setupVars");
|
||||
return;
|
||||
}
|
||||
strcpy(old_setupVars, config.files.setupVars.v.s);
|
||||
strcat(old_setupVars, ".old");
|
||||
if(rename(config.files.setupVars.v.s, old_setupVars) != 0)
|
||||
log_warn("Could not move %s to %s", config.files.setupVars.v.s, old_setupVars);
|
||||
// Move the setupVars.conf file to the migration directory
|
||||
const char *setupVars_target = MIGRATION_TARGET_V6"/setupVars.conf";
|
||||
if(rename(config.files.setupVars.v.s, setupVars_target) != 0)
|
||||
log_warn("Could not move %s to %s", config.files.setupVars.v.s, setupVars_target);
|
||||
else
|
||||
log_info("Moved %s to %s", config.files.setupVars.v.s, old_setupVars);
|
||||
free(old_setupVars);
|
||||
log_info("Moved %s to %s", config.files.setupVars.v.s, setupVars_target);
|
||||
|
||||
log_info("setupVars.conf migration complete");
|
||||
}
|
||||
|
||||
char* __attribute__((pure)) find_equals(char *s)
|
||||
|
||||
+39
-36
@@ -23,6 +23,8 @@
|
||||
#include <limits.h>
|
||||
// escape_json()
|
||||
#include "webserver/http-common.h"
|
||||
// chown_pihole()
|
||||
#include "files.h"
|
||||
|
||||
// Open the TOML file for reading or writing
|
||||
FILE * __attribute((malloc)) __attribute((nonnull(1))) openFTLtoml(const char *mode, const unsigned int version)
|
||||
@@ -59,8 +61,8 @@ FILE * __attribute((malloc)) __attribute((nonnull(1))) openFTLtoml(const char *m
|
||||
// Return early if opening failed
|
||||
if(!fp)
|
||||
{
|
||||
log_info("Config %sfile %s not available: %s",
|
||||
version > 0 ? "backup " : "", filename, strerror(errno));
|
||||
log_info("Config %sfile %s not available (%s): %s",
|
||||
version > 0 ? "backup " : "", filename, mode, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -68,8 +70,8 @@ FILE * __attribute((malloc)) __attribute((nonnull(1))) openFTLtoml(const char *m
|
||||
if(flock(fileno(fp), LOCK_EX) != 0)
|
||||
{
|
||||
const int _e = errno;
|
||||
log_err("Cannot open config file %s in exclusive mode: %s",
|
||||
filename, strerror(errno));
|
||||
log_err("Cannot open config file %s in exclusive mode (%s): %s",
|
||||
filename, mode, strerror(errno));
|
||||
fclose(fp);
|
||||
errno = _e;
|
||||
return NULL;
|
||||
@@ -96,26 +98,8 @@ void closeFTLtoml(FILE *fp)
|
||||
|
||||
// Chown file if we are root
|
||||
if(geteuid() == 0)
|
||||
{
|
||||
// Get UID and GID of user with name "pihole"
|
||||
struct passwd *pwd = getpwnam("pihole");
|
||||
if(pwd == NULL)
|
||||
{
|
||||
log_warn("Cannot get UID and GID of user pihole: %s", strerror(errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
const uid_t pihole_uid = pwd->pw_uid;
|
||||
const gid_t pihole_gid = pwd->pw_gid;
|
||||
// Chown file to pihole user
|
||||
if(chown(GLOBALTOMLPATH, pihole_uid, pihole_gid) != 0)
|
||||
log_warn("Cannot chown "GLOBALTOMLPATH" to pihole:pihole (%u:%u): %s",
|
||||
(unsigned int)pihole_uid, (unsigned int)pihole_gid, strerror(errno));
|
||||
else
|
||||
log_debug(DEBUG_CONFIG, "Chown-ed "GLOBALTOMLPATH" to pihole:pihole (%u:%u)",
|
||||
(unsigned int)pihole_uid, (unsigned int)pihole_gid);
|
||||
}
|
||||
}
|
||||
chown_pihole(GLOBALTOMLPATH, NULL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -209,7 +193,7 @@ void print_comment(FILE *fp, const char *str, const char *intro, const unsigned
|
||||
// If this the first line? If not, add a newline
|
||||
if (i > 0)
|
||||
fputc('\n', fp);
|
||||
// Add intendation
|
||||
// Add indentation
|
||||
for (unsigned int j = 0; j != 2*indent; ++j)
|
||||
fputc(' ', fp);
|
||||
// Start a new line
|
||||
@@ -378,6 +362,9 @@ void writeTOMLvalue(FILE * fp, const int indent, const enum conf_type t, union c
|
||||
case CONF_ENUM_TEMP_UNIT:
|
||||
printTOMLstring(fp, get_temp_unit_str(v->temp_unit), toml);
|
||||
break;
|
||||
case CONF_ENUM_BLOCKING_EDNS_MODE:
|
||||
printTOMLstring(fp, get_edns_mode_str(v->edns_mode), toml);
|
||||
break;
|
||||
case CONF_STRUCT_IN_ADDR:
|
||||
{
|
||||
// Special case: 0.0.0.0 -> return empty string
|
||||
@@ -428,7 +415,7 @@ void writeTOMLvalue(FILE * fp, const int indent, const enum conf_type t, union c
|
||||
if(strlen(item->valuestring) == 0)
|
||||
continue;
|
||||
|
||||
// Add intendation (if we are indenting)
|
||||
// Add indentation (if we are indenting)
|
||||
if(indent > -1)
|
||||
indentTOML(fp, indent + 1);
|
||||
|
||||
@@ -559,7 +546,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
}
|
||||
case CONF_ENUM_PTR_TYPE:
|
||||
{
|
||||
const toml_datum_t val = toml_string_in(toml, key);
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
const int ptr_type = get_ptr_type_val(val.u.s);
|
||||
@@ -575,7 +562,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
}
|
||||
case CONF_ENUM_BUSY_TYPE:
|
||||
{
|
||||
const toml_datum_t val = toml_string_in(toml, key);
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
const int busy_reply = get_busy_reply_val(val.u.s);
|
||||
@@ -591,7 +578,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
}
|
||||
case CONF_ENUM_BLOCKING_MODE:
|
||||
{
|
||||
const toml_datum_t val = toml_string_in(toml, key);
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
const int blocking_mode = get_blocking_mode_val(val.u.s);
|
||||
@@ -607,7 +594,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
}
|
||||
case CONF_ENUM_REFRESH_HOSTNAMES:
|
||||
{
|
||||
const toml_datum_t val = toml_string_in(toml, key);
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
const int refresh_hostnames = get_refresh_hostnames_val(val.u.s);
|
||||
@@ -623,7 +610,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
}
|
||||
case CONF_ENUM_LISTENING_MODE:
|
||||
{
|
||||
const toml_datum_t val = toml_string_in(toml, key);
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
const int listeningMode = get_listeningMode_val(val.u.s);
|
||||
@@ -639,7 +626,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
}
|
||||
case CONF_ENUM_WEB_THEME:
|
||||
{
|
||||
const toml_datum_t val = toml_string_in(toml, key);
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
const int web_theme = get_web_theme_val(val.u.s);
|
||||
@@ -655,7 +642,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
}
|
||||
case CONF_ENUM_TEMP_UNIT:
|
||||
{
|
||||
const toml_datum_t val = toml_string_in(toml, key);
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
const int temp_unit = get_temp_unit_val(val.u.s);
|
||||
@@ -669,6 +656,22 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
|
||||
break;
|
||||
}
|
||||
case CONF_ENUM_BLOCKING_EDNS_MODE:
|
||||
{
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
const int edns_mode = get_edns_mode_val(val.u.s);
|
||||
free(val.u.s);
|
||||
if(edns_mode != -1)
|
||||
conf_item->v.edns_mode = edns_mode;
|
||||
else
|
||||
log_warn("Config setting %s is invalid, allowed options are: %s", conf_item->k, conf_item->h);
|
||||
}
|
||||
else
|
||||
log_debug(DEBUG_CONFIG, "%s DOES NOT EXIST or is not a valid string", conf_item->k);
|
||||
break;
|
||||
}
|
||||
case CONF_ENUM_PRIVACY_LEVEL:
|
||||
{
|
||||
const toml_datum_t val = toml_int_in(toml, key);
|
||||
@@ -681,7 +684,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
case CONF_STRUCT_IN_ADDR:
|
||||
{
|
||||
struct in_addr addr4 = { 0 };
|
||||
const toml_datum_t val = toml_string_in(toml, key);
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
if(strlen(val.u.s) == 0)
|
||||
@@ -702,7 +705,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
case CONF_STRUCT_IN6_ADDR:
|
||||
{
|
||||
struct in6_addr addr6 = { 0 };
|
||||
const toml_datum_t val = toml_string_in(toml, key);
|
||||
toml_datum_t val = toml_string_in(toml, key);
|
||||
if(val.ok)
|
||||
{
|
||||
if(strlen(val.u.s) == 0)
|
||||
@@ -733,7 +736,7 @@ void readTOMLvalue(struct conf_item *conf_item, const char* key, toml_table_t *t
|
||||
for(unsigned int i = 0; i < nelem; i++)
|
||||
{
|
||||
// Get string from TOML
|
||||
const toml_datum_t d = toml_string_at(array, i);
|
||||
toml_datum_t d = toml_string_at(array, i);
|
||||
if(!d.ok)
|
||||
{
|
||||
log_warn("Config %s is an invalid array (found at index %u)", conf_item->k, i);
|
||||
|
||||
@@ -115,10 +115,14 @@ bool readFTLtoml(struct config *oldconf, struct config *newconf,
|
||||
return false;
|
||||
}
|
||||
|
||||
// First, get an array of keys of config items that have been forced
|
||||
// through environment variables
|
||||
cJSON *env_vars = read_forced_vars(version);
|
||||
|
||||
// Try to read debug config. This is done before the full config
|
||||
// parsing to allow for debug output further down
|
||||
// First try to read env variable, if this fails, read TOML
|
||||
if(teleporter || !readEnvValue(&newconf->debug.config, newconf))
|
||||
if(teleporter || !readEnvValue(&newconf->debug.config, newconf, env_vars, NULL))
|
||||
{
|
||||
toml_table_t *conf_debug = toml_table_in(toml, "debug");
|
||||
if(conf_debug)
|
||||
@@ -140,12 +144,28 @@ bool readFTLtoml(struct config *oldconf, struct config *newconf,
|
||||
// First try to read this config option from an environment variable
|
||||
// Skip reading environment variables when importing from Teleporter
|
||||
// If this succeeds, skip searching the TOML file for this config item
|
||||
if(!teleporter && readEnvValue(new_conf_item, newconf))
|
||||
bool reset = false;
|
||||
if(!teleporter && readEnvValue(new_conf_item, newconf, env_vars, &reset))
|
||||
{
|
||||
new_conf_item->f |= FLAG_ENV_VAR;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip this variable if it has been reset (forced by
|
||||
// environment variable before but not anymore)
|
||||
if(reset)
|
||||
{
|
||||
if(new_conf_item->t == CONF_ALL_DEBUG_BOOL)
|
||||
{
|
||||
// Reset all debug flags to false if debug.all
|
||||
// has been reset
|
||||
set_all_debug(newconf, false);
|
||||
set_debug_flags(newconf);
|
||||
}
|
||||
log_info("Skipping %s as it has been reset", new_conf_item->k);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get config path depth
|
||||
unsigned int level = config_path_depth(new_conf_item->p);
|
||||
|
||||
@@ -200,6 +220,7 @@ bool readFTLtoml(struct config *oldconf, struct config *newconf,
|
||||
|
||||
// Free memory allocated by the TOML parser and return success
|
||||
toml_free(toml);
|
||||
cJSON_Delete(env_vars);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+51
-13
@@ -27,6 +27,19 @@ extern uint8_t last_checksum[SHA256_DIGEST_SIZE];
|
||||
|
||||
bool writeFTLtoml(const bool verbose)
|
||||
{
|
||||
// Return early without writing if we are in config read-only mode
|
||||
if(config.misc.readOnly.v.b)
|
||||
{
|
||||
log_debug(DEBUG_CONFIG, "Config file is read-only, not writing");
|
||||
|
||||
// We need to (re-)calculate the checksum here as it'd otherwise
|
||||
// be outdated (in non-read-only mode, it's calculated at the
|
||||
// end of this function)
|
||||
if(!sha256sum(GLOBALTOMLPATH, last_checksum, false))
|
||||
log_err("Unable to create checksum of %s", GLOBALTOMLPATH);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to open a temporary config file for writing
|
||||
FILE *fp;
|
||||
if((fp = openFTLtoml("w", 0)) == NULL)
|
||||
@@ -39,7 +52,7 @@ bool writeFTLtoml(const bool verbose)
|
||||
fprintf(fp, "# Pi-hole configuration file (%s)\n", get_FTL_version());
|
||||
fputs("# Encoding: UTF-8\n", fp);
|
||||
fputs("# This file is managed by pihole-FTL\n", fp);
|
||||
char timestring[TIMESTR_SIZE] = "";
|
||||
char timestring[TIMESTR_SIZE];
|
||||
get_timestr(timestring, time(NULL), false, false);
|
||||
fputs("# Last updated on ", fp);
|
||||
fputs(timestring, fp);
|
||||
@@ -47,7 +60,8 @@ bool writeFTLtoml(const bool verbose)
|
||||
|
||||
// Iterate over configuration and store it into the file
|
||||
char *last_path = (char*)"";
|
||||
unsigned int modified = 0, env_vars = 0;
|
||||
unsigned int modified = 0;
|
||||
cJSON *env_vars = cJSON_CreateArray();
|
||||
for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++)
|
||||
{
|
||||
// Get pointer to memory location of this conf_item
|
||||
@@ -82,13 +96,6 @@ bool writeFTLtoml(const bool verbose)
|
||||
print_toml_allowed_values(conf_item->a, fp, 85, level-1);
|
||||
}
|
||||
|
||||
// Print info if this value is overwritten by an env var
|
||||
if(conf_item->f & FLAG_ENV_VAR)
|
||||
{
|
||||
print_comment(fp, ">>> This config is overwritten by an environmental variable <<<", "", 85, level-1);
|
||||
env_vars++;
|
||||
}
|
||||
|
||||
// Write value
|
||||
indentTOML(fp, level-1);
|
||||
fprintf(fp, "%s = ", conf_item->p[level-1]);
|
||||
@@ -105,7 +112,12 @@ bool writeFTLtoml(const bool verbose)
|
||||
|
||||
if(changed)
|
||||
{
|
||||
fprintf(fp, " ### CHANGED, default = ");
|
||||
|
||||
// Print info if this value is overwritten by an env var
|
||||
if(conf_item->f & FLAG_ENV_VAR)
|
||||
cJSON_AddItemToArray(env_vars, cJSON_CreateStringReference(conf_item->k));
|
||||
|
||||
fprintf(fp, " ### CHANGED%s, default = ", conf_item->f & FLAG_ENV_VAR ? " (env)" : "");
|
||||
writeTOMLvalue(fp, -1, conf_item->t, &conf_item->d);
|
||||
modified++;
|
||||
}
|
||||
@@ -114,6 +126,29 @@ bool writeFTLtoml(const bool verbose)
|
||||
fputs("\n\n", fp);
|
||||
}
|
||||
|
||||
// Print config file statistics at the end of the file as comment
|
||||
fputs("# Configuration statistics:\n", fp);
|
||||
fprintf(fp, "# %zu total entries out of which %zu %s default\n",
|
||||
CONFIG_ELEMENTS, CONFIG_ELEMENTS - modified,
|
||||
CONFIG_ELEMENTS - modified == 1 ? "entry is" : "entries are");
|
||||
fprintf(fp, "# --> %u %s modified\n",
|
||||
modified, modified == 1 ? "entry is" : "entries are");
|
||||
|
||||
const unsigned int num_env_vars = cJSON_GetArraySize(env_vars);
|
||||
if(num_env_vars > 0)
|
||||
{
|
||||
fprintf(fp, "# %u %s forced through environment:\n",
|
||||
num_env_vars, num_env_vars == 1 ? "entry is" : "entries are");
|
||||
|
||||
for(unsigned int i = 0; i < num_env_vars; i++)
|
||||
{
|
||||
const char *env_var = cJSON_GetArrayItem(env_vars, i)->valuestring;
|
||||
fprintf(fp, "# - %s\n", env_var);
|
||||
}
|
||||
}
|
||||
else
|
||||
fputc('\n', fp);
|
||||
|
||||
// Log some statistics in verbose mode
|
||||
if(verbose || config.debug.config.v.b)
|
||||
{
|
||||
@@ -123,10 +158,13 @@ bool writeFTLtoml(const bool verbose)
|
||||
CONFIG_ELEMENTS - modified == 1 ? "entry is" : "entries are");
|
||||
log_info(" - %u %s modified", modified,
|
||||
modified == 1 ? "entry is" : "entries are");
|
||||
log_info(" - %u %s forced through environment", env_vars,
|
||||
env_vars == 1 ? "entry is" : "entries are");
|
||||
log_info(" - %u %s forced through environment", num_env_vars,
|
||||
num_env_vars == 1 ? "entry is" : "entries are");
|
||||
}
|
||||
|
||||
// Free cJSON array
|
||||
cJSON_Delete(env_vars);
|
||||
|
||||
// Close file and release exclusive lock
|
||||
closeFTLtoml(fp);
|
||||
|
||||
@@ -171,7 +209,7 @@ bool writeFTLtoml(const bool verbose)
|
||||
log_debug(DEBUG_CONFIG, "pihole.toml unchanged");
|
||||
}
|
||||
|
||||
if(!sha256sum(GLOBALTOMLPATH, last_checksum))
|
||||
if(!sha256sum(GLOBALTOMLPATH, last_checksum, false))
|
||||
log_err("Unable to create checksum of %s", GLOBALTOMLPATH);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -123,12 +123,6 @@ bool validate_dns_cnames(union conf_value *val, const char *key, char err[VALIDA
|
||||
return false;
|
||||
}
|
||||
|
||||
// Count the number of elements in the string
|
||||
unsigned int elements = 1;
|
||||
for(unsigned int j = 0; j < strlen(item->valuestring); j++)
|
||||
if(item->valuestring[j] == ',')
|
||||
elements++;
|
||||
|
||||
// Check if it's in the form "<cname>,[<cnameX>,]<target>[,<TTL>]"
|
||||
// <cnameX> is optional and may be repeated
|
||||
char *str = strdup(item->valuestring);
|
||||
@@ -398,12 +392,6 @@ bool validate_dns_revServers(union conf_value *val, const char *key, char err[VA
|
||||
return false;
|
||||
}
|
||||
|
||||
// Count the number of elements in the string
|
||||
unsigned int elements = 1;
|
||||
for(unsigned int j = 0; j < strlen(item->valuestring); j++)
|
||||
if(item->valuestring[j] == ',')
|
||||
elements++;
|
||||
|
||||
// Check if it's in the form "<enabled>,<ip-address>[/<prefix-len>],<server>[#<port>],<domain>"
|
||||
// Mandatory elements are: <enabled>, <ip-address>, <server>, and <domain>
|
||||
// Optional elements are: [/<prefix-len>] and [#<port>]
|
||||
|
||||
+39
-12
@@ -265,16 +265,19 @@ pid_t FTL_gettid(void)
|
||||
|
||||
static void terminate_threads(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
// Terminate threads before closing database connections and finishing shared memory
|
||||
killed = true;
|
||||
// Try to join threads to ensure cancellation has succeeded
|
||||
log_info("Waiting for threads to join");
|
||||
for(int i = 0; i < THREADS_MAX; i++)
|
||||
{
|
||||
log_debug(DEBUG_EXTRA, "Joining %s thread (%d)", thread_names[i], i);
|
||||
// Skip threads that have never been started or which are already stopped
|
||||
if(!thread_running[i])
|
||||
if(threads[i] == 0)
|
||||
{
|
||||
log_debug(DEBUG_EXTRA, "Skipping thread as it was never started");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cancel thread if it is idle
|
||||
if(thread_cancellable[i])
|
||||
@@ -282,9 +285,12 @@ static void terminate_threads(void)
|
||||
log_info("Thread %s (%d) is idle, terminating it.",
|
||||
thread_names[i], i);
|
||||
pthread_cancel(threads[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cancel thread if we cannot set a timeout for joining
|
||||
struct timespec ts;
|
||||
memset(&ts, 0, sizeof(ts));
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
|
||||
{
|
||||
log_info("Thread %s (%d) is busy, cancelling it (cannot set timeout).",
|
||||
@@ -297,8 +303,7 @@ static void terminate_threads(void)
|
||||
ts.tv_sec += 2;
|
||||
|
||||
// Try to join thread and cancel it if it is still busy
|
||||
const int s = pthread_timedjoin_np(threads[i], NULL, &ts);
|
||||
if(s != 0)
|
||||
if(pthread_timedjoin_np(threads[i], NULL, &ts) != 0)
|
||||
{
|
||||
log_info("Thread %s (%d) is still busy, cancelling it.",
|
||||
thread_names[i], i);
|
||||
@@ -326,13 +331,32 @@ void set_nice(void)
|
||||
// Set nice value
|
||||
const int ret = setpriority(which, pid, config.misc.nice.v.i);
|
||||
if(ret == -1)
|
||||
// ERROR EPERM: The calling process attempted to increase its priority
|
||||
// by supplying a negative value but has insufficient privileges.
|
||||
// On Linux, the RLIMIT_NICE resource limit can be used to define a limit to
|
||||
// which an unprivileged process's nice value can be raised. We are not
|
||||
// affected by this limit when pihole-FTL is running with CAP_SYS_NICE
|
||||
log_warn("Cannot set process priority to %d: %s. Process priority remains at %d",
|
||||
config.misc.nice.v.i, strerror(errno), priority);
|
||||
{
|
||||
if(errno == EACCES || errno == EPERM)
|
||||
{
|
||||
// from man 2 setpriority:
|
||||
//
|
||||
// ERRORS
|
||||
// [...]
|
||||
// EACCES The caller attempted to set a lower nice value (i.e., a higher
|
||||
// process priority), but did not have the required privilege (on
|
||||
// Linux: did not have the CAP_SYS_NICE capability).
|
||||
//
|
||||
// EPERM A process was located, but its effective user ID did not match
|
||||
// either the effective or the real user ID of the caller, and was
|
||||
// not privileged (on Linux: did not have the CAP_SYS_NICE capabil‐
|
||||
// ity).
|
||||
// [...]
|
||||
log_warn("Insufficient permissions to set process priority to %d (CAP_SYS_NICE required), process priority remains at %d",
|
||||
config.misc.nice.v.i, priority);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other error
|
||||
log_warn("Cannot set process priority to %d: %s. Process priority remains at %d",
|
||||
config.misc.nice.v.i, strerror(errno), priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +401,10 @@ void cleanup(const int ret)
|
||||
|
||||
char buffer[42] = { 0 };
|
||||
format_time(buffer, 0, timer_elapsed_msec(EXIT_TIMER));
|
||||
log_info("########## FTL terminated after%s (code %i)! ##########", buffer, ret);
|
||||
if(ret == RESTART_FTL_CODE)
|
||||
log_info("########## FTL terminated after%s (internal restart)! ##########", buffer);
|
||||
else
|
||||
log_info("########## FTL terminated after%s (code %i)! ##########", buffer, ret);
|
||||
}
|
||||
|
||||
static float last_clock = 0.0f;
|
||||
|
||||
@@ -18,7 +18,11 @@ set(sqlite3_sources
|
||||
)
|
||||
|
||||
add_library(sqlite3 OBJECT ${sqlite3_sources})
|
||||
target_compile_options(sqlite3 PRIVATE -Wno-implicit-fallthrough -Wno-cast-function-type -Wno-sign-compare)
|
||||
target_compile_options(sqlite3 PRIVATE -Wno-implicit-fallthrough -Wno-cast-function-type -Wno-sign-compare -Wno-implicit-function-declaration -Wno-int-conversion)
|
||||
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
target_compile_options(sqlite3 PRIVATE "-Wno-null-pointer-subtraction")
|
||||
endif()
|
||||
|
||||
set(database_sources
|
||||
common.c
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
* 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 "FTL.h"
|
||||
#include "aliasclients.h"
|
||||
#include "common.h"
|
||||
// global counters variable
|
||||
#include "../shmem.h"
|
||||
#include "shmem.h"
|
||||
// global config variable
|
||||
#include "../config/config.h"
|
||||
#include "config/config.h"
|
||||
// logging routines
|
||||
#include "../log.h"
|
||||
#include "log.h"
|
||||
// getAliasclientIDfromIP()
|
||||
#include "network-table.h"
|
||||
|
||||
@@ -109,6 +109,7 @@ bool import_aliasclients(sqlite3 *db)
|
||||
|
||||
// Loop until no further data is available
|
||||
int imported = 0;
|
||||
const double now = double_time();
|
||||
while((rc = sqlite3_step(stmt)) != SQLITE_DONE)
|
||||
{
|
||||
// Check if we ran into an error
|
||||
@@ -132,7 +133,7 @@ bool import_aliasclients(sqlite3 *db)
|
||||
}
|
||||
|
||||
// Try to open existing client
|
||||
const int clientID = findClientID(aliasclient_str, false, true);
|
||||
const int clientID = findClientID(aliasclient_str, false, true, now);
|
||||
|
||||
clientsData *client = getClient(clientID, true);
|
||||
if(client == NULL)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#define ALIASCLIENTS_TABLE_H
|
||||
|
||||
// type clientsData
|
||||
#include "../datastructure.h"
|
||||
#include "datastructure.h"
|
||||
|
||||
|
||||
bool create_aliasclients_table(sqlite3 *db);
|
||||
|
||||
+146
-39
@@ -103,6 +103,15 @@ sqlite3* _dbopen(const bool readonly, const bool create, const char *func, const
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// If the database is opened in read-write mode, actually check if it is
|
||||
// writable. If it is not, close the database and return an error
|
||||
if(!readonly && sqlite3_db_readonly(db, NULL))
|
||||
{
|
||||
log_err("Cannot open database in read-write mode");
|
||||
dbclose(&db);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Explicitly set busy handler to value defined in FTL.h
|
||||
rc = sqlite3_busy_timeout(db, DATABASE_BUSY_TIMEOUT);
|
||||
if( rc != SQLITE_OK )
|
||||
@@ -239,25 +248,23 @@ void SQLite3LogCallback(void *pArg, int iErrCode, const char *zMsg)
|
||||
// Note: pArg is NULL and not used
|
||||
// See https://sqlite.org/rescode.html#extrc for details
|
||||
// concerning the return codes returned here
|
||||
if(strncmp(zMsg, "file renamed while open: ", sizeof("file renamed while open: ")-1) == 0)
|
||||
if(zMsg != NULL && strncmp(zMsg, "file renamed while open: ", sizeof("file renamed while open: ")-1) == 0)
|
||||
{
|
||||
// This happens when gravity.db is replaced while FTL is running
|
||||
// We can safely ignore this warning
|
||||
return;
|
||||
}
|
||||
|
||||
// Log backtrace if any debug flag is set
|
||||
if(config.debug.extra.v.b)
|
||||
generate_backtrace();
|
||||
|
||||
if(iErrCode == SQLITE_WARNING)
|
||||
log_warn("SQLite3: %s (%d)", zMsg, iErrCode);
|
||||
else if(iErrCode == SQLITE_NOTICE || iErrCode == SQLITE_SCHEMA)
|
||||
{
|
||||
// SQLITE_SCHEMA is returned when the database schema has changed
|
||||
// This is not necessarily an error, as sqlite3_step() will re-prepare
|
||||
// the statement and try again. If it cannot, it will return an error
|
||||
// and this will be handled over there.
|
||||
log_debug(DEBUG_ANY, "SQLite3: %s (%d)", zMsg, iErrCode);
|
||||
}
|
||||
else
|
||||
log_err("SQLite3: %s (%d)", zMsg, iErrCode);
|
||||
}
|
||||
@@ -284,9 +291,9 @@ void db_init(void)
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly set permissions to 0644
|
||||
// 644 = u+w u+r g+w g+r o+r
|
||||
const mode_t mode = S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP| S_IROTH;
|
||||
// Explicitly set permissions to 0640
|
||||
// 640 = u+w u+r g+r
|
||||
const mode_t mode = S_IWUSR | S_IRUSR | S_IRGRP;
|
||||
chmod_file(config.files.database.v.s, mode);
|
||||
|
||||
// Open database
|
||||
@@ -564,10 +571,48 @@ void db_init(void)
|
||||
dbversion = db_get_int(db, DB_VERSION);
|
||||
}
|
||||
|
||||
// Update to version 18 if lower
|
||||
if(dbversion < 18)
|
||||
{
|
||||
// Update to version 18: Add cli column to session table
|
||||
log_info("Updating long-term database to version 18");
|
||||
if(!add_session_cli_column(db))
|
||||
{
|
||||
log_info("Session table cannot be updated, database not available");
|
||||
dbclose(&db);
|
||||
return;
|
||||
}
|
||||
// Get updated version
|
||||
dbversion = db_get_int(db, DB_VERSION);
|
||||
}
|
||||
|
||||
// Update to version 19 if lower
|
||||
if(dbversion < 19)
|
||||
{
|
||||
// Update to version 19: Add x_forwarded_for column to session table
|
||||
log_info("Updating long-term database to version 19");
|
||||
if(!add_session_x_forwarded_for_column(db))
|
||||
{
|
||||
log_info("Session table cannot be updated, database not available");
|
||||
dbclose(&db);
|
||||
return;
|
||||
}
|
||||
// Get updated version
|
||||
dbversion = db_get_int(db, DB_VERSION);
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * IMPORTANT * * * * * * * * * * * * *
|
||||
* If you add a new database version, check if the in-memory
|
||||
* schema needs to be update as well (always recreated from
|
||||
* scratch on every FTL (re)start). Also, ensure to update the
|
||||
* MEMDB_VERSION in src/database/query-table.h as well as the
|
||||
* expected database schema in the CI tests.
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
// Last check after all migrations, if this happens, it will cause the
|
||||
// CI to fail the tests
|
||||
if(dbversion != MEMDB_VERSION)
|
||||
log_err("Database version %i does not match MEMDB_VERSION %i", dbversion, MEMDB_VERSION);
|
||||
log_err("Expected query database version %d but found %d", MEMDB_VERSION, dbversion);
|
||||
|
||||
lock_shm();
|
||||
import_aliasclients(db);
|
||||
@@ -633,17 +678,6 @@ bool db_set_FTL_property(sqlite3 *db, const enum ftl_table_props ID, const int v
|
||||
return true;
|
||||
}
|
||||
|
||||
bool db_set_FTL_property_double(sqlite3 *db, const enum ftl_table_props ID, const double value)
|
||||
{
|
||||
int ret = dbquery(db, "INSERT OR REPLACE INTO ftl (id, value) VALUES ( %u, %f );", ID, value);
|
||||
if(ret != SQLITE_OK)
|
||||
{
|
||||
checkFTLDBrc(ret);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool db_set_counter(sqlite3 *db, const enum counters_table_props ID, const int value)
|
||||
{
|
||||
int ret = dbquery(db, "INSERT OR REPLACE INTO counters (id, value) VALUES ( %u, %d );", ID, value);
|
||||
@@ -655,25 +689,6 @@ bool db_set_counter(sqlite3 *db, const enum counters_table_props ID, const int v
|
||||
return true;
|
||||
}
|
||||
|
||||
bool db_update_counters(sqlite3 *db, const int total, const int blocked)
|
||||
{
|
||||
int ret = dbquery(db, "UPDATE counters SET value = value + %i WHERE id = %i;", total, DB_TOTALQUERIES);
|
||||
if(ret != SQLITE_OK)
|
||||
{
|
||||
checkFTLDBrc(ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = dbquery(db, "UPDATE counters SET value = value + %i WHERE id = %i;", total, DB_TOTALQUERIES);
|
||||
if(ret != SQLITE_OK)
|
||||
{
|
||||
checkFTLDBrc(ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int db_query_int(sqlite3 *db, const char* querystr)
|
||||
{
|
||||
log_debug(DEBUG_DATABASE, "dbquery: \"%s\"", querystr);
|
||||
@@ -713,6 +728,98 @@ int db_query_int(sqlite3 *db, const char* querystr)
|
||||
return result;
|
||||
}
|
||||
|
||||
int db_query_int_int(sqlite3 *db, const char* querystr, const int arg)
|
||||
{
|
||||
log_debug(DEBUG_DATABASE, "db_query_int_arg: \"%s\"", querystr);
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
if( rc != SQLITE_BUSY )
|
||||
log_err("Encountered prepare error in db_query_int(\"%s\"): %s",
|
||||
querystr, sqlite3_errstr(rc));
|
||||
return DB_FAILED;
|
||||
}
|
||||
|
||||
// Bind argument to prepared statement
|
||||
if((rc = sqlite3_bind_int(stmt, 1, arg)) != SQLITE_OK)
|
||||
{
|
||||
log_err("Encountered bind error in db_query_int(\"%s\"): %s",
|
||||
querystr, sqlite3_errstr(rc));
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
int result;
|
||||
|
||||
if( rc == SQLITE_ROW )
|
||||
{
|
||||
result = sqlite3_column_int(stmt, 0);
|
||||
log_debug(DEBUG_DATABASE, " ---> Result %i (int)", result);
|
||||
}
|
||||
else if( rc == SQLITE_DONE )
|
||||
{
|
||||
// No rows available
|
||||
result = DB_NODATA;
|
||||
log_debug(DEBUG_DATABASE, " ---> No data");
|
||||
}
|
||||
else
|
||||
{
|
||||
log_err("Encountered step error in db_query_int(\"%s\"): %s",
|
||||
querystr, sqlite3_errstr(rc));
|
||||
return DB_FAILED;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
int db_query_int_str(sqlite3 *db, const char* querystr, const char *arg)
|
||||
{
|
||||
log_debug(DEBUG_DATABASE, "db_query_int_str: \"%s\"", querystr);
|
||||
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
if( rc != SQLITE_BUSY )
|
||||
log_err("Encountered prepare error in db_query_int(\"%s\"): %s",
|
||||
querystr, sqlite3_errstr(rc));
|
||||
return DB_FAILED;
|
||||
}
|
||||
|
||||
// Bind argument to prepared statement
|
||||
if((rc = sqlite3_bind_text(stmt, 1, arg, -1, SQLITE_STATIC)) != SQLITE_OK)
|
||||
{
|
||||
log_err("Encountered bind error in db_query_int(\"%s\"): %s",
|
||||
querystr, sqlite3_errstr(rc));
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
int result;
|
||||
|
||||
if( rc == SQLITE_ROW )
|
||||
{
|
||||
result = sqlite3_column_int(stmt, 0);
|
||||
log_debug(DEBUG_DATABASE, " ---> Result %i (int)", result);
|
||||
}
|
||||
else if( rc == SQLITE_DONE )
|
||||
{
|
||||
// No rows available
|
||||
result = DB_NODATA;
|
||||
log_debug(DEBUG_DATABASE, " ---> No data");
|
||||
}
|
||||
else
|
||||
{
|
||||
log_err("Encountered step error in db_query_int(\"%s\"): %s",
|
||||
querystr, sqlite3_errstr(rc));
|
||||
return DB_FAILED;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
double db_query_double(sqlite3 *db, const char* querystr)
|
||||
{
|
||||
log_debug(DEBUG_DATABASE, "dbquery: \"%s\"", querystr);
|
||||
|
||||
@@ -33,10 +33,9 @@ int db_get_int(sqlite3* db, const enum ftl_table_props ID);
|
||||
int db_get_FTL_property(sqlite3* db, const enum ftl_table_props ID);
|
||||
double db_get_FTL_property_double(sqlite3* db, const enum ftl_table_props ID);
|
||||
bool db_set_FTL_property(sqlite3* db, const enum ftl_table_props ID, const int value);
|
||||
bool db_set_FTL_property_double(sqlite3* db, const enum ftl_table_props ID, const double value);
|
||||
|
||||
/// Execute a formatted SQL query and get the return code
|
||||
int dbquery(sqlite3* db, const char *format, ...) __attribute__ ((format (gnu_printf, 2, 3)));;
|
||||
int dbquery(sqlite3* db, const char *format, ...) __attribute__ ((format (printf, 2, 3)));;
|
||||
|
||||
#define dbopen(readonly, create) _dbopen(readonly, create, __FUNCTION__, __LINE__, __FILE__)
|
||||
sqlite3 *_dbopen(const bool readonly, const bool create, const char *func, const int line, const char *file) __attribute__((warn_unused_result));
|
||||
@@ -45,13 +44,14 @@ void _dbclose(sqlite3 **db, const char *func, const int line, const char *file);
|
||||
|
||||
void piholeFTLDB_reopen(void);
|
||||
int db_query_int(sqlite3 *db, const char *querystr);
|
||||
int db_query_int_int(sqlite3 *db, const char* querystr, const int arg);
|
||||
int db_query_int_str(sqlite3 *db, const char* querystr, const char *arg);
|
||||
double db_query_double(sqlite3 *db, const char *querystr);
|
||||
int db_query_int_from_until(sqlite3 *db, const char* querystr, const double from, const double until);
|
||||
int db_query_int_from_until_type(sqlite3 *db, const char* querystr, const double from, const double until, const int type);
|
||||
|
||||
void SQLite3LogCallback(void *pArg, int iErrCode, const char *zMsg);
|
||||
bool db_set_counter(sqlite3 *db, const enum counters_table_props ID, const int value);
|
||||
bool db_update_counters(sqlite3 *db, const int total, const int blocked);
|
||||
const char *get_sqlite3_version(void);
|
||||
|
||||
extern bool DBdeleteoldqueries;
|
||||
|
||||
@@ -78,14 +78,11 @@ static bool analyze_database(sqlite3 *db)
|
||||
}
|
||||
|
||||
#define DBOPEN_OR_AGAIN() { if(!db) db = dbopen(false, false); if(!db) { thread_sleepms(DB, 5000); continue; } }
|
||||
#define BREAK_IF_KILLED() { if(killed) break; }
|
||||
#define DBCLOSE_OR_BREAK() { dbclose(&db); BREAK_IF_KILLED(); }
|
||||
|
||||
void *DB_thread(void *val)
|
||||
{
|
||||
// Set thread name
|
||||
thread_names[DB] = "database";
|
||||
thread_running[DB] = true;
|
||||
prctl(PR_SET_NAME, thread_names[DB], 0, 0, 0);
|
||||
|
||||
// Save timestamp as we do not want to store immediately
|
||||
@@ -243,6 +240,5 @@ void *DB_thread(void *val)
|
||||
dbclose(&db);
|
||||
|
||||
log_info("Terminating database thread");
|
||||
thread_running[DB] = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+42
-17
@@ -8,29 +8,30 @@
|
||||
* 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 "FTL.h"
|
||||
#include "sqlite3.h"
|
||||
#include "gravity-db.h"
|
||||
// struct config
|
||||
#include "../config/config.h"
|
||||
#include "config/config.h"
|
||||
// logging routines
|
||||
#include "../log.h"
|
||||
#include "log.h"
|
||||
// getstr()
|
||||
#include "../shmem.h"
|
||||
#include "shmem.h"
|
||||
// SQLite3 prepared statement vectors
|
||||
#include "../vector.h"
|
||||
#include "vector.h"
|
||||
// log_subnet_warning()
|
||||
// logg_inaccessible_adlist
|
||||
#include "message-table.h"
|
||||
// getMACfromIP()
|
||||
#include "network-table.h"
|
||||
// struct DNSCacheData
|
||||
#include "../datastructure.h"
|
||||
#include "datastructure.h"
|
||||
// reset_aliasclient()
|
||||
#include "aliasclients.h"
|
||||
|
||||
// Definition of struct regexData
|
||||
#include "../regex_r.h"
|
||||
#include "regex_r.h"
|
||||
// file_readable()
|
||||
#include "files.h"
|
||||
|
||||
// Prefix of interface names in the client table
|
||||
#define INTERFACE_SEP ":"
|
||||
@@ -49,6 +50,17 @@ static sqlite3_stmt* table_stmt = NULL;
|
||||
bool gravityDB_opened = false;
|
||||
static bool gravity_abp_format = false;
|
||||
|
||||
// Variables memorizing the parent gravity database connection and prepared
|
||||
// statements to avoid valgrind warnings about memory leaks
|
||||
static sqlite3 *parent_gravity_db = NULL;
|
||||
sqlite3_stmt_vec *parent_whitelist_stmt = NULL;
|
||||
sqlite3_stmt_vec *parent_gravity_stmt = NULL;
|
||||
sqlite3_stmt_vec *parent_antigravity_stmt = NULL;
|
||||
sqlite3_stmt_vec *parent_blacklist_stmt = NULL;
|
||||
|
||||
// Private prototypes
|
||||
static bool gravityDB_open(void);
|
||||
|
||||
// Table names corresponding to the enum defined in gravity-db.h
|
||||
static const char* tablename[] = { "vw_gravity", "vw_blacklist", "vw_whitelist", "vw_regex_blacklist", "vw_regex_whitelist" , "client", "group", "adlist", "denied_domains", "allowed_domains", "" };
|
||||
|
||||
@@ -85,12 +97,17 @@ void gravityDB_forked(void)
|
||||
// is clear that this in not what we want to do as this is a slow
|
||||
// process and many TCP queries could lead to a DoS attack.
|
||||
gravityDB_opened = false;
|
||||
parent_gravity_db = gravity_db;
|
||||
gravity_db = NULL;
|
||||
|
||||
// Also pretend we have not yet prepared the list statements
|
||||
parent_whitelist_stmt = whitelist_stmt;
|
||||
whitelist_stmt = NULL;
|
||||
parent_blacklist_stmt = blacklist_stmt;
|
||||
blacklist_stmt = NULL;
|
||||
parent_gravity_stmt = gravity_stmt;
|
||||
gravity_stmt = NULL;
|
||||
parent_antigravity_stmt = antigravity_stmt;
|
||||
antigravity_stmt = NULL;
|
||||
|
||||
// Open the database
|
||||
@@ -132,7 +149,7 @@ static void gravity_check_ABP_format(void)
|
||||
}
|
||||
|
||||
// Open gravity database
|
||||
bool gravityDB_open(void)
|
||||
static bool gravityDB_open(void)
|
||||
{
|
||||
struct stat st;
|
||||
if(stat(config.files.gravity.v.s, &st) != 0)
|
||||
@@ -954,6 +971,7 @@ void gravityDB_close(void)
|
||||
free_sqlite3_stmt_vec(&antigravity_stmt);
|
||||
|
||||
// Close table
|
||||
log_debug(DEBUG_ANY, "Closing gravity database");
|
||||
sqlite3_close(gravity_db);
|
||||
gravity_db = NULL;
|
||||
gravityDB_opened = false;
|
||||
@@ -1045,7 +1063,7 @@ inline const char* gravityDB_getDomain(int *rowid)
|
||||
// Finalize statement of a gravity database transaction
|
||||
void gravityDB_finalizeTable(void)
|
||||
{
|
||||
if(!gravityDB_opened)
|
||||
if(!gravityDB_opened || table_stmt == NULL)
|
||||
return;
|
||||
|
||||
// Finalize statement
|
||||
@@ -1094,12 +1112,6 @@ int gravityDB_count(const enum gravity_tables list)
|
||||
case ADLISTS_TABLE:
|
||||
querystr = "SELECT COUNT(1) FROM adlist WHERE enabled != 0";
|
||||
break;
|
||||
case DENIED_DOMAINS_TABLE:
|
||||
querystr = "SELECT COUNT(1) FROM domainlist WHERE (type = 0 OR type = 2) AND enabled != 0";
|
||||
break;
|
||||
case ALLOWED_DOMAINS_TABLE:
|
||||
querystr = "SELECT COUNT(1) FROM domainlist WHERE (type = 1 OR type = 3) AND enabled != 0";
|
||||
break;
|
||||
case UNKNOWN_TABLE:
|
||||
log_err("List type %u unknown!", list);
|
||||
gravityDB_close();
|
||||
@@ -2715,9 +2727,17 @@ bool gravity_updated(void)
|
||||
sqlite3 *db = NULL;
|
||||
sqlite3_stmt *query_stmt = NULL;
|
||||
|
||||
// Check if database is a readable file
|
||||
if(file_readable(config.files.gravity.v.s) == false)
|
||||
{
|
||||
log_err("Cannot read gravity database at %s - file does not exist or is not readable",
|
||||
config.files.gravity.v.s);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open database
|
||||
int rc = sqlite3_open_v2(config.files.gravity.v.s, &db, SQLITE_OPEN_READONLY, NULL);
|
||||
if(db == NULL)
|
||||
if(db == NULL || rc != SQLITE_OK)
|
||||
{
|
||||
log_err("gravity_updated(): %s - SQL error open: %s", config.files.gravity.v.s, sqlite3_errstr(rc));
|
||||
return false;
|
||||
@@ -2770,3 +2790,8 @@ bool gravity_updated(void)
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
time_t __attribute__((pure)) gravity_last_updated(void)
|
||||
{
|
||||
return last_updated > 0 ? (time_t)last_updated : 0;
|
||||
}
|
||||
|
||||
@@ -11,11 +11,9 @@
|
||||
#define GRAVITY_H
|
||||
|
||||
// clients data structure
|
||||
#include "../datastructure.h"
|
||||
// enum http_method
|
||||
#include "../webserver/http-common.h"
|
||||
#include "datastructure.h"
|
||||
// Definition of struct regexData
|
||||
#include "../regex_r.h"
|
||||
#include "regex_r.h"
|
||||
|
||||
// Table row record, not all fields are used by all tables
|
||||
typedef struct {
|
||||
@@ -41,7 +39,6 @@ typedef struct {
|
||||
time_t date_updated;
|
||||
} tablerow;
|
||||
|
||||
bool gravityDB_open(void);
|
||||
bool gravityDB_reopen(void);
|
||||
void gravityDB_forked(void);
|
||||
void gravityDB_reload_groups(clientsData* client);
|
||||
@@ -73,4 +70,6 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
|
||||
bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups,
|
||||
const tablerow *row, const char **message);
|
||||
|
||||
time_t gravity_last_updated(void) __attribute__((pure));
|
||||
|
||||
#endif //GRAVITY_H
|
||||
|
||||
+440
-88
@@ -29,6 +29,45 @@
|
||||
#include "files.h"
|
||||
// get_memdb()
|
||||
#include "database/query-table.h"
|
||||
// escape_html()
|
||||
#include "webserver/http-common.h"
|
||||
// GIT_HASH, FTL_ARCH
|
||||
#include "version.h"
|
||||
|
||||
// Number of arguments in a variadic macro
|
||||
// Credit: https://stackoverflow.com/a/35693080/2087442
|
||||
#define PP_NARG(...) \
|
||||
PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
|
||||
#define PP_NARG_(...) \
|
||||
PP_128TH_ARG(__VA_ARGS__)
|
||||
#define PP_128TH_ARG( \
|
||||
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
|
||||
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
|
||||
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
|
||||
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
|
||||
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
|
||||
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
|
||||
_61,_62,_63,_64,_65,_66,_67,_68,_69,_70, \
|
||||
_71,_72,_73,_74,_75,_76,_77,_78,_79,_80, \
|
||||
_81,_82,_83,_84,_85,_86,_87,_88,_89,_90, \
|
||||
_91,_92,_93,_94,_95,_96,_97,_98,_99,_100, \
|
||||
_101,_102,_103,_104,_105,_106,_107,_108,_109,_110, \
|
||||
_111,_112,_113,_114,_115,_116,_117,_118,_119,_120, \
|
||||
_121,_122,_123,_124,_125,_126,_127,N,...) N
|
||||
#define PP_RSEQ_N() \
|
||||
127,126,125,124,123,122,121,120, \
|
||||
119,118,117,116,115,114,113,112,111,110, \
|
||||
109,108,107,106,105,104,103,102,101,100, \
|
||||
99,98,97,96,95,94,93,92,91,90, \
|
||||
89,88,87,86,85,84,83,82,81,80, \
|
||||
79,78,77,76,75,74,73,72,71,70, \
|
||||
69,68,67,66,65,64,63,62,61,60, \
|
||||
59,58,57,56,55,54,53,52,51,50, \
|
||||
49,48,47,46,45,44,43,42,41,40, \
|
||||
39,38,37,36,35,34,33,32,31,30, \
|
||||
29,28,27,26,25,24,23,22,21,20, \
|
||||
19,18,17,16,15,14,13,12,11,10, \
|
||||
9,8,7,6,5,4,3,2,1,0
|
||||
|
||||
static const char *get_message_type_str(const enum message_type type)
|
||||
{
|
||||
@@ -58,6 +97,12 @@ static const char *get_message_type_str(const enum message_type type)
|
||||
return "DISK_EXTENDED";
|
||||
case CERTIFICATE_DOMAIN_MISMATCH_MESSAGE:
|
||||
return "CERTIFICATE_DOMAIN_MISMATCH";
|
||||
case CONNECTION_ERROR_MESSAGE:
|
||||
return "CONNECTION_ERROR";
|
||||
case NTP_MESSAGE:
|
||||
return "NTP";
|
||||
case VERIFY_MESSAGE:
|
||||
return "VERIFY";
|
||||
case MAX_MESSAGE:
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
@@ -90,15 +135,21 @@ static enum message_type get_message_type_from_string(const char *typestr)
|
||||
return DISK_MESSAGE_EXTENDED;
|
||||
else if (strcmp(typestr, "CERTIFICATE_DOMAIN_MISMATCH") == 0)
|
||||
return CERTIFICATE_DOMAIN_MISMATCH_MESSAGE;
|
||||
else if (strcmp(typestr, "CONNECTION_ERROR") == 0)
|
||||
return CONNECTION_ERROR_MESSAGE;
|
||||
else if (strcmp(typestr, "NTP") == 0)
|
||||
return NTP_MESSAGE;
|
||||
else if (strcmp(typestr, "VERIFY") == 0)
|
||||
return VERIFY_MESSAGE;
|
||||
else
|
||||
return MAX_MESSAGE;
|
||||
}
|
||||
|
||||
static unsigned char message_blob_types[MAX_MESSAGE][5] =
|
||||
{
|
||||
{ // REGEX_MESSAGE: The message column contains the regex warning text
|
||||
{ // REGEX_MESSAGE: The message column contains the regex text (the erroring regex filter itself)
|
||||
SQLITE_TEXT, // regex type ("deny", "allow")
|
||||
SQLITE_TEXT, // regex text (the erroring regex filter itself)
|
||||
SQLITE_TEXT, // regex warning text
|
||||
SQLITE_INTEGER, // database index of regex (so the dashboard can show a link)
|
||||
SQLITE_NULL, // not used
|
||||
SQLITE_NULL // not used
|
||||
@@ -181,6 +232,30 @@ static unsigned char message_blob_types[MAX_MESSAGE][5] =
|
||||
SQLITE_NULL, // not used
|
||||
SQLITE_NULL, // not used
|
||||
SQLITE_NULL // not used
|
||||
},
|
||||
{
|
||||
// CONNECTION_ERROR_MESSAGE: The message column contains the server address
|
||||
SQLITE_TEXT, // reason
|
||||
SQLITE_TEXT, // error message
|
||||
SQLITE_NULL, // not used
|
||||
SQLITE_NULL, // not used
|
||||
SQLITE_NULL // not used
|
||||
},
|
||||
{
|
||||
// NTP: The message column contains the warning/error
|
||||
SQLITE_TEXT, // level (warning/error)
|
||||
SQLITE_TEXT, // component (server/client)
|
||||
SQLITE_NULL, // not used
|
||||
SQLITE_NULL, // not used
|
||||
SQLITE_NULL // not used
|
||||
},
|
||||
{
|
||||
// VERIFY_MESSAGE: The message column contains the error
|
||||
SQLITE_TEXT, // expected checksum
|
||||
SQLITE_TEXT, // actual checksum
|
||||
SQLITE_TEXT, // FTL commit hash
|
||||
SQLITE_TEXT, // FTL architecture
|
||||
SQLITE_NULL // not used
|
||||
}
|
||||
};
|
||||
// Create message table in the database
|
||||
@@ -224,21 +299,59 @@ bool flush_message_table(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static int add_message(const enum message_type type,
|
||||
const char *message, const int count,...)
|
||||
static int _add_message(const enum message_type type,
|
||||
const char *message, const size_t count, ...);
|
||||
#define add_message(type, message, ...) _add_message(type, message, PP_NARG(__VA_ARGS__), __VA_ARGS__)
|
||||
#define add_message_no_args(type, message) _add_message(type, message, 0)
|
||||
|
||||
static int _add_message(const enum message_type type,
|
||||
const char *message, const size_t count,...)
|
||||
{
|
||||
// Log to database only if not in CLI mode
|
||||
if(cli_mode)
|
||||
return -1;
|
||||
|
||||
int rowid = -1;
|
||||
// Return early if database is known to be broken
|
||||
if(FTLDBerror())
|
||||
return rowid;
|
||||
return -1;
|
||||
|
||||
// Check if message type is known
|
||||
if(type >= MAX_MESSAGE)
|
||||
{
|
||||
log_err("add_message(type=%u, message=%s) - Invalid message type with %zu arguments",
|
||||
type, message, count);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if number of arguments is valid
|
||||
// Total number of arguments
|
||||
if(count > 5)
|
||||
{
|
||||
log_err("add_message(type=%u, message=%s) - Too many arguments (%zu), expected at most 5",
|
||||
type, message, count);
|
||||
return -1;
|
||||
}
|
||||
// No arguments check
|
||||
if(count == 0 && message_blob_types[type][0] != SQLITE_NULL)
|
||||
{
|
||||
log_err("add_message(type=%u, message=%s) - Invalid number of arguments: No arguments passed for message type requiring arguments",
|
||||
type, message);
|
||||
return -1;
|
||||
}
|
||||
// Non-zero arguments check
|
||||
else if(count > 1 && message_blob_types[type][count - 2] == SQLITE_NULL)
|
||||
{
|
||||
log_err("add_message(type=%u, message=%s) - Invalid number of arguments: Too many (%zu) arguments passed for this message type",
|
||||
type, message, count);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sqlite3 *db;
|
||||
// Open database connection
|
||||
if((db = dbopen(false, false)) == NULL)
|
||||
{
|
||||
log_err("add_message() - Failed to open DB");
|
||||
return rowid;
|
||||
}
|
||||
// Reason for failure is logged in dbopen()
|
||||
return -1;
|
||||
|
||||
// Ensure there are no duplicates when adding messages
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
@@ -315,7 +428,7 @@ static int add_message(const enum message_type type,
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, count);
|
||||
for (int j = 0; j < count; j++)
|
||||
for (size_t j = 0; j < count; j++)
|
||||
{
|
||||
const unsigned char datatype = message_blob_types[type][j];
|
||||
switch (datatype)
|
||||
@@ -343,7 +456,7 @@ static int add_message(const enum message_type type,
|
||||
// Bind message to prepared statement
|
||||
if(rc != SQLITE_OK)
|
||||
{
|
||||
log_err("add_message(type=%u, message=%s) - Failed to bind argument %d (type %u): %s",
|
||||
log_err("add_message(type=%u, message=%s) - Failed to bind argument %zu (type %u): %s",
|
||||
type, message, 3 + j, datatype, sqlite3_errstr(rc));
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
@@ -441,14 +554,22 @@ static void format_regex_message(char *plain, const int sizeof_plain, char *html
|
||||
char *escaped_regex = escape_html(regex);
|
||||
char *escaped_warning = escape_html(warning);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_regex == NULL || escaped_warning == NULL)
|
||||
{
|
||||
if(escaped_regex != NULL)
|
||||
free(escaped_regex);
|
||||
if(escaped_warning != NULL)
|
||||
free(escaped_warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if(snprintf(html, sizeof_html, "Encountered an error when processing <a href=\"groups-domains.lp?domainid=%d\">regex %s filter with ID %d</a>: <pre>%s</pre>Error message: <pre>%s</pre>",
|
||||
dbindex, type, dbindex, escaped_regex, escaped_warning))
|
||||
dbindex, type, dbindex, escaped_regex, escaped_warning) > sizeof_html)
|
||||
log_warn("format_regex_message(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
if(escaped_regex != NULL)
|
||||
free(escaped_regex);
|
||||
if(escaped_warning != NULL)
|
||||
free(escaped_warning);
|
||||
free(escaped_regex);
|
||||
free(escaped_warning);
|
||||
}
|
||||
|
||||
static void format_subnet_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html, const char *ip, const int matching_count, const char *names, const char *matching_ids, const char *chosen_match_text, const int chosen_match_id)
|
||||
@@ -467,17 +588,26 @@ static void format_subnet_message(char *plain, const int sizeof_plain, char *htm
|
||||
char *escaped_ids = escape_html(matching_ids);
|
||||
char *escaped_names = escape_html(names);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_ip == NULL || escaped_ids == NULL || escaped_names == NULL)
|
||||
{
|
||||
if(escaped_ip != NULL)
|
||||
free(escaped_ip);
|
||||
if(escaped_ids != NULL)
|
||||
free(escaped_ids);
|
||||
if(escaped_names != NULL)
|
||||
free(escaped_names);
|
||||
return;
|
||||
}
|
||||
|
||||
if(snprintf(html, sizeof_html, "Client <code>%s</code> is managed by %i groups (IDs [%s]), all describing the same subnet:<pre>%s</pre>"
|
||||
"FTL chose the most recent entry (ID %i) to obtain the group configuration for this client.",
|
||||
escaped_ip, matching_count, escaped_ids, escaped_names, chosen_match_id) > sizeof_html)
|
||||
log_warn("format_subnet_message(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
if(escaped_ip != NULL)
|
||||
free(escaped_ip);
|
||||
if(escaped_ids != NULL)
|
||||
free(escaped_ids);
|
||||
if(escaped_names != NULL)
|
||||
free(escaped_names);
|
||||
free(escaped_ip);
|
||||
free(escaped_ids);
|
||||
free(escaped_names);
|
||||
}
|
||||
|
||||
static void format_hostname_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html, const char *ip, const char *name, const int pos)
|
||||
@@ -517,14 +647,24 @@ static void format_hostname_message(char *plain, const int sizeof_plain, char *h
|
||||
char *escaped_ip = escape_html(ip);
|
||||
char *escaped_name = escape_html(namep);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_ip == NULL || escaped_name == NULL)
|
||||
{
|
||||
if(escaped_ip != NULL)
|
||||
free(escaped_ip);
|
||||
if(escaped_name != NULL)
|
||||
free(escaped_name);
|
||||
if(namep != NULL)
|
||||
free(namep);
|
||||
return;
|
||||
}
|
||||
|
||||
if(snprintf(html, sizeof_html, "Host name of client <code>%s</code> => <code>%s</code> contains (at least) one invalid character (hex %02x) at position %i",
|
||||
escaped_ip, escaped_name, (unsigned char)name[pos], pos) > sizeof_html)
|
||||
log_warn("format_hostname_message(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
if(escaped_ip != NULL)
|
||||
free(escaped_ip);
|
||||
if(escaped_name != NULL)
|
||||
free(escaped_name);
|
||||
free(escaped_ip);
|
||||
free(escaped_name);
|
||||
if(namep != NULL)
|
||||
free(namep);
|
||||
}
|
||||
@@ -540,11 +680,14 @@ static void format_dnsmasq_config_message(char *plain, const int sizeof_plain, c
|
||||
|
||||
char *escaped_message = escape_html(message);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_message == NULL)
|
||||
return;
|
||||
|
||||
if(snprintf(html, sizeof_html, "FTL failed to start due to %s.", escaped_message) > sizeof_html)
|
||||
log_warn("format_dnsmasq_config_message(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
if(escaped_message != NULL)
|
||||
free(escaped_message);
|
||||
free(escaped_message);
|
||||
}
|
||||
|
||||
static void format_rate_limit_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html, const char *clientIP, const unsigned int count, const unsigned int interval, const time_t turnaround)
|
||||
@@ -559,12 +702,15 @@ static void format_rate_limit_message(char *plain, const int sizeof_plain, char
|
||||
|
||||
char *escaped_clientIP = escape_html(clientIP);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_clientIP == NULL)
|
||||
return;
|
||||
|
||||
if(snprintf(html, sizeof_html, "Client <code>%s</code> has been rate-limited for at least %lu second%s (current limit: %u queries per %u seconds)",
|
||||
escaped_clientIP, (unsigned long int)turnaround, turnaround == 1 ? "" : "s", count, interval) > sizeof_html)
|
||||
log_warn("format_rate_limit_message(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
if(escaped_clientIP != NULL)
|
||||
free(escaped_clientIP);
|
||||
free(escaped_clientIP);
|
||||
}
|
||||
|
||||
static void format_dnsmasq_warn_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html, const char *message)
|
||||
@@ -608,14 +754,22 @@ static void format_shmem_message(char *plain, const int sizeof_plain, char *html
|
||||
char *escaped_path = escape_html(path);
|
||||
char *escaped_msg = escape_html(msg);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_path == NULL || escaped_msg == NULL)
|
||||
{
|
||||
if(escaped_path != NULL)
|
||||
free(escaped_path);
|
||||
if(escaped_msg != NULL)
|
||||
free(escaped_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if(snprintf(html, sizeof_html, "Shared memory shortage (<code>%s</code>) ahead: <strong>%d%%</strong> is used<br>%s",
|
||||
escaped_path, shmem, escaped_msg) > sizeof_html)
|
||||
log_warn("log_resource_shortage(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
if(escaped_path != NULL)
|
||||
free(escaped_path);
|
||||
if(escaped_msg != NULL)
|
||||
free(escaped_msg);
|
||||
free(escaped_path);
|
||||
free(escaped_msg);
|
||||
}
|
||||
|
||||
static void format_disk_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html,
|
||||
@@ -632,10 +786,22 @@ static void format_disk_message(char *plain, const int sizeof_plain, char *html,
|
||||
char *escaped_path = escape_html(path);
|
||||
char *escaped_msg = escape_html(msg);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_path == NULL || escaped_msg == NULL)
|
||||
{
|
||||
if(escaped_path != NULL)
|
||||
free(escaped_path);
|
||||
if(escaped_msg != NULL)
|
||||
free(escaped_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if(snprintf(html, sizeof_html, "Disk shortage ahead: <strong>%d%%</strong> is used (%s) on partition containing the file <code>%s</code>",
|
||||
disk, escaped_msg, escaped_path) > sizeof_html)
|
||||
log_warn("format_disk_message(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
free(escaped_path);
|
||||
free(escaped_msg);
|
||||
}
|
||||
|
||||
static void format_disk_message_extended(char *plain, const int sizeof_plain, char *html, const int sizeof_html,
|
||||
@@ -653,16 +819,25 @@ static void format_disk_message_extended(char *plain, const int sizeof_plain, ch
|
||||
char *escaped_mnt_dir = escape_html(mnt_dir);
|
||||
char *escaped_msg = escape_html(msg);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_mnt_type == NULL || escaped_mnt_dir == NULL || escaped_msg == NULL)
|
||||
{
|
||||
if(escaped_mnt_type != NULL)
|
||||
free(escaped_mnt_type);
|
||||
if(escaped_mnt_dir != NULL)
|
||||
free(escaped_mnt_dir);
|
||||
if(escaped_msg != NULL)
|
||||
free(escaped_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if(snprintf(html, sizeof_html, "Disk shortage ahead: <strong>%d%%</strong> is used (%s) on %s filesystem mounted at <code>%s</code>",
|
||||
disk, escaped_msg, escaped_mnt_type, escaped_mnt_dir) > sizeof_html)
|
||||
log_warn("format_disk_message_extended(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
if(escaped_mnt_type != NULL)
|
||||
free(escaped_mnt_type);
|
||||
if(escaped_mnt_dir != NULL)
|
||||
free(escaped_mnt_dir);
|
||||
if(escaped_msg != NULL)
|
||||
free(escaped_msg);
|
||||
free(escaped_mnt_type);
|
||||
free(escaped_mnt_dir);
|
||||
free(escaped_msg);
|
||||
}
|
||||
|
||||
static void format_inaccessible_adlist_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html,
|
||||
@@ -678,12 +853,15 @@ static void format_inaccessible_adlist_message(char *plain, const int sizeof_pla
|
||||
|
||||
char *escaped_address = escape_html(address);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_address == NULL)
|
||||
return;
|
||||
|
||||
if(snprintf(html, sizeof_html, "<a href=\"groups/lists?listid=%i\">List with ID <strong>%d</strong> (<code>%s</code>)</a> was inaccessible during last gravity run",
|
||||
dbindex, dbindex, escaped_address) > sizeof_html)
|
||||
log_warn("format_inaccessible_adlist_message(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
if(escaped_address != NULL)
|
||||
free(escaped_address);
|
||||
free(escaped_address);
|
||||
}
|
||||
|
||||
static void format_certificate_domain_mismatch(char *plain, const int sizeof_plain, char *html, const int sizeof_html,
|
||||
@@ -699,13 +877,102 @@ static void format_certificate_domain_mismatch(char *plain, const int sizeof_pla
|
||||
char *escaped_certfile = escape_html(certfile);
|
||||
char *escaped_domain = escape_html(domain);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_certfile == NULL || escaped_domain == NULL)
|
||||
{
|
||||
if(escaped_certfile != NULL)
|
||||
free(escaped_certfile);
|
||||
if(escaped_domain != NULL)
|
||||
free(escaped_domain);
|
||||
return;
|
||||
}
|
||||
|
||||
if(snprintf(html, sizeof_html, "SSL/TLS certificate %s does not match domain <strong>%s</strong>!", escaped_certfile, escaped_domain) > sizeof_html)
|
||||
log_warn("format_certificate_domain_mismatch(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
if(escaped_certfile != NULL)
|
||||
free(escaped_certfile);
|
||||
if(escaped_domain != NULL)
|
||||
free(escaped_domain);
|
||||
free(escaped_certfile);
|
||||
free(escaped_domain);
|
||||
}
|
||||
|
||||
static void format_connection_error(char *plain, const int sizeof_plain, char *html, const int sizeof_html,
|
||||
const char *server, const char *reason, const char *error)
|
||||
{
|
||||
if(snprintf(plain, sizeof_plain, "Connection error (%s): %s (%s)", server, reason, error) > sizeof_plain)
|
||||
log_warn("format_connection_error(): Buffer too small to hold plain message, warning truncated");
|
||||
|
||||
// Return early if HTML text is not required
|
||||
if(sizeof_html < 1 || html == NULL)
|
||||
return;
|
||||
|
||||
char *escaped_reason = escape_html(reason);
|
||||
char *escaped_error = escape_html(error);
|
||||
char *escaped_server = escape_html(server);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_reason == NULL || escaped_error == NULL || escaped_server == NULL)
|
||||
{
|
||||
if(escaped_reason != NULL)
|
||||
free(escaped_reason);
|
||||
if(escaped_error != NULL)
|
||||
free(escaped_error);
|
||||
if(escaped_server != NULL)
|
||||
free(escaped_server);
|
||||
return;
|
||||
}
|
||||
|
||||
if(snprintf(html, sizeof_html, "Connection error (<strong>%s</strong>): %s (<strong>%s</strong>)", server, reason, error) > sizeof_html)
|
||||
log_warn("format_connection_error(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
free(escaped_reason);
|
||||
free(escaped_error);
|
||||
free(escaped_server);
|
||||
}
|
||||
|
||||
static void format_ntp_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html,
|
||||
const char *message, const char *level, const char *who)
|
||||
{
|
||||
if(snprintf(plain, sizeof_plain, "%s NTP %s: %s", level, who, message) > sizeof_plain)
|
||||
log_warn("format_ntp_message(): Buffer too small to hold plain message, warning truncated");
|
||||
|
||||
// Return early if HTML text is not required
|
||||
if(sizeof_html < 1 || html == NULL)
|
||||
return;
|
||||
|
||||
if(snprintf(html, sizeof_html, "%s in NTP %s:<pre>%s</pre>", level, who, message) > sizeof_html)
|
||||
log_warn("format_ntp_message(): Buffer too small to hold HTML message, warning truncated");
|
||||
}
|
||||
|
||||
static void format_verify_message(char *plain, const int sizeof_plain, char *html, const int sizeof_html,
|
||||
const char *message, const char *expected, const char *actual,
|
||||
const char *commit, const char *arch)
|
||||
{
|
||||
if(snprintf(plain, sizeof_plain, "%s - expected \"%s\", but got \"%s\" - FTL commit is %s on %s",
|
||||
message, expected, actual, commit, arch) > sizeof_plain)
|
||||
log_warn("format_verify_message(): Buffer too small to hold plain message, warning truncated");
|
||||
|
||||
// Return early if HTML text is not required
|
||||
if(sizeof_html < 1 || html == NULL)
|
||||
return;
|
||||
|
||||
char *escaped_message = escape_html(message);
|
||||
char *escaped_expected = escape_html(expected);
|
||||
char *escaped_actual = escape_html(actual);
|
||||
char *escaped_commit = escape_html(commit);
|
||||
char *escaped_arch = escape_html(arch);
|
||||
|
||||
// Return early if memory allocation failed
|
||||
if(escaped_message == NULL || escaped_expected == NULL || escaped_actual == NULL || escaped_commit == NULL || escaped_arch == NULL)
|
||||
return;
|
||||
|
||||
if(snprintf(html, sizeof_html, "%s<br>Expected: <pre>%s</pre><br>Actual: <pre>%s</pre><br>FTL commit is <code>%s</code> on <code>%s</code>",
|
||||
escaped_message, escaped_expected, escaped_actual, escaped_commit, escaped_arch) > sizeof_html)
|
||||
log_warn("format_verify_message(): Buffer too small to hold HTML message, warning truncated");
|
||||
|
||||
free(escaped_message);
|
||||
free(escaped_expected);
|
||||
free(escaped_actual);
|
||||
free(escaped_commit);
|
||||
free(escaped_arch);
|
||||
}
|
||||
|
||||
int count_messages(const bool filter_dnsmasq_warnings)
|
||||
@@ -796,14 +1063,14 @@ bool format_messages(cJSON *array)
|
||||
|
||||
// Generate messages
|
||||
char plain[1024] = { 0 }, html[2048] = { 0 };
|
||||
const int mtype = get_message_type_from_string(mtypestr);
|
||||
const enum message_type mtype = get_message_type_from_string(mtypestr);
|
||||
switch(mtype)
|
||||
{
|
||||
case REGEX_MESSAGE:
|
||||
{
|
||||
const char *warning = (const char*)sqlite3_column_text(stmt, 3);
|
||||
const char *regex = (const char*)sqlite3_column_text(stmt, 3);
|
||||
const char *type = (const char*)sqlite3_column_text(stmt, 4);
|
||||
const char *regex = (const char*)sqlite3_column_text(stmt, 5);
|
||||
const char *warning = (const char*)sqlite3_column_text(stmt, 5);
|
||||
const int dbindex = sqlite3_column_int(stmt, 6);
|
||||
|
||||
format_regex_message(plain, sizeof(plain), html, sizeof(html),
|
||||
@@ -942,6 +1209,49 @@ bool format_messages(cJSON *array)
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CONNECTION_ERROR_MESSAGE:
|
||||
{
|
||||
const char *server = (const char*)sqlite3_column_text(stmt, 3);
|
||||
const char *reason = (const char*)sqlite3_column_text(stmt, 4);
|
||||
const char *error = (const char*)sqlite3_column_text(stmt, 5);
|
||||
|
||||
format_connection_error(plain, sizeof(plain), html, sizeof(html),
|
||||
server, reason, error);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case NTP_MESSAGE:
|
||||
{
|
||||
const char *message = (const char*)sqlite3_column_text(stmt, 3);
|
||||
const char *level = (const char*)sqlite3_column_text(stmt, 4);
|
||||
const char *who = (const char*)sqlite3_column_text(stmt, 5);
|
||||
|
||||
format_ntp_message(plain, sizeof(plain), html, sizeof(html),
|
||||
message, level, who);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case VERIFY_MESSAGE:
|
||||
{
|
||||
const char *message = (const char*)sqlite3_column_text(stmt, 3);
|
||||
const char *expected = (const char*)sqlite3_column_text(stmt, 4);
|
||||
const char *actual = (const char*)sqlite3_column_text(stmt, 5);
|
||||
const char *hash = (const char*)sqlite3_column_text(stmt, 6);
|
||||
const char *arch = (const char*)sqlite3_column_text(stmt, 7);
|
||||
|
||||
format_verify_message(plain, sizeof(plain), html, sizeof(html),
|
||||
message, expected, actual, hash, arch);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MAX_MESSAGE: // Fall through
|
||||
default:
|
||||
log_warn("format_messages() - Unknown message type: %s", mtypestr);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the plain message
|
||||
@@ -992,14 +1302,8 @@ void logg_regex_warning(const char *type, const char *warning, const int dbindex
|
||||
// Log to FTL.log
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database only if not in CLI mode
|
||||
if(cli_mode)
|
||||
return;
|
||||
|
||||
// Add to database
|
||||
const int rowid = add_message(REGEX_MESSAGE, warning, 3, type, regex, dbindex);
|
||||
if(rowid == -1)
|
||||
log_err("logg_regex_warning(): Failed to add message to database");
|
||||
add_message(REGEX_MESSAGE, regex, type, warning, dbindex);
|
||||
}
|
||||
|
||||
void logg_subnet_warning(const char *ip, const int matching_count, const char *matching_ids,
|
||||
@@ -1017,10 +1321,8 @@ void logg_subnet_warning(const char *ip, const int matching_count, const char *m
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = add_message(SUBNET_MESSAGE, ip, 5, matching_count, names, matching_ids, chosen_match_text, chosen_match_id);
|
||||
add_message(SUBNET_MESSAGE, ip, matching_count, names, matching_ids, chosen_match_text, chosen_match_id);
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("logg_subnet_warning(): Failed to add message to database");
|
||||
|
||||
free(names);
|
||||
}
|
||||
@@ -1039,10 +1341,8 @@ void logg_hostname_warning(const char *ip, const char *name, const unsigned int
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = add_message(HOSTNAME_MESSAGE, ip, 2, name, (const int)pos);
|
||||
add_message(HOSTNAME_MESSAGE, ip, name, (const int)pos);
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("logg_hostname_warning(): Failed to add message to database");
|
||||
}
|
||||
|
||||
void logg_fatal_dnsmasq_message(const char *message)
|
||||
@@ -1055,10 +1355,8 @@ void logg_fatal_dnsmasq_message(const char *message)
|
||||
log_crit("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = add_message(DNSMASQ_CONFIG_MESSAGE, message, 0);
|
||||
add_message_no_args(DNSMASQ_CONFIG_MESSAGE, message);
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("logg_fatal_dnsmasq_message(): Failed to add message to database");
|
||||
}
|
||||
|
||||
void logg_rate_limit_message(const char *clientIP, const unsigned int rate_limit_count)
|
||||
@@ -1073,10 +1371,8 @@ void logg_rate_limit_message(const char *clientIP, const unsigned int rate_limit
|
||||
log_info("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = add_message(RATE_LIMIT_MESSAGE, clientIP, 3, config.dns.rateLimit.count.v.ui, config.dns.rateLimit.interval.v.ui, turnaround);
|
||||
add_message(RATE_LIMIT_MESSAGE, clientIP, config.dns.rateLimit.count.v.ui, config.dns.rateLimit.interval.v.ui, turnaround);
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("logg_rate_limit_message(): Failed to add message to database");
|
||||
}
|
||||
|
||||
void logg_warn_dnsmasq_message(char *message)
|
||||
@@ -1089,10 +1385,8 @@ void logg_warn_dnsmasq_message(char *message)
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = add_message(DNSMASQ_WARN_MESSAGE, message, 0);
|
||||
add_message_no_args(DNSMASQ_WARN_MESSAGE, message);
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("logg_warn_dnsmasq_message(): Failed to add message to database");
|
||||
}
|
||||
|
||||
void log_resource_shortage(const double load, const int nprocs, const int shmem, const int disk, const char *path, const char *msg)
|
||||
@@ -1108,10 +1402,9 @@ void log_resource_shortage(const double load, const int nprocs, const int shmem,
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = add_message(LOAD_MESSAGE, "excessive load", 2, load, nprocs);
|
||||
add_message(LOAD_MESSAGE, "excessive load", load, nprocs);
|
||||
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("log_resource_shortage(): Failed to add message to database");
|
||||
}
|
||||
else if(shmem > -1)
|
||||
{
|
||||
@@ -1121,16 +1414,32 @@ void log_resource_shortage(const double load, const int nprocs, const int shmem,
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = add_message(SHMEM_MESSAGE, path, 2, shmem, msg);
|
||||
add_message(SHMEM_MESSAGE, path, shmem, msg);
|
||||
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("log_resource_shortage(): Failed to add message to database");
|
||||
}
|
||||
else if(disk > -1)
|
||||
{
|
||||
// Get filesystem details for this path
|
||||
struct mntent *fsdetails = get_filesystem_details(path);
|
||||
|
||||
// Log filesystem details if in debug mode
|
||||
if(config.debug.gc.v.b)
|
||||
{
|
||||
if(fsdetails != NULL)
|
||||
{
|
||||
log_debug(DEBUG_GC, "Disk details for path \"%s\":", path);
|
||||
log_debug(DEBUG_GC, " Device or server for filesystem: %s", fsdetails->mnt_fsname);
|
||||
log_debug(DEBUG_GC, " Directory mounted on: %s", fsdetails->mnt_dir);
|
||||
log_debug(DEBUG_GC, " Type of filesystem: %s", fsdetails->mnt_type);
|
||||
log_debug(DEBUG_GC, " Comma-separated options for fs: %s", fsdetails->mnt_opts);
|
||||
log_debug(DEBUG_GC, " Dump frequency (in days): %d", fsdetails->mnt_freq);
|
||||
log_debug(DEBUG_GC, " Pass number for `fsck': %d", fsdetails->mnt_passno);
|
||||
}
|
||||
else
|
||||
log_debug(DEBUG_GC, "Failed to get filesystem details for path \"%s\"", path);
|
||||
}
|
||||
|
||||
// Create plain message
|
||||
if(fsdetails != NULL)
|
||||
format_disk_message_extended(buf, sizeof(buf), NULL, 0, disk, msg, fsdetails->mnt_type, fsdetails->mnt_dir);
|
||||
@@ -1141,12 +1450,11 @@ void log_resource_shortage(const double load, const int nprocs, const int shmem,
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = fsdetails != NULL ?
|
||||
add_message(DISK_MESSAGE_EXTENDED, path, 4, disk, fsdetails->mnt_type, fsdetails->mnt_dir) :
|
||||
add_message(DISK_MESSAGE, path, 2, disk, msg);
|
||||
fsdetails != NULL ?
|
||||
add_message(DISK_MESSAGE_EXTENDED, path, disk, msg, fsdetails->mnt_type, fsdetails->mnt_dir) :
|
||||
add_message(DISK_MESSAGE, path, disk, msg);
|
||||
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("log_resource_shortage(): Failed to add message to database");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1160,10 +1468,8 @@ void logg_inaccessible_adlist(const int dbindex, const char *address)
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = add_message(INACCESSIBLE_ADLIST_MESSAGE, address, 1, dbindex);
|
||||
add_message(INACCESSIBLE_ADLIST_MESSAGE, address, dbindex);
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("logg_inaccessible_adlist(): Failed to add message to database");
|
||||
}
|
||||
|
||||
void log_certificate_domain_mismatch(const char *certfile, const char *domain)
|
||||
@@ -1176,8 +1482,54 @@ void log_certificate_domain_mismatch(const char *certfile, const char *domain)
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
const int rowid = add_message(CERTIFICATE_DOMAIN_MISMATCH_MESSAGE, certfile, 1, domain);
|
||||
add_message(CERTIFICATE_DOMAIN_MISMATCH_MESSAGE, certfile, domain);
|
||||
|
||||
}
|
||||
|
||||
void log_connection_error(const char *server, const char *reason, const char *error)
|
||||
{
|
||||
// Create message
|
||||
char buf[2048];
|
||||
format_connection_error(buf, sizeof(buf), NULL, 0, server, reason, error);
|
||||
|
||||
// Log to FTL.log
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
add_message(CONNECTION_ERROR_MESSAGE, server, reason, error);
|
||||
|
||||
}
|
||||
|
||||
void log_ntp_message(const bool error, const bool server, const char *message)
|
||||
{
|
||||
const char *who = server ? "server" : "client";
|
||||
const char *level = error ? "Error" : "Warning";
|
||||
|
||||
// Create message
|
||||
char buf[2048];
|
||||
format_ntp_message(buf, sizeof(buf), NULL, 0, message, level, who);
|
||||
|
||||
// Log to FTL.log
|
||||
if(error)
|
||||
log_err("%s", buf);
|
||||
else
|
||||
log_warn("%s", buf);
|
||||
|
||||
// Log to database
|
||||
add_message(NTP_MESSAGE, message, level, who);
|
||||
|
||||
}
|
||||
|
||||
void log_verify_message(const char *expected, const char *actual)
|
||||
{
|
||||
// Create message
|
||||
char buf[2048];
|
||||
snprintf(buf, sizeof(buf), "Corrupt binary detected - this may lead to unexpected behaviour!");
|
||||
|
||||
// Log to FTL.log
|
||||
log_crit("%s", buf);
|
||||
|
||||
// Log to database
|
||||
add_message(VERIFY_MESSAGE, buf, expected, actual, GIT_HASH, FTL_ARCH);
|
||||
|
||||
if(rowid == -1)
|
||||
log_err("log_certificate_domain_mismatch(): Failed to add message to database");
|
||||
}
|
||||
|
||||
@@ -29,5 +29,8 @@ void logg_warn_dnsmasq_message(char *message);
|
||||
void log_resource_shortage(const double load, const int nprocs, const int shmem, const int disk, const char *path, const char *msg);
|
||||
void logg_inaccessible_adlist(const int dbindex, const char *address);
|
||||
void log_certificate_domain_mismatch(const char *certfile, const char *domain);
|
||||
void log_connection_error(const char *server, const char *reason, const char *error);
|
||||
void log_ntp_message(const bool error, const bool server, const char *message);
|
||||
void log_verify_message(const char *expected, const char *actual);
|
||||
|
||||
#endif //MESSAGETABLE_H
|
||||
|
||||
+121
-133
@@ -8,21 +8,21 @@
|
||||
* 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 "FTL.h"
|
||||
#include "network-table.h"
|
||||
#include "common.h"
|
||||
#include "../shmem.h"
|
||||
#include "../log.h"
|
||||
#include "shmem.h"
|
||||
#include "log.h"
|
||||
// timer_elapsed_msec()
|
||||
#include "../timers.h"
|
||||
#include "../config/config.h"
|
||||
#include "../datastructure.h"
|
||||
#include "timers.h"
|
||||
#include "config/config.h"
|
||||
#include "datastructure.h"
|
||||
// struct config
|
||||
#include "../config/config.h"
|
||||
#include "config/config.h"
|
||||
// resolve_this_name()
|
||||
#include "../resolve.h"
|
||||
#include "resolve.h"
|
||||
// killed
|
||||
#include "../signals.h"
|
||||
#include "signals.h"
|
||||
|
||||
// Private prototypes
|
||||
static char *getMACVendor(const char *hwaddr) __attribute__ ((malloc));
|
||||
@@ -210,23 +210,13 @@ static int find_device_by_recent_ip(sqlite3 *db, const char *ipaddr)
|
||||
if(FTLDBerror())
|
||||
return -1;
|
||||
|
||||
char *querystr = NULL;
|
||||
int ret = asprintf(&querystr,
|
||||
"SELECT network_id FROM network_addresses "
|
||||
"WHERE ip = \'%s\' AND "
|
||||
"lastSeen > (cast(strftime('%%s', 'now') as int)-86400) "
|
||||
"ORDER BY lastSeen DESC LIMIT 1;", ipaddr);
|
||||
if(querystr == NULL || ret < 0)
|
||||
{
|
||||
log_warn("Memory allocation failed in find_device_by_recent_ip(\"%s\"): %i",
|
||||
ipaddr, ret);
|
||||
return -1;
|
||||
}
|
||||
const char *querystr = "SELECT network_id FROM network_addresses "
|
||||
"WHERE ip = ?1 AND "
|
||||
"lastSeen > (cast(strftime('%%s', 'now') as int)-86400) "
|
||||
"ORDER BY lastSeen DESC LIMIT 1;";
|
||||
|
||||
// Perform SQL query
|
||||
int network_id = db_query_int(db, querystr);
|
||||
free(querystr);
|
||||
querystr = NULL;
|
||||
int network_id = db_query_int_str(db, querystr, ipaddr);
|
||||
|
||||
if(network_id == DB_FAILED)
|
||||
{
|
||||
@@ -252,20 +242,10 @@ static int find_device_by_mock_hwaddr(sqlite3 *db, const char *ipaddr)
|
||||
if(FTLDBerror())
|
||||
return DB_FAILED;
|
||||
|
||||
char *querystr = NULL;
|
||||
int ret = asprintf(&querystr, "SELECT id FROM network WHERE hwaddr = \'ip-%s\';", ipaddr);
|
||||
if(querystr == NULL || ret < 0)
|
||||
{
|
||||
log_warn("Memory allocation failed in find_device_by_mock_hwaddr(\"%s\"): %i",
|
||||
ipaddr, ret);
|
||||
return -1;
|
||||
}
|
||||
const char *querystr = "SELECT id FROM network WHERE hwaddr = concat('ip-',?1)";
|
||||
|
||||
// Perform SQL query
|
||||
int network_id = db_query_int(db, querystr);
|
||||
free(querystr);
|
||||
|
||||
return network_id;
|
||||
return db_query_int_str(db, querystr, ipaddr);
|
||||
}
|
||||
|
||||
// Try to find device by hardware address
|
||||
@@ -275,20 +255,10 @@ static int find_device_by_hwaddr(sqlite3 *db, const char hwaddr[])
|
||||
if(FTLDBerror())
|
||||
return DB_FAILED;
|
||||
|
||||
char *querystr = NULL;
|
||||
int ret = asprintf(&querystr, "SELECT id FROM network WHERE hwaddr = \'%s\' COLLATE NOCASE;", hwaddr);
|
||||
if(querystr == NULL || ret < 0)
|
||||
{
|
||||
log_warn("Memory allocation failed in find_device_by_hwaddr(\"%s\"): %i",
|
||||
hwaddr, ret);
|
||||
return -1;
|
||||
}
|
||||
const char *querystr = "SELECT id FROM network WHERE hwaddr = ?1 COLLATE NOCASE;";
|
||||
|
||||
// Perform SQL query
|
||||
int network_id = db_query_int(db, querystr);
|
||||
free(querystr);
|
||||
|
||||
return network_id;
|
||||
return db_query_int_str(db, querystr, hwaddr);
|
||||
}
|
||||
|
||||
// Try to find device by RECENT mock hardware address (generated from IP address)
|
||||
@@ -298,24 +268,12 @@ static int find_recent_device_by_mock_hwaddr(sqlite3 *db, const char *ipaddr)
|
||||
if(FTLDBerror())
|
||||
return DB_FAILED;
|
||||
|
||||
char *querystr = NULL;
|
||||
int ret = asprintf(&querystr,
|
||||
"SELECT id FROM network WHERE "
|
||||
"hwaddr = \'ip-%s\' AND "
|
||||
"firstSeen > (cast(strftime('%%s', 'now') as int)-3600);",
|
||||
ipaddr);
|
||||
if(querystr == NULL || ret < 0)
|
||||
{
|
||||
log_warn("Memory allocation failed in find_device_by_recent_mock_hwaddr(\"%s\"): %i",
|
||||
ipaddr, ret);
|
||||
return -1;
|
||||
}
|
||||
const char *querystr = "SELECT id FROM network WHERE "
|
||||
"hwaddr = concat('ip-',?1) AND "
|
||||
"firstSeen > (cast(strftime('%%s', 'now') as int)-3600)";
|
||||
|
||||
// Perform SQL query
|
||||
int network_id = db_query_int(db, querystr);
|
||||
free(querystr);
|
||||
|
||||
return network_id;
|
||||
return db_query_int_str(db, querystr, ipaddr);
|
||||
}
|
||||
|
||||
// Store hostname of device identified by dbID
|
||||
@@ -510,8 +468,8 @@ static int add_netDB_network_address(sqlite3 *db, const int network_id, const ch
|
||||
}
|
||||
|
||||
// Insert a new record into the network table
|
||||
static int insert_netDB_device(sqlite3 *db, const char *hwaddr, time_t now, time_t lastQuery,
|
||||
unsigned int numQueriesARP, const char *macVendor)
|
||||
static int insert_netDB_device(sqlite3 *db, const char *hwaddr, const time_t firstSeen, const time_t lastQuery,
|
||||
const unsigned int numQueriesARP, const char *macVendor)
|
||||
{
|
||||
// Return early if database is known to be broken
|
||||
if(FTLDBerror())
|
||||
@@ -526,29 +484,29 @@ static int insert_netDB_device(sqlite3 *db, const char *hwaddr, time_t now, time
|
||||
if(rc != SQLITE_OK)
|
||||
{
|
||||
log_err("insert_netDB_device(\"%s\", %lu, %lu, %u, \"%s\") - SQL error prepare (%i): %s",
|
||||
hwaddr, (unsigned long)now, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
checkFTLDBrc(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
log_debug(DEBUG_DATABASE, "dbquery: \"%s\" with arguments ?1-?5 = (\"%s\", %lu, %lu, %u, \"%s\")",
|
||||
querystr, hwaddr, (unsigned long)now, (unsigned long)lastQuery, numQueriesARP, macVendor);
|
||||
querystr, hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor);
|
||||
|
||||
// Bind hwaddr to prepared statement (1st argument)
|
||||
if((rc = sqlite3_bind_text(query_stmt, 1, hwaddr, -1, SQLITE_STATIC)) != SQLITE_OK)
|
||||
{
|
||||
log_err("insert_netDB_device(\"%s\", %lu, %lu, %u, \"%s\"): Failed to bind hwaddr (error %d): %s",
|
||||
hwaddr, (unsigned long)now, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
sqlite3_reset(query_stmt);
|
||||
checkFTLDBrc(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Bind now to prepared statement (2nd argument)
|
||||
if((rc = sqlite3_bind_int(query_stmt, 2, now)) != SQLITE_OK)
|
||||
// Bind firstSeen to prepared statement (2nd argument)
|
||||
if((rc = sqlite3_bind_int(query_stmt, 2, firstSeen)) != SQLITE_OK)
|
||||
{
|
||||
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to bind now (error %d): %s",
|
||||
hwaddr, (unsigned long)now, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to bind firstSeen (error %d): %s",
|
||||
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
sqlite3_reset(query_stmt);
|
||||
checkFTLDBrc(rc);
|
||||
return rc;
|
||||
@@ -558,7 +516,7 @@ static int insert_netDB_device(sqlite3 *db, const char *hwaddr, time_t now, time
|
||||
if((rc = sqlite3_bind_int(query_stmt, 3, lastQuery)) != SQLITE_OK)
|
||||
{
|
||||
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to bind lastQuery (error %d): %s",
|
||||
hwaddr, (unsigned long)now, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
sqlite3_reset(query_stmt);
|
||||
checkFTLDBrc(rc);
|
||||
return rc;
|
||||
@@ -568,7 +526,7 @@ static int insert_netDB_device(sqlite3 *db, const char *hwaddr, time_t now, time
|
||||
if((rc = sqlite3_bind_int(query_stmt, 4, numQueriesARP)) != SQLITE_OK)
|
||||
{
|
||||
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to bind numQueriesARP (error %d): %s",
|
||||
hwaddr, (unsigned long)now, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
sqlite3_reset(query_stmt);
|
||||
checkFTLDBrc(rc);
|
||||
return rc;
|
||||
@@ -578,7 +536,7 @@ static int insert_netDB_device(sqlite3 *db, const char *hwaddr, time_t now, time
|
||||
if((rc = sqlite3_bind_text(query_stmt, 5, macVendor, -1, SQLITE_STATIC)) != SQLITE_OK)
|
||||
{
|
||||
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to bind macVendor (error %d): %s",
|
||||
hwaddr, (unsigned long)now, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
sqlite3_reset(query_stmt);
|
||||
checkFTLDBrc(rc);
|
||||
return rc;
|
||||
@@ -588,7 +546,7 @@ static int insert_netDB_device(sqlite3 *db, const char *hwaddr, time_t now, time
|
||||
if ((rc = sqlite3_step(query_stmt)) != SQLITE_DONE)
|
||||
{
|
||||
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to step (error %d): %s",
|
||||
hwaddr, (unsigned long)now, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
sqlite3_reset(query_stmt);
|
||||
checkFTLDBrc(rc);
|
||||
return rc;
|
||||
@@ -598,7 +556,7 @@ static int insert_netDB_device(sqlite3 *db, const char *hwaddr, time_t now, time
|
||||
if ((rc = sqlite3_finalize(query_stmt)) != SQLITE_OK)
|
||||
{
|
||||
log_err("insert_netDB_device(\"%s\",%lu, %lu, %u, \"%s\"): Failed to finalize (error %d): %s",
|
||||
hwaddr, (unsigned long)now, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
hwaddr, (unsigned long)firstSeen, (unsigned long)lastQuery, numQueriesARP, macVendor, rc, sqlite3_errstr(rc));
|
||||
sqlite3_reset(query_stmt);
|
||||
checkFTLDBrc(rc);
|
||||
return rc;
|
||||
@@ -901,9 +859,10 @@ static bool add_FTL_clients_to_network_table(sqlite3 *db, const enum arp_status
|
||||
|
||||
// Add new device to database
|
||||
const time_t lastQuery = client->lastQuery;
|
||||
const unsigned int numQueriesARP = client->numQueriesARP;
|
||||
const time_t firstSeen = client->firstSeen;
|
||||
const unsigned int numQueries = client->count;
|
||||
unlock_shm();
|
||||
insert_netDB_device(db, hwaddr, now, lastQuery, numQueriesARP, macVendor);
|
||||
insert_netDB_device(db, hwaddr, firstSeen, lastQuery, numQueries, macVendor);
|
||||
lock_shm();
|
||||
|
||||
// Reacquire client pointer (if may have changed when unlocking above)
|
||||
@@ -1136,30 +1095,9 @@ static bool add_local_interfaces_to_network_table(sqlite3 *db, time_t now, unsig
|
||||
int lastQuery = 0, firstSeen = now, numQueries = 0;
|
||||
if(mockID >= 0)
|
||||
{
|
||||
char *querystr = NULL;
|
||||
if(asprintf(&querystr, "SELECT lastQuery from network where id = %i", mockID) < 10)
|
||||
{
|
||||
free(macVendor);
|
||||
return false;
|
||||
}
|
||||
lastQuery = db_query_int(db, querystr);
|
||||
free(querystr);
|
||||
|
||||
if(asprintf(&querystr, "SELECT firstSeen from network where id = %i", mockID) < 10)
|
||||
{
|
||||
free(macVendor);
|
||||
return false;
|
||||
}
|
||||
firstSeen = db_query_int(db, querystr);
|
||||
free(querystr);
|
||||
|
||||
if(asprintf(&querystr, "SELECT numQueries from network where id = %i", mockID) < 10)
|
||||
{
|
||||
free(macVendor);
|
||||
return false;
|
||||
}
|
||||
numQueries = db_query_int(db, querystr);
|
||||
free(querystr);
|
||||
lastQuery = db_query_int_int(db, "SELECT lastQuery from network where id = ?1", mockID);
|
||||
firstSeen = db_query_int_int(db, "SELECT firstSeen from network where id = ?1", mockID);
|
||||
numQueries = db_query_int_int(db, "SELECT numQueries from network where id = ?1", mockID);
|
||||
}
|
||||
|
||||
// Add new device to database
|
||||
@@ -1251,7 +1189,7 @@ void parse_neighbor_cache(sqlite3* db)
|
||||
char *linebuffer = NULL;
|
||||
size_t linebuffersize = 0u;
|
||||
unsigned int entries = 0u, additional_entries = 0u;
|
||||
time_t now = time(NULL);
|
||||
const time_t now = time(NULL);
|
||||
|
||||
// Start ARP timer
|
||||
if(config.debug.arp.v.b)
|
||||
@@ -1298,6 +1236,7 @@ void parse_neighbor_cache(sqlite3* db)
|
||||
if((arpfp = popen(cmd, "r")) == NULL)
|
||||
{
|
||||
log_warn("Command \"%s\" failed: %s", cmd, strerror(errno));
|
||||
free(client_status);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1330,8 +1269,11 @@ void parse_neighbor_cache(sqlite3* db)
|
||||
{
|
||||
// This line is incomplete, remember this to skip
|
||||
// mock-device creation after ARP processing
|
||||
// both false = do not create a new record if the client
|
||||
// is unknown (only DNS requesting clients
|
||||
// do this), the now value is ignored
|
||||
lock_shm();
|
||||
int clientID = findClientID(ip, false, false);
|
||||
int clientID = findClientID(ip, false, false, 0.0);
|
||||
unlock_shm();
|
||||
if(clientID >= 0 && clientID < clients)
|
||||
client_status[clientID] = CLIENT_ARP_INCOMPLETE;
|
||||
@@ -1364,16 +1306,19 @@ void parse_neighbor_cache(sqlite3* db)
|
||||
|
||||
// If we reach this point, we can check if this client
|
||||
// is known to pihole-FTL
|
||||
// false = do not create a new record if the client is
|
||||
// unknown (only DNS requesting clients do this)
|
||||
// both false = do not create a new record if the client
|
||||
// is unknown (only DNS requesting clients
|
||||
// do this), the now value is ignored
|
||||
lock_shm();
|
||||
int clientID = findClientID(ip, false, false);
|
||||
int clientID = findClientID(ip, false, false, 0.0);
|
||||
|
||||
// Get hostname of this client if the client is known
|
||||
// Set default values for a new device, may be updated
|
||||
// below if the client is known to pihole-FTL
|
||||
char *hostname = NULL;
|
||||
bool client_valid = false;
|
||||
time_t lastQuery = 0;
|
||||
unsigned int numQueries = 0;
|
||||
time_t firstSeen = now;
|
||||
unsigned int numQueries = 0, totalQueries = 0;
|
||||
|
||||
// This client is known (by its IP address) to pihole-FTL if
|
||||
// findClientID() returned a non-negative index
|
||||
@@ -1383,14 +1328,21 @@ void parse_neighbor_cache(sqlite3* db)
|
||||
if(!client)
|
||||
continue;
|
||||
|
||||
// Client is known to Pi-hole, update properties
|
||||
// with their real values
|
||||
client_valid = true;
|
||||
hostname = strdup(getstr(client->namepos));
|
||||
firstSeen = client->firstSeen;
|
||||
lastQuery = client->lastQuery;
|
||||
numQueries = client->numQueriesARP;
|
||||
totalQueries = client->count;
|
||||
client_status[clientID] = CLIENT_ARP_COMPLETE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Client is not known to Pi-hole, create a
|
||||
// mock-device with the default values set above
|
||||
// and an empty hostname
|
||||
hostname = strdup("");
|
||||
}
|
||||
unlock_shm();
|
||||
@@ -1405,6 +1357,15 @@ void parse_neighbor_cache(sqlite3* db)
|
||||
// and the ARP entry just came a bit delayed (reported by at least one user)
|
||||
dbID = find_recent_device_by_mock_hwaddr(db, ip);
|
||||
|
||||
// Exception for the case where the device is
|
||||
// not yet in the database: Use total count of
|
||||
// queries as the number of queries for the new
|
||||
// device instead of the special ARP cache
|
||||
// counter to add also the number of queries in
|
||||
// the DNS history imported from the long-term
|
||||
// database
|
||||
numQueries = totalQueries;
|
||||
|
||||
if(dbID == DB_NODATA)
|
||||
{
|
||||
// Device not known AND no recent mock-device found ---> create new device record
|
||||
@@ -1412,7 +1373,7 @@ void parse_neighbor_cache(sqlite3* db)
|
||||
hwaddr, ip, hostname, macVendor);
|
||||
|
||||
// Create new record (INSERT)
|
||||
insert_netDB_device(db, hwaddr, now, lastQuery, numQueries, macVendor);
|
||||
insert_netDB_device(db, hwaddr, firstSeen, lastQuery, numQueries, macVendor);
|
||||
|
||||
lock_shm();
|
||||
clientsData *client = getClient(clientID, true);
|
||||
@@ -1790,33 +1751,56 @@ void updateMACVendorRecords(sqlite3 *db)
|
||||
|
||||
// Get vendor for MAC
|
||||
char *vendor = getMACVendor(hwaddr);
|
||||
|
||||
// Free allocated memory
|
||||
free(hwaddr);
|
||||
hwaddr = NULL;
|
||||
|
||||
// Prepare UPDATE statement
|
||||
char *updatestr = NULL;
|
||||
if(asprintf(&updatestr, "UPDATE network SET macVendor = \'%s\' WHERE id = %i", vendor, id) < 1)
|
||||
// Prepare statement
|
||||
sqlite3_stmt *stmt2 = NULL;
|
||||
const char *updatestr = "UPDATE network SET macVendor = ?1 WHERE id = ?2";
|
||||
rc = sqlite3_prepare_v2(db, updatestr, -1, &stmt2, NULL);
|
||||
if(rc != SQLITE_OK)
|
||||
{
|
||||
log_err("updateMACVendorRecords() - Allocation error");
|
||||
log_err("updateMACVendorRecords() - SQL error prepare \"%s\": %s", updatestr, sqlite3_errstr(rc));
|
||||
checkFTLDBrc(rc);
|
||||
free(vendor);
|
||||
break;
|
||||
}
|
||||
|
||||
// Execute prepared statement
|
||||
char *zErrMsg = NULL;
|
||||
rc = sqlite3_exec(db, updatestr, NULL, NULL, &zErrMsg);
|
||||
if(rc != SQLITE_OK)
|
||||
// Bind vendor to prepared statement
|
||||
if((rc = sqlite3_bind_text(stmt2, 1, vendor, -1, SQLITE_STATIC)) != SQLITE_OK)
|
||||
{
|
||||
log_err("updateMACVendorRecords() - SQL exec error: \"%s\": %s", updatestr, zErrMsg);
|
||||
log_err("updateMACVendorRecords() - Failed to bind vendor: %s", sqlite3_errstr(rc));
|
||||
sqlite3_reset(stmt2);
|
||||
sqlite3_finalize(stmt2);
|
||||
free(vendor);
|
||||
break;
|
||||
}
|
||||
|
||||
// Bind id to prepared statement
|
||||
if((rc = sqlite3_bind_int(stmt2, 2, id)) != SQLITE_OK)
|
||||
{
|
||||
log_err("updateMACVendorRecords() - Failed to bind id: %s", sqlite3_errstr(rc));
|
||||
sqlite3_reset(stmt2);
|
||||
sqlite3_finalize(stmt2);
|
||||
free(vendor);
|
||||
break;
|
||||
}
|
||||
|
||||
// Execute statement
|
||||
rc = sqlite3_step(stmt2);
|
||||
if(rc != SQLITE_DONE)
|
||||
{
|
||||
log_err("updateMACVendorRecords() - SQL error step: %s", sqlite3_errstr(rc));
|
||||
checkFTLDBrc(rc);
|
||||
sqlite3_free(zErrMsg);
|
||||
free(updatestr);
|
||||
sqlite3_reset(stmt2);
|
||||
sqlite3_finalize(stmt2);
|
||||
free(vendor);
|
||||
break;
|
||||
}
|
||||
|
||||
// Free allocated memory
|
||||
free(updatestr);
|
||||
free(vendor);
|
||||
}
|
||||
if(rc != SQLITE_DONE)
|
||||
@@ -2004,11 +1988,12 @@ char *__attribute__((malloc)) getNameFromIP(sqlite3 *db, const char *ipaddr)
|
||||
// Return early if database is known to be broken
|
||||
if(FTLDBerror())
|
||||
return NULL;
|
||||
log_debug(DEBUG_RESOLVER, "Trying to obtain host name of \"%s\" from network_addresses table", ipaddr);
|
||||
|
||||
// Check if we want to resolve host names
|
||||
if(!resolve_this_name(ipaddr))
|
||||
{
|
||||
log_debug(DEBUG_DATABASE, "getNameFromIP(\"%s\") - configured to not resolve host name", ipaddr);
|
||||
log_debug(DEBUG_RESOLVER, "getNameFromIP(\"%s\") - configured to not resolve host name", ipaddr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -2055,6 +2040,8 @@ char *__attribute__((malloc)) getNameFromIP(sqlite3 *db, const char *ipaddr)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
log_debug(DEBUG_RESOLVER, "Check for a host name associated with IP address %s", ipaddr);
|
||||
|
||||
char *name = NULL;
|
||||
rc = sqlite3_step(stmt);
|
||||
if(rc == SQLITE_ROW)
|
||||
@@ -2062,7 +2049,7 @@ char *__attribute__((malloc)) getNameFromIP(sqlite3 *db, const char *ipaddr)
|
||||
// Database record found (result might be empty)
|
||||
name = strdup((char*)sqlite3_column_text(stmt, 0));
|
||||
|
||||
log_debug(DEBUG_DATABASE, "Found database host name (same address) %s -> %s", ipaddr, name);
|
||||
log_debug(DEBUG_RESOLVER, "Found database host name (same address) %s -> %s", ipaddr, name);
|
||||
}
|
||||
else if(rc != SQLITE_DONE)
|
||||
{
|
||||
@@ -2083,6 +2070,8 @@ char *__attribute__((malloc)) getNameFromIP(sqlite3 *db, const char *ipaddr)
|
||||
return name;
|
||||
}
|
||||
|
||||
log_debug(DEBUG_RESOLVER, " ---> not found");
|
||||
|
||||
// Nothing found for the exact IP address
|
||||
// Check for a host name associated with the same device (but another IP address)
|
||||
querystr = "SELECT name FROM network_addresses "
|
||||
@@ -2113,6 +2102,8 @@ char *__attribute__((malloc)) getNameFromIP(sqlite3 *db, const char *ipaddr)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
log_debug(DEBUG_RESOLVER, "Checking for a host name associated with the same device (but another IP address)");
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
if(rc == SQLITE_ROW)
|
||||
{
|
||||
@@ -2152,8 +2143,6 @@ char *__attribute__((malloc)) getNameFromMAC(const char *client)
|
||||
if(FTLDBerror())
|
||||
return NULL;
|
||||
|
||||
log_debug(DEBUG_DATABASE,"Looking up host name for %s", client);
|
||||
|
||||
// Open pihole-FTL.db database file
|
||||
sqlite3 *db = NULL;
|
||||
if((db = dbopen(false, false)) == NULL)
|
||||
@@ -2191,6 +2180,8 @@ char *__attribute__((malloc)) getNameFromMAC(const char *client)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
log_debug(DEBUG_RESOLVER, "Check for a host name associated with MAC address %s", client);
|
||||
|
||||
char *name = NULL;
|
||||
rc = sqlite3_step(stmt);
|
||||
if(rc == SQLITE_ROW)
|
||||
@@ -2261,11 +2252,8 @@ char *__attribute__((malloc)) getIfaceFromIP(sqlite3 *db, const char *ipaddr)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(config.debug.resolver.v.b)
|
||||
{
|
||||
log_debug(DEBUG_RESOLVER, "getIfaceFromIP(): \"%s\" with ? = \"%s\"",
|
||||
querystr, ipaddr);
|
||||
}
|
||||
log_debug(DEBUG_DATABASE, "getIfaceFromIP(): \"%s\" with ? = \"%s\"",
|
||||
querystr, ipaddr);
|
||||
|
||||
// Bind ipaddr to prepared statement
|
||||
if((rc = sqlite3_bind_text(stmt, 1, ipaddr, -1, SQLITE_STATIC)) != SQLITE_OK)
|
||||
|
||||
+159
-108
@@ -21,12 +21,28 @@
|
||||
#include "overTime.h"
|
||||
#include "database/common.h"
|
||||
#include "timers.h"
|
||||
// runGC()
|
||||
#include "gc.h"
|
||||
|
||||
static sqlite3 *_memdb = NULL;
|
||||
static bool store_in_database = false;
|
||||
static double new_last_timestamp = 0;
|
||||
static unsigned int new_total = 0, new_blocked = 0;
|
||||
static unsigned long last_mem_db_idx = 0, last_disk_db_idx = 0;
|
||||
static unsigned int mem_db_num = 0, disk_db_num = 0;
|
||||
static sqlite3_stmt *query_stmt = NULL;
|
||||
static sqlite3_stmt *domain_stmt = NULL;
|
||||
static sqlite3_stmt *client_stmt = NULL;
|
||||
static sqlite3_stmt *forward_stmt = NULL;
|
||||
static sqlite3_stmt *addinfo_stmt = NULL;
|
||||
static sqlite3_stmt **stmts[] = { &query_stmt,
|
||||
&domain_stmt,
|
||||
&client_stmt,
|
||||
&forward_stmt,
|
||||
&addinfo_stmt };
|
||||
|
||||
// Private prototypes
|
||||
static void load_queries_from_disk(void);
|
||||
|
||||
// Return the maximum ID of the in-memory database
|
||||
unsigned long __attribute__((pure)) get_max_db_idx(void)
|
||||
@@ -155,6 +171,60 @@ bool init_memory_database(void)
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare persistent insertion/replace statements
|
||||
rc = sqlite3_prepare_v3(_memdb, "REPLACE INTO query_storage VALUES "\
|
||||
"(?1," \
|
||||
"?2," \
|
||||
"?3," \
|
||||
"?4," \
|
||||
"(SELECT id FROM domain_by_id WHERE domain = ?5)," \
|
||||
"(SELECT id FROM client_by_id WHERE ip = ?6 AND name = ?7)," \
|
||||
"(SELECT id FROM forward_by_id WHERE forward = ?8)," \
|
||||
"(SELECT id FROM addinfo_by_id WHERE type = ?9 AND content = ?10),"
|
||||
"?11," \
|
||||
"?12," \
|
||||
"?13," \
|
||||
"?14)", -1, SQLITE_PREPARE_PERSISTENT, &query_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
log_err("init_memory_database(query_storage) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_prepare_v3(_memdb, "INSERT OR IGNORE INTO domain_by_id (domain) VALUES (?)",
|
||||
-1, SQLITE_PREPARE_PERSISTENT, &domain_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
log_err("init_memory_database(domain_by_id) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_prepare_v3(_memdb, "INSERT OR IGNORE INTO client_by_id (ip,name) VALUES (?,?)",
|
||||
-1, SQLITE_PREPARE_PERSISTENT, &client_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
log_err("init_memory_database(client_by_id) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_prepare_v3(_memdb, "INSERT OR IGNORE INTO forward_by_id (forward) VALUES (?)",
|
||||
-1, SQLITE_PREPARE_PERSISTENT, &forward_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
log_err("init_memory_database(forward_by_id) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_prepare_v3(_memdb, "INSERT OR IGNORE INTO addinfo_by_id (type,content) VALUES (?,?)",
|
||||
-1, SQLITE_PREPARE_PERSISTENT, &addinfo_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
log_err("init_memory_database(addinfo_by_id) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
load_queries_from_disk();
|
||||
|
||||
// Everything went well
|
||||
return true;
|
||||
}
|
||||
@@ -166,6 +236,15 @@ void close_memory_database(void)
|
||||
if(_memdb == NULL)
|
||||
return;
|
||||
|
||||
// Finalize all statements
|
||||
for(unsigned int i = 0; i < ArraySize(stmts); i++)
|
||||
{
|
||||
if(*stmts[i] == NULL)
|
||||
continue;
|
||||
sqlite3_finalize(*stmts[i]);
|
||||
*stmts[i] = NULL;
|
||||
}
|
||||
|
||||
// Detach disk database
|
||||
if(!detach_database(_memdb, NULL, "disk"))
|
||||
log_err("close_memory_database(): Failed to detach disk database");
|
||||
@@ -402,14 +481,6 @@ int get_number_of_queries_in_DB(sqlite3 *db, const char *tablename)
|
||||
rc = sqlite3_step(stmt);
|
||||
if( rc == SQLITE_ROW )
|
||||
num = sqlite3_column_int(stmt, 0);
|
||||
else
|
||||
{
|
||||
log_err("get_number_of_queries_in_DB(%s): Step error: %s",
|
||||
tablename, sqlite3_errstr(rc));
|
||||
free(querystr);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
free(querystr);
|
||||
|
||||
@@ -504,10 +575,14 @@ bool import_queries_from_disk(void)
|
||||
|
||||
// Export in-memory queries to disk - either due to periodic dumping (final =
|
||||
// false) or because of a shutdown (final = true)
|
||||
// When final is false, we only export queries that are older than REPLY_TIMEOUT
|
||||
// seconds. This is to give queries some time to complete before they are
|
||||
// exported to disk. When final is true, we export all queries (nothing is going
|
||||
// to be added to the in-memory database anymore).
|
||||
bool export_queries_to_disk(bool final)
|
||||
{
|
||||
bool okay = false;
|
||||
const double time = double_time() - (final ? 0.0 : 30.0);
|
||||
const double time = double_time() - (final ? 0.0 : REPLY_TIMEOUT);
|
||||
const char *querystr = "INSERT INTO disk.query_storage SELECT * FROM query_storage WHERE id > ? AND timestamp < ?";
|
||||
|
||||
log_debug(DEBUG_DATABASE, "Storing queries on disk WHERE id > %lu (max is %lu) and timestamp < %f",
|
||||
@@ -562,7 +637,6 @@ bool export_queries_to_disk(bool final)
|
||||
// Finalize statement
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
|
||||
// Update last_disk_db_idx
|
||||
// Prepare SQLite3 statement
|
||||
log_debug(DEBUG_DATABASE, "Accessing in-memory database");
|
||||
@@ -603,6 +677,35 @@ bool export_queries_to_disk(bool final)
|
||||
log_debug(DEBUG_DATABASE, "Exported %i rows to disk.%s", sqlite3_changes(memdb), subtable_names[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* If there are any insertions, we:
|
||||
* 1. Insert (or replace) the last timestamp into the `disk.ftl` table.
|
||||
* 2. Update the total queries counter in the `disk.counters` table.
|
||||
* 3. Update the blocked queries counter in the `disk.counters` table.
|
||||
*
|
||||
* Note that new_total does not need to match the total number of
|
||||
* insertions here as storing queries to the database happens
|
||||
* time-delayed. In the end, the total number of queries will be
|
||||
* correct (after final synchronization during FTL shutdown).
|
||||
*/
|
||||
if(insertions > 0)
|
||||
{
|
||||
if((rc = dbquery(memdb, "INSERT OR REPLACE INTO disk.ftl (id, value) VALUES ( %i, %f );", DB_LASTTIMESTAMP, new_last_timestamp)) != SQLITE_OK)
|
||||
log_err("export_queries_to_disk(): Cannot update timestamp: %s", sqlite3_errstr(rc));
|
||||
|
||||
if((rc = dbquery(memdb, "UPDATE disk.counters SET value = value + %u WHERE id = %i;", new_total, DB_TOTALQUERIES)) != SQLITE_OK)
|
||||
log_err("export_queries_to_disk(): Cannot update total queries counter: %s", sqlite3_errstr(rc));
|
||||
else
|
||||
// Success
|
||||
new_total = 0;
|
||||
|
||||
if((rc = dbquery(memdb, "UPDATE disk.counters SET value = value + %u WHERE id = %i;", new_blocked, DB_BLOCKEDQUERIES)) != SQLITE_OK)
|
||||
log_err("export_queries_to_disk(): Cannot update blocked queries counter: %s", sqlite3_errstr(rc));
|
||||
else
|
||||
// Success
|
||||
new_blocked = 0;
|
||||
}
|
||||
|
||||
// End transaction
|
||||
if((rc = sqlite3_exec(memdb, "END TRANSACTION", NULL, NULL, NULL)) != SQLITE_OK)
|
||||
{
|
||||
@@ -616,17 +719,6 @@ bool export_queries_to_disk(bool final)
|
||||
// All temp queries were stored to disk, update the IDs
|
||||
last_disk_db_idx += insertions;
|
||||
|
||||
if(insertions > 0)
|
||||
{
|
||||
sqlite3 *db = dbopen(false, false);
|
||||
if(db != NULL)
|
||||
{
|
||||
db_set_FTL_property_double(db, DB_LASTTIMESTAMP, new_last_timestamp);
|
||||
db_update_counters(db, new_total, new_blocked);
|
||||
dbclose(&db);
|
||||
}
|
||||
}
|
||||
|
||||
log_debug(DEBUG_DATABASE, "Exported %u rows for disk.query_storage (took %.1f ms, last SQLite ID %lu)",
|
||||
insertions, timer_elapsed_msec(DATABASE_WRITE_TIMER), last_disk_db_idx);
|
||||
|
||||
@@ -670,8 +762,7 @@ bool delete_old_queries_from_db(const bool use_memdb, const double mintime)
|
||||
mintime, sqlite3_errstr(rc));
|
||||
|
||||
// Update number of queries in in-memory database
|
||||
sqlite3 *memdb = get_memdb();
|
||||
const int new_num = get_number_of_queries_in_DB(memdb, "query_storage");
|
||||
const int new_num = get_number_of_queries_in_DB(NULL, "query_storage");
|
||||
log_debug(DEBUG_GC, "delete_old_queries_from_db(): Deleted %i (%u) queries, new number of queries in memory: %i",
|
||||
sqlite3_changes(db), (mem_db_num - new_num), new_num);
|
||||
mem_db_num = new_num;
|
||||
@@ -1046,13 +1137,13 @@ void DB_read_queries(void)
|
||||
(buffer = (const char *)sqlite3_column_text(stmt, 6)) != NULL)
|
||||
{
|
||||
// Get IP address and port of upstream destination
|
||||
char serv_addr[INET6_ADDRSTRLEN] = { 0 };
|
||||
char serv_addr[INET6_ADDRSTRLEN + 16] = { 0 };
|
||||
unsigned int serv_port = 53;
|
||||
// We limit the number of bytes written into the serv_addr buffer
|
||||
// to prevent buffer overflows. If there is no port available in
|
||||
// the database, we skip extracting them and use the default port
|
||||
sscanf(buffer, "%"xstr(INET6_ADDRSTRLEN)"[^#]#%u", serv_addr, &serv_port);
|
||||
serv_addr[INET6_ADDRSTRLEN-1] = '\0';
|
||||
serv_addr[INET6_ADDRSTRLEN + 15] = '\0';
|
||||
upstreamID = findUpstreamID(serv_addr, (in_port_t)serv_port);
|
||||
}
|
||||
|
||||
@@ -1074,7 +1165,7 @@ void DB_read_queries(void)
|
||||
// Obtain IDs only after filtering which queries we want to keep
|
||||
const int timeidx = getOverTimeID(queryTimeStamp);
|
||||
const int domainID = findDomainID(domainname, true);
|
||||
const int clientID = findClientID(clientIP, true, false);
|
||||
const int clientID = findClientID(clientIP, true, false, queryTimeStamp);
|
||||
|
||||
// Set index for this query
|
||||
const int queryIndex = counters->queries;
|
||||
@@ -1182,9 +1273,10 @@ void DB_read_queries(void)
|
||||
case QUERY_GRAVITY: // Blocked by gravity
|
||||
case QUERY_REGEX: // Blocked by regex denylist
|
||||
case QUERY_DENYLIST: // Blocked by exact denylist
|
||||
case QUERY_EXTERNAL_BLOCKED_IP: // Blocked by external provider
|
||||
case QUERY_EXTERNAL_BLOCKED_NULL: // Blocked by external provider
|
||||
case QUERY_EXTERNAL_BLOCKED_NXRA: // Blocked by external provider
|
||||
case QUERY_EXTERNAL_BLOCKED_IP: // Blocked upstream
|
||||
case QUERY_EXTERNAL_BLOCKED_NULL: // Blocked upstream
|
||||
case QUERY_EXTERNAL_BLOCKED_NXRA: // Blocked upstream
|
||||
case QUERY_EXTERNAL_BLOCKED_EDE15: // Blocked upstream
|
||||
case QUERY_GRAVITY_CNAME: // Blocked by gravity (inside CNAME path)
|
||||
case QUERY_REGEX_CNAME: // Blocked by regex denylist (inside CNAME path)
|
||||
case QUERY_DENYLIST_CNAME: // Blocked by exact denylist (inside CNAME path)
|
||||
@@ -1232,6 +1324,7 @@ void DB_read_queries(void)
|
||||
log_info(" %d queries parsed...", counters->queries);
|
||||
}
|
||||
|
||||
// Release shared memory
|
||||
unlock_shm();
|
||||
|
||||
if( rc != SQLITE_DONE )
|
||||
@@ -1279,16 +1372,6 @@ bool queries_to_database(void)
|
||||
int rc;
|
||||
unsigned int added = 0, updated = 0;
|
||||
sqlite3_int64 idx = 0;
|
||||
sqlite3_stmt *query_stmt = NULL;
|
||||
sqlite3_stmt *domain_stmt = NULL;
|
||||
sqlite3_stmt *client_stmt = NULL;
|
||||
sqlite3_stmt *forward_stmt = NULL;
|
||||
sqlite3_stmt *addinfo_stmt = NULL;
|
||||
sqlite3_stmt **stmts[] = { &query_stmt,
|
||||
&domain_stmt,
|
||||
&client_stmt,
|
||||
&forward_stmt,
|
||||
&addinfo_stmt };
|
||||
|
||||
// Skip, we never store nor count queries recorded while have been in
|
||||
// maximum privacy mode in the database
|
||||
@@ -1302,83 +1385,39 @@ bool queries_to_database(void)
|
||||
log_debug(DEBUG_DATABASE, "Not storing query in database as there are none");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Start preparing query
|
||||
sqlite3 *memdb = get_memdb();
|
||||
rc = sqlite3_prepare_v3(memdb, "REPLACE INTO query_storage VALUES "\
|
||||
"(?1," \
|
||||
"?2," \
|
||||
"?3," \
|
||||
"?4," \
|
||||
"(SELECT id FROM domain_by_id WHERE domain = ?5)," \
|
||||
"(SELECT id FROM client_by_id WHERE ip = ?6 AND name = ?7)," \
|
||||
"(SELECT id FROM forward_by_id WHERE forward = ?8)," \
|
||||
"(SELECT id FROM addinfo_by_id WHERE type = ?9 AND content = ?10),"
|
||||
"?11," \
|
||||
"?12," \
|
||||
"?13," \
|
||||
"?14)", -1, SQLITE_PREPARE_PERSISTENT, &query_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
if(!store_in_database)
|
||||
{
|
||||
log_err("queries_to_database(query_storage) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
log_debug(DEBUG_DATABASE, "Not storing query in database as this is disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
rc = sqlite3_prepare_v3(memdb, "INSERT OR IGNORE INTO domain_by_id (domain) VALUES (?)",
|
||||
-1, SQLITE_PREPARE_PERSISTENT, &domain_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
log_err("queries_to_database(domain_by_id) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_prepare_v3(memdb, "INSERT OR IGNORE INTO client_by_id (ip,name) VALUES (?,?)",
|
||||
-1, SQLITE_PREPARE_PERSISTENT, &client_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
log_err("queries_to_database(client_by_id) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_prepare_v3(memdb, "INSERT OR IGNORE INTO forward_by_id (forward) VALUES (?)",
|
||||
-1, SQLITE_PREPARE_PERSISTENT, &forward_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
log_err("queries_to_database(forward_by_id) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_prepare_v3(memdb, "INSERT OR IGNORE INTO addinfo_by_id (type,content) VALUES (?,?)",
|
||||
-1, SQLITE_PREPARE_PERSISTENT, &addinfo_stmt, NULL);
|
||||
if( rc != SQLITE_OK )
|
||||
{
|
||||
log_err("queries_to_database(addinfo_by_id) - SQL error step: %s", sqlite3_errstr(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop over recent queries and store new or changed ones in the in-memory database
|
||||
const unsigned int min_iter = counters->queries - 1;
|
||||
unsigned int max_iter = min_iter > DB_QUERY_MAX_ITER ? min_iter - DB_QUERY_MAX_ITER : 0;
|
||||
for(unsigned int queryID = min_iter; queryID > max_iter; queryID--)
|
||||
// Loop over recent queries and store new or changed ones in the
|
||||
// in-memory database
|
||||
// The upper bound is the last query in the array, the lower bound is
|
||||
// indirectly given by the first query older than 30 seconds - we do not
|
||||
// expect replies to still arrive after 30 seconds - they are anyway
|
||||
// useless as the client will have already timed out tis particular
|
||||
// query and retried or failed
|
||||
const double limit_timestamp = double_time() - REPLY_TIMEOUT;
|
||||
for(unsigned int queryID = counters->queries - 1; queryID > 0; queryID--)
|
||||
{
|
||||
// Get query pointer
|
||||
queriesData *query = getQuery(queryID, true);
|
||||
if(query == NULL)
|
||||
{
|
||||
// Encountered memory error, skip query
|
||||
log_err("Memory error in queries_to_database()");
|
||||
log_err("Memory error in queries_to_database() when trying to access query %u", queryID);
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip too old queries (see note above the loop)
|
||||
if(query->timestamp < limit_timestamp)
|
||||
break;
|
||||
|
||||
// Skip queries which have not changed since the last iteration
|
||||
if(!query->flags.database.changed)
|
||||
continue;
|
||||
|
||||
// Update max_iter in case we have changes queries very close to
|
||||
// the end of the iteration interval
|
||||
if(min_iter - max_iter < 10)
|
||||
max_iter = max_iter > DB_QUERY_MAX_ITER ? max_iter - DB_QUERY_MAX_ITER : 0;
|
||||
|
||||
// Explicitly set ID to match what is in the on-disk database
|
||||
if(query->db > -1)
|
||||
{
|
||||
@@ -1599,15 +1638,8 @@ bool queries_to_database(void)
|
||||
query->flags.database.changed = false;
|
||||
}
|
||||
|
||||
// Finalize all statements
|
||||
for(unsigned int i = 0; i < ArraySize(stmts); i++)
|
||||
{
|
||||
sqlite3_finalize(*stmts[i]);
|
||||
*stmts[i] = NULL;
|
||||
}
|
||||
|
||||
// Update number of queries in in-memory database
|
||||
mem_db_num = get_number_of_queries_in_DB(memdb, "query_storage");
|
||||
mem_db_num = get_number_of_queries_in_DB(NULL, "query_storage");
|
||||
|
||||
if(config.debug.database.v.b && updated + added > 0)
|
||||
{
|
||||
@@ -1617,3 +1649,22 @@ bool queries_to_database(void)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void load_queries_from_disk(void)
|
||||
{
|
||||
// Compensate for possible jumps in time
|
||||
runGC(time(NULL), NULL, false);
|
||||
|
||||
// Skip if we are not supposed to load queries from disk
|
||||
if(!config.database.DBimport.v.b)
|
||||
return;
|
||||
|
||||
// Try to import queries from long-term database if available
|
||||
import_queries_from_disk();
|
||||
DB_read_queries();
|
||||
|
||||
// Log some information about the imported queries (if any)
|
||||
log_counter_info();
|
||||
|
||||
store_in_database = true;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"client TEXT NOT NULL, " \
|
||||
"forward TEXT );"
|
||||
|
||||
#define MEMDB_VERSION 17
|
||||
#define MEMDB_VERSION 19
|
||||
#define CREATE_QUERY_STORAGE_TABLE "CREATE TABLE query_storage ( id INTEGER PRIMARY KEY AUTOINCREMENT, " \
|
||||
"timestamp INTEGER NOT NULL, " \
|
||||
"type INTEGER NOT NULL, " \
|
||||
|
||||
@@ -65,6 +65,48 @@ bool add_session_app_column(sqlite3 *db)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool add_session_cli_column(sqlite3 *db)
|
||||
{
|
||||
// Start transaction of database update
|
||||
SQL_bool(db, "BEGIN TRANSACTION;");
|
||||
|
||||
// Create session table
|
||||
SQL_bool(db, "ALTER TABLE session ADD COLUMN cli BOOL;");
|
||||
|
||||
// Update database version to 18
|
||||
if(!db_set_FTL_property(db, DB_VERSION, 18))
|
||||
{
|
||||
log_err("add_session_cli_column(): Failed to update database version!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finish transaction
|
||||
SQL_bool(db, "COMMIT");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool add_session_x_forwarded_for_column(sqlite3 *db)
|
||||
{
|
||||
// Start transaction of database update
|
||||
SQL_bool(db, "BEGIN TRANSACTION;");
|
||||
|
||||
// Create session table
|
||||
SQL_bool(db, "ALTER TABLE session ADD COLUMN x_forwarded_for TEXT;");
|
||||
|
||||
// Update database version to 18
|
||||
if(!db_set_FTL_property(db, DB_VERSION, 19))
|
||||
{
|
||||
log_err("add_session_x_forwarded_for_column(): Failed to update database version!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finish transaction
|
||||
SQL_bool(db, "COMMIT");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Store all session in database
|
||||
bool backup_db_sessions(struct session *sessions, const uint16_t max_sessions)
|
||||
{
|
||||
@@ -83,7 +125,7 @@ bool backup_db_sessions(struct session *sessions, const uint16_t max_sessions)
|
||||
|
||||
// Insert session into database
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
if(sqlite3_prepare_v2(db, "INSERT INTO session (login_at, valid_until, remote_addr, user_agent, sid, csrf, tls_login, tls_mixed, app) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);", -1, &stmt, 0) != SQLITE_OK)
|
||||
if(sqlite3_prepare_v2(db, "INSERT INTO session (login_at, valid_until, remote_addr, user_agent, sid, csrf, tls_login, tls_mixed, app, cli, x_forwarded_for) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", -1, &stmt, 0) != SQLITE_OK)
|
||||
{
|
||||
log_err("SQL error in backup_db_sessions(): %s (%d)",
|
||||
sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
@@ -105,63 +147,77 @@ bool backup_db_sessions(struct session *sessions, const uint16_t max_sessions)
|
||||
if(sqlite3_bind_int64(stmt, 1, sess->login_at) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind login_at = %ld in backup_db_sessions(): %s (%d)",
|
||||
(long int)sess->login_at, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
(long int)sess->login_at, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 2: valid_until
|
||||
if(sqlite3_bind_int64(stmt, 2, sess->valid_until) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind valid_until = %ld in backup_db_sessions(): %s (%d)",
|
||||
(long int)sess->valid_until, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
(long int)sess->valid_until, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 3: remote_addr
|
||||
if(sqlite3_bind_text(stmt, 3, sess->remote_addr, -1, SQLITE_STATIC) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind remote_addr = %s in backup_db_sessions(): %s (%d)",
|
||||
sess->remote_addr, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sess->remote_addr, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 4: user_agent
|
||||
if(sqlite3_bind_text(stmt, 4, sess->user_agent, -1, SQLITE_STATIC) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind user_agent = %s in backup_db_sessions(): %s (%d)",
|
||||
sess->user_agent, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sess->user_agent, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 5: sid
|
||||
if(sqlite3_bind_text(stmt, 5, sess->sid, -1, SQLITE_STATIC) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind sid = %s in backup_db_sessions(): %s (%d)",
|
||||
sess->sid, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sess->sid, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 6: csrf
|
||||
if(sqlite3_bind_text(stmt, 6, sess->csrf, -1, SQLITE_STATIC) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind csrf = %s in backup_db_sessions(): %s (%d)",
|
||||
sess->csrf, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sess->csrf, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 7: tls_login
|
||||
if(sqlite3_bind_int(stmt, 7, sess->tls.login ? 1 : 0) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind tls_login = %d in backup_db_sessions(): %s (%d)",
|
||||
sess->tls.login ? 1 : 0, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sess->tls.login ? 1 : 0, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 8: tls_mixed
|
||||
if(sqlite3_bind_int(stmt, 8, sess->tls.mixed ? 1: 0) != SQLITE_OK)
|
||||
if(sqlite3_bind_int(stmt, 8, sess->tls.mixed ? 1 : 0) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind tls_mixed = %d in backup_db_sessions(): %s (%d)",
|
||||
sess->tls.mixed ? 1 : 0, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sess->tls.mixed ? 1 : 0, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 9: app
|
||||
if(sqlite3_bind_int(stmt, 8, sess->app ? 1: 0) != SQLITE_OK)
|
||||
if(sqlite3_bind_int(stmt, 9, sess->app ? 1 : 0) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind app = %d in backup_db_sessions(): %s (%d)",
|
||||
sess->app ? 1 : 0, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sess->app ? 1 : 0, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 10: cli
|
||||
if(sqlite3_bind_int(stmt, 10, sess->cli ? 1 : 0) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind cli = %d in backup_db_sessions(): %s (%d)",
|
||||
sess->cli ? 1 : 0, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
// 11: x_forwarded_for
|
||||
if(sqlite3_bind_text(stmt, 11, sess->x_forwarded_for, -1, SQLITE_STATIC) != SQLITE_OK)
|
||||
{
|
||||
log_err("Cannot bind x_forwarded_for = %s in backup_db_sessions(): %s (%d)",
|
||||
sess->x_forwarded_for, sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -169,7 +225,7 @@ bool backup_db_sessions(struct session *sessions, const uint16_t max_sessions)
|
||||
if(sqlite3_step(stmt) != SQLITE_DONE)
|
||||
{
|
||||
log_err("SQL error in backup_db_sessions(): %s (%d)",
|
||||
sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -177,7 +233,7 @@ bool backup_db_sessions(struct session *sessions, const uint16_t max_sessions)
|
||||
if(sqlite3_clear_bindings(stmt) != SQLITE_OK)
|
||||
{
|
||||
log_err("SQL error in backup_db_sessions(): %s (%d)",
|
||||
sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -185,7 +241,7 @@ bool backup_db_sessions(struct session *sessions, const uint16_t max_sessions)
|
||||
if(sqlite3_reset(stmt) != SQLITE_OK)
|
||||
{
|
||||
log_err("SQL error in backup_db_sessions(): %s (%d)",
|
||||
sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
sqlite3_errmsg(db), sqlite3_errcode(db));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -225,7 +281,7 @@ bool restore_db_sessions(struct session *sessions, const uint16_t max_sessions)
|
||||
|
||||
// Get all sessions from database
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
if(sqlite3_prepare_v2(memdb, "SELECT login_at, valid_until, remote_addr, user_agent, sid, csrf, tls_login, tls_mixed, app FROM disk.session;", -1, &stmt, 0) != SQLITE_OK)
|
||||
if(sqlite3_prepare_v2(memdb, "SELECT login_at, valid_until, remote_addr, user_agent, sid, csrf, tls_login, tls_mixed, app, cli, x_forwarded_for FROM disk.session;", -1, &stmt, 0) != SQLITE_OK)
|
||||
{
|
||||
log_err("SQL error in restore_db_sessions(): %s (%d)",
|
||||
sqlite3_errmsg(memdb), sqlite3_errcode(memdb));
|
||||
@@ -284,9 +340,20 @@ bool restore_db_sessions(struct session *sessions, const uint16_t max_sessions)
|
||||
// 8: tls_mixed
|
||||
sess->tls.mixed = sqlite3_column_int(stmt, 7) == 1 ? true : false;
|
||||
|
||||
// 8: app
|
||||
// 9: app
|
||||
sess->app = sqlite3_column_int(stmt, 8) == 1 ? true : false;
|
||||
|
||||
// 10: cli
|
||||
sess->cli = sqlite3_column_int(stmt, 9) == 1 ? true : false;
|
||||
|
||||
// 11: x_forwarded_for
|
||||
const char *x_forwarded_for = (const char *)sqlite3_column_text(stmt, 10);
|
||||
if(x_forwarded_for != NULL)
|
||||
{
|
||||
strncpy(sess->x_forwarded_for, x_forwarded_for, sizeof(sess->x_forwarded_for)-1);
|
||||
sess->x_forwarded_for[sizeof(sess->x_forwarded_for)-1] = '\0';
|
||||
}
|
||||
|
||||
// Mark session as used
|
||||
sess->used = true;
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
bool create_session_table(sqlite3 *db);
|
||||
bool add_session_app_column(sqlite3 *db);
|
||||
bool add_session_cli_column(sqlite3 *db);
|
||||
bool add_session_x_forwarded_for_column(sqlite3 *db);
|
||||
bool backup_db_sessions(struct session *sessions, const uint16_t max_sessions);
|
||||
bool restore_db_sessions(struct session *sessions, const uint16_t max_sessions);
|
||||
|
||||
|
||||
+1657
-194
File diff suppressed because it is too large
Load Diff
@@ -22,9 +22,9 @@
|
||||
// free()
|
||||
#include <stdlib.h>
|
||||
// logging routines
|
||||
#include "../log.h"
|
||||
#include "log.h"
|
||||
// struct config
|
||||
#include "../config/config.h"
|
||||
#include "config/config.h"
|
||||
|
||||
// isMAC()
|
||||
#include "network-table.h"
|
||||
@@ -215,4 +215,4 @@ int sqlite3_pihole_extensions_init(sqlite3 *db, const char **pzErrMsg, const str
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user