Merge branch 'development' into update/dnsmasq

This commit is contained in:
DL6ER
2024-10-04 19:21:07 +02:00
254 changed files with 19565 additions and 7358 deletions
+5 -3
View File
@@ -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"
]
}
+6
View File
@@ -7,3 +7,9 @@ requestor
requestors
punycode
bitap
mmapped
dnsmasq
iif
prefered
padd
rabit
+2
View File
@@ -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
View File
@@ -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 . && \
+19 -3
View File
@@ -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
-20
View File
@@ -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:
- "*"
+15 -3
View File
@@ -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
+147
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 }}"
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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
View File
@@ -14,9 +14,8 @@ version~
# IDE files
.idea/
*.sw*
/.vscode
.vscode/
/.vscode/
.vscode/*
!.vscode/c_cpp_properties.json
/build/
# __pycache__ files (API tests)
+18
View File
@@ -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
View File
@@ -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)
+10 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)")
+13 -9
View File
@@ -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
View File
@@ -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,
+1
View File
@@ -20,6 +20,7 @@ set(sources
dhcp.c
dns.c
network.c
padd.c
history.c
info.c
list.c
+1 -4
View File
@@ -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
View File
@@ -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;
}
+20
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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");
}
+1
View File
@@ -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
+23 -2
View File
@@ -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)
+9 -3
View File
@@ -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:
+76 -9
View File
@@ -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
+15
View File
@@ -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:
+8
View File
@@ -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:
+424 -74
View File
@@ -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
+248
View File
@@ -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"
-1
View File
@@ -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':
+12
View File
@@ -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:
+47 -1
View File
@@ -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
+5
View File
@@ -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
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
&regex_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(&regex_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(&regex_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,
&regex_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(&regex_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(&regex_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)
&regex_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);
}
+5 -7
View File
@@ -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
View File
@@ -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
View File
@@ -11,7 +11,7 @@
// NULL
#include <stddef.h>
// strcasecmp()
#include <string.h>
#include <strings.h>
#include "theme.h"
+118 -16
View File
@@ -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);
+2
View File
@@ -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[]);
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+42 -12
View File
@@ -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
View File
@@ -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;
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+5 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+23 -2
View File
@@ -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
View File
@@ -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;
-12
View File
@@ -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
View File
@@ -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;
+5 -1
View File
@@ -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
+6 -5
View File
@@ -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)
+1 -1
View File
@@ -11,7 +11,7 @@
#define ALIASCLIENTS_TABLE_H
// type clientsData
#include "../datastructure.h"
#include "datastructure.h"
bool create_aliasclients_table(sqlite3 *db);
+146 -39
View File
@@ -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);
+3 -3
View File
@@ -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;
-4
View File
@@ -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
View File
@@ -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;
}
+4 -5
View File
@@ -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
View File
@@ -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");
}
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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, " \
+84 -17
View File
@@ -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;
+2
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -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