Compare commits

...

1812 Commits

Author SHA1 Message Date
Olliver Schinagl 5d32c4ce96
Merge e7eaff5377 into e10bb5c605 2024-05-09 07:26:35 +00:00
Olliver Schinagl e7eaff5377
Avoid use-after-free warning
More recent versions of GCC try to check for use after free. Realloc
will take the input pointer, `ptr_in` and with free it after a successful
allocation. However, it will not change the contents of the pointer, and
thus GCC in some cases thinks it can/could be used. By explicitly
setting `ptr_in` to NULL after an successful allocation, we can keep GCC
happy and our codebase sane.

Signed-off-by: Olliver Schinagl <oliver@schinagl.nl>
2024-05-09 09:26:27 +02:00
Dominik e10bb5c605
Merge pull request #1944 from pi-hole/fix/clients_docs
Improve API /clients documentation
2024-05-05 07:09:42 +02:00
Dominik 240a2fe7a1
Merge pull request #1945 from pi-hole/tweak/tcp_conn_err
Improve error logging when TCP connections are prematurely closed by remote server
2024-05-05 07:08:07 +02:00
DL6ER 1611da221c
Improve error logging when TCP connections are prematurely closed by remote server
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-05-04 10:09:03 +02:00
DL6ER 01697669ac
API /clients: Add note that {client} needs to be URI-encoded (if specified) and add documentation of read-only optional {name0} field
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-05-03 20:05:00 +02:00
Dominik af521c9219
Merge pull request #1939 from pi-hole/new/session_message
Provide human-readable message for session status
2024-05-02 19:38:10 +02:00
yubiuser db32e4da4c
Merge pull request #1940 from pi-hole/dependabot-github_actions-development-v6-github_action-dependencies-b93eff89fb
Bump actions/checkout from 4.1.2 to 4.1.4 in the github_action-dependencies group across 1 directory
2024-04-27 13:19:57 +02:00
dependabot[bot] 771db50d8f
Bump actions/checkout
Bumps the github_action-dependencies group with 1 update in the / directory: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4.1.2 to 4.1.4
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.2...v4.1.4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-27 10:40:49 +00:00
Dominik 44377ad893
Address review comments
Co-authored-by: RD WebDesign <github@rdwebdesign.com.br>
Signed-off-by: Dominik <DL6ER@users.noreply.github.com>
2024-04-27 06:38:39 +02:00
DL6ER 49a5a0c60c
Provide human-readable message about the session status when authenticating
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-25 21:14:05 +02:00
Dominik 1b71a440f5
Merge pull request #1936 from pi-hole/fix/connection_error_crash
Fix a crash resulting from a bad interaction between PRs #1928 and #1930
2024-04-21 10:28:14 +02:00
DL6ER e29bcb0695
Fix a crash resulting from a bad interaction between PRs #1928 and #1930
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-20 09:39:39 +02:00
Dominik 4734e01b1e
Merge pull request #1930 from pi-hole/tweak/ede_neterr
Add extra logging around network issues (EDE: network error)
2024-04-18 21:28:52 +02:00
Dominik a05cce00ed
Merge pull request #1934 from pi-hole/update/sqlite_3.45.3
Update embedded SQLite3 to 3.45.3
2024-04-16 09:12:13 +02:00
DL6ER c7ce555382
Update embedded SQLite3 to 3.45.3
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-16 08:22:53 +02:00
Dominik 0cffd8ccdc
Merge pull request #1928 from pi-hole/fix/check_disk
Fix disk space checking on macOS hosts
2024-04-07 21:00:59 +02:00
Dominik b12f93c0ff
Merge pull request #1931 from pi-hole/tweak/selective_teleporter
Be more verbose in which tables are imported during teleporter importing
2024-04-07 20:36:29 +02:00
DL6ER f2a7662e95
Be more verbose in which tables are imported during teleporter importing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-02 21:59:45 +02:00
DL6ER 563b02ccc7
Add new CONNECTION_ERROR message to the Pi-hole diagnosis system
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-02 13:43:43 +02:00
DL6ER ed41584c92
Add extra logging around network issues (EDE: network error)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-02 12:05:28 +02:00
DL6ER d88e52d8c8
Improve diagnosis message adding subroutine to not require manually typed in number of arguments and do strict testing against the number of given arguments (instead of crashing if fewer are given and ignoring if more are given)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-01 10:08:45 +02:00
DL6ER 5b2dda886c
Store message in database as well
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-01 09:43:34 +02:00
DL6ER 90dda14276
Use fragment size when computing filesystem sizes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-04-01 09:11:06 +02:00
Dominik 485faaceb5
Merge pull request #1874 from pi-hole/tweak/select_teleporter
Implement selective Teleporter restoring
2024-03-31 20:19:38 +02:00
DL6ER 16c541e233
Show warning when in debug mode and stat() failed to get file system details
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-31 13:44:58 +02:00
DL6ER df1f70dd09
Add further debug output concerning disk usage when debug.gc=true
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-31 12:21:21 +02:00
DL6ER 58ca959cf2
Add proper memory allocation checking in the message formatting subroutines
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-30 20:21:26 +01:00
DL6ER 27ff979ef8
Reintroduce a workaround for docker on macOS accidentally removed in bd266d6589
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-30 20:14:15 +01:00
DL6ER 5075144bbe
Fix importing logic for v5 teleporter files
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-30 20:00:52 +01:00
DL6ER 8875a0e29a
Add further debugging output if files are NOT imported
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-30 18:08:16 +01:00
DL6ER ed36a9a7d5
Simplify v5 gravity table import condition
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-30 17:14:28 +01:00
Dominik fff34fb2e3
Merge pull request #1927 from pi-hole/dependabot-github_actions-development-v6-github_action-dependencies-b9ee4988fc
Bump the github_action-dependencies group with 1 update
2024-03-30 15:32:22 +01:00
dependabot[bot] 40864d5c38
Bump the github_action-dependencies group with 1 update
Bumps the github_action-dependencies group with 1 update: [eps1lon/actions-label-merge-conflict](https://github.com/eps1lon/actions-label-merge-conflict).


Updates `eps1lon/actions-label-merge-conflict` from 2.1.0 to 3.0.0
- [Release notes](https://github.com/eps1lon/actions-label-merge-conflict/releases)
- [Changelog](https://github.com/eps1lon/actions-label-merge-conflict/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eps1lon/actions-label-merge-conflict/compare/v2.1.0...v3.0.0)

---
updated-dependencies:
- dependency-name: eps1lon/actions-label-merge-conflict
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-30 10:42:21 +00:00
DL6ER dab6ec75bf
Merge branch 'development-v6' into tweak/select_teleporter
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-30 08:23:11 +01:00
Dominik f7cd4fb406
Merge pull request #1919 from pi-hole/tweak/cJSON_threadsafe
Ensure thread-safety for cJSON engine
2024-03-30 06:49:52 +01:00
Dominik ea9005693e
Merge pull request #1922 from pi-hole/tweak/tld_checks
Allow one-character TLDs in hostname validation
2024-03-30 06:40:03 +01:00
Dominik 70a02b1bdf
Merge pull request #1923 from pi-hole/fix/antigravity_mark
Fix antigravity not preventing further checks on queries
2024-03-29 21:09:39 +01:00
DL6ER daf5eb8934
Remove two characters TLDs constraint in hostname validation. Empty labels are still forbidden.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-29 08:37:19 +01:00
Dominik b20df82ae2
Merge pull request #1924 from pi-hole/tweak/ci_risvc64
Slightly simplify the CI tests
2024-03-29 06:56:32 +01:00
DL6ER 549bc164ea
Slightly simplify the CI tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-28 18:55:46 +01:00
Dominik 9dcdfa865c
Merge pull request #1921 from pi-hole/tweak/allowed_debug
Fix a left-over "whitelisted" instead of "allowed" message in debug mode
2024-03-28 18:43:39 +01:00
DL6ER f94fe11755
Mark query as allowed when atigravity matches to prevent further checks such as CNAME inspection. This ensures antigravity matches have similar effects than explicitly allowed domains.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-28 15:26:20 +01:00
DL6ER bc46302717
Merge branch 'development-v6' into HEAD
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-28 09:50:50 +01:00
DL6ER 557d6a44af
Fix a left-over "whitelisted" instead of "allowed" message in debug mode
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-28 09:46:17 +01:00
Dominik 91d8738a7f
Merge pull request #1916 from pi-hole/update/sqlite_3.45.2
Update embedded SQLite3 to 3.45.2
2024-03-28 07:11:15 +01:00
Dominik a23f3347dc
Merge pull request #1917 from pi-hole/tweak/hostname_err_pos
Report the hex-code of the found invalid character
2024-03-26 21:00:57 +01:00
Dominik 63571bb15f
Merge pull request #1918 from pi-hole/new/log-dhcp
Add new dhcp.logging option
2024-03-26 20:51:05 +01:00
Dominik 8e9b1dc731
Merge pull request #1914 from pi-hole/dependabot-github_actions-development-v6-github_action-dependencies-2d71499196
Bump the github_action-dependencies group with 1 update
2024-03-24 20:32:39 +01:00
DL6ER bfd2136def
Further reduce code-duplication by using the new escape_json() function for TOML as well. It is doing the exact same thing. Remove TOML_UTF8 as this has always been the standard encoding anyway.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-24 11:56:48 +01:00
DL6ER c82561cf4e
Define a general function escape_json() similar to the already existing escape_html()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-24 11:35:43 +01:00
DL6ER 20cc8c3983
Only check non-empty hostnames
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-24 09:38:06 +01:00
DL6ER 079c66c909
Use cJSON to escape hostnames possibly containing control characters
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-24 09:36:26 +01:00
DL6ER 05867e20f2
Ensure cJSON is used in a thread-safe manner and add CI tests ensuring this. Also ensure every JSON parsing is doing error checking and reduce some code duplication. No functional change.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-24 08:55:12 +01:00
DL6ER 9c7384e3a4
Add new dhcp.logging option
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-24 08:35:33 +01:00
DL6ER 2a2c4d188e
Report the hex-code of the found invalid character
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-23 19:39:36 +01:00
dependabot[bot] 16fb8b6366
Bump the github_action-dependencies group with 1 update
Bumps the github_action-dependencies group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4.1.1 to 4.1.2
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.1.2)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-23 10:42:38 +00:00
Dominik 0d2fcd09ef
Merge pull request #1872 from pi-hole/tweak/DBexport
Remove config option database.DBexport
2024-03-17 20:34:36 +01:00
DL6ER 75b792f589
Update embedded SQLite3 to 3.45.2
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-17 06:21:24 +01:00
Dominik c835795a7b
Merge pull request #1884 from pi-hole/fix/overTimeGraphs
Tweak API history endpoint and fix cached counting
2024-03-16 19:57:08 +01:00
Dominik 779e6aebe1
Merge pull request #1911 from pi-hole/fix/enviroment_after_restart
Ensure env vars survive restart
2024-03-16 19:44:31 +01:00
DL6ER ccbe642dae
Add validator for webserver.api.client_history_global_max
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-16 10:12:11 +01:00
DL6ER 66653ae0d5
Merge branch 'development-v6' into fix/overTimeGraphs 2024-03-16 09:05:00 +01:00
DL6ER e3f9aa712b
Work on a copy of the env vars to avoid modifying the original causing issues down the line when FTL restarts internally and re-reads them
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-16 08:52:58 +01:00
DL6ER 33d5472825
Remove obsoleted config option from test/pihole.toml
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-16 08:34:23 +01:00
DL6ER 74eba5f605
Remove config option database.DBexport. Its implementation was broken by design as its value was always overwritten by the condition (database.maxDBdays > 0). Replace it with exactly this condition and document the behavior in the config file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-16 08:34:19 +01:00
Dominik 4f6225cf3f
Merge pull request #1910 from pi-hole/tweak/config-validation
Allow upper case letters in domains/hosts
2024-03-16 06:46:47 +01:00
Dominik a2a77ebecb
Merge pull request #1909 from pi-hole/v6/suppress_hwmon_warning
Demote hwmon warning log to debug
2024-03-16 06:35:18 +01:00
Adam Warner 8d4bf22008
Uncomment previously commented line which prevented the domain validator from accepting hostnames with capital letters in
Signed-off-by: Adam Warner <me@adamwarner.co.uk>
2024-03-16 00:16:59 +00:00
Adam Warner 4cd500dfac
demote warning log message about not being able to open /sys/class/hwmon to the API debug log to prevent it from filling up the log when not available
Signed-off-by: Adam Warner <me@adamwarner.co.uk>
2024-03-15 23:50:17 +00:00
Dominik a0d655432a
Merge pull request #1908 from pi-hole/new/query_log_search
Add searching for domains and clients in the Query Log
2024-03-15 17:17:34 +01:00
DL6ER 2e23e76e0a
Add searching for domains and clients in the Query Log. Wildcards (*) are supported everywhere in the search string.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-14 19:45:45 +01:00
Dominik 2f38061f02
Merge pull request #1906 from pi-hole/tweak/goodbye_adam_mode
Goodbye Adam mode
2024-03-12 21:34:48 +01:00
Dominik ad06e920a3
Merge pull request #1907 from pi-hole/fix/validate_useWAL
Add missing validator for database.useWAL
2024-03-12 21:30:39 +01:00
DL6ER ac21427e55
Add missing validator for database.useWAL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-12 20:35:35 +01:00
Dominik 1bb74b3025
Merge pull request #1773 from pi-hole/new/validator
Add additional config validation
2024-03-12 20:29:06 +01:00
DL6ER 0eeeb8109c
Goodbye Adam mode
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-12 17:21:20 +01:00
DL6ER aa7deb968e
Add CI tests for deep config validation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-11 21:01:11 +01:00
Dominik abcbcc58f5
Merge pull request #1901 from pi-hole/fix/revServerImporting
Fix revServer importing from former setupVars.conf
2024-03-11 19:14:08 +01:00
Dominik c6a0b81921
Merge pull request #1902 from pi-hole/new/self-hosted
Accelerate CI builds substantially
2024-03-11 19:13:58 +01:00
DL6ER 9cfd936621
Merge branch 'development-v6' into new/self-hosted
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-09 22:08:15 +01:00
DL6ER d41fbeef26
Include RISCV64 in GHA job
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-09 20:25:15 +01:00
yubiuser 62566e97f0
Merge pull request #1903 from pi-hole/dependabot-github_actions-development-v6-github_action-dependencies-7baaa1528e
Bump the github_action-dependencies group with 2 updates
2024-03-09 12:06:35 +01:00
dependabot[bot] 082543ea97
Bump the github_action-dependencies group with 2 updates
Bumps the github_action-dependencies group with 2 updates: [Wandalen/wretry.action](https://github.com/wandalen/wretry.action) and [softprops/action-gh-release](https://github.com/softprops/action-gh-release).


Updates `Wandalen/wretry.action` from 1.4.5 to 1.4.8
- [Release notes](https://github.com/wandalen/wretry.action/releases)
- [Commits](https://github.com/wandalen/wretry.action/compare/v1.4.5...v1.4.8)

Updates `softprops/action-gh-release` from 1 to 2
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: Wandalen/wretry.action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-09 10:35:47 +00:00
DL6ER af7d521bfc
Download documentation in amd64 build and further reduce code duplication
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-09 07:56:48 +01:00
DL6ER 4019bd7745
Try direct deployment
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-09 07:32:55 +01:00
DL6ER 753bdd3574
Do not try to upload documentation in deferred riscv64 upload step
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-09 07:28:04 +01:00
DL6ER 9318909368
Reduce code duplication by outsourcing into composite actions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-08 19:50:48 +01:00
DL6ER 4f3bd87807
Only add active entries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-08 08:18:05 +01:00
DL6ER ef84283a69
Use correct variable when migrating possible revServer settings from setupVars.conf to the new multiple-servers-aware JSON string formulation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-07 21:35:51 +01:00
DL6ER d85492d113
Independent building of RISCV64
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-07 21:19:58 +01:00
DL6ER 59c1e2b6bf
Build x86 binaries on GHA, only build ARM/RISCV on our self-hosted ARM64 runners
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-07 20:41:20 +01:00
DL6ER 84d6e2db42
Try building on self-hosted GHA runner
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-03-07 20:12:12 +01:00
Dominik 595fc6c840
Merge pull request #1899 from bungh0l10/fix-mdns-typo
Update config.c fix typo 5335 instead of 5353
2024-03-07 19:58:42 +01:00
bungh0l10 b53d969b83
Update config.c fix typo 5335 instead of 5353
Signed-off-by: bungh0l10 <77304055+bungh0l10@users.noreply.github.com>
2024-03-05 21:59:46 +01:00
Dominik 93d01d9c5d
Merge pull request #1891 from pi-hole/fix/migration_message
Improve config migration logging
2024-03-04 19:41:57 +01:00
Dominik 4b2a4be94c
Merge pull request #1868 from pi-hole/new/useWAL
Make WAL mode for pihole-FTL.db optional
2024-03-04 19:40:29 +01:00
Dominik 7ef8e6c916
Merge pull request #1882 from pi-hole/merge-v5-6
Sync v5 -> v6
2024-03-02 18:30:31 +01:00
Dominik d4792bb70c
Merge pull request #1886 from pi-hole/tweak/allow_adlist_dups
Allow adlist duplicates
2024-03-02 18:28:41 +01:00
yubiuser 661d9e3be0
Merge pull request #1897 from pi-hole/dependabot-github_actions-development-v6-github_action-dependencies-b4913446bb
Bump the github_action-dependencies group with 3 updates
2024-03-02 12:49:10 +01:00
dependabot[bot] a1d6f85bf1
Bump the github_action-dependencies group with 3 updates
Bumps the github_action-dependencies group with 3 updates: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [Wandalen/wretry.action](https://github.com/wandalen/wretry.action) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `docker/setup-buildx-action` from 3.0.0 to 3.1.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.0.0...v3.1.0)

Updates `Wandalen/wretry.action` from 1.4.4 to 1.4.5
- [Release notes](https://github.com/wandalen/wretry.action/releases)
- [Commits](https://github.com/wandalen/wretry.action/compare/v1.4.4...v1.4.5)

Updates `actions/download-artifact` from 4.1.2 to 4.1.4
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.1.2...v4.1.4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github_action-dependencies
- dependency-name: Wandalen/wretry.action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-02 10:26:16 +00:00
Dominik bffd2bd4ba
Merge pull request #1892 from pi-hole/fix/dnsmasq_resource_warning
Fix spurious "resource limit exceeded" messages
2024-02-20 05:27:37 +01:00
DL6ER cf06e53872
Update embedded dnsmasq version to 2.90+1
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-19 13:59:52 +01:00
Simon Kelley 157b589391
Fix spurious "resource limit exceeded" messages.
Replies from upstream with a REFUSED rcode can result in
log messages stating that a resource limit has been exceeded,
which is not the case.

Thanks to Dominik Derigs and the Pi-hole project for
spotting this.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-19 13:59:52 +01:00
DL6ER 485080a51d
Use correct index for domains in the Top Lists
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-18 21:06:43 +01:00
DL6ER 814362c256
Apply the same fix also to /stats/upstreams - this endpoint is actually not affected, however, it has a different logic that all the other endpoints now - fixing this eases maintanance and doesn't require us remembering that upstream destinations are never recycled (because there are only very few)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-18 21:06:36 +01:00
DL6ER d5cf5799c1
Merge branch 'development' into merge-v5-6
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-17 16:43:15 +01:00
DL6ER d74d2a1e1c
Improve config migration logging
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-17 16:27:40 +01:00
DL6ER e3c4dcf215
Restructure API response from /history/clients and /history/database/clients to allow for sparse data.
Add new config option webserver.api.client_history_global_max controling if the activities chart should sort and show the *global* (integrated over 24 hours) or the `local` (measured individually in each time slot) most active clients
Allow setting webserver.api.maxClients to 0 to always return all clients in /api/history/clients

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-16 17:15:20 +01:00
DL6ER b986a82845
apply the same fix to other places in the API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-15 19:52:38 +01:00
DL6ER 93e621cd54
Report only as many clients as we have added to the sorting array
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-14 14:42:20 +01:00
DL6ER 750db7ecda
Merge branch 'development-v6' into tweak/allow_adlist_dups
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 21:06:43 +01:00
Dominik 41725fd343
Merge pull request #1887 from pi-hole/tweak/apppw_descrition
Improve description of GET /auth/app endpoint
2024-02-13 21:05:42 +01:00
Dominik 6681804b3c
Merge pull request #1875 from pi-hole/update/dnsmasq
Update embedded dnsmasq to v2.90
2024-02-13 20:21:55 +01:00
Dominik 2582043a2d
Apply code review
Co-authored-by: RD WebDesign <github@rdwebdesign.com.br>
Signed-off-by: Dominik <DL6ER@users.noreply.github.com>
2024-02-13 19:58:27 +01:00
DL6ER a326d80869
Update dnsmasq version to 2.90
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 17:10:05 +01:00
DL6ER 2c7c19c1ae
Update expected dnsmasq warnings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 17:07:17 +01:00
DL6ER 96f4acb516
Add documentation for automatically added new DNSSEC-related metrics
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 17:03:05 +01:00
Simon Kelley c95816f3b9
Reverse suppression of ANY query answer logging.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:46:14 +01:00
Simon Kelley 4b351cbe36
Add --dnssec-limits option.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:45:54 +01:00
Simon Kelley cb577f318e
Better allocation code for DS digest cache.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:45:19 +01:00
Simon Kelley dcd12a2be6
Better stats and logging from DNSSEC resource limiting.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:45:12 +01:00
Simon Kelley 84161ed295
Overhaul data checking in NSEC code.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:44:06 +01:00
Simon Kelley 1dbbdc96ca
Rework validate-by-DS to avoid DoS vuln without arbitrary limits.
By calculating the hash of a DNSKEY once for each digest algo,
we reduce the hashing work from (no. DS) x (no. DNSKEY) to
(no. DNSKEY) x (no. distinct digests)

The number of distinct digests can never be more than 255 and
it's limited by which hashes we implement, so currently only 4.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:43:59 +01:00
Simon Kelley b5e7dd448b
Update EDE code -> text conversion.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:43:11 +01:00
Simon Kelley f141efdeba
Parameterise work limits for DNSSEC validation.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:43:03 +01:00
Simon Kelley d92159a836
Fix error introduced in 635bc51cac3d5d7dd49ce9e27149cf7e402b7e79
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:40:32 +01:00
Simon Kelley 0b9e5543d2
Measure cryptographic work done by DNSSEC.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:40:27 +01:00
Simon Kelley ddf44bf916
Update NSEC3 iterations handling to conform with RFC 9276.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:38:55 +01:00
Simon Kelley aa565b1dff
Update header with new EDE values.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:38:50 +01:00
Simon Kelley ebbfb893bd
Protection against pathalogical DNSSEC domains.
An attacker can create DNSSEC signed domains which need a lot of
work to verfify. We limit the number of crypto operations to
avoid DoS attacks by CPU exhaustion.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 16:38:46 +01:00
DL6ER 81a1da69cb
Improve description of GET /auth/app endpoint
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 14:52:33 +01:00
DL6ER fc635e084f
Ensure webserver.api.maxClients is honored even in the presence of recycled clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 14:44:49 +01:00
DL6ER 8325db3cc8
Set REPLY of queries that failed DNSSEC validation to NONE (if not already set elsehow)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 14:28:47 +01:00
DL6ER fbd7aa7761
Check for UNKNOWN status and replies during CI testing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 10:38:30 +01:00
DL6ER 8b3c39015d
Add RFC 8482 filtering (Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY) by default
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 09:51:53 +01:00
DL6ER 7a919cbf59
Allow narrowing down the (ad)list type using the (optional) query parameter ?type={allow,block}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 08:42:08 +01:00
DL6ER f6fb93e997
Update embedded dnsmasq version to 2.90test4
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 07:19:15 +01:00
Simon Kelley 75648b4def
Make --filter-rr=ANY filter the answer to ANY queries.
Thanks to Dominik Derigs for an earlier patch which inspired this.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 07:15:03 +01:00
Simon Kelley 8cdead96a5
Tweak logging and special handling of T_ANY in rr-filter code.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-13 07:12:25 +01:00
DL6ER e3e839880e
Merge branch 'development-v6' into update/dnsmasq 2024-02-13 07:12:16 +01:00
DL6ER beb839e69b
Simplify how alias-clients are skipped in api/history/clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-12 15:51:21 +01:00
DL6ER 54fe14f1bf
Include forwarded queries in /api/history and count stale-cache queries as cached queries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-11 20:35:15 +01:00
DL6ER fda33662cb
Add validation for dns.revServers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-11 07:06:41 +01:00
DL6ER 6009a1ac09
Merge branch 'development-v6' into new/validator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-11 06:54:48 +01:00
Dominik 9e3ccd917d
Merge pull request #1797 from pi-hole/new/multi_revServer
Add support for multiple reverse servers
2024-02-11 06:52:31 +01:00
DL6ER 3b9badd7fd
Merge branch 'development-v6' into new/validator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-10 21:02:20 +01:00
Dominik 54262aeba2
Merge pull request #1878 from pi-hole/fix/debug_api_description
Fix debug.api config options description.
2024-02-10 20:56:25 +01:00
Dominik 5940c62763
Merge pull request #1719 from pi-hole/tweak/env_vars_list
Check all env vars and suggest alternatives for misspelled keys
2024-02-10 16:21:42 +01:00
yubiuser c994dad4f5
Merge pull request #1880 from pi-hole/dependabot-github_actions-development-v6-github_action-dependencies-d9773afac8
Bump the github_action-dependencies group with 3 updates
2024-02-10 12:36:38 +01:00
dependabot[bot] c2f690b3ab
Bump the github_action-dependencies group with 3 updates
Bumps the github_action-dependencies group with 3 updates: [Wandalen/wretry.action](https://github.com/wandalen/wretry.action), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `Wandalen/wretry.action` from 1.3.0 to 1.4.4
- [Release notes](https://github.com/wandalen/wretry.action/releases)
- [Commits](https://github.com/wandalen/wretry.action/compare/v1.3.0...v1.4.4)

Updates `actions/upload-artifact` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.0...v4.3.1)

Updates `actions/download-artifact` from 4.1.1 to 4.1.2
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.1.1...v4.1.2)

---
updated-dependencies:
- dependency-name: Wandalen/wretry.action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github_action-dependencies
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-10 11:00:33 +00:00
DL6ER ec37efd77f
Fix debug.api config options description.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-10 09:34:25 +01:00
DL6ER 9b18127867
Merge branch 'development-v6' into new/validator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-10 09:27:15 +01:00
Dominik 82f8abe71f
Merge pull request #1877 from pi-hole/tweak/dnssec_default
DNSSEC validation should not be enabled by default in v6
2024-02-09 22:45:57 +01:00
DL6ER 16cc1027fb
DNSSEC validation should not be enabled by default - it wasn't in v5, either. The reason for this is that it may be causing issues on devices with broken/missing RTCs where NTP time synchronization relies on DNS resolution
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-09 21:51:31 +01:00
Dominik 94521b8124
Merge pull request #1876 from pi-hole/fix/merge
Fix failed auto-merge in https://github.com/pi-hole/FTL/pull/1841
2024-02-09 21:43:59 +01:00
DL6ER 6a74642232
Fix failed auto-merge in https://github.com/pi-hole/FTL/pull/1841
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-09 21:14:40 +01:00
Dominik 276ba4210f
Merge pull request #1841 from pi-hole/tweak/list_id
Make adlist ID available, rename queries.regex_id -> queries.list_id
2024-02-09 20:52:35 +01:00
Dominik 34a885ab1c
Merge pull request #1869 from pi-hole/update/sqlite_3.45.1
Update embedded SQLite3 to 3.45.1
2024-02-09 20:41:35 +01:00
DL6ER 7bb49d6c7a
Adjust expected dnsmasq warnings after most recent upstream dnsmasq patch importing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-08 18:15:11 +01:00
Heikki Linnakangas 48a0c4591f
Don't create a useless inotify file desrcriptor when --port=0
If there are no dynamic configuration directories configured with
dhcp-hostsdir, dhcp-optsdir and hostsdir then we need to use inotify
only to track changes to resolv-files, but we don't need to do
that when DNS is disabled (port=0) or no resolv-files are configured.

It turns out that inotify slots can be a scarce resource, so not
using one when it's not needed is a Goood Thing.

Patch by HL, description above from SRK.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-08 18:11:37 +01:00
Simon Kelley ef007f2bb1
Refactor the accumulated crud of years in process_reply().
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-08 18:11:37 +01:00
Simon Kelley 68594df9b7
Handle caching SOA for negative PTR queries.
Also deal with the fact that a root SOA is a thing.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-08 18:11:37 +01:00
Simon Kelley 027d1587cb
Fix logic error in signed RR handling.
In extract_addresses() the "secure" argument is only set if the
whole reply is validated (ie the AD bit can be set). Even without
that, some records may be validated, and should be marked
as such in the cache.

Related, the DNS doctor code has to update the flags for individual
RRs as it works, not the global "secure" flag.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-08 18:11:37 +01:00
Simon Kelley 31a6b2dbc7
Fix compiler warning.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-08 18:11:37 +01:00
Simon Kelley 54658e9058
Cache SOAs and return them with cached NXDOMAIN/NODATA replies.
Now we can cache arbirary RRs, give more correct answers when
replying negative answers from cache.

To implement this needed the DNS-doctor code to be untangled from
find_soa(), so it should be under suspicion for any regresssions
in that department.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-08 18:11:37 +01:00
DL6ER 049c1ed54b
Update dnsmasq version to v2.90test3
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-03 22:08:18 +01:00
Simon Kelley ae46201c59
Fix FTBFS introduced in 2748d4e901193c919614276e42d6d54b11f3232d
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-03 22:07:22 +01:00
DL6ER f8c07fd78c
Merge branch 'development-v6' into update/dnsmasq 2024-02-03 22:06:02 +01:00
DL6ER 714f14babb
Add optional JSON object .import to POST /api/teleporter that allows the user to pick what is to be restored
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-03 19:45:55 +01:00
DL6ER 0ab53bfc59
A dash is allowed as path for files.log.dnsmasq to stderr
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-02-02 19:52:21 +01:00
Dominik 04cf2e831d
Merge pull request #1871 from mbooth101/mbooth-build-fix-1
Fix failure to build from source
2024-02-01 19:46:23 +01:00
Mat Booth 2059ac2017
Fix failure to build from source on Fedora 39
FTL build was failing at link-time due to undefined references to all
idn2_* functions.

This change fixes an incorrect search name for the idn2 library in the
CMake script.

Signed-off-by: Mat Booth <mbooth@fedoraproject.org>
2024-02-01 16:24:00 +00:00
Dominik a720330239
Apply suggestions from code review
Co-authored-by: RD WebDesign <github@rdwebdesign.com.br>
Signed-off-by: Dominik <DL6ER@users.noreply.github.com>
2024-01-30 19:14:50 +01:00
Dominik 3262d7d7c3
Merge pull request #1826 from pi-hole/fix/resolver
Improve and fix host name resolution
2024-01-30 19:11:54 +01:00
DL6ER f198a4fee4
Update embedded SQLite3 to 3.45.1
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-30 19:05:02 +01:00
DL6ER 3dff85623c
Add new config option database.useWAL defaulting to true making the use of the WAL journal optional for the on-disk query database
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-29 16:02:01 +01:00
yubiuser ce4407b536
Merge pull request #1866 from pi-hole/dependabot-github_actions-development-v6-github_action-dependencies-0f5d477bc4
Bump the github_action-dependencies group with 1 update
2024-01-27 14:40:47 +01:00
dependabot[bot] 562fcf6be9
Bump the github_action-dependencies group with 1 update
Bumps the github_action-dependencies group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact).


Updates `actions/upload-artifact` from 4.2.0 to 4.3.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-27 10:20:20 +00:00
Dominik 32d5af1893
Merge pull request #1864 from pi-hole/fix/dhcp_infinity
Amend description of expiration field in GET /dhcp/leases
2024-01-26 19:22:56 +01:00
DL6ER 52a260ffad
Amend description of expiration field in GET /dhcp/leases: 0 means "infinite". No functional changes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-26 16:24:19 +01:00
Dominik 1218151ef9
Merge pull request #1860 from pi-hole/tweak/x509_ca
X.509: Create and export CA certificate
2024-01-22 13:17:07 +01:00
DL6ER 164434541b
Explicitly log which config item failed to validate. This is useful when a user tries to set multiple values at once (e.g. via the web UI)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-21 20:25:15 +01:00
DL6ER ef4e592ea9
Verify that every config option has a validator. Otherwise, return a log error that will fail the CI tests. This will help ensuring we won't forget to add validators for PRs that are merged in parallel or new config options added in the future
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-21 20:16:09 +01:00
DL6ER 28b0ab27fd
Add regex array validation for webserver.api.excludeClients and webserver.api.excludeDomains
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-21 20:09:12 +01:00
DL6ER b9fc7da559
Merge branch 'development-v6' into new/validator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-21 20:01:11 +01:00
DL6ER 689cee7ec6
Merge branch 'development-v6' into new/multi_revServer
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-21 19:57:01 +01:00
Dominik 8f4272b64d
Merge pull request #1611 from pi-hole/new/queryLogRegex
Add regex filtering support for domains on the Query Log
2024-01-21 19:50:07 +01:00
Dominik 3341f0e497
Merge pull request #1863 from pi-hole/dependabot-github_actions-development-v6-github_action-dependencies-4550b9f164
Bump the github_action-dependencies group with 1 update
2024-01-20 13:51:12 +01:00
dependabot[bot] d30901b322
Bump the github_action-dependencies group with 1 update
Bumps the github_action-dependencies group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact).


Updates `actions/upload-artifact` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.1.0...v4.2.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-20 11:47:52 +00:00
DL6ER 619a8b1cf4
Fix pointer magic going wrong
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-20 12:44:03 +01:00
DL6ER bafbc780ed
Apply review comments
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-20 09:38:50 +01:00
DL6ER 24b6df4cb4
Reduce code duplication by factoring out filter regex compilation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-20 09:33:06 +01:00
DL6ER 0a48d7a4c4
Change how Pi-hole generates from self-signing our certificate to first creating a self-signed root certificate authority (CA) and then using this CA to ordinarily sign the server's certificate. This has the advantage of being able to import the CA in places where importing a self-signed certificate is discouraged or not possible (e.g. Firefox browser)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-16 22:55:02 +01:00
DL6ER 862d4922e1
Merge branch 'development-v6' into new/queryLogRegex
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-16 22:46:31 +01:00
Dominik c39261131a
Merge pull request #1832 from pi-hole/tweak/limit_history_clients
Limit number of clients returned by /api/history/clients
2024-01-16 22:37:33 +01:00
DL6ER 5c4355f1b1
Compile exclude regexes only once, not N^2 times
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-16 22:35:53 +01:00
DL6ER 3c58e3089b
Change order of objects in documentation (small nit pick)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-16 21:51:55 +01:00
Dominik ff452fc68d
Merge pull request #1859 from pi-hole/fix/remote_version
Return null as version in /api/info/version if not available
2024-01-15 22:09:33 +01:00
Dominik 9fc4a50805
Merge pull request #1858 from pi-hole/update/sqlite_3.45.0
Update embedded SQLite3 to 3.45.0
2024-01-15 21:03:03 +01:00
DL6ER 12e7f44b92
Return null as version in /api/info/version if not available
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-15 20:58:44 +01:00
DL6ER 1bf26f0de8
Update embedded SQLite 3 to 3.45.0
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-15 20:09:08 +01:00
DL6ER edc4da9f49
Merge branch 'development-v6' into update/dnsmasq 2024-01-14 17:33:44 +01:00
Simon Kelley 9eb920ad7c
Bump copyright to 2024.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-14 17:33:35 +01:00
Petr Menšík 18bf4fd530
Introduce new --local-service=host parameter
Similar to local-service, but more strict. Listen only on localhost
unless other interface is specified. Has no effect when interface is
provided explicitly. I had multiple bugs fillen on Fedora, because I have
changed default configuration to:

interface=lo
bind-interfaces

People just adding configuration parts to /etc/dnsmasq.d or appending to
existing configuration often fail to see some defaults are already there.
Give them auto-ignored configuration as smart default.

Signed-off-by: Petr Menšík <pemensik@redhat.com>

Do not add a new parameter on command line. Instead add just parameter
for behaviour modification of existing local-service option. Now it
accepts two optional values:
- net: exactly the same as before
- host: bind only to lo interface, do not listen on any other addresses
  than loopback.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-14 17:33:35 +01:00
Simon Kelley da901cbd23
Fix --synth-domain NXDOMAIN responses.
By design, dnsmasq forwards queries for RR-types it has no data
on, even if it has data for the same domain and other RR-types.

This can lead to an inconsitent view of the DNS when an upstream
server returns NXDOMAIN for an RR-type and domain but the same domain
but a different RR-type gets an answer from dnsmasq. To avoid this,
dnsmasq converts NXDOMAIN answer from upstream to NODATA answers if
it would answer a query for the domain and a different RR-type.

An oversight missed out --synth-domain from the code to do this, so
--synth-domain=thekelleys.org.uk,192.168.0.0/24
would result in the correct answer to an A query for
192-168.0.1.thekelleys.org.uk and an AAAA query for the same domain
would be forwarded upstream and the resulting NXDOMAIN reply
returned.

After the fix, the reply gets converted to NODATA.

Thanks to Matt Wong for spotting the bug.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-14 17:33:35 +01:00
Simon Kelley 6ce28da714
Fix problem with domains associated with DHCP hosts at startup.
At startup, the leases file is read by lease_init(), and
in lease_init() undecorated hostnames are expanded into
FQDNs by adding the domain associated with the address
of the lease.

lease_init() happens relavtively early in the startup, party because
if it calls  the dhcp-lease helper script, we don't want that to inherit
a load of sensitive file descriptors. This has implications if domains
are defined using the --domain=example.com,eth0 format since it's long
before we call enumerate_interfaces(), so get_domain fails for such domains.

The patch just moves the hostname expansion function to a seperate
subroutine that gets called later, after enumerate_interfaces().

Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-14 17:33:33 +01:00
yubiuser 806d6086a4
Merge pull request #1856 from pi-hole/dependabot-github_actions-development-v6-github_action-dependencies-515e419fdb
Bump the github_action-dependencies group with 2 updates
2024-01-13 20:44:39 +01:00
dependabot[bot] 82f7986a89
Bump the github_action-dependencies group with 2 updates
Bumps the github_action-dependencies group with 2 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/upload-artifact` from 4.0.0 to 4.1.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.0.0...v4.1.0)

Updates `actions/download-artifact` from 4.1.0 to 4.1.1
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github_action-dependencies
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github_action-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-13 10:24:06 +00:00
DL6ER 4e5521f400
Add new config option to API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-13 10:50:18 +01:00
DL6ER b05fe82a3f
Merge branch 'development-v6' into tweak/env_vars_list 2024-01-13 10:46:51 +01:00
Dominik 833291323f
Merge pull request #1752 from pi-hole/remove/local.list
Remove obsolet local.list
2024-01-13 10:45:27 +01:00
Dominik dfde9949e4
Merge pull request #1831 from pi-hole/update/ftl-build
Update ftl-build container to v2.5
2024-01-13 10:39:51 +01:00
DL6ER 03466460c3
Add webserver.api.maxClients setting to set default number of clients to be returned for the client activity graph. This setting can be overwritten at run-time
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-13 10:39:22 +01:00
DL6ER 58637597bf
Merge branch 'development-v6' into tweak/limit_history_clients 2024-01-13 10:32:56 +01:00
DL6ER 55339f01b5
Adjust webserver.api.exclude{Clients,Domains} description
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-13 10:17:58 +01:00
DL6ER e35aa78030
Only free API data when the API was started
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-13 10:08:43 +01:00
DL6ER b7f49e9636
Add Pi-hole v5 -> v6 regex migration for webserver.api.exclude{Domains,Clients}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-13 08:24:55 +01:00
DL6ER e9e43094bf
Remove webserver.api.excludeRegex and instead allow regex to be used in the existing excludeDomains and excludeClients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-13 08:20:53 +01:00
DL6ER aa8285846b
Remove excludeClients from Client activity over time (/api/history/clients)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-13 07:59:18 +01:00
Dominik b0c2ab9da2
Merge pull request #1703 from pi-hole/fix/delete_dhcp_lease
Managing DHCP leases is only possible when the DHCP server is enabled
2024-01-11 21:58:42 +01:00
DL6ER 4024af9cdd
Only compare against valid filter strings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-11 17:50:21 +01:00
DL6ER ded6692fec
Clarify which API endpoints are affected by the exclusion settings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-11 17:44:54 +01:00
DL6ER 0eb1aaa8f4
Extend webserver.api.excludeClients and webserver.api.excludeDomains to the Query Log
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-11 17:35:47 +01:00
DL6ER cff605b14e
Further simplify skipping logic
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-10 21:49:54 +01:00
DL6ER 23d116caac
Regex filtering is filtering: We need to do full counting to get the correct number of rows
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-10 21:44:51 +01:00
DL6ER e24c36327d
Merge branch 'development-v6' into fix/delete_dhcp_lease
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-10 20:02:09 +01:00
Dominik 02962d9244
Merge pull request #1839 from pi-hole/new/who_murders_me
If someone terminates FTL, try to obtain who is the murderer and log it
2024-01-10 19:43:02 +01:00
Dominik 4372797636
Merge pull request #1836 from pi-hole/fix/delete_codes
Fix DELETE API endpoints return codes
2024-01-08 19:38:35 +01:00
Dominik 7497937e66
Merge pull request #1854 from pi-hole/no_double
Don't print double newlines after invalid domains
2024-01-08 18:52:28 +01:00
Christian König 4174fe3b09
Don't print double newlines after invalid domains
Signed-off-by: Christian König <ckoenig@posteo.de>
2024-01-07 22:23:19 +01:00
DL6ER 14af354979
Do not rely on the old behavior of empty password is always correct when no password is set when changing the latter
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 21:16:30 +01:00
DL6ER 35e1acb533
Do not accept password login when the system is configured to not require a password
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 15:26:56 +01:00
DL6ER a961a4d14f
Do not accept DELETE session if no session is used (this also applies to password-less or localhost-no-auth mode)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 15:20:24 +01:00
DL6ER ee367a6c38
Merge branch 'development-v6' into new/queryLogRegex
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 09:59:10 +01:00
DL6ER 901d838cf9
Merge branch 'development-v6' into tweak/limit_history_clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 09:55:07 +01:00
DL6ER 1d83e39421
Extend 204/404 logic to /dhcp/leases/{ip}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 09:51:39 +01:00
DL6ER 85e3d4dd08
Extend 204/404 logic to /config/{element}/{value}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 09:51:35 +01:00
DL6ER 06147abd5d
A 204 response must not contain a body (https://tools.ietf.org/html/rfc7231#section-6.3.5)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 09:51:35 +01:00
DL6ER 55e0e84a92
Extend 204/404 logic to /network/devices/{device_id}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 09:51:31 +01:00
DL6ER c0172130fd
Extend 204/404 logic to /info/messages/{message_id}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 09:51:27 +01:00
DL6ER bb50a106d1
A few fixed for the response code documentation of the :batchDelete elements
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 09:20:28 +01:00
DL6ER e5e9d11211
Fix DELETE API endpoints. They should return 204 when something was deleted and 404 is nothing was found at this resource
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-07 09:04:20 +01:00
Dominik 933e6f605f
Merge pull request #1728 from pi-hole/tweak/query_auth
Add authentication via query string
2024-01-07 07:50:58 +01:00
Dominik 34464923cf
Merge pull request #1851 from pi-hole/new/location
Add Location header for newly created groups/clients/domains/lists
2024-01-07 07:50:20 +01:00
Dominik 9b9e7b7307
Merge pull request #1813 from pi-hole/fix/too_many_top_domains
Only include as many domains as we have in the sorted array
2024-01-07 07:49:58 +01:00
Dominik e48ee5fcd6
Merge pull request #1835 from pi-hole/fix/one_of_string_array
Fix one-of definition in clients, domains, groups, and lists POST
2024-01-07 07:48:42 +01:00
DL6ER 8806f6427f
Add Location header for newly created groups/clients/domains/lists
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-06 18:16:42 +01:00
DL6ER 33df6d48ea
Merge branch 'development-v6' into new/validator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-06 18:09:15 +01:00
Dominik 977acf480c
Merge pull request #1766 from pi-hole/new/teleporter_v5
Add support for legacy Teleporter archives
2024-01-06 16:42:04 +01:00
Dominik c2af66d225
Merge pull request #1806 from pi-hole/tweak/even_better_database
Use WAL, remove (and strip) SQLite3 shared-cache support and improve attached database handling
2024-01-06 13:43:21 +01:00
DL6ER d77547d4d7
Ensure database analysis / MAC vendor update is running also when FTL is frequently restarted (e.g. during development or for users joining a special branch, e.g. during extended bug fixing or a beta release period)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-06 09:13:29 +01:00
DL6ER 710e0bb3b7
Merge branch 'development-v6' into tweak/even_better_database 2024-01-05 16:46:13 +01:00
Dominik 2fef4e2872
Merge pull request #1846 from akordowski/fix/dns-blocking-docs
Fix `POST \dns\blocking` request documentation
2024-01-05 16:34:24 +01:00
Artur Kordowski 98127f12b9
Fix API dns/blocking documentation
Signed-off-by: Artur Kordowski <9746197+akordowski@users.noreply.github.com>
2024-01-05 15:45:26 +01:00
DL6ER da7c3a7735
Run ANALYZE instead of PRAMGA optimize after some discussion with the SQlite3 developers. Also ensure notices and mere messages are not always logged as errors in FTL's log. Furhtermore, reduce the frequency of running ANALYZE from once per day to once per week.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-04 23:30:14 +01:00
Dominik c24c1e18c4
Merge pull request #1840 from pi-hole/fix/cache_recycling
Implement DNS cache recycling
2024-01-04 23:25:52 +01:00
yubiuser e1cdad4d73
Merge pull request #1848 from pi-hole/simplify/download
Simplify artifacts download
2024-01-04 18:26:55 +01:00
Christian König 54041665b6
Account for api-docs
Signed-off-by: Christian König <ckoenig@posteo.de>
2024-01-03 22:33:38 +01:00
Christian König 136982c9dc
Simplify artifacts download
Signed-off-by: Christian König <ckoenig@posteo.de>
2024-01-03 22:28:33 +01:00
DL6ER 83dfd6250c
Merge branch 'development-v6' into tweak/list_id
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-01 20:43:44 +01:00
DL6ER 3d41513121
Use IN where = was used but a multi-value result may occur
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-01 08:59:33 +01:00
DL6ER 975c46817b
Optimize database on close (gravity) or frequently (queries)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2024-01-01 08:41:37 +01:00
DL6ER 72aea366fa
Port gravity.db update to version 19 into FTL's testing harness
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-28 11:10:58 +01:00
DL6ER e022600fef
Recheck statements in forks to avoid edge-case collisions possibly leading to a crash in heavy TCP worker activity
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-27 19:08:36 +01:00
Dominik a8d299155a
Merge pull request #1842 from pi-hole/tweak/rollback_error
Do not overwrite errors on ROLLBACK
2023-12-26 22:59:39 +01:00
Dominik 88a0ee4b9e
Merge pull request #1843 from pi-hole/update/cJSON_1.7.17
Update cJSON
2023-12-26 20:06:10 +01:00
DL6ER 5f0e405d82
Update bundled cJSON from 1.7.15 -> 1.7.17 released yesterday
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-26 09:36:06 +01:00
DL6ER d92e1a056c
Do not check errors on ROLLBACK TRANSACTION when gravityDB_delFromTable() fails. We remove this to avoid overwriting the initial cause of the error.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-25 22:19:03 +01:00
DL6ER 7c29048009
Translate anti-/gravity list IDs to negative numbers so they can be distinguish from domains rather easily. Users are free to foil this method when they force negative IDs into the database but they will never be automatically created
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-25 06:10:01 +01:00
DL6ER 1bc8e7fc06
Rename query_storage.regex_id to query_storage.list_id as it is already now used to also store exact matching domainlist entries by their ID. This commit further extends this to also store the (first) matching anti-/gravity list (if available)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-25 04:57:05 +01:00
DL6ER 7b4c0aa362
When FTL_check_blocking() is called with a different domain than the one already stored in the query, we have to re-lookup the cache ID. This can happen when a CNAME chain is followed and analyzed
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-25 04:29:44 +01:00
DL6ER 8ec30408c8
Remove unused upstream recycle check
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-24 21:44:35 +01:00
DL6ER 3084b1c507
Also log number of DNS cache records after history import
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-24 21:38:45 +01:00
DL6ER 5c95d26305
Simplify recycler debug summary
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-24 11:46:40 +01:00
DL6ER a5d1d4477b
Move debug messages meant for debug.status from debug.gc over
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-24 11:42:04 +01:00
DL6ER bb23ef090a
DNS cache entries were never recycled, possibly causing incorrect blocking of certain domain/client/type combinations
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-24 10:37:20 +01:00
DL6ER c81f1a3f6a
If someone terminates FTL, try to obtain who is the murderer and log it
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 16:06:40 +01:00
DL6ER 2719121b53
Allow SQLite3 to start up to four auxiliary worker threads for work-intense prepared statements. This is most useful with complex queries as it allows parallel sorting and indexing.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:37:52 +01:00
DL6ER e9763c69d0
Add recommended HAVE_FDATASYNC compile-time option
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:30:09 +01:00
DL6ER e34208e38f
Add recommended HAVE_MALLOC_USABLE_SIZE compile-time option
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:27:48 +01:00
DL6ER 867d146623
Add recommended SQLITE_LIKE_DOESNT_MATCH_BLOBS compile-time option
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:25:40 +01:00
DL6ER 96c2f6cb21
Greatly simplify memory db initialization by defining normal sync mode globally as compile-time option
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:23:14 +01:00
yubiuser 07c403a358
Merge pull request #1837 from pi-hole/dependabot-github_actions-development-v6-actions-download-artifact-4.1.0
Bump actions/download-artifact from 4.0.0 to 4.1.0
2023-12-23 12:21:38 +01:00
DL6ER cbec12a657
Set disk.synchronous=NORMAL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:19:13 +01:00
DL6ER c52d20bdc5
Initialize database only after forking
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:19:13 +01:00
DL6ER 2665da72f4
Remove SQLite3 URI feature - we do not need it any longer. It is not part of the regular SQLite3 shell builds.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:19:13 +01:00
DL6ER f683244444
Attach disk database once when initializing memory database and don't bother detaching it - it will finally be detached when FTL terminates
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:19:13 +01:00
DL6ER 6c921e75ae
Use WAL, remove (and strip) SQLite3 shared-cache support
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-23 12:19:11 +01:00
dependabot[bot] 2872ffc161
Bump actions/download-artifact from 4.0.0 to 4.1.0
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-23 10:48:12 +00:00
DL6ER 7915b0a4a0
Fix one-of definition in clients, domains, groups, and lists POST request payloads
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-20 17:15:41 +01:00
DL6ER fb1160df71
Reduce default N from 20 to 10 as experiments suggest that even 10 is at the border of being still readable
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-20 16:59:12 +01:00
DL6ER b32d5adbdb
Add special client "other clients" summing all client activity that has not been included due to more clients being active than being returned (due to limited N)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-20 16:57:44 +01:00
DL6ER fa6a8b38e9
Limit the number of clients to return to the number of clients to avoid possible overflows for very large N and add N=0 as special option to return all clients (even if the user does not know how many there are)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-18 22:49:37 +01:00
DL6ER aa123cacde
Limit number of clients returned by the /api/history/clients endpoint to 20. This can be overwritten at compile-time and at run-time using the query parameter ?N=...
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-18 22:00:44 +01:00
DL6ER 4cbe8b42c6
Fix dns.piholePTR description and add another fallback layer for obtaining the DNS domain used by the system (if not specified through Pi-hole)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-18 21:04:37 +01:00
DL6ER 43e976509e
Update ftl-build container to v2.5
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-17 09:50:27 +01:00
Dominik 7b59c6515c
Merge pull request #1827 from pi-hole/dependabot-github_actions-development-v6-actions-upload-artifact-4.0.0
Bump actions/upload-artifact from 3.1.3 to 4.0.0
2023-12-17 06:52:04 +01:00
Christian König a1cf6e4ff0
Adjust workflow for upload/download v4 changes
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-12-16 23:14:42 +01:00
DL6ER 3b37ca9966
Add clarifying comments about the rather obscure DNS message compression feature. DNS should have used LZ77 instead of its own sophomoric compression algorithm.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-16 21:30:29 +01:00
DL6ER 48184ad6df
Add static assertion for DNS struct sizes ans work aroung gcc bug on arm32 issusing a warning where there should be none
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-16 21:02:34 +01:00
dependabot[bot] bc48f63ed0
Bump actions/upload-artifact from 3.1.3 to 4.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.3 to 4.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3.1.3...v4.0.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-16 10:18:38 +00:00
DL6ER 6bc033e537
Add CI tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-15 20:58:57 +01:00
DL6ER c2d4f06864
Implement IP -> hostname resolver
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-15 20:45:25 +01:00
Dominik 1a0921cd48
Merge pull request #1810 from pi-hole/new/batchDelete
Implement special POST :batchDelete callbacks
2023-12-15 20:19:09 +01:00
DL6ER 799d22750c
Merge branch 'development-v6' into tweak/env_vars_list
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-13 19:50:53 +01:00
DL6ER 2e2104bbf5
Merge branch 'development-v6' into fix/too_many_top_domains
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-13 19:49:46 +01:00
Dominik 8eef4d81bb
Merge pull request #1786 from pi-hole/tweak/session_no_ip
Do not pin SID to a specific IP address
2023-12-13 19:46:43 +01:00
Dominik 17a2038b22
Merge pull request #1807 from pi-hole/tweak/special_domains_prio
Implement special domains allowing
2023-12-13 19:45:14 +01:00
Dominik 6d8cbe5b52
Merge pull request #1823 from pi-hole/tweak/soc_thermal
Add soc_thermal as CPU temperature sensor
2023-12-13 19:44:52 +01:00
Dominik a67babf801
Merge pull request #1821 from pi-hole/tweak/small_changes
A few (actually many) small changes
2023-12-13 10:11:45 +01:00
DL6ER 048e32e38d
soc_thermal is a suitable CPU temperature sensor
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-11 21:44:06 +01:00
DL6ER c73b042b0f
Merge branch 'development-v6' into new/batchDelete 2023-12-11 20:43:49 +01:00
DL6ER 26499f9bd3
Reduce performance test time, this is still sufficient to be pretty accurate and makes CI builds faster
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-09 23:31:21 +01:00
DL6ER 0b8735d494
Merge branch 'development-v6' into tweak/small_changes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-09 23:25:23 +01:00
Dominik d6f13ac777
Merge pull request #1819 from pi-hole/new/sql_ni
Add pihole-FTL sqlite3 -ni ...
2023-12-09 22:59:41 +01:00
Dominik 272c2f64b7
Fix comment
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: Dominik <DL6ER@users.noreply.github.com>
2023-12-09 22:57:35 +01:00
DL6ER 284ff72f9c
Add CI tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-09 20:23:29 +01:00
DL6ER 3ab4082326
Add special non-interactive mode for the embedded sqlite3 engine accessible via "-ni"
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-09 19:26:48 +01:00
Dominik 2b2ab3f5c7
Merge pull request #1816 from pi-hole/update/node
Update node version to 20
2023-12-09 18:48:08 +01:00
Christian König 9de29f639b
Update node version to 20
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-12-09 14:38:21 +01:00
yubiuser 9fb859a450
Merge pull request #1815 from pi-hole/dependabot-github_actions-development-v6-actions-stale-9.0.0
Bump actions/stale from 8.0.0 to 9.0.0
2023-12-09 12:49:27 +01:00
dependabot[bot] f170f9e7b5
Bump actions/stale from 8.0.0 to 9.0.0
Bumps [actions/stale](https://github.com/actions/stale) from 8.0.0 to 9.0.0.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v8.0.0...v9.0.0)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-09 10:28:22 +00:00
DL6ER 700d1c3730
Merge branch 'development-v6' into new/multi_revServer 2023-12-09 11:21:43 +01:00
DL6ER d91b237a50
Merge branch 'development-v6' into tweak/env_vars_list
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-09 11:21:18 +01:00
DL6ER 64051cd4be
Merge branch 'development-v6' into new/teleporter_v5
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-09 11:19:45 +01:00
DL6ER e9a55f8836
Only include as many domains as we have in the sorted array
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-09 11:03:36 +01:00
Dominik 8507953b50
Merge pull request #1811 from pi-hole/regex_id
Use regex_id instead of regex.id for query log sorting
2023-12-09 09:39:07 +01:00
Dominik 826d060c00
Merge pull request #1796 from pi-hole/new/ftl.gravity_tmp
Add config option files.gravity_tmp
2023-12-09 09:30:13 +01:00
Christian König 9f197ee457
Use regex_id instead of regex.id for query log sorting
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-12-08 21:28:54 +01:00
Dominik 3ef8498e5e
Apply suggestions from code review
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: Dominik <DL6ER@users.noreply.github.com>
2023-12-08 21:28:06 +01:00
DL6ER c7a6a88103
If we have an exact match for pihole-FTL --config <option>, we print only this one
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 04:56:16 +01:00
DL6ER 86213b34b9
Reduce variable scope
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:59:51 +01:00
DL6ER 651d3d6512
Lenght -1 means users wants to get all queries (no server-side pagination)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER 34cfc9c465
Remove undocumented audit feature
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER cc8d6294c0
Slightly simplify args code, argc is always > 1 so it cannot be < 2
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER b82db05411
When using void pointers in calculations, the behaviour may be undefined
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER dae8327277
Password-related code improvements and fixes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER 198b272783
Condition 'fp!=NULL' is always true
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER a2df36ec47
Add missing legacy config import code for NICE -> misc.nice
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER 835fcde327
Condition 'hwaddr!=NULL' is always true and reduce amount of allocated memory for synthesized MAC addresses from 324 to 18 bytes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER a455a40d01
Reduce variable scope
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER d5f68f0022
sscanf() without field width limits can crash with huge input data
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER d75b142d00
Local variable 'hostname' shadows outer function
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-08 07:53:49 +01:00
DL6ER d996697e29
Clarify calculation precedence for '&' and '?'
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:41:45 +01:00
DL6ER dc9dc0d6cf
regextest is always true in these cases
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:39:44 +01:00
DL6ER d77cddbdf9
Local variable 'hostname' shadows outer function with the same name
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:38:11 +01:00
DL6ER 895f326dac
Reduce variable scope
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:36:47 +01:00
DL6ER 1ff6c17395
Condition 'web_layout!=NULL' is always true
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:35:58 +01:00
DL6ER dd5f1c1ffa
Fix wrong format in printf() in disabled debugging code
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:35:19 +01:00
DL6ER 742110d7ae
Fix possible resouce leak on errors in the GZIP routines
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:34:47 +01:00
DL6ER b52258c2a1
Fix resource/memory leak on error in teleporter ZIP processing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:33:56 +01:00
DL6ER 43722cc7a1
Use fixed string lengths where available
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:31:58 +01:00
DL6ER 66cc0e87c3
Reduce code complexity slightly
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:30:24 +01:00
DL6ER d802f0e301
Reduce number of warnings coming from third-party modules we use shown during compilation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 22:27:14 +01:00
DL6ER 19cfed227f
Fix possible deferencing of NULL pointers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 21:00:01 +01:00
DL6ER 0d0cdae84f
Fix missing declaration
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 20:59:58 +01:00
DL6ER 6a42fc5757
Tests: Add CI test for allowing special domains per group
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 20:54:49 +01:00
DL6ER 9bcee72512
Add migration code from setupVars.conf:GRAVITY_TMPDIR to pihole.toml:files.gravity_tmp
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 16:59:55 +01:00
DL6ER dc1b1d1e08
Rename src/{ => config}/setupVars.{c,h}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-07 16:58:22 +01:00
DL6ER 38a6443ada
Change priorities such that special domains (Firefox and Apple at this time) can be explicitly allowed for some clients (per group assignments) while they stay blocked for all others in the network
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-06 23:44:14 +01:00
DL6ER 0ef89ab3eb
Implement special POST :batchDelete callbacks for /api/groups, /api/domains/, /api/clients, and /api/lists
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-06 23:27:20 +01:00
Dominik 797fb5d581
Merge pull request #1804 from pi-hole/tweak/cpu_usage
Fix CPU utilization calculation on 32 bit systems
2023-12-06 16:42:40 +01:00
Dominik 37e8388215
Merge pull request #1805 from pi-hole/tweak/no_port
Do not start webserver if webserver.port is empty
2023-12-04 23:21:32 +01:00
DL6ER 21d393b4be
Ensure CPU usage check is really run only every ten seconds
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-04 23:20:13 +01:00
DL6ER 066a38f466
Don't even try to start webserver if webserver.port is an empty string
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-03 22:57:02 +01:00
DL6ER 56fd63b9d8
Use resource details obtained from kernel to compute a ten minute average of FTL's CPU utilization
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-03 21:08:27 +01:00
Dominik e664756a56
Merge pull request #1801 from pi-hole/fix/extraLogging
Fix extra logging feature
2023-12-02 19:04:24 +01:00
DL6ER 0165d7beb7
Fix extra logging feature
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-02 18:09:01 +01:00
Dominik 4459460f7b
Merge pull request #1800 from pi-hole/new/taillog_prio
Add priority string in logs (if applicable)
2023-12-02 15:03:16 +01:00
Dominik d3eaa9c477
Merge pull request #1798 from pi-hole/new/log-queries-extra
Add new config option misc.extraLogging
2023-12-02 14:48:55 +01:00
Dominik d49be598fb
Merge pull request #1799 from pi-hole/fix/recycle_aliasclients
Never recycle alias-clients
2023-12-02 14:48:39 +01:00
DL6ER 849c48dc6b
Simplify debugstr() function
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-01 15:13:51 +01:00
DL6ER d01a62f053
Add priority string in logs (if applicable)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-01 14:59:57 +01:00
DL6ER af361c17b6
Add allowed values for dns.revServers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-01 12:44:10 +01:00
DL6ER f89f207246
Never recycle alias-clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-01 12:30:22 +01:00
DL6ER 3a59b49df0
Add misc.extraLogging to enable log-queries=extra defaulting to false
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-01 12:20:29 +01:00
DL6ER 61502fb5a1
Add dns.revServers and migrate dns.revServer. This allows multiple reverse servers to be added
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-01 12:09:00 +01:00
DL6ER 00d6ae6bd6
Add config option ftl.gravity_tmp
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-12-01 10:26:18 +01:00
DL6ER 2db977612c
Merge pull request #1785 from pi-hole/fix/files_different
Improve FTL's file comparsion algorithm
2023-12-01 07:50:17 +01:00
DL6ER da493adca2
Merge pull request #1795 from pi-hole/tweak/txt_privacylevel
Remove CH TXT privacylevel.pihole entry
2023-11-30 22:14:00 +01:00
DL6ER 1d80ffbdb2
Merge pull request #1784 from pi-hole/tweak/parseList_escape_invalid
Escape invalid domains possibly containing control characters
2023-11-30 22:13:39 +01:00
DL6ER 242b35eba8
Remove CH TXT privacylevel.pihole entry
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-30 21:59:39 +01:00
DL6ER 78e94b9276
Merge branch 'development-v6' into update/dnsmasq 2023-11-30 21:55:22 +01:00
DL6ER 47c3050822
Expose dnsmasq metrics
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-30 21:54:58 +01:00
DL6ER 884213d5f8
Merge pull request #1793 from pi-hole/new/txt_api
Add CHAOS TXT api.ftl records
2023-11-30 21:44:08 +01:00
DL6ER 27bcb65578
Merge pull request #1794 from pi-hole/tweak/gitignore
Add missing bits to the .gitignore file
2023-11-30 21:43:49 +01:00
Damian Sawicki 7848533e41
Add information on process-forking for TCP connections to metrics.
Add the relevant information to the metrics and to the output of
dump_cache() (which is called when dnsmasq receives SIGUSR1).
Hence, users not collecting metrics will still be able to
troubleshoot with SIGUSR1. In addition to the current usage,
dump_cache() contains the information on the highest usage
since it was last called.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-30 21:04:52 +01:00
DL6ER e0714f445d
Merge pull request #1694 from pi-hole/new/recycle
Recycle memory to reduce footprint
2023-11-30 20:52:52 +01:00
DL6ER c36dbca9e9
Add missing bits to the .gitignore file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-30 20:42:59 +01:00
DL6ER 7f29322439
Domain comparision should be case-insensitive
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-30 20:05:24 +01:00
Simon Kelley e2cb697bfb
Tighten up error checking in --bind-dynamic mode.
In bind-dynamic mode, its OK to fail to bind a socket to an address
given by --listen-address if no interface with that address exists
for the time being. Dnsmasq will attempt to create the socket again
when the host's network configuration changes.

The code used to ignore pretty much any error from bind(), which is
incorrect and can lead to confusing behaviour. This change make ONLY
a return of EADDRNOTAVAIL from bind() a non-error: anything else will be
fatal during startup phase, or logged after startup phase.

Thanks to Petr Menšík for the problem report and first-pass patch.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-30 14:01:56 +01:00
Simon Kelley baf2b0e501
Fix standalone SHA256 implementation.
Bug report here:
https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2023q4/017332.html

This error probably has no practical effect since even if the hash
is wrong, it's only compared internally to other hashes computed using
the same code.

Understanding the error:

hash-questions.c:168:21: runtime error: left shift of 128 by 24 places
cannot be represented in type 'int'

requires a certain amount of c-lawyerliness. I think the problem is that

m[i] = data[j] << 24

promotes the unsigned char data array value to int before doing the shift and
then promotes the result to unsigned char to match the type of m[i].
What needs to happen is to cast the unsigned char to unsigned int
BEFORE the shift.

This patch does that with explicit casts.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-30 14:01:52 +01:00
DL6ER bd7a518da4
Merge branch 'development-v6' into update/dnsmasq 2023-11-30 14:01:39 +01:00
DL6ER fde6c29fec
Add CI tests for CHAOS TXT *.api.ftl
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-30 13:10:50 +01:00
DL6ER d212f26634
Add CHAOS TXT records to dynamically get the available API ports:
- domain.api.ftl will use the configured web domain, e.g. "https://pi.hole:443/api"
- local.api.ftl will use the hard-coded string "localhost" instead, e.g., "https://localhost:443/api"
- api.ftl is an alias for domain.api.ftl

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-30 12:56:52 +01:00
DL6ER 9d0cc59cba
Improve shmem comments
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-29 22:09:38 +01:00
DL6ER a52cae48f4
Remove obsolete string escaping routines. They have been necessary for the Telnet API, however, this is gone and our JSON functions know how to deal with spaces
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-29 22:05:10 +01:00
DL6ER 215a683069
Merge branch 'development-v6' into new/recycle 2023-11-29 21:57:10 +01:00
DL6ER 392b39e0d0
Minor tweak ensuring we also decommission UNKNOWN queries properly when they expire
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-29 21:25:44 +01:00
DL6ER 3eb0e51833
Use case-insensitive matching for the gravity subcommands
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-28 22:44:32 +01:00
DL6ER 61bd30b986
Merge pull request #1788 from pi-hole/tweak/cache_optimizer
Disable cache optimizer for any negative TTL
2023-11-28 22:42:52 +01:00
yubiuser d52174ff40
Merge pull request #1792 from pi-hole/fix/adlist_link
Fix link from message table to list table
2023-11-28 22:30:13 +01:00
DL6ER 50ddbc9181
Merge branch 'development-v6' into new/validator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-28 22:21:53 +01:00
DL6ER 01af195c77
Update src/config/dnsmasq_config.c
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-11-28 22:16:38 +01:00
DL6ER fa6c1c2b74
Merge pull request #1787 from pi-hole/update/sqlite_3.44.2
Update embedded SQLite3 engine to version 3.44.2
2023-11-28 22:09:36 +01:00
DL6ER 03ff2d5651
Merge pull request #1782 from pi-hole/fix/wildcard_crt_check
Implement wildcard X.509 SAN/CN (subject) domain checking
2023-11-28 22:09:03 +01:00
Christian König d982b46052
Fix link from message table to list table
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-11-28 21:05:54 +01:00
DL6ER 7087d18ab0
Merge pull request #1790 from pi-hole/new/deprecate_dhcp_domain
Remove deprecated dhcp.domain setting
2023-11-28 20:38:31 +01:00
DL6ER aef1ea92a2
Merge pull request #1791 from pi-hole/fix/excludeDomains
Minor config comment fix
2023-11-28 19:37:59 +01:00
DL6ER 4422c39307
Minor config comment fix
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-28 19:22:20 +01:00
DL6ER 2755763803
Merge branch 'development-v6' into new/recycle
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-28 15:55:30 +01:00
DL6ER 34e44f2453
Set imported database query IDs to counter value and fix GC pointer magic (update comment to explain better what happens here)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-28 15:44:58 +01:00
DL6ER 71d726c174
Remove deprecated dhcp.domain setting
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-28 11:05:08 +01:00
DL6ER 75247cc6ef
Merge pull request #1789 from pi-hole/tweak/session_timeout
Increase default value of webserver.session.timeout to 30 min
2023-11-28 00:17:08 +01:00
DL6ER b9b373504c
Increase default value of webserver.session.timeout
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-28 00:09:33 +01:00
DL6ER 6a3502a0ec
Disable cache optimizer for any negative TTL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 23:18:56 +01:00
DL6ER 7edec2e2d4
Improve language
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-11-27 23:08:31 +01:00
DL6ER e4417692b8
Extend escaping to the checkList function
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 23:06:42 +01:00
DL6ER 3f4502c01b
Add comments
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 14:41:38 +01:00
DL6ER 1d03a5356d
Also check wildcards prefixed by "CN=" in the subject name of the certificate
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 14:25:25 +01:00
DL6ER 4f0800b854
Update embedded SQLite3 engine to version 3.44.2
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 14:07:26 +01:00
DL6ER 9cfb107cc7
Improve API POST /auth documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 13:45:34 +01:00
DL6ER bd12616610
Do not pin SID to one specific IP address
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 13:34:00 +01:00
DL6ER b017c1c20c
The SAN is not NUL-terminated, we need to use the specified length explicitly
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 13:27:04 +01:00
DL6ER 9ef42fb375
Fix comparison of the final line and add more debug logging during file comparison (debug.config)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 10:46:34 +01:00
DL6ER 7727c04425
Escape invalid domains possibly containing control characters
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-27 09:41:01 +01:00
DL6ER 01983299bc
Merge pull request #1781 from pi-hole/fix/zero_clients
API: Clients without queries should not be included
2023-11-26 23:35:38 +01:00
DL6ER 8b8218b2a3
Fix comment
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-26 22:31:51 +01:00
DL6ER 0f5d3970bb
Implement wildcard X.509 SAN/CN (subject) domain checking
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-26 22:24:23 +01:00
DL6ER f7b47e6b51
Merge branch 'development-v6' into new/validator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-26 21:48:13 +01:00
DL6ER 039c0fdc48
Merge branch 'development-v6' into tweak/env_vars_list
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-26 21:46:04 +01:00
DL6ER 399ca11038
Add extra debug statements
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-26 21:44:55 +01:00
DL6ER 30c3e9cbb0
Merge pull request #1778 from pi-hole/new/config_checksum
Only reload pihole.toml if changed
2023-11-26 21:39:30 +01:00
DL6ER d3826c9743
Remove obsolete overTime structure stored for each upstream. This reduces memory consumption per upstream destination from 640 to 56 bytes each
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-26 10:48:59 +01:00
DL6ER 164aeef036
Simplify /api/stats/upstreams to use global upstream counter instead of looping of over the respective overTime structure
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-26 10:45:15 +01:00
DL6ER b2884505ab
Merge branch 'development-v6' into new/recycle 2023-11-26 10:41:42 +01:00
DL6ER 409b6a40a3
Do not allocate too much memory for /api/stats/upstreams
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-26 10:41:30 +01:00
DL6ER d5eb664e1f
Remove undocumented withzero parameter
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 22:18:10 +01:00
DL6ER a7dbcc678c
Merge branch 'development-v6' into new/config_checksum 2023-11-23 22:11:07 +01:00
DL6ER 8bba6fac5d
Merge pull request #1780 from pi-hole/tweak/logs_pid
Include PID in /api/logs
2023-11-23 22:08:33 +01:00
DL6ER ff37972f81
Clients with zweo queries should not be returned unless ?withzero=true is set
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:27:58 +01:00
DL6ER b6e475225e
Warnings about incorrect ENV vars are expected and actually intended during CI testing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:19:13 +01:00
DL6ER a92e656f38
Also suggest keys starting with the given key. This can be helpful to, e.g. find misc.privacylevel is misc.priv is entered as the Hamming distance to misc.nice is shorter
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:12:10 +01:00
DL6ER fc4bcebf1e
Standardize CLI --config exit codes and add tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:12:10 +01:00
DL6ER e8039ede14
Log config file statistics after writing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:12:08 +01:00
DL6ER 01d1644102
Also suggest possible alternatives for mistyped config keys on the CLI
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:11:49 +01:00
DL6ER a23dad8abf
Explicitly log why env vars were invalid in the overview
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:10:46 +01:00
DL6ER 5c20f4095f
Use WARNING for unknown and ERROR for invalid env vars, also rename "ERR" to "ERROR" when printing errors (we also have INFO, WARNING, DEBUG, NOTICE, ALERT, ...)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:10:46 +01:00
DL6ER 0fbf0d3da7
Add the (fuzzy) Bitap algorithm and suggest up to three alternatives for typos
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:10:46 +01:00
DL6ER 2932ca34d5
Add env vars tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:10:46 +01:00
DL6ER 5ddbdf4607
Log invalid environment variables
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:10:30 +01:00
DL6ER d55b1b8d93
Suggest closest env var key when we find unknown keys
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:09:53 +01:00
DL6ER d74c4ad302
Log unused FTLCONF env vars
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:09:53 +01:00
DL6ER 58249a76fb
Store a list of FTLCONF_ environment variables
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:09:48 +01:00
DL6ER 392d0227a6
Move environment variables related functions/definitions into env.{c,h} without any further changes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 10:08:59 +01:00
DL6ER 0734a2769a
Include PID of the currently running FTL instance in the logs response. This allows clients to easily detect when FTL is restarted to reset their nextID to zero.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-23 09:36:51 +01:00
DL6ER 70a34e26c5
Merge pull request #1738 from pi-hole/fix/read_rotated_toml_on_error
Try to restore broken pihole.toml from rotated files
2023-11-22 23:16:30 +01:00
DL6ER ea7753d3ec
Add SHA256 CI test
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 23:15:19 +01:00
DL6ER adf8a13e9d
Add pihole-FTL sha256sum <file>
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 23:02:01 +01:00
DL6ER f4798929e1
Only reload pihole.toml if the content changed
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 23:01:19 +01:00
DL6ER 29ce1ee8e8
Do not rotate pihole.toml when opening pihole.toml.tmp for writing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 22:40:50 +01:00
DL6ER 7ade77599e
Merge branch 'development-v6' into new/teleporter_v5
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 22:18:00 +01:00
DL6ER a79bfe077f
Merge branch 'development-v6' into fix/read_rotated_toml_on_error
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 22:13:13 +01:00
yubiuser c569a4916b
Merge pull request #1772 from pi-hole/log/config
Log successful processing of pihole.toml on startup
2023-11-22 22:07:17 +01:00
DL6ER 42e6ff1a89
Merge pull request #1775 from pi-hole/fix/inotify_move
Inotify: Watch for config file MOVED_TO
2023-11-22 22:05:59 +01:00
DL6ER 3db7172dbc
Merge pull request #1776 from pi-hole/update/sqlite_3.44.1
Update embedded SQLite3 engine to version 3.44.1
2023-11-22 22:05:23 +01:00
yubiuser ca617b6946
Address reviewer's comment
Co-authored-by: DL6ER <DL6ER@users.noreply.github.com>
Signed-off-by: yubiuser <ckoenig@posteo.de>
2023-11-22 21:54:46 +01:00
DL6ER 3ea9ce2bbc
Update embedded SQLite3 engine to version 3.44.1
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 21:47:17 +01:00
DL6ER 3f5fb98956
Update tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 21:42:00 +01:00
DL6ER b6aa531bbb
Also monitor when file was moved to overwrite our watched file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 21:25:11 +01:00
Simon Kelley 5ce58e012d
Fix misuse of const pointer in src/nftset.c.
Thanks to  Kevin Darbyshire-Bryant for the initial patch, which was
modified by srk - any remaining bugs are his.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 17:45:46 +01:00
Simon Kelley d89b222a87
Fix use-after-free in cache_remove_uid().
Thanks to Kevin Darbyshire-Bryant for the bug report.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 17:45:42 +01:00
Simon Kelley 863c2f2c4a
Fix crash when DNS disabled, introduced in 416390f9962e455769aa8ab6df0e105cae07ae55
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 17:45:34 +01:00
Damian Sawicki fa03fbb0ec
Add --max-tcp-connections option to make this dynamically configurable.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 17:45:34 +01:00
Simon Kelley 116a670958
Fix compile warning introduced by a889c554a7df71ff93a8299ef96037fbe05f2f55
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 17:45:34 +01:00
Dominik Derigs 58fc7dd07c
Add RESINFO RR-type to the table of RR-type names.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 17:45:31 +01:00
DL6ER 98b5a92b52
Allow multiple hostnames in dns.hosts
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 14:22:24 +01:00
yubiuser 3bdf652d42
Merge pull request #1774 from pi-hole/tweak/log
Only log number of stored sessions not total number
2023-11-22 13:57:50 +01:00
Christian König c341b73d0f
Only log number of stored sessions not total number
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-11-22 13:50:34 +01:00
DL6ER 2fa856673b
Check there is no tail in validator routines
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-22 07:35:42 +01:00
DL6ER 12e8bd773f
Merge pull request #1771 from pi-hole/tweak/clients_mac_hostnames
/api/client: Include hostnames of clients specified by MAC
2023-11-22 06:33:51 +01:00
DL6ER 247a3c4669
Apply suggestions from code review
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-11-21 22:37:10 +01:00
DL6ER a110940231
Improve config file opening logging
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 22:36:08 +01:00
DL6ER afe65fbb9b
Add filepath validation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 22:04:00 +01:00
DL6ER 2700bbb68b
Add domain validation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 21:56:50 +01:00
DL6ER 0d27c37d40
Add IP/Port validation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 21:52:27 +01:00
DL6ER 2e478ab7c7
Add CIDR validation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 21:50:58 +01:00
DL6ER 239ffe355f
Add validation routines for certain config options
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 21:32:05 +01:00
DL6ER 268146d9c7
Move the setupVars.conf file to setupVars.conf.old
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 18:14:10 +01:00
Christian König 7c53ce8d34
Log successful processing of pihole.toml on startup
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-11-21 18:00:27 +01:00
DL6ER 1cde68bce7
Add ability to get most recent client hostname from network table if specified by MAC address
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 14:11:40 +01:00
DL6ER 1230165baa
Merge pull request #1767 from pi-hole/fix/config_crash
Fix possible crash when importing legacy DHCP config
2023-11-21 13:37:33 +01:00
DL6ER 1e6279173d
Merge pull request #1770 from pi-hole/tweak/restored_sessions_log
Only log how many sessions have been restored
2023-11-21 13:31:58 +01:00
Christian König f4e830ca17
Only log how many sessions have been restored
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-11-21 12:41:18 +01:00
DL6ER 2c765c94bb
Add WEB_PORTS to setupVars.conf when importing v5 Teleporter files
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 12:04:48 +01:00
DL6ER 46eca50db0
Fix logging when reading the TLS certificate
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 09:43:08 +01:00
DL6ER 9483a09ade
Merge pull request #1765 from pi-hole/new/idn2
Switch from libidn to libidn2 to get IDNA2008 support
2023-11-21 09:41:39 +01:00
DL6ER e2f35017c0
Fix legacy settings parsing: All the DHCP-related IP addresses are not of type INADDR, not STRING!
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-21 09:30:35 +01:00
DL6ER 015708226e
Add dedicated function to reset config values to their defaults
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 22:38:47 +01:00
DL6ER 5320dc3a84
Verify we have no default string pointers to NULL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 22:38:47 +01:00
DL6ER d6f30b3931
Install safe-guards for string-related functions to not crash when accidentially supplied with a NULL pointer
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 22:38:33 +01:00
DL6ER ce4db73c24
Merge branch 'development-v6' into new/idn2 2023-11-20 19:44:39 +01:00
DL6ER 1f320f83ee
Merge pull request #1761 from pi-hole/tweak/validate_domains
Validate domains before adding them to the database via /api/list
2023-11-20 19:43:10 +01:00
DL6ER 65aef156cd
Add parsing support for NULL-values
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 19:38:59 +01:00
DL6ER f30c1e89a8
Also remove all rotated files in light of the upcoming https://github.com/pi-hole/FTL/pull/1738
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 15:56:13 +01:00
DL6ER 41f01ae4c9
Relax filename constraints in archive type detection
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 15:44:40 +01:00
DL6ER d901dae526
Use new ftl-build container v2.4.1
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 15:38:45 +01:00
DL6ER 40e2e97259
Install also remaining files
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 15:36:15 +01:00
DL6ER 6c62d3e3f9
Add import_json_table() routine that can read, parse, and import <table>.json files from Pi-hole v5.x Teleporter archives
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 14:07:17 +01:00
DL6ER 93d0c557e8
Add CI test for IDN2 conversion
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 09:30:52 +01:00
DL6ER b85f85d73c
Add CLI IDN2 conversion interface
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 09:11:42 +01:00
DL6ER 07f040ea0b
Switch from libidn to libidn2 to get IDN conversion conforming to IDNA2008 + TR46 specifications (RFC 5890, RFC 5891, RFC 5892, RFC 5893, TR 46)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-20 08:58:48 +01:00
DL6ER 68d6f4ab94
Test parse adlist.json
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 23:33:53 +01:00
DL6ER 98f7ff8e62
Do not compress rotated files - they are not expected to be large
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 22:58:29 +01:00
DL6ER 7f0a0a77da
Add "unicode" field to API response for GET /api/domain
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 22:54:19 +01:00
DL6ER 8c26e03f8e
Merge pull request #1764 from pi-hole/tweak/auth_domain
Set CivetWeb authentication_domain
2023-11-19 22:36:43 +01:00
DL6ER abbcbdaf5a
Convert IDNs to punycode before validation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 22:34:43 +01:00
DL6ER 50a72afcef
Add TAR routines for efficient parsing of a tar archive in memory
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 22:22:06 +01:00
DL6ER 998addc5b1
Merge pull request #1763 from pi-hole/fix/group_comments
Fix group comments
2023-11-19 22:20:28 +01:00
DL6ER b9fa29c18c
Add some rule along which we will decide the user supplied somethings that (superficially) looks like a Teleporter v6 ZIP file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 19:04:00 +01:00
DL6ER 00a9bc8d17
Generalize upload handing scripts to possibly accept other files than ZIP archives
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 18:43:08 +01:00
DL6ER c97239aced
Remove CivetWeb patch which is not needed when authentication_domain is set
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 11:20:40 +01:00
DL6ER 262965375a
Require restarting after domain change - this also re-reads the TLS certificate file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 09:34:38 +01:00
DL6ER 5926c5b246
Set civetweb's authentication_domain to config.webserver.domain
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 09:33:36 +01:00
DL6ER ad5d078193
The group table's column is called "description" but we expose it in the API as "comment". Adjust internally used SQL to translate between them (this was already implemented and working for comment editing)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-19 07:46:58 +01:00
DL6ER d8a452f8f3
Update src/tools/gravity-parseList.c
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-11-18 21:58:06 +01:00
DL6ER ef679f2edb
Update src/config/config.c
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-11-18 21:57:04 +01:00
DL6ER 04742405ab
Hostnames (non-FQDN) are valid domains in the context of allow/deny domains, too
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-18 16:56:31 +01:00
DL6ER 1a517c7358
Merge branch 'development-v6' into fix/read_rotated_toml_on_error
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-18 16:13:27 +01:00
DL6ER d0345cb4fa
Validate domains before adding the to the database via /api/list
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-18 12:51:30 +01:00
DL6ER 17716ef51a
Merge pull request #1746 from pi-hole/tweak/conf_writing
Do not rewrite config files if unchanged
2023-11-16 22:36:08 +01:00
DL6ER 2dbb7f3b63
Merge pull request #1731 from pi-hole/fix/dhcp-range
Improve DHCP handling
2023-11-16 22:25:09 +01:00
DL6ER 503c0538ed
IPv4 address 0.0.0.0 and IPv6 address :: correspond to empty strings in FTL settings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-16 12:43:47 +01:00
DL6ER 57f3b4c37f
Merge pull request #1759 from pi-hole/fix/list_error_free
Prevent unconditional free on error reporting
2023-11-15 12:59:00 +01:00
DL6ER 67a630c790
Explicitly check hint being NULL before trying to free it in send_json_error_free()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-15 12:06:08 +01:00
DL6ER 2a465b5ea4
Don't call cJSON_free unconditionally on errors in api_list_write()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-15 12:00:22 +01:00
DL6ER a69130585e
Use package ipaddress for IP address validation in API tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-15 11:49:03 +01:00
DL6ER a356cbd13b
Add additional DHCP range tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-15 11:43:10 +01:00
DL6ER 90de6c1b83
Merge pull request #1727 from pi-hole/fix/toml_utf8
Declare pihole.toml as UTF-8 document
2023-11-14 22:25:04 +01:00
DL6ER 02a742fd4b
Merge pull request #1756 from pi-hole/fix/abp_tld
(Re-)Allow TLD blocking using ABP style
2023-11-14 21:04:23 +01:00
DL6ER ebe8e248d8
Allow TLD blocking using ABP style
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-14 10:26:19 +01:00
DL6ER 8e043444fc
Adjust tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-13 23:18:18 +01:00
Christian König fbd9da842c
Remove obsolet local.list
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-11-13 23:05:58 +01:00
DL6ER fdad1b7be2
Merge branch 'development-v6' into tweak/conf_writing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-13 22:58:18 +01:00
DL6ER 6e25f5ce66
Merge pull request #1754 from pi-hole/new/hostsdir
Use hostsdir for custom.list to avoid cache flushing on changes
2023-11-13 22:53:43 +01:00
DL6ER e3ebcc28c1
Merge pull request #1755 from pi-hole/fix/space
Remove additonal spaces in dnsmasq.conf
2023-11-13 22:27:01 +01:00
DL6ER 6424cf0316
Merge pull request #1739 from pi-hole/tweak/dns-domain
Move dhcp.domain -> dns.domain
2023-11-13 22:21:36 +01:00
Christian König 76d6fcab6e
Remove additonal spaces in dnsmasq.conf
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-11-13 13:48:26 +01:00
DL6ER 4b2b81f45f
Stop rotating dnsmasq.conf
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-13 10:09:42 +01:00
DL6ER af4214d8f9
Use hostsdir for custom.list to avoid cache flushing on changes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-13 09:58:04 +01:00
DL6ER d43b0eed28
Add deprecation note for dhcp.domain
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-13 09:25:32 +01:00
yubiuser 33639beaf1
Merge pull request #1751 from pi-hole/tweak/dnsmasq.conf
Improve readability of `dnsmasq.conf` by adding blank lines to group similar settings
2023-11-12 22:37:06 +01:00
DL6ER 1d5118736f
Merge pull request #1750 from pi-hole/fix/config_text
Fix hint of dns.cnameRecords
2023-11-12 22:36:09 +01:00
Christian König 4d41603141
Impove readability of `dnsmasq.conf` by adding blank lines to group similar settings
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-11-12 22:25:53 +01:00
DL6ER 19d37a6020
Fix hint of dns.cnameRecords
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-12 22:05:13 +01:00
DL6ER fc8a72bc60
Merge pull request #1749 from pi-hole/fix/config_restart
Add "requires restart" for config items where it is missing
2023-11-12 21:42:00 +01:00
DL6ER af1af596fd
Merge pull request #1734 from pi-hole/new/etc_dnsmasq_d
Make inclusion of /etc/dnsmasq.d/*.conf files configurable
2023-11-12 21:27:53 +01:00
DL6ER 1fec47907e
Add restart flag to config items where this is missing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-12 21:07:57 +01:00
DL6ER ea156f6d10
Restart FTL on change of misc.etc_dnsmasq_d
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-11-12 20:59:41 +01:00
DL6ER ea5ff0df7b
Merge pull request #1735 from pi-hole/fix/group_errors
Fix domain modification
2023-11-12 13:59:33 +01:00
DL6ER 31986a893a
Merge pull request #1747 from pi-hole/tweak/x509_valid_from_now
Create dynamic validity period when generating X.509 certificate
2023-11-11 23:17:46 +01:00
DL6ER 4644843c92
Use thread-safe variant of localtime()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-11 20:58:24 +01:00
DL6ER 9f71bc4814
Merge pull request #1733 from pi-hole/new/num_sessions
Make number of maximum concurrent API sessions adjustable
2023-11-11 20:49:59 +01:00
DL6ER afefe4ca30
Create dynamic validity period when generating X.509 certificate
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-10 22:18:06 +01:00
DL6ER 28c8d263d1
Merge pull request #1744 from pi-hole/new/check_certificate
Add X.509 parsing capabilities
2023-11-10 22:14:01 +01:00
DL6ER 5779db9ca5
Warn if NULL is bound to a message row even when we actually want to bind a blob
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-10 21:09:31 +01:00
DL6ER 1bcb7541dc
Add missing database type definition for CERTIFICATE_DOMAIN_MISMATCH_MESSAGE
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-10 21:06:19 +01:00
DL6ER df2ba4c724
Fix escape_html() crashing for NULL input
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-10 21:03:28 +01:00
DL6ER ec064d9c6e
Use correct type when manipulating domainlist entries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-10 20:47:33 +01:00
DL6ER 84ba6da404
max_sessions can only change across FTL restarts - not while it is running
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-10 20:05:06 +01:00
DL6ER dcdca19cae
Move 2FA success message more human-readable and move it to debug printing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-10 04:48:07 +01:00
DL6ER c2eca93af8
Add tests and unify success/error messages across the files we are writing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 21:18:25 +01:00
DL6ER 6aedb9ec68
Merge pull request #1745 from pi-hole/tweak/codespell
Run codespell on push
2023-11-09 19:16:43 +01:00
DL6ER d340765b33
Remove logging if unchanged config file if not in DEBUG_CONFIG mode
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 19:13:29 +01:00
DL6ER b6656808c8
Do not rewrite config file custom.list (dnsmasq) when the content did not change
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 19:11:15 +01:00
DL6ER a3526b842f
Do not rewrite config file pihole.conf (dnsmasq) when the content did not change
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 19:08:50 +01:00
DL6ER 10c635b018
Do not rewrite config file pihole.toml when the content did not change
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 19:05:03 +01:00
DL6ER 69db4c18e8
Run codespell on push
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 18:49:19 +01:00
DL6ER d310f8efb3
Spellcheck fixes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 17:50:07 +01:00
DL6ER 58b402be6f
Add tests for --read-x509(-key)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 16:30:22 +01:00
DL6ER 6498e6bf93
Add logging to the Pi-hole diagnosis system when we detect a certificate domain mismatch
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 16:00:26 +01:00
DL6ER 651bb7f065
Add X.509 certificate/key parser
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-09 06:29:02 +01:00
DL6ER c21d9764c5
Merge pull request #1736 from pi-hole/fix/overTimeEmptyDatabase-v6
Always return full overTime data
2023-11-09 02:47:32 +01:00
DL6ER a49c4c9cdd
Merge pull request #1732 from pi-hole/tweak/hosts_no_restart
Changing dns.hosts does not need a restart
2023-11-09 02:47:05 +01:00
DL6ER ca0a3e50b8
Merge pull request #1743 from pi-hole/new/zero_after_free
NULL after free()
2023-11-08 14:15:36 +01:00
DL6ER 1f42708f14
Always set freed pointers to NULL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-08 13:23:02 +01:00
DL6ER b4ef3b5453
Merge pull request #1742 from pi-hole/fix/api_search_invalid_free
Fix free() being used in the wrong page
2023-11-08 13:22:08 +01:00
DL6ER 1f5b15e511
Fix free() being used in the wrong page
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-08 11:12:37 +01:00
DL6ER 5da282eb8d
Tests: Add tests that both /api/history and /api/history/clients return full 24h data
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-08 09:47:08 +01:00
DL6ER ac534ecfb6
Also always send all history/clients data
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-08 09:04:49 +01:00
DL6ER b192c2b202
Merge pull request #1740 from pi-hole/new/sudo-pihole-config
Suggest sudo for pihole-FTL --config changes
2023-11-08 07:26:42 +01:00
DL6ER 7dec259514
Suggest using sudo if insufficient permissions to edit the config file were detected
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-07 21:07:21 +01:00
DL6ER 5dc17a1ed3
Move dhcp.domain -> dns.domain
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-07 08:47:39 +01:00
DL6ER 51fb66b80f
If reading pihole.toml failed, we recreated one from the possibly still existing setupVars.conf, etc. files. However, it arguably makes more sense to instead restore from the last known-to-be-good rotated config file in /etc/pihole/config_backups
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-06 15:27:42 +01:00
DL6ER 6e860f0c81
Add special reply for reused TOTP tokens
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-06 11:35:26 +01:00
DL6ER 28fe8dd499
Return 429 Too Many Requests with useful hint when number of available API seats is exceeded, also log currently configured number of API seats in the issues warning
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-06 10:38:15 +01:00
DL6ER 1902fadd18
Do not reduce the number of sent timeslots based on the real activity but instead always send everything (even if there are many zeros). This brings https://github.com/pi-hole/FTL/pull/1345 to FTL v6.0
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-06 08:55:09 +01:00
DL6ER c450d488a4
Fix domain modification. The UNIQUE key in the domainlist table is the combination of domain+type, not domain alone so you can add the same domain, e.g. once as allowed and once as denied entries and assign them to different clients using appropriate groups
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-05 20:59:55 +02:00
DL6ER 9345841b10
Fix error message shown when gravityDB_addToTable() fails
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-05 20:58:19 +02:00
DL6ER ca97616ed1
Add config option misc.etc_dnsmasq_d to allow setting whether files in /etc/dnsmasq.d are loaded as additional config files for Pi-hole. This resolves a long standing issue with Pi-hole not being compatible with software that installs custom files with conflicting lines in this directory (e.g., lxc) as well as improve the coexistence of Pi-hole with an already running dnsmasq on the host (by default, we do not share any config files in the future)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-05 10:27:03 +01:00
DL6ER 11127f0f13
Fix OpenAPI checker not being able to discover properties that are returned by FTL but not documented in the OpenAPI specs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 17:11:37 +01:00
DL6ER add7ceadda
Make number of maximum concurrent API sessions adjustable
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 17:00:59 +01:00
DL6ER 7e094ecb3b
Changing dns.hosts does not need a full FTL restart but only a cache flush and re-read
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 16:36:34 +01:00
DL6ER afb6f86518
Merge pull request #1730 from pi-hole/fix/2fa-domain
Use webserver.domain as "account" in the TOTP QR code
2023-11-04 15:12:18 +01:00
DL6ER c54f0a7871
Add tests for international local CNAME records
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 13:50:07 +01:00
DL6ER b967799057
Reject invalid config items (error 400) instead of merely logging a warning to the log
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 12:40:43 +01:00
DL6ER 75cd372d0e
Add string format verification in API checker
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 12:38:24 +01:00
DL6ER 6f2c6fe801
Add dhcp.netmask (type IPv4 address) and change the type of dhcp.{start,end,router} from string to IPv4 address to allow detection of invalid settings (e.g., "192.168.1.3000") early on
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 12:38:13 +01:00
DL6ER 39f350af5d
Only add dhcp.leaseTime if it actually set. If not given, the default lease time is one hour for IPv4 and one day for IPv6 (dnsmasq defaults, see their man page).
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 12:24:34 +01:00
DL6ER 91b0c23f84
Merge branch 'development-v6' into new/recycle 2023-11-04 08:05:03 +01:00
DL6ER 08245ce21a
Merge branch 'fix/numbers' into new/recycle 2023-11-04 08:04:01 +01:00
DL6ER c89a2396be
Backslashs need to be escaped to avoid invalid escape sequences in the TOML file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 08:00:29 +01:00
DL6ER aa55962215
Merge branch 'development-v6' into new/queryLogRegex
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 07:52:03 +01:00
DL6ER ff3b8db24f
Use webserver.domain as "account" in the TOTP QR code
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-04 07:27:08 +01:00
DL6ER a96c283c0c
Add authentication via query string
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-03 19:41:08 +01:00
DL6ER 73e265ad70
Merge pull request #1724 from pi-hole/fix/test_all_the_api
API tests: Use /api/endpoints
2023-11-03 13:23:39 +01:00
DL6ER fe8798e1b1
Add tests for international custom DNS records
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-03 12:22:26 +01:00
DL6ER f1c59db0f6
Declare pihole.toml as UTF-8 document. we add a compile-time switch to use ASCII with UTF-8 escaping instead in case this is a necessity for anyone (I don't really expect this in the third millenial but we also know people are still using Windows XP on the web...)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-03 10:12:07 +01:00
DL6ER 2efdfe999c
RapiDoc does not really support schemas without examples, so add a dummy example for /docs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-03 06:52:32 +01:00
DL6ER d805bd75f4
Update test/api/checkAPI.py
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-11-03 06:47:32 +01:00
DL6ER 7d50efc54b
Merge pull request #1725 from pi-hole/tweak/test_ipv6
Only use IPv6 in the webserver when available
2023-11-03 06:24:32 +01:00
DL6ER 7870723a7d
Check IPv6 support is not disabled either via the boot command line or at runtime before trying to launch the webserver for the first time
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-02 13:00:00 +01:00
DL6ER 15ae21e39c
Clarify error wording
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-02 05:55:27 +01:00
DL6ER 3a67a77a08
Report number of checked endpoints in the result and warn if the number of specified endpoints in FTL and the OpenAPI specs do not match
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-02 05:50:32 +01:00
DL6ER 253feef597
Merge branch 'development-v6' into fix/test_all_the_api 2023-11-02 05:32:16 +01:00
DL6ER 2eba3651d2
Fix all docs issues found by the improved API verification excluding the ones fixed by #1722
This currently returns:

Verifying the /api/endpoints endpoint...
  Errors:
  - Endpoint POST /action/reboot not found in FTL endpoints
  - Endpoint POST /action/poweroff not found in FTL endpoints

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-02 05:31:46 +01:00
DL6ER 4a78d886f3
Merge pull request #1722 from pi-hole/poweroff
Remove traces of reboot and poweroff
2023-11-02 05:30:47 +01:00
DL6ER 5993b66bca
Add checking of all endpoints defined in FTL but not in the OpenAPI specs and vice versa
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-02 05:28:07 +01:00
Christian König f26c7b45c3
Remove traces of reboot and poweroff
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-11-01 22:23:44 +01:00
DL6ER 38463dcbc2
Merge pull request #1716 from NittanySeaLion/adlist_message
Update Error Message Referring to "Adlist"
2023-11-01 18:23:58 +01:00
DL6ER b6743220da
Merge pull request #1721 from pi-hole/update/sqlite_3.44.0
Update embedded SQLite3 engine to version 3.44.0
2023-11-01 18:16:18 +01:00
DL6ER 011c1af8ac
Update embedded SQLite3 engine to version 3.44.0
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-11-01 07:11:10 +02:00
NittanySeaLion 187beb7302
Convert Adlist to List in Error Messages
Signed-off-by: NittanySeaLion <119230128+NittanySeaLion@users.noreply.github.com>
2023-10-31 17:52:55 -04:00
DL6ER c8391fdcd5
Merge pull request #1715 from pi-hole/new/search_punycode
/api/search/{domain}: Add IDN compatibility
2023-10-31 21:00:17 +01:00
DL6ER 5ca121cc70
Merge pull request #1720 from pi-hole/fix/pw_log
Remove left-over debug output
2023-10-31 20:43:05 +01:00
DL6ER 45fd5c8d36
Remove left-over debug output
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-31 19:40:33 +01:00
DL6ER 4fe037812e
Merge pull request #1717 from pi-hole/fix/no_swap
Ensure %used is 0.0 if no swap is used
2023-10-31 14:36:44 +01:00
DL6ER 2a0520d552
Ensure %used of zero if no swap is used
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-31 12:20:13 +01:00
DL6ER cc4f99e3f2
Add most recent CivetWeb patch
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-31 08:36:34 +01:00
DL6ER ebb27741b1
Allow extended ASCII characters in URIs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-31 08:35:31 +01:00
DL6ER 2639b6cb93
Merge pull request #1713 from pi-hole/fix/API_arrays
API tests: Implement deep-recursion of arrays
2023-10-31 05:47:54 +01:00
DL6ER bfd084e3e9
Add a comment about the new behavior to the API specs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-30 10:05:38 +01:00
DL6ER e0c55ce5b5
Convert searched domains to punycode (if applicable) and lowercase them
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-30 10:00:11 +01:00
DL6ER 64870b7d8b
Merge pull request #1714 from pi-hole/update/tre
Update embedded tre-regex engine
2023-10-30 08:57:08 +01:00
Tom Rushworth 132397675e
Fix some macro re-definitions on MacOS when using the system regex ABI.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-30 08:05:22 +01:00
Tom Rushworth abd333420b
Hand merge of PR#90 from h3xx/fix-compilation-error-when-using-system-regex.h. This includes some additional changes by trushworth to allow GNU C's refinements to regex.h to build without warnings.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-30 08:05:22 +01:00
DL6ER 52dadacdbe
Incorporate most recent changes from upstream tre-regex repository
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-30 08:05:22 +01:00
DL6ER 21ecfa3da5
GET /api/network/devices .devices.X.macVendor may be NULL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-30 07:57:17 +01:00
DL6ER f4e6dfc061
Implement deep-recursion of API arrays
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-30 07:42:51 +01:00
DL6ER 4fea41c4d9
Add missing .sessions.app boolean to the docs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-30 05:40:44 +01:00
DL6ER 6b5f58dbf8
Merge pull request #1711 from pi-hole/fix/query-table
Improve query table parsing warnings
2023-10-30 05:31:15 +01:00
DL6ER 1600fd06d8
Merge pull request #1705 from pi-hole/new/app_password
Add app password support
2023-10-29 19:43:47 +01:00
DL6ER 1d3e574f9c
Merge pull request #1708 from pi-hole/tweak/setupvars
Clarify comment on setupVars.conf file
2023-10-29 19:12:22 +01:00
DL6ER f5fdc5d7fd
Merge pull request #1706 from pi-hole/fix/env_vars_put_delete
PUT and DELETE on forced config items are errors
2023-10-29 19:12:09 +01:00
Adam Warner 9cdb0828cf
Merge pull request #1707 from pi-hole/tweak/env_semicolon
Env var arrays are ;-delimited
2023-10-29 17:48:40 +00:00
DL6ER dd6fb21a8d
Clarify comment on setupVars.conf file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-29 18:02:34 +01:00
DL6ER 86a20fb834
Env var arrays are ;-delimited
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-29 18:00:14 +01:00
DL6ER 5af48abf20
PUT and DELETE on config items which are forced by env variable should be rejected with 400 Bad Request + explanation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-29 17:57:19 +01:00
DL6ER a35de09a60
Managing DHCP leases is only possible when the DHCP server is enabled
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-29 07:16:39 +01:00
DL6ER 692787ef93
Merge pull request #1702 from pi-hole/fix/empty_password
Fix setting empty password
2023-10-28 18:56:06 +02:00
DL6ER 2c0c5a9185
Fix small logic bug preventing setting an empty (= no) password via API/CLI/file. So far, it was only possible by directly interacting with .pwhash
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-28 18:19:50 +02:00
DL6ER 0700bf0f9d
Merge pull request #1689 from pi-hole/new/keep_sessions
Backup and restore API sessions to/from database
2023-10-28 17:33:16 +02:00
DL6ER 1d611309f8
Merge pull request #1700 from pi-hole/docs/api_auth_401
Fix API GET /api/auth documentation
2023-10-28 17:32:48 +02:00
DL6ER 33798b0256
Merge pull request #1698 from pi-hole/fix/config_put_delete
PUT/DELETE /api/config fixes
2023-10-28 17:32:24 +02:00
Adam Warner baea4a86a9
Merge pull request #1693 from pi-hole/new/env_vars_readonly
Config items set via environment variables are read-only
2023-10-28 13:39:16 +01:00
yubiuser 76e4cd98e4
Merge pull request #1701 from pi-hole/dependabot-github_actions-development-v6-actions-setup-node-4
Bump actions/setup-node from 3 to 4
2023-10-28 13:59:46 +02:00
dependabot[bot] ac9c011afa
Bump actions/setup-node from 3 to 4
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-28 10:16:22 +00:00
DL6ER c367d442dd
Documentation-only change: Add 401 being returned by the API for unauthorized access to GET /api/auth and add a few more examples
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-27 16:30:51 +02:00
DL6ER c7f50a372a
Merge pull request #1629 from pi-hole/tweak/domain_array
Add ability to specify domains, lists, clients and group names as arrays
2023-10-26 22:34:33 +02:00
DL6ER 0b4abb2bd3
Optimize status and reply handling in the code. Add status object to /api/stats/summary
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-26 19:13:57 +02:00
DL6ER c4fd7dd04f
Do not issue a warning if encountering a recycled client during periodic name resolution
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-26 18:15:06 +02:00
DL6ER fab6865bff
Merge pull request #1695 from pi-hole/tweak/optimize_list_manipulation
Use UPSERT instead of special REPLACE INTO statements to update exist…
2023-10-26 17:06:23 +02:00
DL6ER e994d63bda
Merge pull request #1696 from pi-hole/tweak/retry-action
Retry failed FTL builds
2023-10-26 17:06:14 +02:00
DL6ER 8df49a51c8
Fix broken internal dnsmasq config test
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-26 00:13:17 +02:00
DL6ER abf0b735a4
Return empty object with code as specified in the API when calling PUT/DELETE /api/config/...
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-26 00:07:45 +02:00
DL6ER f2960cb703
Use more precise examples in the API documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-26 00:03:59 +02:00
DL6ER 9dbe1071ef
Free regex after successful test compilation in API lists
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-26 00:01:13 +02:00
DL6ER a7ba71447c
Try retry-action
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-23 23:21:18 +02:00
DL6ER 87272c2178
Use UPSERT instead of special REPLACE INTO statements to update existing group, adlist, domainlist, and client rows in the gravity database
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-23 22:35:14 +02:00
DL6ER 1dc4449a34
Improve recycler reporting
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-23 21:47:36 +02:00
DL6ER e741ba4c94
Add app password support
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-23 18:13:12 +02:00
DL6ER 2aff05c78a
Reuse existing shared clients, domains, and cache memory slots which are not referenced by any active query within the past 24 hours. Furthermore, always scan the shared strings and reuse them before allocating new memory with the same content
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-23 18:13:12 +02:00
DL6ER 88db8bb144
Use secure_delete mode to ensure the backed up sessions are overwritten with zeros after restore
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-22 23:33:34 +02:00
DL6ER f9a69a06ac
Store sessions only momentarily in between FTL restarts
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-22 23:32:11 +02:00
DL6ER 8664165ebc
Merge pull request #1691 from pi-hole/fix/rotation_permissions
Explicitly chown all rotated files
2023-10-22 23:16:05 +02:00
DL6ER 29274ad411
Config items set via environment variables cannot be changed, they remain readonly for the entire lifespan of the FTL process
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-22 23:11:52 +02:00
DL6ER 492759ac03
Merge pull request #1687 from pi-hole/tweak/parseList_sync_journal
Minor speedup for parseList (pedal to the metal)
2023-10-22 22:47:52 +02:00
DL6ER 3d571ba862
Add missing CAP_CHOWN to CMakeLists install target
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-22 22:44:29 +02:00
DL6ER f2426ff2f4
Explicitly chown all rotated files to pihole:pihole
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-22 21:23:30 +02:00
DL6ER fb76ea0968
Merge pull request #1690 from pi-hole/tweak/env_semicolon
Allow both commas and semicolons for separating array env var entries
2023-10-22 19:02:36 +02:00
DL6ER 13950825f9
Allow both commans and semicolons for separating array env var entries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-22 18:37:36 +02:00
DL6ER a2a8787239
Add new config option webserver.api.session.restore defaulting to true and move existing config option webserver.api.sessionTimeout -> webserver.api.session.timeout
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-22 08:51:11 +02:00
DL6ER 85b5c94858
Add save/restore of API sessions to the database to avoid a forced logout on FTL restarts. This updated the database to version 15
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-22 08:44:10 +02:00
DL6ER 71685f3926
Merge pull request #1688 from pi-hole/tweak/active_clients
Include number of active clients in summary
2023-10-22 08:04:36 +02:00
DL6ER 705d366e05
Merge pull request #1677 from pi-hole/fix/privacy_restart
Always restart FTL when changing the privacy level
2023-10-21 19:46:06 +02:00
DL6ER c951e253a5
Include number of active clients in the response of GET /api/stats/summary
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-21 18:44:38 +02:00
DL6ER 657052a303
Log restarting reason
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-21 14:43:15 +02:00
DL6ER 7aaccbf29a
Immediately restart FTL if requested, do not wait for the DNS loop
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-21 13:29:30 +02:00
DL6ER f798e569a6
Merge pull request #1686 from pi-hole/dependabot-github_actions-development-v6-actions-checkout-4.1.1
Bump actions/checkout from 4.1.0 to 4.1.1
2023-10-21 13:20:41 +02:00
DL6ER b458bd5677
Disable journaling and synchronous mode when building the gravity.db file in parseList
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-21 13:03:59 +02:00
dependabot[bot] cccca0b201
Bump actions/checkout from 4.1.0 to 4.1.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-21 10:46:53 +00:00
DL6ER 0fb16c2745
Merge pull request #1682 from pi-hole/fix/database_upgrade_transactions
Ensure database upgrades use transactions
2023-10-21 09:52:30 +02:00
DL6ER 66a618678d
Merge pull request #1683 from pi-hole/fix/network_clients_heap
Fix possible heap overflow
2023-10-21 00:35:22 +02:00
DL6ER b88a249d36
Update tests to reflect the new database version 14
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-20 19:53:58 +02:00
DL6ER da3a70da0c
Merge pull request #1681 from pi-hole/drop/debian
Remove tests/builds on Debian builders
2023-10-20 19:33:14 +02:00
Adam Warner 4eca25e2a5
Merge pull request #1680 from pi-hole/fix/gravity_dash_dot
Fix regression of #1667
2023-10-20 14:57:01 +01:00
Christian König b97828d8a9
Remove tests/build on debian builders
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-10-20 13:28:20 +02:00
Adam Warner 4c6dadde71
Merge pull request #1679 from pi-hole/new/envvars
Add environmental variables support
2023-10-20 11:13:06 +01:00
DL6ER 2bd4e6b695
Subdomains beginning and ending in dash are actually fine (pattern [a-z0-9_-]{0,63}\\.), move the corresponding tests to a concluding TLD check region (pattern [a-z0-9][a-z0-9-]{0,61}[a-z0-9])
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-19 19:40:40 +02:00
DL6ER 74432a9476
Merge pull request #1674 from pi-hole/tweak/x509_san
Add SAN field into self-generated certificates
2023-10-18 22:48:40 +02:00
DL6ER d00c9df972
Update comment in src/webserver/x509.c
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-10-18 22:47:55 +02:00
DL6ER 04269f3aaa
Merge pull request #1678 from pi-hole/new/query_log_wildcards
Add wildcard support for the server-side Query Log
2023-10-18 22:46:28 +02:00
DL6ER ce9fd82a3b
Merge pull request #1675 from pi-hole/update/dnsmasq
Update embedded dnsmasq
2023-10-18 19:37:40 +02:00
DL6ER 6f8bc4ee82
Implement setting API password via env variable
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-18 19:23:05 +02:00
DL6ER bd8a975348
Merge pull request #1672 from pi-hole/fix/api_settings
Fix a few API config issues
2023-10-18 18:55:58 +02:00
DL6ER 434d944a07
Also add pi.hole as alternative subject if domain != "pi.hole"
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-18 16:52:11 +02:00
DL6ER 188ddf66e6
Merge pull request #1666 from pi-hole/tweak/docs_host_port_protocol
Minor integrated docs changes
2023-10-17 22:09:03 +02:00
DL6ER 8bb7e57968
Merge branch 'development-v6' into tweak/domain_array 2023-10-17 21:39:03 +02:00
DL6ER 7487e2ef8f
Merge branch 'development-v6' into new/queryLogRegex
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 21:38:44 +02:00
DL6ER 74417de091
Merge pull request #1673 from pi-hole/uodate/ftl-build_v2.3
Update ftl-build containers
2023-10-17 21:27:57 +02:00
DL6ER 0e81faadb2
Add SAN filed into self-generated X.509 TLS certificates. It is mandatory since RFCs 2818 and 3280
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 21:27:13 +02:00
DL6ER 0647b064f3
Use latest ftl-build containers to incorporate latest mbedtls release
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 21:01:57 +02:00
DL6ER 704afe351f
Tweak misc.privacylevel to also accept string numbers. This can happen when using a drowdown select as done, e.g., on the web interface -> System-> Settings -> All settings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 17:03:21 +02:00
DL6ER 8501577092
Ensure we are using the same constants everywhere for our enums
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 17:00:51 +02:00
DL6ER 306cdce260
Fix detection of unchanged password. This fixes a forced invalidation of all currently active API session on every config change because every saving was misinterpreted as a password change.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 16:54:11 +02:00
DL6ER b853e2a855
Add Adam mode for FTLCONF_ENV_ONLY=true
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 16:53:12 +02:00
DL6ER c193c4a8e7
Should check the env's value, not its key
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 16:53:12 +02:00
DL6ER f80044c3e2
Make FTL read and parse FTLCONF_* environmental variables. If they exist, they take precedence over config file values. The config file is updated from environmental variables so any changes can be followed therein.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 16:53:12 +02:00
DL6ER 529d7f7a30
Merge pull request #1671 from pi-hole/tweak/sort_by_time
Add support for sorting Query Log by reply time
2023-10-16 23:42:36 +02:00
DL6ER 4f702355c0
Merge pull request #1665 from pi-hole/new/search_results
Add number of matches into /api/search/{domain} results
2023-10-16 23:21:17 +02:00
DL6ER 3502be015b
Merge pull request #1668 from pi-hole/fix/redirect_admin_slash
Add /admin -> /admin/ redirect handler
2023-10-16 23:21:01 +02:00
DL6ER 1e719ace44
Merge pull request #1670 from pi-hole/tweak/cipher_suite
Initialize mbedtls with default instead of SuiteB presets
2023-10-16 23:16:01 +02:00
DL6ER 3167d90b52
Merge pull request #1669 from pi-hole/fix/empty_conf_array_elems
Skip empty elements in config arrays both when reading and writing
2023-10-16 21:06:22 +02:00
DL6ER 0823207657
Merge pull request #1667 from pi-hole/tweak/gravity_booster
Run gravity parseList without regex
2023-10-16 17:14:27 +02:00
RD WebDesign e43a67464c
Design changes on the API page
- apply the same color to every button and add hover effect;
- change header and fotter background;
- use display:flex on header and footer to better fit items;
- separate theme buttons from style buttons (on the footer);
- keeping visible outline to help navigation for visual impaired users;
2023-10-16 01:39:45 -03:00
DL6ER c818adfedd
Install safety-measured to prevent possible heap overflow in the network table processing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 16:55:08 +02:00
DL6ER e18ac693fa
Gravity enforces that there must be at least two labels (i.e. one dot) so TLD-blocking is actually not possible
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 16:37:01 +02:00
DL6ER a2a2f7a35d
Add label length check
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 16:21:21 +02:00
DL6ER a13ecf4d23
Reject > 255 character domains
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 15:45:54 +02:00
DL6ER efa118ebf1
Another minor speed improvement
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 14:35:21 +02:00
DL6ER 7e1d55ee71
Make HTTPS scheme the default case and fix paths to allow loading of the favicon
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 12:52:28 +02:00
DL6ER 71c0d66338
Remove semicolon actually preventing the correct filtering on regex IDs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 11:31:25 +02:00
DL6ER ab64721621
Include .results.total (will never be clipped) and remove .total (may have been clipped)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 11:23:20 +02:00
DL6ER ae36ddc686
Skip empty elements in config arrays both when reading and writing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 11:12:15 +02:00
DL6ER 1556b72abe
Add /admin -> /admin/ redirect handler
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-15 10:44:54 +02:00
DL6ER ba325b0e2c
Add number of matches into /api/search/{domain} results
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-14 21:06:48 +02:00
DL6ER c5067b3a60
Merge pull request #1664 from pi-hole/new/error_pages
Add error pages support and extended webserver debugging option
2023-10-14 20:44:48 +02:00
DL6ER 1cfe77f703
Merge pull request #1662 from pi-hole/tweak/searchAPIauth
Add new config option webserver.api.searchAPIauth
2023-10-14 16:21:47 +02:00
DL6ER 8df1fdcd3f
Add error_pages option to CivetWeb causing the webserver to search in the web interface's root for error pages
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-14 15:50:43 +02:00
DL6ER 6dc0cb2b6a
Add new CivetWeb patch
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-14 15:40:21 +02:00
DL6ER 445aed4fd5
Log debug messages to webserver.log when debug.webserver is true
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-14 15:39:21 +02:00
DL6ER 425bb5a8ec
Add debug.webserver
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-14 15:31:24 +02:00
DL6ER a61d9554d4
Merge pull request #1663 from pi-hole/tweak/civetweb_error
Add error reporting during webserver initialization
2023-10-13 22:09:46 +02:00
DL6ER a7417a62b7
Only hint to checking the webserver log file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-13 21:36:57 +02:00
DL6ER 5d55e13cbd
Enquote configureable strings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-13 20:54:31 +02:00
DL6ER 6636916f3c
Add hint to check the webserver log (and where to find it)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-13 20:53:37 +02:00
DL6ER 382469ec91
Merge pull request #1660 from pi-hole/new/query_log_sorting
Implement Query Log sorting (server-side pagination)
2023-10-13 20:28:02 +02:00
DL6ER bc72fe6772
Merge branch 'development-v6' into tweak/domain_array 2023-10-13 20:12:03 +02:00
DL6ER b017b02486
Add error reporting during library initialization
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-13 19:57:35 +02:00
DL6ER 5542e35256
Update src/api/queries.c
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-13 18:03:02 +02:00
DL6ER 78a424b5af
Merge pull request #1659 from pi-hole/fix/domains
Fix domain type and kind not being added for certain /api/domains calls
2023-10-13 09:57:58 +02:00
DL6ER c8c70f4800
Merge pull request #1656 from pi-hole/new/dns_listeningMode_NONE
Add new option dns.listeningMode = NONE
2023-10-12 18:15:49 +02:00
DL6ER e432842592
Merge pull request #1657 from pi-hole/fix/tls_cert_info
Fix wording of info message concerning self-generated TLS certificate
2023-10-12 18:15:40 +02:00
DL6ER 2a9b9906de
Merge pull request #1655 from pi-hole/update/sqlite_3.43.2
Update SQLite3 to 3.43.2
2023-10-12 18:15:26 +02:00
DL6ER 5ef6111840
Simplify the authentication handling and fix the logic. The compiler optimizes this explicity away (checked in assembler)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-12 18:12:27 +02:00
DL6ER 64baa94395
Add new config option webserver.api.searchAPIauth defaulting to false
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-13 18:51:49 +02:00
DL6ER 71f384f999
Fix domain type and kind not being added for certain /api/domains calls
This is a regression of https://github.com/pi-hole/FTL/pull/1649

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-12 18:12:27 +02:00
DL6ER f4e0a904ac
Implement Query Log sorting (server-side pagination)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-12 04:55:43 +02:00
DL6ER 39a6324426
Fix wording of info message concerning self-generated TLS certificate. Before this commit, we incorrectly logged that we created a TLS certificate not only when we really did this but also after each restart when simply using it.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-11 22:17:46 +02:00
DL6ER 3630bb1adb
Add new option dns.listeningMode = NONE
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-11 22:08:31 +02:00
DL6ER 05a4f72828
Update SQLite3 to 3.43.2
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-11 20:21:10 +02:00
DL6ER 80d4a0efec
Merge pull request #1653 from pi-hole/tweak/api_auth_https
Add /api/info/login
2023-10-10 20:36:04 +02:00
DL6ER c30418bddc
Merge branch 'development-v6' into fix/privacy_restart
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
DL6ER e923e88c33
The most recent commit improved config file rereading in such a way that the DNS cache can stay intact for most changes. This needs to be reflected in the tests, expecting only one instead of two compilations of the regex filters.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-19 21:44:22 +02:00
DL6ER c3a0e9aec1
Restart FTL when privacy lever is decreased but not when it is increased
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-19 19:33:55 +02:00
DL6ER d73d138087
Be crystal clear that the FLAG_RESTART_DNSMASQ is actually a full FLAG_RESTART_FTL flag (it hasn't always been like that)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-18 16:56:11 +02:00
DL6ER 9d5c80b227
Always restart FTL when changing the privacy level
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-18 16:54:24 +02:00
DL6ER 36965ffd7b
Add descriptions to the ftl database table. This updates the database to version 14
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
DL6ER 17eb3b3f6f
Simplify timerange filtering and fix total number of queries in the last 24 hours being used even when a limited timeframe has been requested
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
DL6ER c5a999f810
Update dnsmasq version
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
DL6ER 2950424b8b
Add new dnsmasq warning to CI test
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-17 22:31:19 +02:00
DL6ER 2edeec57e2
Merge remote-tracking branch 'origin/development-v6' into update/dnsmasq 2023-10-17 22:30:29 +02:00
Simon Kelley e1de9c2b98
Fix bad reply to DHCPCONFIRM messages (wrong message type).
Thanks to renmingshuai <renmingshuai@huawei.com> for
spotting the error, and making the initial patch.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-12 18:12:27 +02:00
Simon Kelley eedb74a9bc
Work around possible Linux bug with VRF interfaces and DHCPv6.
The scope_id in the source address of recieved packets gets set
to the index of the VRF interface, not the slave. Fortunately,
the interface index returned by packetinfo is correct so we use
instead.

Thanks to Luci Stanescu <luci@safebits.tech> for characterising this.

Ref: https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2023q4/017276.html
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-12 18:12:27 +02:00
DL6ER c9bd317ea2
Merge branch 'development-v6' into update/dnsmasq 2023-10-12 18:12:27 +02:00
DL6ER 2c16f9ea11
Add support for sorting Query Log by reply time
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
DL6ER 3c9b123e44
Initialize mbedtls with default instead of SuiteB presets
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
DL6ER 05bc297c39
Run parseList without regex
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
DL6ER 13f0ed7335
Ensure all database upgrade steps are wrapped into transactions. When the database upgrade is interrupted (FTL is killed, or crashed or power loss, ...), then the rollback journal file is left on disk. The next time another application attempts to open the database file, it notices the presence of the abandoned rollback journal (we call it a "hot journal" in this circumstance) and uses the information in the journal to restore the database to its state prior to the start of the incomplete transaction. This fixes sporadic issues with only partially initialized databases mostly seen on devices with unstable power supply.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
DL6ER 10fd60b399
Add wildcard support for server-side Query Log (domain, client-ip, client-by-name, upstream)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
Simon Kelley 52b11b3236
Cache zero-TTL DNS replies when stale-caching is enabled.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
Simon Kelley f5235d0262
Fix memory leak in arbitrary-RR caching.
If the cache insertion process fails for any reason, any
blockdata storage allocated needs to be freed.

Thanks to Damian Sawicki for spotting the problem and
supplying patches against earlier releases. This patch by SRK,
and any bugs are his.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:28:14 +02:00
DL6ER 3649badc42
Merge branch 'development-v6' into update/dnsmasq 2023-10-10 19:28:14 +02:00
DL6ER 231f8f876e
Fix endpoint security requirements for /info/login, /auth/totp, /info/client
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-10 19:21:14 +02:00
DL6ER f9e6e55a25
Merge pull request #1654 from pi-hole/tweak/x509_domain
Allow generating X.509 TLS certificates for arbitrary domain names
2023-10-09 21:48:56 +02:00
DL6ER 687e489e2a
Allow generating X.509 TLS certificates for arbitrary domain names. When auto-generated, FTL uses the config value webserver.domain defaulting to "pi.hole".
The generated certificate may be checked using, e.g. openssl x509 -in /etc/pihole/tls.pem -text -noout

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-09 16:55:12 +02:00
DL6ER b60e8bdc2f
Add /api/info/login and remove some parts from /api/auth
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 22:55:24 +02:00
DL6ER 0edd0bf4cb
Merge pull request #1651 from pi-hole/tweak/redirections
Simplify/fix redirection rules
2023-10-08 22:40:02 +02:00
DL6ER cd16053c08
Include processed object only in POST and PUT responses
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 22:38:39 +02:00
DL6ER 970695b65f
Adjust tests to include the new https_port property
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 19:57:35 +02:00
DL6ER 2dadecbc7f
Merge pull request #1649 from pi-hole/tweak/search_abp
Add ABP-support for /api/search
2023-10-08 19:21:33 +02:00
DL6ER d75599c7ab
Merge pull request #1646 from pi-hole/new/performance_test
Add performance tests and prevent online-brute forcing
2023-10-08 14:33:01 +02:00
DL6ER 24baccee3a
Merge branch 'development-v6' into new/queryLogRegex 2023-10-08 09:11:32 +02:00
DL6ER c01567aa4f
Merge branch 'development-v6' into tweak/redirections 2023-10-08 09:11:32 +02:00
DL6ER 23c082b401
Merge branch 'development-v6' into tweak/domain_array
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 09:11:32 +02:00
DL6ER ac056f07eb
Merge branch 'development-v6' into tweak/api_auth_https 2023-10-08 09:11:32 +02:00
DL6ER 678d014a26
Merge branch 'development-v6' into tweak/search_abp
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 09:11:32 +02:00
DL6ER b46d0019bf
Add allocation memory explanation as code comments
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 09:11:32 +02:00
DL6ER 7e5357cbe3
Add lists_processed to all corresponding OpenAPI elements
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 09:11:32 +02:00
DL6ER c4237f1846
Include HTTPS port (if any) in /api/auth response
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 09:11:32 +02:00
DL6ER 3427c04396
Add new Civetweb patch
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 09:11:32 +02:00
DL6ER e41d902b5b
Do not try to guess server hostname in Civetweb when redirecting directory URIs to end with a slash
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 09:11:32 +02:00
DL6ER 97cfa69557
Remove unnecessary redirection from **/$ -> **$
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 09:11:32 +02:00
DL6ER 425f791a19
Also return a list of items that have been successfully added
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 09:00:49 +02:00
DL6ER 97cc85553f
Send processed object after list insertion to inform the client how many items have been successfully added, how many have failed and, if, what the individual errors were
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 08:31:58 +02:00
DL6ER 856aae1bef
Add hint to login rate-limiting logging. We also remove the debug logging as there will always be a WARN
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-08 07:40:19 +02:00
DL6ER cc795fba2a
Merge branch 'development-v6' into new/queryLogRegex 2023-10-07 21:58:58 +02:00
DL6ER d8ccdfd5ce
Merge branch 'development-v6' into tweak/domain_array 2023-10-07 21:56:49 +02:00
DL6ER 28358d45aa
Spellcheck corrections
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 21:55:32 +02:00
DL6ER db1fa3a106
Searchterm should simply be called domain
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 21:52:42 +02:00
DL6ER 470689da47
Add antigravity and /api/search related CI tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 21:52:42 +02:00
DL6ER 88e7b1e5dc
Add ABP-support for /api/search and add new optional debug parameter
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 21:52:39 +02:00
DL6ER b21475fcb4
Better scale performance index
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 19:59:32 +02:00
DL6ER a8839aa14e
Improve the test by setting the T and S costs in the matrix to the same and computing a final average with a reliable error estimate (standard deviation = the square root of the variation of the performance index)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 19:59:32 +02:00
DL6ER 4658995759
Be more explicit in the variable definition (what is constant)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 19:59:32 +02:00
DL6ER 2141db3d64
Add rate-limiting on password login attempts
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 19:59:32 +02:00
DL6ER de7227347b
Run performance test during CI tests run
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 19:59:32 +02:00
DL6ER 0adf71d6bc
Add pihole-FTL --perf
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-07 19:59:30 +02:00
renmingshuai f546935731
Fix memory leak when using --dhcp-optsfile with DHCPv6 options.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-02 19:46:30 +02:00
Simon Kelley 829f9aa732
Remove two-decade old hack.
answer_request() builds answers in the same packet buffer
as the request.  This means that any EDNS0 header from the
original request is overwritten. If the answer is in cache, that's
fine: dnsmasq adds its own EDNS0 header, but if the cache lookup fails
partially and the request needs to be sent upstream, it's a problem.

This was fixed a long, long time ago by running the cache
lookup twice if the request included an EDNS0 header. The first time,
nothing would be written to the answer packet, nad if the cache
lookup failed, the untouched question packet was still available
to forward upstream. If cache lookup succeeded, the whole thing
was done again, this time writing the data into the reply packet.
In a world where EDNS0 was rare and so was memory, this was a
reasonable solution. Today EDNS0 is ubiquitous so basically
every query is being looked up twice in the cache. There's also
the problem that any code change which makes successive cache lookups
for a query possibly return different answers adds a subtle hidden
bug, because this hack depends on absence of that behaviour.

This commit removes the lookup-twice hack entirely. answer_request()
can now return zero and overwrite the question packet. The code which
was previously added to support stale caching by saving a copy of the
query in the block-storage system is extended to always be active.
This handles the case where answer_request() returns no answer OR
a stale answer and a copy of the original query is needed to forward
upstream.
2023-10-02 19:45:25 +02:00
DL6ER c03882667d
Merge pull request #1650 from pi-hole/update/ftl-build
Update ftl-build container version
2023-10-01 22:33:24 +02:00
DL6ER d2597461ed
Update ftl-build container version
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-10-01 16:40:43 +02:00
DL6ER 299850e884
Merge branch 'development-v6' into new/queryLogRegex 2023-09-29 22:32:13 +02:00
DL6ER 5e9327783e
Merge branch 'development-v6' into tweak/domain_array
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-29 22:30:57 +02:00
DL6ER d83bf0fc80
Merge pull request #1647 from pi-hole/tweak/themes
Improve web theme handling
2023-09-29 20:43:08 +02:00
Adam Warner ff8f708948
Merge pull request #1579 from pi-hole/new/antigravity
Add antigravity (subscribed allowlists with wildcard support)
2023-09-29 19:09:27 +01:00
DL6ER 590c1214a9
Optimize ABP comparator generation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-29 18:42:50 +02:00
DL6ER 9a1dd1111c
Merge branch 'development-v6' into new/antigravity 2023-09-28 21:56:51 +02:00
DL6ER fcca595845
Check list type for all request methods (not only POST)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-28 20:17:28 +02:00
DL6ER 914633badd
Implement correct ABP-syntax for antigravity in performance optimization in parse-list
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-27 21:28:00 +02:00
DL6ER b0c452ba9f
Allow only @@||^ in antigravity parse-list runs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-27 19:14:38 +02:00
DL6ER 667a98230f
Use @@||xyz^ instead of ||xyz^ for antigravity list entries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-26 22:46:20 +02:00
DL6ER 858b0f1ed9
Provide theme details through Lua function pihole.webtheme() now returning a full table instead of only a string. Everything concerning theme definition is moved into api/themes.{c,h}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-26 10:13:34 +02:00
yubiuser a194ee15b6
Merge pull request #1645 from pi-hole/dependabot-github_actions-development-v6-actions-checkout-4.1.0
Bump actions/checkout from 4.0.0 to 4.1.0
2023-09-23 13:15:58 +02:00
dependabot[bot] 8ac8acd966
Bump actions/checkout from 4.0.0 to 4.1.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-23 10:17:47 +00:00
DL6ER f2e234f5f3
Merge pull request #1643 from pi-hole/tweak/ftl-build
use tagged v2.0 version of ftl-build
2023-09-21 23:18:46 +02:00
Adam Warner 2512494b02
use tagged v2.0 version of ftl-build 2023-09-21 01:29:26 +01:00
DL6ER 35738920ac
Merge pull request #1639 from pi-hole/workflow_dispatch
Allow build/deploy workflow to be triggered by workflow_dispatch
2023-09-16 23:04:55 +02:00
yubiuser 42139ca3c7
Merge pull request #1641 from pi-hole/dependabot-github_actions-development-v6-docker-build-push-action-5.0.0
Bump docker/build-push-action from 4.2.1 to 5.0.0
2023-09-16 15:20:07 +02:00
yubiuser 19af53af06
Merge pull request #1640 from pi-hole/dependabot-github_actions-development-v6-docker-setup-buildx-action-3.0.0
Bump docker/setup-buildx-action from 2.10.0 to 3.0.0
2023-09-16 15:19:37 +02:00
dependabot[bot] ca3f5ebfc3
Bump docker/build-push-action from 4.2.1 to 5.0.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.2.1 to 5.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.2.1...v5.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-16 10:05:31 +00:00
dependabot[bot] aaa22c51ab
Bump docker/setup-buildx-action from 2.10.0 to 3.0.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.10.0 to 3.0.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.10.0...v3.0.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-16 10:05:27 +00:00
DL6ER 64e20b0800
Further simplify antigravity over gravity priority code
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 21:39:04 +02:00
DL6ER 40d22c97cf
First check against antigravity. Check gravity only if no antogravity match was found
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 21:39:04 +02:00
DL6ER 4abc11afb3
Satisfy foreign key constraints when removing subscribed allowlists by deleting related domains in antigravity
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 21:39:04 +02:00
DL6ER 444fd4c5c9
Ensure subscribed list type is (re)stored to/from database table
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 21:39:04 +02:00
DL6ER f8b8e63044
Add antigravity (subscribed allowlists with wildcard support)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 21:39:04 +02:00
Christian König ff97793f60
Allow build workflow to be trigged by workflow_dispatch
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-09-15 20:47:58 +02:00
DL6ER d37c0d10e5
Add regex filtering support for domains on the Query Log (new config option webserver.api.excludeRegex)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 17:37:13 +02:00
DL6ER 1161463a60
Fix spellcheck error
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 17:35:29 +02:00
DL6ER 35526606a0
Ensure we free the row.items object only when we have actually allocated it (not if we are merely using a copy)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 17:35:29 +02:00
DL6ER eaaf39862d
Free row.items only after having used row.item one last time
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 17:35:29 +02:00
DL6ER d4d7798834
Check eeach element for spaces
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 17:35:29 +02:00
DL6ER 2c516d1d8c
Add ability to specify domains, lists, clients and group names as arrays for creating multiple with the same properties (comment, groups, ...) at the same time
Signed-off-by: Dominik Derigs <dl6er@dl6er.de>
2023-09-15 17:35:29 +02:00
DL6ER c05e23e289
Warn about spaces and newlines in domain/URLs when adding new items to Pi-hole's lists
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-15 17:35:27 +02:00
DL6ER 392db953b3
Merge pull request #1607 from pi-hole/tweak/query_details
Provide regex ID for API
2023-09-15 17:25:17 +02:00
DL6ER 9079c4e68d
Merge pull request #1630 from pi-hole/tweak/clr_sess_on_pw_change
Clear web session on external password change
2023-09-15 17:21:10 +02:00
DL6ER 4f1a9dd2e4
Fix CNAME regex ID propagation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-14 18:51:03 +02:00
DL6ER 9704455591
Always set regex_id when available
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-13 16:44:40 +02:00
DL6ER 11a0080eb1
Merge pull request #1637 from pi-hole/update/sqlite_3.43.1
Update embedded SQLite to 3.43.1
2023-09-13 11:32:26 +02:00
DL6ER 94d08c7e2e
Apply Pi-hole-specific changes to the SQLite3 engine
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-12 20:38:37 +02:00
DL6ER 272918c925
Update embedded SQLite3 engine to version 3.43.1
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-12 20:30:49 +02:00
DL6ER 880ea49266
Merge pull request #1631 from pi-hole/update/sqlite_3.43.0
Update embedded SQLite to 3.43.0
2023-09-10 16:38:55 +02:00
yubiuser e50bea4f12
Merge pull request #1635 from pi-hole/dependabot-github_actions-development-v6-actions-upload-artifact-3.1.3
Bump actions/upload-artifact from 3.1.2 to 3.1.3
2023-09-09 19:57:52 +02:00
yubiuser 48db359556
Merge pull request #1636 from pi-hole/dependabot-github_actions-development-v6-actions-checkout-4.0.0
Bump actions/checkout from 3.6.0 to 4.0.0
2023-09-09 19:56:56 +02:00
yubiuser e1db67f3f7
Merge pull request #1634 from pi-hole/dependabot-github_actions-development-v6-docker-build-push-action-4.2.1
Bump docker/build-push-action from 4.1.1 to 4.2.1
2023-09-09 19:56:06 +02:00
dependabot[bot] 0cbb8aa43c
Bump actions/checkout from 3.6.0 to 4.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.6.0...v4.0.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-09 10:49:55 +00:00
dependabot[bot] 9e6bae44d3
Bump actions/upload-artifact from 3.1.2 to 3.1.3
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3.1.2...v3.1.3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-09 10:49:52 +00:00
dependabot[bot] d4c3a54fd4
Bump docker/build-push-action from 4.1.1 to 4.2.1
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.1.1 to 4.2.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.1.1...v4.2.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-09 10:49:49 +00:00
DL6ER 8a8dd06832
Compare password hashes, not password (we don't store them in memory)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-07 19:53:23 +02:00
DL6ER bb34b1166e
Apply Pi-hole specific changes to SQLite3 code
Signed-off-by: Dominik Derigs <dl6er@dl6er.de>
2023-09-07 00:47:46 +02:00
DL6ER 384dc5390f
Update embedded SQLite3 engine to 3.43.0
Signed-off-by: Dominik Derigs <dl6er@dl6er.de>
2023-09-07 00:46:38 +02:00
DL6ER 7772188498
Simplify deletion of sessions
Signed-off-by: Dominik Derigs <dl6er@dl6er.de>
2023-09-04 22:11:03 +02:00
DL6ER 4e40e40ade
Compare password hashes after config reload and invalidate all currently active web sessions if they are different
Signed-off-by: Dominik Derigs <dl6er@dl6er.de>
2023-09-04 21:58:06 +02:00
DL6ER cb33ed3d54
We do not need to invalidate web sessions on CLI changes as we are not serving the web interface in this case
Signed-off-by: Dominik Derigs <dl6er@dl6er.de>
2023-09-04 21:45:38 +02:00
DL6ER 718ba53259
Merge pull request #1628 from pi-hole/update/dnsmasq
Update embedded dnsmasq to latest master
2023-09-04 11:39:01 +02:00
DL6ER 83e2cceda5
Merge pull request #1627 from pi-hole/new/pcap
Add config option to enable packet dumping to PCAP file
2023-09-03 19:57:30 +02:00
DL6ER b8d1db64f9
Merge branch 'development-v6' into update/dnsmasq 2023-09-03 06:57:06 +02:00
Simon Kelley b012828544
Fix problem with arbitrary RR caching.
Caching an answer which has more that one RR, with at least
one answer being <=13 bytes and at least one being >13 bytes
can screw up the F_KEYTAG flag bit, resulting in the wrong
type of the address union being used and either a bad value
return or a crash in the block code.

Thanks to Dominik Derigs and the Pi-hole project for finding
and characterising this.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-03 06:56:50 +02:00
DL6ER dda95b4645
Fix hostname test
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-02 20:02:52 +02:00
DL6ER 2788fa0137
Add files.pcap to expose integrated packet (PCAP) dumping to a file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-09-02 18:25:34 +02:00
yubiuser 9da3e60533
Merge pull request #1626 from pi-hole/dependabot-github_actions-development-v6-docker-setup-buildx-action-2.10.0
Bump docker/setup-buildx-action from 2.9.1 to 2.10.0
2023-09-02 17:08:38 +02:00
dependabot[bot] a7c32ea908
Bump docker/setup-buildx-action from 2.9.1 to 2.10.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.9.1 to 2.10.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.9.1...v2.10.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-02 10:36:35 +00:00
yubiuser 0705c01625
Merge pull request #1625 from pi-hole/dependabot-github_actions-development-v6-actions-checkout-3.6.0
Bump actions/checkout from 3.5.3 to 3.6.0
2023-08-26 13:16:14 +02:00
dependabot[bot] a990439831
Bump actions/checkout from 3.5.3 to 3.6.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.5.3...v3.6.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-26 10:14:22 +00:00
DL6ER d1b2f03f10
Update src/database/query-table.c
Co-authored-by: yubiuser <ckoenig@posteo.de>
Signed-off-by: DL6ER <DL6ER@users.noreply.github.com>
2023-08-12 09:44:55 +02:00
DL6ER 21956ff325
Merge pull request #1622 from pi-hole/10000_is_enough
Hard limit for searching the adlists is 10,000
2023-08-12 09:42:29 +02:00
Christian König 22954823b5
Hard limit for searching the adlists is 10,000
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-08-09 22:13:34 +02:00
DL6ER 6f77d4ed91
Improve warning messages printed during query table parsing (history import)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-08-06 21:09:46 +02:00
DL6ER 9c4e132bd6
Merge pull request #1618 from pi-hole/fix/DBimport
Always get max query ID from disk
2023-08-05 21:09:22 +02:00
DL6ER c498fb9db2
We need to get the MAX(id) from the on-disk database even if database.DBimport is false
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-08-04 19:42:50 +02:00
DL6ER 28839bfb96
Merge pull request #1616 from pi-hole/fix/api_info_sensors
Fix 404 for GET /api/info/sensors when hwmon isn't available
2023-08-04 06:07:46 +02:00
DL6ER b9688fb81d
Re-add the warning to FTL.log
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-08-03 09:26:24 +02:00
DL6ER 889eb8bc84
GET /api/info/sensors should return no error but simply no values when we cannot parse temperature sensors
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-08-03 09:05:26 +02:00
DL6ER 9b066d9b6c
Merge pull request #1615 from pi-hole/fix/diskdb
Ensure (at least try to!) detach disk database on SQL issues
2023-08-03 08:46:11 +02:00
DL6ER 3f1aa4d29f
Merge pull request #1612 from pi-hole/fix/api_docs
Embed missing file API specs file specs/action.yaml
2023-08-02 19:25:31 +02:00
DL6ER 66bab5cb0d
Merge pull request #1613 from pi-hole/fix/api_type_warning
Fix API config value interpretation warning
2023-08-02 19:02:29 +02:00
DL6ER d1f4f0602f
Fix warning printed when an argument should be floating point but is not a number
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-08-02 18:25:47 +02:00
DL6ER 52c08c42b9
Embed missing file API specs file specs/action.yaml
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-08-02 18:17:38 +02:00
DL6ER 84d2b660a1
Merge pull request #1570 from pi-hole/remove/manpages
Copy missing information from man pihole-FTL to pihole-FTL -h
2023-08-02 16:52:28 +02:00
Christian König e211140598
Tidy up
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-08-02 16:19:19 +02:00
Christian König 3af3abf553
Copy missing information from man pihole-FTL to pihole-FTL -h
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-08-02 16:19:19 +02:00
DL6ER e42779533b
Ensure disk database is detached on SQL issues
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-08-01 21:55:14 +02:00
DL6ER 1c50611cd1
Merge pull request #1609 from pi-hole/tweak/webserver_port
Tweaks for the webserver port
2023-08-01 21:35:17 +02:00
Christian König da544dcbb2
Change default webport to 80
Signed-off-by: Christian König <ckoenig@posteo.de>
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-08-01 15:54:20 +02:00
DL6ER a9d47713e4
Provide regex ID for API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-30 21:49:23 +02:00
DL6ER 70bb611ede
Initialized webserver ports dynamically depending on whether ports 80 and 433 are already taken on a system
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-30 21:48:36 +02:00
DL6ER 4913510f10
Also start TLS webserver by default
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-30 21:48:34 +02:00
DL6ER 4ac9f120f5
Merge pull request #1610 from pi-hole/fix/balloonization
Fix upgrade of the password hash
2023-07-30 21:46:09 +02:00
DL6ER 11ff6b6545
Use the right subroutine when upgrading the existing password to BALLOON hash
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-30 19:46:47 +02:00
DL6ER 60fb480cbb
Merge pull request #1606 from pi-hole/tweak/no_reboot
Remove reboot and poweroff actions from the API
2023-07-28 23:59:43 +02:00
DL6ER e085728e3e
Merge pull request #1601 from pi-hole/fix/redirect_slash
Slash the slash
2023-07-28 23:07:30 +02:00
DL6ER 4ea9c1b444
Remove reboot and poweroff actions from the API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-28 22:46:28 +02:00
DL6ER 55785f1b0c
Simplify code and handle case of missing trailing slashes for index pages properly
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-28 22:36:41 +02:00
DL6ER b1c2c29ce8
Merge pull request #1605 from pi-hole/tweak/query_log_sum
Fix Query Log total number without filtering
2023-07-28 18:44:40 +02:00
DL6ER d6b2b86b04
Tweak criterion we use for deciding if we are filtering on the Query Log or not. This is necessary as DataTables also sends a query string if no filters are applied. This is a direct consequence of PR #1599 which missed this special case
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-28 16:22:29 +02:00
DL6ER f2b32054cf
Merge pull request #1604 from pi-hole/fix/domain_search_N
Fix API parameter parsing on 32bit architectures
2023-07-27 20:55:56 +02:00
DL6ER 1ca6a765f7
Merge pull request #1603 from pi-hole/tweak/TRUNCATED2
Add support for TRUNCATED DNSSEC status (2)
2023-07-27 18:48:16 +02:00
DL6ER 438b5401bb
Actually apply TRUNCATED status when found
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-27 04:10:44 +02:00
DL6ER 723db23703
Use explicitly sized integers (u/int64_t) to ensure consistent operation on both 32 and 64 bit architectures
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-27 04:10:23 +02:00
DL6ER 854af4796d
Use long long for parameter parsing to avoid overflowing for unsigned integers on 32bit architectures
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-27 04:10:23 +02:00
DL6ER b0c31985cb
Log warnings when parameters of invalid type are passed to the API (e.g. a numeric parameter receiving an out-of-bounds or non-numeric argument)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-27 04:10:23 +02:00
DL6ER 2984c64753
Handle index page (= dashboard) correctly
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-26 19:17:24 +02:00
DL6ER 5753b50df4
Merge pull request #1599 from pi-hole/tweak/SSP_sum
Fix total number in Query Log
2023-07-25 18:14:49 +02:00
Adam Warner d24506fbda
Merge pull request #1600 from pi-hole/v6/devcontainer
Tweaks to devcontainer config
2023-07-25 17:04:41 +01:00
DL6ER 9f2834437a
As a consequence of https://github.com/pi-hole/AdminLTE/pull/2655, redirection from the login page to any other page than the dashboard is broken. This PR fixes this in the proper way by redirecting from URIs such as "/admin/queries/" to "/admin/queries" which is where the actual scripts are (they'd have to be in "/admin/queries/index.html" otherwise)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-25 17:42:10 +02:00
Adam Warner 4d2a9f47bf
Mount local users .ssh directory into the dev container in order to use SSH keys for signing off git commits
Signed-off-by: Adam Warner <me@adamwarner.co.uk>
2023-07-25 15:33:43 +00:00
Adam Warner 9f1c3851c5
Switch build container image to use new buildx builder
Signed-off-by: Adam Warner <me@adamwarner.co.uk>
2023-07-25 15:32:31 +00:00
DL6ER fb409801b9
Fix the sum returned for the number of queries in the Query Log when filtering
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-25 16:59:48 +02:00
DL6ER 51872d175f
Merge pull request #1595 from pi-hole/tweak/dnssec_truncated
Add support for TRUNCATED DNSSEC status
2023-07-24 21:41:41 +02:00
DL6ER be59d03847
Add TRUNCATED status to enumeration of valid DNSSEC status codes. Truncated answer can't be validated. If this is an answer to a DNSSEC-generated query, we still need to get the client to retry over TCP, so we return an answer with the TC bit set, even if the actual answer fits into buffer. All the other code is already there, just the display code needs addition.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-22 22:15:52 +02:00
Adam Warner 2469d1f8e5
Merge pull request #1594 from pi-hole/fix/apidocslogin
Fix OpenAPI documentation login
2023-07-21 22:37:20 +01:00
DL6ER b6b08f0c55
Prefer other authentication methods over COOKIE (which needs an CORS token in addition)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-21 21:51:01 +02:00
DL6ER e772442ea7
Use header SID authentication instead of the implicit cookie authentication to circumvent CORS issues
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-18 22:01:58 +02:00
DL6ER 756a688f05
Fix OpenAPI documentation login
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-18 22:00:06 +02:00
DL6ER 5a6d86ac10
Merge pull request #1580 from pi-hole/update/dnsmasq
Update embedded dnsmasq
2023-07-17 05:50:00 +02:00
Adam Warner 4a68427639
Merge pull request #1593 from pi-hole/dependabot-github_actions-development-v6-actions-checkout-3.5.3
Bump actions/checkout from 3.3.0 to 3.5.3
2023-07-16 22:39:47 +01:00
DL6ER 13e3e6759b
Merge pull request #1588 from pi-hole/openAPI/security
Add OpenAPI Authentication and Authorization details for the API
2023-07-14 19:25:55 +02:00
dependabot[bot] 9842bedb5f
Bump actions/checkout from 3.3.0 to 3.5.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3.3.0...v3.5.3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-13 19:47:27 +00:00
DL6ER c1f1387b7d
Merge pull request #1592 from pi-hole/dependabot-github_actions-development-v6-docker-build-push-action-4.1.1
Bump docker/build-push-action from 4.0.0 to 4.1.1
2023-07-13 21:47:08 +02:00
DL6ER f70ce6816d
Merge pull request #1591 from pi-hole/dependabot-github_actions-development-v6-docker-setup-buildx-action-2.9.1
Bump docker/setup-buildx-action from 2.5.0 to 2.9.1
2023-07-13 21:46:44 +02:00
yubiuser 761b3581e6
Merge pull request #1590 from pi-hole/dependabot-github_actions-development-v6-actions-upload-artifact-3.1.2
Bump actions/upload-artifact from 3.1.1 to 3.1.2
2023-07-13 21:10:25 +02:00
dependabot[bot] 4f0a01c6fd
Bump docker/build-push-action from 4.0.0 to 4.1.1
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.0.0 to 4.1.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.0.0...v4.1.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-13 18:59:40 +00:00
dependabot[bot] c559518607
Bump docker/setup-buildx-action from 2.5.0 to 2.9.1
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.5.0 to 2.9.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.5.0...v2.9.1)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-13 18:59:37 +00:00
dependabot[bot] 4348ae8f0b
Bump actions/upload-artifact from 3.1.1 to 3.1.2
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3.1.1...v3.1.2)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-13 18:59:32 +00:00
DL6ER de3e9bf0a5
Fix logo path to use the embedded SVG icon
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-13 20:03:31 +02:00
DL6ER ea40474635
Add OpenAPI Authentication and Authorization details for the API. Every endpoint except GET,POST /api/auth needs authentication
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-13 20:00:11 +02:00
yubiuser bc847ac727
Merge pull request #1585 from pi-hole/tweak/debug_4865165
Remove left-over debug output
2023-07-11 07:16:00 +02:00
DL6ER 628d9d5ebb
Merge pull request #1582 from pi-hole/tweak/chown-config
Chown config file after writing (if root)
2023-07-08 14:55:32 +02:00
DL6ER 1369d2161a
Merge pull request #1581 from pi-hole/fix/adlist_sql
Fix SQL used to update existing adlists via the API
2023-07-08 14:39:55 +02:00
DL6ER 43e85f4609
Merge pull request #1583 from pi-hole/tweak/secp384r1
Change generated elliptic curve from secp521r1 to secp384r1
2023-07-08 14:39:42 +02:00
DL6ER 2dbfc2b481
Fix SQL used to update existing adlists via the API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-08 13:26:30 +02:00
DL6ER 85c2159b1b
Chown config file after writing to avoid file ownership/permission conflicts
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-08 13:25:22 +02:00
DL6ER 206b8f0d5c
Change generated elliptic curve from secp521r1 to secp384r1 because the former is not supported any longer by current Google Chrome (and, presumably, Chromium-based browsers like Edge). The reason for the removal is that NSA Suite B does not *mention* P-521 in the document, leading to Chrome removing support (even though Firefox keeps support for it). Furthermore, FIPS 140-2 also focuses on P-256 and P-384. There is nothing wrong about this curve but with the removal of it, it doesn't make sense to still keep it for automatic certificate generation in Pi-hole. There are debates on the web if P-256 wouldn't be enough as it is somewhat more efficient, but - at the end of the day - the difference is small and it still worth mentioning that even the largest standardized EC at this point is faster than any standardized and trusted non-EC cryptography used today. If you dislike ECC, Pi-hole offers the generation of a 4096 bit RSA key, instead.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-07 04:55:23 +02:00
DL6ER 45b2c65e68
Merge pull request #1578 from pi-hole/new/mg.request_info.is_authenticated
Add mg.request_info.is_authenticated to check if a user is authenticated
2023-07-05 22:44:57 +02:00
DL6ER 6474c37e44
Add mg.request_info.is_authenticated to check if a user is authenticated
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-07-05 21:11:58 +02:00
Adam Warner 6edb6773c1
Merge pull request #1577 from pi-hole/fix/parseList_argument
The new checkOnly argument should be `false` when parsing lists
2023-07-04 09:41:57 +01:00
RD WebDesign 41556de516
The new checkOnly argument should be `false` when parsing lists
Signed-off-by: RD WebDesign <github@rdwebdesign.com.br>
2023-07-03 20:05:36 -03:00
DL6ER d71e174c46
Fix config output on CLI and add tests for it
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-18 08:11:36 +02:00
DL6ER 85c797ae3a
Also backup and restore IPv6 ports
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-17 14:01:35 +02:00
DL6ER 3dcbaf7d9f
Add support for IPv6-only nameserver system configurations
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-17 13:54:03 +02:00
DL6ER 61931f948b
Add enum values for high-cotnrast theme family
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-17 10:54:53 +02:00
DL6ER 73f9ad02a0
Allow defining lines to inject into the generated dnsmasq configuration (misc.dnsmasq_lines)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-15 17:53:21 +02:00
DL6ER b2ad7af693
Add pihole-fTL gravity checkList <file> option
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-15 11:36:34 +02:00
DL6ER 3e10f67a65
There is a certain level of arbitrariness in how spell-check works ... and behaves different when running locally (act -j spell-check) and online (GHA)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-15 10:56:34 +02:00
DL6ER 16d43a5329
Spell-check fixes (RFC 6891 uses requestor with o so I assume this is a valid word)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-15 10:52:04 +02:00
DL6ER 9f808b94ad
Pass new abp_entries number to API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-15 10:00:43 +02:00
DL6ER 07f1f7df44
Save number of ABP-style entries in adlist table's new column abp_entries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-15 09:38:37 +02:00
DL6ER 0002399916
Add high-contrast themes to list of available themes)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-14 15:16:48 +02:00
DL6ER 5e8525ee30
Rename default theme to auto theme
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-14 15:13:48 +02:00
DL6ER a7f47a5e1f
The number of entries on a list should be the sum of domains and ABP-style entries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-12 21:44:24 +02:00
DL6ER 1644ef0866
Merge branch 'development' into new/http 2023-06-12 21:41:21 +02:00
DL6ER c54a9e2860
Verify generated Tleporter ZIP files after creation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-08 14:18:31 +02:00
DL6ER ba507ef7ef
Merge branch 'special/CI_development' into new/http 2023-06-08 12:13:02 +02:00
DL6ER b976be26e0
Update default HTTP headers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-08 12:12:57 +02:00
DL6ER 9bb7c7c0d3
Merge branch 'development-v6' into update/dnsmasq 2023-05-31 21:13:27 +02:00
DL6ER 57ab940036
Remove left-over debug output
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-31 21:13:27 +02:00
DL6ER cd5ff54e3b
Add minimum GLIBC version check
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-31 21:13:27 +02:00
DL6ER d9c09b66f0
Use pi-hole/ftl-build:ftl-build-buildx containers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-04 18:46:17 +02:00
DL6ER af4ce5cbba
Add more recent commit to Civetweb patch series
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-03 20:52:59 +02:00
DL6ER 4890e1c258
Register CSRF token in conn->request_info
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-03 20:52:02 +02:00
DL6ER f5f0354b3c
Generate and store CSRF token in the session
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-03 20:51:13 +02:00
DL6ER 6975a17c7c
Allow fractional delay for blocking mode changes and fix a few smaller memory leaks
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-03 13:26:58 +02:00
DL6ER 7cacf66f1c
Fix "Invalid write/read of size 1" possibly leading to a crash in webserver.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-03 13:10:45 +02:00
DL6ER 8468e342ce
Fix off-by-one mistake in query type counters
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-03 13:00:13 +02:00
DL6ER 7ad68b785b
Fix favicon for API documentation (in the same way we did for the web interface a few minutes ago)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-03 12:50:15 +02:00
DL6ER d3fac0fe8a
Merge pull request #1571 from pi-hole/fix/spelling_v6
Fix spelling in v6
2023-06-02 15:40:12 +02:00
DL6ER 2baa91b572
Enforece cookie auth only for API endpoints
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-31 21:13:27 +02:00
DL6ER 8176254fb0
Merge branch 'new/http-buildx' into new/http 2023-06-04 19:29:58 +02:00
DL6ER 813509841b
Accept cookie authentication only when CSRF header is provided (and correct)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-06-04 19:29:54 +02:00
DL6ER 59463db321
Modify arch-tests, refine how we detect an already running pihole-FTL process and how we get other processes names from proc (the existing method didn't work on alpine:arm) and remove the obsolete struct size checking
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-31 21:13:27 +02:00
DL6ER a3466e4db6
sizeof(time_t) is 8 on both 32 and 64 bit with musl
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-31 02:57:41 +02:00
DL6ER a4c2e3e624
Use new buildx-provided containers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-31 02:57:41 +02:00
DL6ER 44e7007378
Fix warnings shown when compiling with musl-gcc for 32bit targets
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-31 02:01:15 +02:00
Christian König 40e325de3a
Fix getrandom() for glibc <2.25
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-05-30 23:04:17 +02:00
Christian König 62cfc25b95
Fix spelling in v6
Signed-off-by: Christian König <ckoenig@posteo.de>
2023-05-30 22:42:13 +02:00
DL6ER 19c72d354e
!!! BREAKING CHANGE !!! Switch to the proven memory-hard password-hashing alogorithm BALLOON. The stored password hash will be upgraded on the first successdful login. To wave the necessity to implement BALLOON with every client trying to access the API, we remove the existing challenge-response authentication in favor of allowing login straight with the password. This has been avoided in the past, however, seems now acceptable that FTL (even by default) offers secure end-to-end encryption over HTTPS.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-30 21:22:45 +02:00
DL6ER 159e6a5447
Store certificate-only file during X.509 generation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-28 14:52:21 +02:00
DL6ER d42d4be97b
Add webserver.tls.rev_server boolean useful to tell FTL that unencrypted connections are still secure (in the context of Pi-hole solely being reachable through a reverse proxy)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-28 14:45:03 +02:00
DL6ER c5da10a4e2
Use mbedTLS PRNG to generate X.509 certificate serial number
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-28 12:59:31 +02:00
DL6ER 73ae7e9474
Move password-related functions into a dedicated file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-28 12:25:56 +02:00
DL6ER 13e3421d4a
Write-only property webserver.api.password should reset all sessions when being used
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-28 08:21:44 +02:00
DL6ER fe3ed0bb74
Add LUA pihole.needLogin(remote_addr)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-28 08:16:27 +02:00
DL6ER 38c7372b1d
get_blocked_statuslist() and get_cached_statuslist() are pure functions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 20:49:49 +02:00
DL6ER 87a1e4a2de
Add magic upstream destinations "blocklist" and "cache"
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 18:54:16 +02:00
DL6ER 4f7e347893
Simplify GET string parsing and ensure we decode URI components where necessary
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 18:27:57 +02:00
DL6ER 7774e3724d
Fix GC removing too many queries from the in-memory database
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 14:00:31 +02:00
DL6ER b88e8d282a
Update number of queries in databases when they are changed
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 13:35:11 +02:00
DL6ER 69e49d351d
Do not lock shm during /api/queries operation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 13:29:53 +02:00
DL6ER 8966c718dc
Keep query string when redirecting from /abc.lp to /abc
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 13:07:36 +02:00
DL6ER 18a3d6d828
Update changed indentation of known DNSMASQ warning
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 12:52:26 +02:00
Simon Kelley de5a5a42f6
=/== typo in last commit.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 12:46:24 +02:00
Simon Kelley f5c9d2ae2b
Behave better when attempting to contact unresponsive TCP servers.
By default TCP connect takes minutes to fail when trying to
connect a server which is not responding and for which the
network layer doesn't generate HOSTUNREACH errors.

This is doubled because having failed to connect in FASTOPEN
mode, the code then tries again with a call to connect().

We set TCP_SYNCNT to 2, which make the timeout about 10 seconds.
This in an unportable Linux feature, so it doesn't work on other
platforms.

No longer try connect() if sendmsg in fastopen mode fails with
ETIMEDOUT or EHOSTUNREACH since the story will just be the same.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 12:45:11 +02:00
DL6ER b55cfed3c7
Necessary changed to handle the most recent dnsmasq changes in FTL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 12:45:01 +02:00
Simon Kelley 6fa3b6f3c0
Log truncated DNS replies.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-27 12:34:13 +02:00
DL6ER d86a2f1c95
Add pihole.webhome() and add settings + group pages deep URI rewrite
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-26 21:50:26 +02:00
DL6ER 04e9c7e5f1
Enlarge size of FIFO to 512 lines (before 128 lines). As we reduce the maximul allowed line length from 1024 to 256 bytes, this comes at no extra memory costs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-26 20:17:04 +02:00
DL6ER 54cf9ad1f9
Add new debug.tls option logging any mbedTLS debug output to webserver.log
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-25 18:28:34 +02:00
DL6ER f785e181f8
Add mbedTLS debug logging hook
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-25 18:26:45 +02:00
DL6ER 182a10701b
Add new CivetWeb patch needed for URL rewriting
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-25 18:04:01 +02:00
DL6ER 44a0a3a277
Add FTL URI rewriting changes to CivetWeb
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-25 18:02:13 +02:00
DL6ER 94a11352c9
Implement *.lp URI rewriting
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-25 18:01:43 +02:00
DL6ER 0aecb57375
Add to /api/info/ftl if FTL is allowed to perform destructive operations (such as poweroff or reboot)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-24 20:59:23 +02:00
DL6ER 6623700e8d
Allow printing the entire configuration (or parts of it) using, e.g. "pihole-FTL --config debug". Before, --config could only print exact config key matches
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-24 20:57:18 +02:00
DL6ER c672120123
Add a setting to block possibly harmful actions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-24 20:49:27 +02:00
DL6ER b5a6ae44aa
Add /api/action/flush/arp flushing both the network and network_addresses tables in pihole-FTL.db
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-24 20:47:51 +02:00
DL6ER 3b404ff9a0
Add /api/actions/flush/logs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-24 20:39:18 +02:00
DL6ER 7b72c762ce
Add dns boolean to /api/auth signalling if the DNS server is up and running
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-24 20:25:02 +02:00
DL6ER f133596880
Improve request handler. No ".lp" pages are to be served without authentication (except login.lp). However, static contant as css, images, etc. are always served to allow serving the login page
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-24 19:56:39 +02:00
DL6ER 1083128828
Fix OpenAPI definition of /api/dns/blocking
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-24 18:48:55 +02:00
DL6ER f48683a11e
Continue to run webserver even when dnsmasq fails to still serve the web interface. Change the type of api/dns/blocking from bool to enum-string to support the new "failure" state
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-24 17:19:12 +02:00
DL6ER 479fc912e5
Rely on optimizer to decide whether or not to inline is_term() to restore compatibility with ancient Debian (armv4t)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-23 20:25:53 +02:00
DL6ER 21ac19bf22
Merge remote-tracking branch 'origin/development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-23 20:08:39 +02:00
DL6ER f5cd3b00d2
Add strict_tls property to list of sessions showing if really every connection of this session happened over TLS/SSL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-22 20:30:56 +02:00
DL6ER a02f4eb42a
Add new Kepler syntax commit to Civetweb-related patch series
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-22 19:13:17 +02:00
DL6ER 19efd3e2e1
Always Kepler syntax for Lua server pages
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-22 19:11:44 +02:00
DL6ER 0b05860b4b
Fix SQLite3 history + autocompletion
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-14 09:27:58 +02:00
DL6ER 391d1c9d0c
Extend /info/metrics to show details also about stale cache records
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-11 14:18:21 +02:00
DL6ER dba3fa1f33
Replace test RSA (4096 bit) by ECDSA (521 bit) self-signed certificate for TLS due to the recent changes of supporting only EC-based ciphers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-10 11:30:28 +02:00
DL6ER 5bcefa396e
Type of misc.privacylevel is "enum (unsigned integer)" not "enum (string)"
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-10 11:19:07 +02:00
DL6ER dd79cb792f
Changing dns.hosts always need a full dnsmasq restart as it is a single file and not a hostsdir (watched by inotify) component
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-09 10:34:00 +02:00
DL6ER 8c3ed20c6c
Conform internal TLS encryption to TLS NSA Suite B Profile (RFC 6460) suitable for protecting national security applications. The distinguishing part is that no RSA or classic DH is used. Instead, this profile is fully based on ECC
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-09 10:04:53 +02:00
DL6ER f06b2e5397
Add tls boolean to list of sessions to indicate whether this session was established over a secure (end-to-end encrypted) connection
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-09 10:01:05 +02:00
DL6ER 507606b893
Improve API config handling. Users may now set all config values whether or not they have changed. FTL takes care for them to properly compare the individual items and acts accordingly (either partial restart, full restart or even nothing at all (when all config options remained exactly the same))
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-08 13:07:55 +02:00
DL6ER 0564d7d385
Preserve gravity-specific quantities when updating lists via the API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-07 13:14:16 +02:00
DL6ER d4f30e4f3d
Send more gravity-specific quantities in /search/{domain}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-07 13:10:24 +02:00
DL6ER c12625f0c1
Add global object "took" to all API endpoints
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-07 12:20:07 +02:00
DL6ER c3468b7403
Include regex results in domains array when using /search/{domain}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-07 11:19:02 +02:00
DL6ER b7b63506fd
Add method to delete network table entires by their database ID
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-07 11:04:31 +02:00
DL6ER b97d99ae3e
Start timer thread keeping an eye on timed blocking mode changes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-07 09:15:12 +02:00
DL6ER 7f0bd7481e
Ensure both type and enabled are returned when requesting domains through the API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-06 20:08:07 +02:00
DL6ER 7281da34b4
Add /api/clients/_suggestions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-06 07:31:21 +02:00
DL6ER f31de37b38
Merge remote-tracking branch 'origin/development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-04 12:19:39 +02:00
DL6ER 469e73e0ff
Allow deleteing multiple messages at once (provide them comma-separated)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-04 12:13:09 +02:00
DL6ER c9dc537a6b
Also analyze UDP reply headers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-04 10:37:11 +02:00
DL6ER 3f0b8f4280
Ensure we always read diagnosis messages from the database
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-03 21:16:51 +02:00
DL6ER 87ea7d7fc7
Merge branch 'update/dnsmasq' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-02 20:03:30 +02:00
Simon Kelley 0f51b52f3e
Code tidying.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-02 19:25:29 +02:00
Simon Kelley 644a37d63e
Fix issue with stale caching.
After replying with stale data, dnsmasq sends the query upstream to
refresh the cache asynchronously and sometimes sends the wrong packet:
packet length can be wrong, and if an EDE marking stale data is added
to the answer that can end up in the query also. This bug only seems
to cause problems when the usptream server is a DOH/DOT proxy. Thanks
to Justin He for the bug report.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-02 19:25:27 +02:00
Simon Kelley ec31fd7f9b
Improve RFC3315 para 15 packet validation.
Thanks to Shashikumar Shashil for spotting the ommision.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-05-02 19:25:22 +02:00
Simon Kelley a57bc2fd48
Handle SERVFAIL responses to DS queries better.
On 15/5/2023 8.8.8.8 was returning SERVFAIL for a query on ec.europa.eu

ec.europa.eu is not a domain cut, that happens at jrc.ec.europa.eu. which
does return a signed proof of non-existance for a DS record.
Abandoning the search for a DS or proof of non existence at ec.europa.eu
renders everything within that domain BOGUS, since nothing is signed.

This code changes behaviour on a SERVFAIL to continue looking
deeper for a DS or proof of its nonexistence.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-30 21:11:51 +02:00
DL6ER 564161c5ac
Merge remote-tracking branch 'origin/development' into update/dnsmasq 2023-04-30 21:11:51 +02:00
DL6ER e6b46a2c60
Simplify redirection when user is already authenticated
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-26 22:10:43 +02:00
DL6ER 5c0dd19fb0
Preserve possible query arguments (GET) when routing throught the login page
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-26 22:03:41 +02:00
DL6ER f18c79d637
Move Lua-related functions into dedicated source file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-26 21:47:59 +02:00
DL6ER b642bacf08
Rename the API endpoint from /logs/http to /logs/webserver for consistency with the config option renameing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-26 21:39:29 +02:00
DL6ER 1d246d5340
Log LUA errors into webserver.log. The CivetWeb documentation is wrong about the default (logging to file) as it is in fact logging to the output where the information might be hidden due to HTML/CSS
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-26 21:31:41 +02:00
DL6ER af1df8a81e
Add special handling of time_t for 32bit architectures
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-26 21:19:01 +02:00
DL6ER f025598bd2
Simplify webserver logging routines
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-26 21:16:23 +02:00
DL6ER 2b2c02f0c7
Remove PH7
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-26 21:09:33 +02:00
DL6ER c1d07cbb63
Add new LUA functions useful for the web interface and define .lp files as index files
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-26 21:01:10 +02:00
DL6ER c081e9f647
Limit data returned by the API for the history endpointy by webserver.api.maxHistory
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-25 22:37:03 +02:00
DL6ER 7359b496ff
Move database.maxHistory to webserver.api.maxHistory
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-25 22:26:25 +02:00
DL6ER fb21b33b32
Necessary changes due to dnsmasq patch 451bd35ad6 overloading K_KEYTAG with cache record storage status
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-20 21:16:30 +02:00
Simon Kelley a59bbf979b
Log failure to determine MAC address in DHCPv6.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-20 19:31:02 +02:00
Petr Menšík ffb90c31b2
Optimization of socket events handling of dbus.
Reduces calls to locate the file descriptor structure. Should lower CPU usage when monitoring
dbus watches.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-20 19:30:58 +02:00
Petr Menšík 851a36730f
Fix crash in dbus code.
If I configure dnsmasq to use dbus and then restart dbus.service with watchers present,
it crashes dnsmasq. The reason is simple, it uses loop to walk over watchers to call
dbus handling code. But from that code the same list can be modified and watchers removed.
But the list iteration continues anyway.

Restart the loop if list were modified.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-20 19:30:55 +02:00
Simon Kelley 150cb710fe
Fix paren blunder in aaba66efbd3b4e7283993ca3718df47706a8549b
Thanks to Dominik Derigs for spotting this.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-20 19:30:47 +02:00
DL6ER 313c423258
Fix not sending domains that have not been seen on the current timeframe when we blocked no domains
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-18 21:13:57 +02:00
DL6ER b3ef031a22
Various memory improvements to (a) avoid crash during the final cleanup (don't allow reusing of pointer to memory that may already have been munmapped) , and (b) ensure we have release all memory after exporting to the on-disk database (finalize all prepared statements in queries_to_database())
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-18 21:13:48 +02:00
DL6ER bf5bbb32f9
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 setups
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-15 09:53:05 +02:00
Simon Kelley c093b48731
Add --no-dhcpv4-interface and --no-dhcpv6-interface options.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:34:26 +02:00
Simon Kelley 936703b457
Turn "used" member of struct iname into flags in preparation for more.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:33:11 +02:00
Simon Kelley e2f7ae6914
Missed copyright date.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:33:08 +02:00
Simon Kelley 5f3b789f94
Make --server=/#/<addr> behave the same as --server=<addr>
For consistency with --address and older dnsmasq releases.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:33:03 +02:00
Simon Kelley 0159047782
Bump copyrights to 2023.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:58 +02:00
DL6ER 109347083e
FTL changes related to most recent dnsmasq commits
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley 28a746b2f6
Fix long-term bug in TCP caching code which would lose NXDOMAIN.
A NXDOMAIN answer recieved over TCP by a child process would
be correctly sent back to the master process which would then
fail to insert it into the cache.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley 451bd35ad6
Use a simpler arrangement for the all_addr union to avoid the compiler padding it with an extra 8 bytes.
Use the F_KEYTAG flag in a a cache record to discriminate between
an arbitrary RR stored entirely in the addr union and one
which has a point to block storage.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
DL6ER 0f764e445f
FTL changes related to most recent dnsmasq commits
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley afde3591d3
Fix copy-n-paste error in 138e1e2a2d918b37cb0274fe310d53be35acf4cf
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley 401e129ab0
--domain=# is valid. --synth-domain=# isn't.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley 777dc37c37
Allow --cache-rr=ANY with the obvious meaning.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley fe007b7507
Optimse memory use for arbitrary-RR caching.
RRs 13 bytes or less don't need to allocate block storage.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
DL6ER bfa44353ae
ANY is RRNAME not BLOB after the most recent dnsmasq code changes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
DL6ER a530775396
Apply necessasry changes to FTL due to most recent dnsmasq patch
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley df6b37a56c
Optimise no-action case in rrfilter().
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley d7240fab44
Add filtering of arbitrary RR-types.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley 6d9cceb221
Remove code for caching SRV.
Function replaced by the ability to cache any RR type.

For backwards compatibilty SRV records are always on the
list of cacheable RR-types.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
DL6ER aa56241e67
Apply necessasry changes to FTL due to most recent dnsmasq patch
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley 5776b74974
Add --cache-rr to enable caching of arbitrary RR types.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
DL6ER 07dec79448
Apply necessasry changes to FTL due to most recent dnsmasq commit
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley 4945fab728
Fold F_NOERR and F_DNSSEC to make space for new F_RR.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley 466022e73e
Add EDE "filtered" extended error when --filter-A or --filter-AAAA act.
If a NODATA answer is returned instead of actual data for A or AAAA
queries because of the existence of --filter-A or --filter-AAAA
config options, then mark the replies with an EDE "filtered" tag.

Basic patch by Petr Menšík, tweaked by Simon Kelley to apply onto
the preceding caching patches.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley f0ec0f2a8d
More --filter-AAAA caching improvements.
Cache answers before filtering and filter coming out of the cache.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:09 +02:00
Simon Kelley 49063e3ef3
Improve cache use with --filter-A and --filter-AAAA
If --filter-AAAA is set and we have cached entry for
the domain in question fpr any RR type that allows us to
return a NODATA reply when --filter-AAAA is set without
going upstream. Similarly for --filter-A.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-14 20:32:06 +02:00
DL6ER 49b6bff990
Adjust struct sizes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-11 17:35:10 +02:00
DL6ER 48b059065f
Adjust tests for recent EDNS0 changes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-11 13:08:07 +02:00
DL6ER db0157451f
Apply Pi-hole CivetWeb patches
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-11 12:23:52 +02:00
DL6ER 9bda9f4cd6
Update CivetWeb to 1.16
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-11 12:15:33 +02:00
DL6ER 677c560a40
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-11 12:08:02 +02:00
DL6ER 75683b5da4
Apply the same logic also for reverse lookups (PTR)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-09 12:39:49 +02:00
DL6ER 9c0082fc91
Explicitly set INSECURE status for replies received either from upstream (if they are not already validated as SECURE) or from cache. This is a direct consequence from the previous commit.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-09 12:39:10 +02:00
DL6ER 77c4f91814
Analyse pseudeoheader before it might get stripped off
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-09 10:51:16 +02:00
DL6ER 98400b5416
Log if EDNS header is NULL and we are in debug mode
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-08 14:44:43 +02:00
DL6ER 32d7bfb4bf
Only try to interpret EDNS EDE when EDE data is available
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-08 12:57:59 +02:00
DL6ER 40369b0c7c
Ignore possible EXTRA-TEXT field in EDNS0 EDE data
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-07 19:16:57 +02:00
DL6ER 4b4f113350
Use AD bit for IN/SECURE and EDE in SERVFAIL when prox for BOGUSy-dnsmasq option is used
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-07 19:16:37 +02:00
DL6ER a6337b8b5e
Implement EDNS(0) EDE
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-07 18:01:28 +02:00
DL6ER e79b40d831
Simplify EDNS handling code and also interpret replies received from upstream
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-07 17:55:47 +02:00
DL6ER 705355b82e
Disable zipinfo test
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-05 21:29:01 +02:00
DL6ER 020a94f4aa
Generate TLS certificate when not present
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-05 21:27:33 +02:00
DL6ER 95653e7581
Only load TLS certificate when the specified file exists and is readable
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-05 21:23:59 +02:00
DL6ER 515fe8b440
Add certificate generation command (pihole-FTL --gen-x509 ...)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-05 21:21:14 +02:00
DL6ER 137a1519ba
Now that ZIP is available in the CI environment, use an external tool to check the integrity of Teleporter files
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-05 15:57:11 +02:00
DL6ER efed77809b
Use v1.27 build containers containing mbed TLS and ZIP
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-05 15:55:21 +02:00
DL6ER c05502f24a
Reimplement DNS cache metrics in the light of arbitrary RRTYPE caching
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-01 16:10:49 +02:00
DL6ER a1332582ff
Use new shortcut cache-rr=ANY to enable caching of all query types
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-01 14:47:45 +02:00
DL6ER 46bc3c9161
Merge remote-tracking branch 'origin/update/dnsmasq' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-01 14:47:21 +02:00
DL6ER 879708d6b7
FTL changes related to most recent dnsmasq commits
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-01 14:13:22 +02:00
Simon Kelley d71d0aee12
Fix copy-n-paste error in 138e1e2a2d918b37cb0274fe310d53be35acf4cf
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-01 13:37:02 +02:00
Simon Kelley a3aaf7c88b
--domain=# is valid. --synth-domain=# isn't.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-01 13:36:59 +02:00
Simon Kelley d1b3494e74
Allow --cache-rr=ANY with the obvious meaning.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-01 13:36:55 +02:00
Simon Kelley ab7bdcd869
Optimse memory use for arbitrary-RR caching.
RRs 13 bytes or less don't need to allocate block storage.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-04-01 13:36:50 +02:00
DL6ER fa2aeccc0e
ANY is RRNAME not BLOB after the most recent dnsmasq code changes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 09:55:43 +02:00
DL6ER 8b1d064986
Apply necessasry changes to FTL due to most recent dnsmasq patch
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 08:57:35 +02:00
Simon Kelley 18a77d3018
Optimise no-action case in rrfilter().
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 08:55:42 +02:00
Simon Kelley 91eaa62e2f
Add filtering of arbitrary RR-types.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 08:55:33 +02:00
Simon Kelley 4288757e44
Remove code for caching SRV.
Function replaced by the ability to cache any RR type.

For backwards compatibilty SRV records are always on the
list of cacheable RR-types.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 08:55:15 +02:00
DL6ER bc523726ba
Apply necessasry changes to FTL due to most recent dnsmasq patch
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 08:02:41 +02:00
Simon Kelley 4c2090c9a8
Add --cache-rr to enable caching of arbitrary RR types.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 08:00:18 +02:00
DL6ER ee9564ccc0
Apply necessasry changes to FTL due to most recent dnsmasq commit
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 07:58:11 +02:00
Simon Kelley fe95fa5511
Fold F_NOERR and F_DNSSEC to make space for new F_RR.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 07:55:11 +02:00
Simon Kelley 3c40a9846b
Add EDE "filtered" extended error when --filter-A or --filter-AAAA act.
If a NODATA answer is returned instead of actual data for A or AAAA
queries because of the existence of --filter-A or --filter-AAAA
config options, then mark the replies with an EDE "filtered" tag.

Basic patch by Petr Menšík, tweaked by Simon Kelley to apply onto
the preceding caching patches.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 07:54:52 +02:00
Simon Kelley 875d5184ba
More --filter-AAAA caching improvements.
Cache answers before filtering and filter coming out of the cache.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 07:51:23 +02:00
Simon Kelley 68242de9e6
Improve cache use with --filter-A and --filter-AAAA
If --filter-AAAA is set and we have cached entry for
the domain in question fpr any RR type that allows us to
return a NODATA reply when --filter-AAAA is set without
going upstream. Similarly for --filter-A.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 07:51:17 +02:00
DL6ER ce33f58f24
Merge changes from https://github.com/civetweb/civetweb/pull/1144 to be compatible with mbed TLS v3.4.0
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-08 21:10:12 +01:00
DL6ER 064ac81fca
Add SSL/TLS support (mbedTLS)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-08 21:10:12 +01:00
DL6ER 0da1ae7855
inotify.c:event->name is a flexarray and cannot be NULL (fixes RISCV compiler error)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 12:23:26 +02:00
DL6ER 932d30696f
Add option for caching all names RRNAMEs that are not already cached by default
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 10:11:12 +02:00
DL6ER 06da29d56e
Merge remote-tracking branch 'origin/update/dnsmasq' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-31 10:10:54 +02:00
DL6ER 99d64e9071
Merge branch 'update/dnsmasq' into new/http 2023-03-08 21:10:12 +01:00
DL6ER 40fb31a42e
Add "cpu_thermal" as CPU temperature sensor indicator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-13 13:57:55 +01:00
DL6ER 3f3cdf5359
Do not make any assumptions about sensors being present but simply check for existing sensors
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-13 13:14:11 +01:00
DL6ER 9c3820ac3d
!!! BREAKING CHANGE !!! Reduce default value of database.maxDBdays from 365 to 365/4 == 91 days. In case this option is already set (either in pihole.toml or in the imported setupVars.conf), this value will continue to be used instead.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-13 11:43:55 +01:00
DL6ER fba171f151
Delete only up to 1% of the queries in the database in one go to avoid long blocking times with many queries to be deleted.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-13 11:41:14 +01:00
DL6ER d0e3500c4e
Even when database.network.parseARPcache is set to false, FTL should be cleaning the network table, adding in clients from its own knowledge (those that sent queries), and add local interfaces
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-13 11:10:40 +01:00
DL6ER f7a784b34a
Pass SQLite3 errors when finishing the teleporter transaction commit failed
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-13 10:49:44 +01:00
DL6ER e08b14a686
Rely on ENV var STATIC when compiling musl builds to simplify working in our musl devcontainer (x86_64-musl)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-13 10:41:46 +01:00
DL6ER 9b32553a62
Add API documentation for method to delete session by ID
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-13 10:32:19 +01:00
DL6ER 44c3fe4ef4
Merge remote-tracking branch 'origin/development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-13 10:30:39 +01:00
DL6ER 749486273e
Add method for deleteing sessions by their IDs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-08 21:10:12 +01:00
DL6ER 4890e16b39
Use cryptographic randomness also for te API challenge and the SID generation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-08 22:14:36 +01:00
DL6ER efa6912b69
Make webserver.api.temp.unit its own config enum type
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-08 14:37:00 +01:00
DL6ER 10ad7dd873
Add fallback randomness generation method for glibc older than 2.25 (armv4t builds)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-06 21:49:11 +01:00
DL6ER 3c99a35e90
Add --totp option to help text and always return 0 when no TOTP secret is configured
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-07 21:42:27 +01:00
DL6ER 9bf6176a64
Add TOTP 2FA to web interface and API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-03-06 21:49:11 +01:00
DL6ER 4ea0bf7dea
Always update last_disk_db_idx after storing queries on disk as sqlite3_changes() sometimes reports to few rows being inserted. This was a rather tedious debugging...
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-25 18:23:13 +01:00
DL6ER ce96c5baef
Rename config.files.{http_info => log.webserver, ph7_error => log.ph7} and include file path in /api/logs replies
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-23 17:52:10 +01:00
DL6ER 6a199427b4
Add option dhcp.multiDNS
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-20 14:44:58 +01:00
DL6ER fc6a0d0d52
Ensure individual config settings areforced to false when debug.all is false without *all* other options being enabled
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-18 18:42:46 +01:00
DL6ER b0c46dc416
Sub-paths in /api/auth are not allowed (except for explicitly existing API endpoints)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-18 15:09:13 +01:00
DL6ER 4cc66df3f0
Rename /api/auth/session{ => s}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-18 14:58:53 +01:00
DL6ER fe6093f970
Remember login timestamp of API sessions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-18 14:54:38 +01:00
DL6ER dd9c4ad52b
Add special debug.all config option
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-18 14:42:32 +01:00
DL6ER 1bb5e265a0
Add info.ftl.uptime (in ms) and move config.{dns => dhcp}.domain
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-18 13:48:09 +01:00
DL6ER 9eaab48405
Mark currently active session when listing active sessions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-14 22:59:52 +01:00
Taylor R Campbell fd8f788c59
Avoid undefined behaviour with the ctype(3) functions.
As defined in the C standard:

	In all cases the argument is an int, the value of which shall
	be representable as an unsigned char or shall equal the value
	of the macro EOF.  If the argument has any other value, the
	behavior is undefined.

This is because they're designed to work with the int values returned
by getc or fgetc; they need extra work to handle a char value.

If EOF is -1 (as it almost always is), with 8-bit bytes, the allowed
inputs to the ctype(3) functions are:

	{-1, 0, 1, 2, 3, ..., 255}.

However, on platforms where char is signed, such as x86 with the
usual ABI, code like

	char *arg = ...;
	... isspace(*arg) ...

may pass in values in the range:

	{-128, -127, -126, ..., -2, -1, 0, 1, ..., 127}.

This has two problems:

1. Inputs in the set {-128, -127, -126, ..., -2} are forbidden.

2. The non-EOF byte 0xff is conflated with the value EOF = -1, so
   even though the input is not forbidden, it may give the wrong
   answer.

Casting char to int first before passing the result to ctype(3)
doesn't help: inputs like -128 are unchanged by this cast.  It is
necessary to cast char inputs to unsigned char first; you can then
cast to int if you like but there's no need because the functions
will always convert the argument to int by definition.  So the above
fragment needs to be:

	char *arg = ...;
	... isspace((unsigned char)*arg) ...

This patch inserts unsigned char casts where necessary, and changes
int casts to unsigned char casts where the input is char.

I left alone int casts where the input is unsigned char already --
they're not immediately harmful, although they would have the effect
of suppressing some compiler warnings if the input is ever changed to
be char instead of unsigned char, so it might be better to remove
those casts too.

I also left alone calls where the input is int to begin with because
it came from getc; casting to unsigned char here would be wrong, of
course.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-12 20:49:16 +01:00
DL6ER db0217f6f7
Add GET /api/auth/sessions for listing the currently active sessions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-12 20:49:16 +01:00
DL6ER 5e436312f7
Invalidate all currently active sessions when password/pwhash are changed
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-12 20:42:04 +01:00
DL6ER 9483ed6778
Add new regex extension ";reply=CNAME,target.domain" suitable for defining CNAME records per-group
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-11 22:00:33 +01:00
DL6ER e039fd12f8
Allow removing password by sending a blank value to the magic password item (both via CLI and API)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-11 17:25:21 +01:00
DL6ER 83195e678e
Change /api/info/cache -> /api/info/metrics and add other metrics such as query reply sources and DHCP metrics
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-11 13:38:37 +01:00
DL6ER bcda8cc29a
Add dns.cache.optimizer as option to control the usage of expired/stale DNS queries while Pi-hole is looking for new ones. This option has always been available, we just make it more visible by assigning an individual config option for it.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-11 10:58:41 +01:00
DL6ER 37d9c15101
Remove leftover debug output when setting the password via CLI
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-11 08:15:38 +01:00
DL6ER b85a14fc92
Return exit code 2 if specified config type is invalid in --config
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-11 08:13:14 +01:00
DL6ER a1fe32a369
Do not wrap strings in quotes for the CLI --config option
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-11 08:09:44 +01:00
DL6ER ad43d2d7fd
Add suggested CPU temperature which can be used by the client to display the CPU temperature without having to define selection rules in the clients. Said rules will have to be added in FTL itself, we are not making any assumptions here but only want to identify sensors based on facts. As consequence, the temperature value may be NULL if no reliable detection is possible. Users are asked to raise an issue with FTL in this case so we can have a look together with them on their particular system and add stable detection rules.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-07 21:00:15 +01:00
DL6ER 5fdf7dd340
Include "source" of hwmon items and individual sensor's "max" and "crit" values (if available) in /api/info/sensors
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-07 21:00:15 +01:00
DL6ER 49c978a9e8
Add filesystem type in disk usage warning (if available)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-07 19:18:12 +01:00
DL6ER 4ee054b962
Only warn once about disk shortage in case database and log file are on the same device (e.g. both on / )
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-06 21:16:23 +01:00
DL6ER d7da3f7996
Improve disk shortage warning to point out more precisely which filesystem is getting full. We do this by mentioning the mountpoint instead of the filename
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-06 21:12:47 +01:00
DL6ER daa87f2b60
Rename dns.reply.host.{overwrite_v => force}{4,6}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-06 21:46:06 +01:00
DL6ER b299ca275d
Group sensors by the hardware they belong to
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-06 20:21:13 +01:00
DL6ER 40739ab287
Do not include the high number of sensors that have a temperature of 0.0 exactly
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-03 18:12:55 +01:00
DL6ER 08025c1e1c
Add config->webserver.api.password to OpenAPI specs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-05 21:49:22 +01:00
DL6ER 6f72b9845a
Slightly restructure output from /api/info/sensors and include all sensors we can find using the following patterns:
- /sys/class/thermal/thermal_zone*/temp
- /sys/class/hwmon/hwmon*/temp*_input
- /sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_input

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-05 21:47:19 +01:00
DL6ER 42c496e950
Add pseudo-element config->webserver.api.password which will compute and set webserver.api.pwhash to provide a convenient method of changing the password both via CLI and API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-05 21:11:28 +01:00
DL6ER 501455e76e
Add quiet CLI config mode (e.g., "pihole-FTL --config -q dns.blocking.active") which makes boolean values accessible via FTL's return code
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-05 17:23:47 +01:00
DL6ER 8f33706743
Ensure no debug output is leaking into the output of pihole-FTL --config ... even when debug.config = true is set
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-05 17:00:06 +01:00
DL6ER 9833fe1af0
Reload gravity database when 'updated' property in table 'info' has changed
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-05 16:44:14 +01:00
DL6ER ac4c9e789d
Add DELETE /api/dhcp/leases/{ip}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-05 13:55:45 +01:00
DL6ER 9fdecaeea9
Add GET /api/dhcp/leases
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-05 13:04:03 +01:00
DL6ER 78a09dfa9d
Make a few more config values camelCase
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-05 12:24:42 +01:00
DL6ER 39c91f995c
Integrate former /api/config/_{topic,servers} into /api/config?detailed=true
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-04 12:31:43 +01:00
DL6ER 904e1c7e3a
Allow importing teleporter archives from the CLI
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-03 18:12:55 +01:00
DL6ER cf82495d6c
Create new webtheme() PHP function and make the webtheme an enum
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-03 18:12:55 +01:00
DL6ER 187b0be39d
Rename config.webserver.{api => }.sessionTimeout and ensure it is also used for the cookie
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-03 18:12:55 +01:00
DL6ER a442f36e79
Ensure config struct is always initialized (also when reading the legacy pihole-FTL.conf config file)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-03 03:44:12 +01:00
DL6ER 141b6879ce
Move /api/{dns -> info}/cache
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-01 22:43:44 +01:00
DL6ER 1e83b08d7c
Implement FTL-provided PHP function fileversion()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-01 22:38:15 +01:00
DL6ER 65a31f0241
Improvements for the query log filter suggestions (increase default number of suggestions from 10 to 30 and differentiate between clients by IP and by name)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-01 22:21:05 +01:00
DL6ER 2f2b88e52a
Append target=... when redirecting to the login page on failed authentication. This allows the script to go back to the page where the user has been before having been redirected to the login page.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-01 22:00:09 +01:00
DL6ER 5e2e6e6fa4
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-02-01 21:48:41 +01:00
DL6ER da6a1ae053
Add {GET,DELETE} /api/info/messages
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-30 21:00:08 +01:00
DL6ER ec31584f74
Add clients.total and gravity.domains_being_blocked to /stats/summary
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-30 20:33:28 +01:00
DL6ER eba4b908b8
Also automatically redirect from login.php to index.php when a user is already authenticated (or when authentication is not needed)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-30 20:13:12 +01:00
DL6ER 3a71b3e23d
Enforce authentication for all PHP files (except login.php)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-30 20:11:31 +01:00
DL6ER dd05b4595f
Use the copy_file_range() system call during log rotation. It performs an in-kernel copy between two file descriptors without the additional cost of transferring data from the kernel to user space and then back into the kernel. The copy_file_range() system call first appeared in Linux 4.5, but glibc 2.27 provides a user-space emulation when it is not available. For earlier versions than 2.27 (the armv4t cross-compiler is currently still at 2.24), we use sendfile() as fallback solution.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-30 19:31:51 +01:00
DL6ER 9f969984e2
Mention how many host lines we have written in custom.list
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-30 19:23:01 +01:00
DL6ER 50e951b216
Copy zeroth-file instead of renaming it during rotation to prevent some (yes, I know, unlikely) file permission issues down the road
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-30 19:19:55 +01:00
DL6ER 54beca32db
Reload config on change of pihole.toml. This is done using an inotify watcher on /etc/pihole. This also means that there is no need to send SIGHUP to FTL after a config change, this is triggered internally.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-30 19:05:19 +01:00
DL6ER 4d8a6eddc5
Place search results into dedicated search object
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-29 20:11:04 +01:00
DL6ER 57245c6f95
Update RapiDoc 8.4.3 -> 9.3.4
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-29 20:09:14 +01:00
DL6ER a02899f101
API Docs: Automatically log out when FTL is restarted and session has been invalidated
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-29 20:05:04 +01:00
DL6ER 5bfc1fd122
Add /api/search as a batter replacement of the currently existing "pihole -q" command
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-29 19:55:12 +01:00
DL6ER 8753a8d69b
Add more compiler warnings and fix a few things they pointed out worth improving/being more explicit about. This adds GCC-12 compatibility out of the box.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-29 18:12:19 +01:00
DL6ER 5f593bfb3b
Remove memory statistics feature from CivetWeb. It makes the webserver somewhat faster and using less memory. We are not using it anyway.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-28 11:50:34 +01:00
DL6ER 37265dffc0
More 32bit compatibility tweaks
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-27 21:27:57 +01:00
DL6ER f3b2e31801
Move src/compression -> src/zip
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-27 20:37:47 +01:00
DL6ER 6ebc999887
Move src/ph7 -> src/webserver/ph7
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-27 20:35:17 +01:00
DL6ER c741e6703f
Move src/cJSON -> src/webserver/cJSON
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-27 20:34:04 +01:00
DL6ER a9e2f298e8
Move src/civetweb -> src/webserver/civetweb
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-27 20:32:19 +01:00
DL6ER d9132108c1
Move src/miniz -> src/compression/miniz
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-27 20:30:28 +01:00
DL6ER 579b55cbd0
GZIP uncompressor tweak for 32bit
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-27 20:28:27 +01:00
DL6ER 149ec4e0dd
Add test for re-importing the just exported Teleporter file during the tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-26 20:11:55 +01:00
DL6ER 48fc06d46b
Add POST /api/teleporter to upload and install backed up configuration
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-25 21:51:12 +01:00
DL6ER d51aa378a3
Add /api/action/reboot and /api/action/poweroff (needs extra capability CAP_SYS_BOOT)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-24 20:26:37 +01:00
DL6ER e4383775d1
Add /api/action/gravity which can be used to trigger a run of pihole -g. The output is live streamed using HTTP/1.1 chunked encoding.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-24 20:15:45 +01:00
DL6ER f9a9f3aa8f
Add "--teleporter" to "pihole -FTL -h"
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-24 19:42:05 +01:00
DL6ER 2ecf2b841e
Add (a stripped down variant of the) pihole-FTL.db database into the Teleporter archive
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-24 19:39:51 +01:00
DL6ER 1e4dbe41b5
Add (a stripped down variant of the) gravity.db database into the Teleporter archive
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-24 19:24:15 +01:00
DL6ER aec188bea1
Add /etc/hosts, /etc/pihole/dhcp.leases (if it exists) and all files in /etc/dnsmasq.d into the Teleporter archive
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-24 19:24:20 +01:00
DL6ER 785c21817e
Add pihole-FTL --teleporter to create Teleporter ZIP file in the current directory. We need to add "zipinfo" as package to our ftl-build containers to be able to verify the fiel independently (this is already done by Python for the ZIP archive received from the API - but it doesn't hurt to test twice)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-23 22:21:19 +01:00
DL6ER 96434c3e9a
Fix a small logic quirk preventing us from being able to change config parameters via PATCH /api/config. This issue was created in 4c62a02026
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-23 22:02:05 +01:00
DL6ER 13168c377b
Add GET /api/teleporter
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-23 21:56:11 +01:00
DL6ER 4c62a02026
Only change config when new value is different from the current value. This allows to set all config options at once but without restarting dnsmasq when none of its config values are changed
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-23 21:35:35 +01:00
DL6ER 6afab76dcc
Give hint about where the JSON parser failed when passing invalid JSON.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-23 21:26:11 +01:00
DL6ER 15dd20b8ec
Mark several config options as "advanced" (will be hidden by default on the future settings page)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-23 21:21:22 +01:00
DL6ER 22941f9490
Update .github/workflows/openapi-validator.yml to account for "Node.js 12 actions are deprecated" warnings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-23 21:06:11 +01:00
DL6ER 88e8ab9fd5
!!! BREAKING CHANGE !!! Redesign TOML config structure
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 12:32:10 +01:00
DL6ER 0856153699
Add ability to list allowed values (for enums) in JSON form. Add pretty-printing for TOML and update all help test descriptions from the official Pi-hole documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 12:01:42 +01:00
DL6ER 2e54ca5577
Add /config/_server offering DNS server suggestions for the settings page
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 10:11:31 +01:00
DL6ER db128644c9
Rotate config files into /etc/pihole/config_backups instead of cluttering /etc/pihole itself
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 10:06:22 +01:00
DL6ER 47ac129a53
!!! BREAKING CHANGE !!! Rename pihole-FTL.toml to pihole.toml and it is a Pi-hole wide config file also covering all the dnsmasq settings, etc.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 10:01:54 +01:00
DL6ER 517e58909a
Include FTL version in headers of pihole-FTL.toml and 01-pihole.conf
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 09:55:34 +01:00
DL6ER d5fd0f7dcf
Remove mictortar, we'll use ZIP instead of TAR.GZ for multi-file archives. This is already available through miniz.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 09:49:11 +01:00
DL6ER b55c1a673a
Add Desktop Management Interface (DMI) properties to /api/info/host
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 09:44:40 +01:00
DL6ER 5e67e488dc
Tests: Add test for embedded GZIP compressor
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 09:24:40 +01:00
DL6ER 024130a638
Expose GZIP uncompressor via CLI
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-22 09:10:49 +01:00
DL6ER acf187d8b3
Expose GZIP compressor via CLI
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-21 19:43:22 +01:00
DL6ER 4026d5f7a2
Include info.yaml in build
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-21 14:54:13 +01:00
DL6ER 6e6b74a453
Add line with error on request for setting an invalid dnsmasq configuration to ease debugging
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-21 13:44:47 +01:00
DL6ER 1142540aac
Single out a few items from /api/info/system into /api/info/{sensors,host,ftl}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 20:16:31 +01:00
DL6ER 37bafc1365
Move /api/{ftl -> info}/client, /api/ftl/dbinfo -> /api/info/database, /api/ftl/sysinfo -> /api/info/system, and /api/version -> /api/info/version
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 20:16:31 +01:00
DL6ER aeb61a3026
Add HTTP OPTIONS method processing. Using something like "curl -X OPTIONS -I pi.hole:8080/api/... will return an Allow header specifying which endpoint supports which method.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-21 12:53:04 +01:00
DL6ER 6233bb489a
Add GET /config/_topics which will be helpful when automatically generating a Pi-hole settings page
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 20:16:31 +01:00
DL6ER 10ed1459b3
Add "allowed" as JSON (where applicable) and "type" in /api/config?detailed=true to allow automatic settings page generation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 20:16:31 +01:00
DL6ER 02b7f50c55
Keep (up to) 15 config files in rotated form. To save space, we leave the most recent 4 uncompressed and compress the remaining files
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 18:51:11 +01:00
DL6ER 88e57ef9e3
Manage custom.list through the universal /config/dns/hosts instead of its own interface. This reduces code duplication.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 18:32:21 +01:00
DL6ER 91f04fd053
Check if /sys/firmware/devicetree/base/model exists before trying to access it
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 18:15:41 +01:00
DL6ER 91f2645ace
Also allow all IPv6 addresses in default webserver ACL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:59:43 +01:00
DL6ER a59efd3ebd
Run config file rotation only when writing to the config file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:58:55 +01:00
DL6ER 28bb27c125
Fix incorrectly returned 404 for existing API endpoints when not logged in
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:56:11 +01:00
DL6ER 19d7582896
Free regex memory in final cleanup routine to ensure allocated regex do not show up as definitely lost memory in valgrind analysis
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:52:54 +01:00
DL6ER d041c494ae
Add missing specs/{logs,endpoints}.yaml to pre-compiled logs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:49:11 +01:00
DL6ER 0f69342811
Rotate config files away instead of forcefully overwriting them. This allows users to easily revert in case they made unwanted changes.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:47:27 +01:00
DL6ER bd266d6589
Further improvements for check_space()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:39:31 +01:00
DL6ER 1a084c3fda
Improve code comments in daemon.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:37:11 +01:00
DL6ER 145d84db95
Add more detailed warning about why writing to or removing the PID file failed (if this is the case)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:33:21 +01:00
DL6ER ea8ef2aa1b
Fix incorrect union member being used in check_space()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-20 04:32:24 +01:00
DL6ER 7b479782fd
Implement proper testing of dnsmasq options before applying new configuration
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 20:12:11 +01:00
DL6ER 2d6c25d573
Add GET /config/{element} for more specific requests as well as PATCH and DELETE /config/{element}/{value} for direct array manipulation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 18:19:19 +01:00
DL6ER a506d38d9f
Materialize the pseudoelement config.port as config.dnsmasq.port (it can now also be changed)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 04:39:09 +01:00
DL6ER 8cd94adbc4
Add ability to query nested config items, e.g. /api/config/dnsmasq/upstreams returning: {"config": { "dnsmasq": { "upstreams": [ "127.0.0.1#5335" ] } } }
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 04:31:11 +01:00
DL6ER 1ae49c7baf
Add /api/logs/{ftl,http,ph7}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 04:22:50 +01:00
DL6ER 2e2253c77c
Fix another memory leak where a payload sent to a non-existing endpoint wasn't freed properly
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-18 03:55:10 +01:00
DL6ER c901194d3b
Rename /api/ftl/logs/dns -> /api/logs/dnsmasq
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-16 21:41:10 +01:00
DL6ER c34975180e
Rename /api/ftl/endpoints -> /api/endpoints
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-16 21:37:11 +01:00
DL6ER 0c1c2b0bc3
Add /api/config:dnsmasq.cnames
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-16 21:33:01 +01:00
DL6ER ee439711c4
Configure if endpoints require authentication in a central place for better overview
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-16 21:23:04 +01:00
DL6ER 5e96022e63
Group endpoints in /api/ftl/endpoints by supported methods
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-16 21:11:57 +01:00
DL6ER ed6b809013
Modify removing and adding local DNS entries via path instead of payload
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-16 19:57:36 +01:00
DL6ER aefb636a34
Add missing API specs for /api/config:dnsmasq.dhcp.hosts
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 22:43:04 +01:00
DL6ER 952a296dd4
Add /api/dns/entries for manipulating the custom.list file (viewing, adding, and removing items)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 22:41:44 +01:00
DL6ER 32413c1297
Rename static DHCP leases file to .bck after parsing it. Everything will now be stored in pihole-FTL.toml
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 22:14:13 +01:00
DL6ER 638827db69
Nicely format TOML arrays as multi-line variables
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 22:08:43 +01:00
DL6ER 7546126bbe
Add config.dnsmasq.dhcp.hosts
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 21:59:01 +01:00
DL6ER 49d2967cb9
Fix more substantial memory leak in the JSON formatter
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 21:47:03 +01:00
DL6ER 4927442c13
Fix possibly small memory leak in regex compilation when there are regular expressions with certain errors
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 21:41:13 +01:00
DL6ER 73539fcf73
Convert swap memory to KB and add %used form RAM and SWAP in /api/ftl/sysinfo
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 21:39:44 +01:00
DL6ER c33e089179
Spawn number of webserver threads porportional to the number of online CPUs. The rule is (2*nprocs if nprocs < 8 else 16)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 21:35:31 +01:00
DL6ER 3a153b6d18
Merge branch 'development' into new/http 2023-01-12 19:35:31 +01:00
DL6ER 70fff27c46
Check if temperature sensors exist before reading them to avoid spamming the log with "no such file or directory" messages for sensors without labels
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 20:43:38 +01:00
DL6ER 64ea7ff5b2
Do not warn about not being able to open setupVars.conf - it may simply not exist
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 20:33:25 +01:00
DL6ER 4e754521c5
Free allocated memory in readTOMLvalue()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 20:32:59 +01:00
DL6ER 2411818ecc
Add null values to /api/version if corresponding lines are not present in /etc/pihole/versions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 13:30:23 +01:00
DL6ER b95dbbe579
Merge branch 'development' into new/http 2023-01-15 12:43:16 +01:00
DL6ER 96c6effa45
Only add "conf-dir=/etc/dnsmasq.d" to the config if this directory exists
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 12:33:32 +01:00
DL6ER ffaa625800
!!! BREAKING CHANGE !!! Use /etc/pihole/dnsmasq.conf as default config file for FTL. Pi-hole will not touch /etc/dnsmasq.conf or any files in /etc/dnsmasq.d/ any longer. They are solely reserved for user-provided scripts. This also adds the ability to easily have a separate dnsmasq instance running on the same host (using anther port or binding to other interfaces, of course)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 11:41:05 +01:00
DL6ER aae3905334
Add /config dnsmasq.logging (bool)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 09:27:47 +01:00
DL6ER a0a9572d7b
Add /api/versions docker.{local,remote}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-15 08:15:11 +01:00
DL6ER 52898d0d05
Rename new option -c to --config to avoid possible ambiguity
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-14 20:40:58 +01:00
DL6ER 3bb8050cf8
Remove extra system.hostname property from /api/sysinfo. It is already contained as system.uname.nodename of the very same API endpoint
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-14 20:36:49 +01:00
DL6ER 8541346fae
Add config getter and setter for the CLI
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-14 20:35:40 +01:00
DL6ER 7afd123530
Add system.hostname and ftl.{pid,%cpu,%mem} to /api/ftl/sysinfo
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-14 10:08:03 +01:00
DL6ER 3b8fd4d4e1
Use NO_DLOPEN extension in civetweb
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-13 21:38:21 +01:00
DL6ER 44937a747a
Add civetweb patch to disable DLOPEN
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-13 21:38:08 +01:00
DL6ER 1b81285fed
Add NO_DLOPEN option to civetweb's LUA routines
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-13 21:37:31 +01:00
DL6ER 0ea3a077ba
!!! Another breaking change !!! Inline 06-rfc6761.conf into FTLs generated config file, too. The symlink should be now be /etc/pihole/dnsmasq.conf -> /etc/dnsmasq.conf
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-13 20:31:04 +01:00
DL6ER 33dd385478
Add LUA support into embedded webserver
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-13 19:50:42 +01:00
DL6ER 393eaa766f
Update microtar (using the version from https://github.com/DL6ER/microtar): We now have support for large files and in-memory processing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-13 18:28:43 +01:00
DL6ER ddd2b02bf4
Further debug logging for setupVars.conf importing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-13 17:38:25 +01:00
DL6ER b9f95a389a
Add library miniz - a lossless, high performance data compression library in a single source file that implements the zlib (RFC 1950) and Deflate (RFC 1951) compressed data format specification standards
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-12 19:35:31 +01:00
DL6ER b952f5e5e2
Add library microtar - A lightweight tar library written in ANSI C
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-12 19:33:42 +01:00
DL6ER 4a033795c1
Add misc.temp.unit and move misc.temp_limit to misc.temp.limit
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-12 19:27:24 +01:00
DL6ER 1ef587f552
Add build.sh ci option to allow for easier co-operation of native and devcontainer builds in the same workspace
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-12 19:15:36 +01:00
DL6ER 41ebe5ec51
!!! BREAKING CHANGE !!! Create new dnsmasq config file and test it.
This needs a manual removal of /etc/dnsmasq.d/01-pihole.conf and /etc/dnsmasq.d/02-pihole-dhcp.conf (if existing).
Furthermore, we need a new symlink /etc/pihole/01-pihole.conf -> /etc/dnsmasq.d/01-pihole.conf

Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-12 18:12:41 +01:00
DL6ER 70127edd52
Add config.dnsmasq and routines to write dnsmasq config files
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-11 20:14:46 +01:00
DL6ER 8033c6c6bf
Add new config items to the test TOML file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-09 20:34:54 +01:00
DL6ER 8bbd49acf4
Modify /version to match the new /etc/pihole/versions file format
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-09 20:31:14 +01:00
DL6ER d8e95f4e46
Add config.http.interface.boxed and config.http.interface.theme primed by setupVars.conf:WEBUIBOXEDLAYOUT and WEBTHEME, respectively
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-09 20:25:24 +01:00
DL6ER 64441ed6d8
Add config.misc.temp_limit setting primed by setupVars.conf:TEMPERATURE_LIMIT (if available) used to signal beyond which temperature the frontend should consider the temperature "hot"
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-09 20:19:59 +01:00
DL6ER 999a9735cb
Do not store the payload on the stack but allocate heap memory for it. Using dozens of KB for each API connection is dangerous, hundreds of KB large payloads is surely a very bad idea. (rmemeber, the answer to how-much-it-too-much is the rather vague: too much is when the stack overflows)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-09 20:08:39 +01:00
DL6ER 140a365806
Tests: Set api.pwhash and dns.blocking.mode using PATCH /api/config
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 21:29:55 +01:00
DL6ER 4abba45878
Add "uname" object to /ftl/sysinfo containing the nodename, domainname, architecture and other details from uname
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 21:29:55 +01:00
DL6ER f199ac2f08
Automatically migrate the API password hash and the lists of clients and domains to be excluded from setupVars.conf
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 21:21:38 +01:00
DL6ER 1414e0d397
Ensure checkAPI.py also accepts situations with localAPIauth = false
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 20:59:18 +01:00
DL6ER 448550fd9a
Add PATCH /config which allows modifying all FTL config values. Changed values go into effect *immediately* and the config file is updated.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 20:55:31 +01:00
DL6ER 8a2ebe197c
Update ftl-build containers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 20:21:42 +01:00
DL6ER a9e93ba1f8
Calculate percentage of blocked queries only of there is at least one blocked query to prevent division-by-zero issues (leading to None)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 19:04:31 +01:00
DL6ER 4ac52263e9
Implement login for python API checking script
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 16:04:26 +01:00
DL6ER 27b4a6d805
Ensure dhcp-discover and regex-test do not overwrite the config file after parsing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 15:06:11 +01:00
DL6ER 3c5c1d52b7
Transform log_debug() into a function-like macro to save some time when we are not in debugging mode (the function is not called in this case)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-08 09:55:51 +01:00
DL6ER b8354c515c
Fix debug flag parsing after changing from bit-wise flags to individual bools
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-07 20:08:38 +01:00
DL6ER 8efd253529
Merge remote-tracking branch 'origin/development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-07 18:37:13 +01:00
DL6ER e145d20d28
Rewrite the entire config-related code to allow for changing data without having to restart. Hereby, we greatly reduce code duplication in the TOML routines so we won't have to touch tme in the future when adding additional options.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-07 18:06:41 +01:00
DL6ER 8c4b4d7ed5
Add checkAPI.py to test suite
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-04 20:22:40 +01:00
DL6ER 51aaea2e96
Update node from 12.x to 19.x for the openapi-validator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-04 18:38:02 +01:00
DL6ER 58fd9aaa36
Use table query_storage instead of view queries in all places (well, except one which will be covered at a later time)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-03 22:11:53 +01:00
DL6ER 6d7239dbc8
Add example verification when (possibly multiple) global examples are provided below the schema level
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-03 22:08:55 +01:00
DL6ER f2d68f20d7
Also verify endpoint structure: Query endpoints from FTL and check if all properties mentioned in the docs are present (and of correct type) and that there are no extra properties we forgot to document. Furthermore, also verify that the provided examples are of correct type, too.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-03 21:51:13 +01:00
DL6ER 5019cc7ac7
Add /api/stats/database/summary documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-03 20:17:34 +01:00
DL6ER 9c19abb86d
Add /api/stats/query_types and /api/stats/database/query_types documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-03 20:06:52 +01:00
DL6ER 8588c5c62d
Add /api/stats/top_clients and /api/stats/database/top_clients documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-03 19:24:14 +01:00
DL6ER 0b3a5baa0b
Add /api/stats/top_domains and /api/stats/database/top_domains documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-03 19:11:02 +01:00
DL6ER 73c8783abc
Add /api/stats/recent_blocked documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 21:41:41 +01:00
DL6ER ffacb1ad9e
Add /history/database/clients documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 21:37:17 +01:00
DL6ER 225187447f
Add /api/history/database documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 21:31:57 +01:00
DL6ER c7803df604
Add /api/stats/upstreams and /api/stats/database/upstreams documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 21:19:11 +01:00
DL6ER 4936e0688f
Add documentation for /api/queries/suggestions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 21:08:01 +01:00
DL6ER 60c09602ac
Remove /api/settings/web
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 20:58:33 +01:00
DL6ER 8df3500c83
Move /api/ftl/gateway -> /api/network/gateway, /api/ftl/interfaces -> /api/network/interfaces, /api/ftl/network -> /api/network/devices, and /api/ftl/config -> /api/config and add documentation for /api/network/devices
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 20:52:13 +01:00
DL6ER 69aaa71c97
Improve checkAPI.py script (add handling of {kind} URI variables)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 20:41:21 +01:00
DL6ER fa71150336
Order network devices descending by lastQuery
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 20:38:20 +01:00
DL6ER 507df1c157
Move /api/network to /api/ftl/network
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 20:37:50 +01:00
DL6ER cde28a0dca
Tests: Update expected ConfigStruct size
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 20:54:02 +01:00
DL6ER ab5722e2a4
Accelerate checkAPI script by ensuring we parse each YAML at most once
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 20:16:01 +01:00
DL6ER 32279e0765
Also rename config options in test pihole-FTL.toml
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-02 20:08:45 +01:00
DL6ER bd5519c6bf
Add API checking script
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-01 20:34:40 +01:00
DL6ER 3389b1f0c2
Add /api/ftl/config which can be used to retrieve the entire configuration of FTL. This endpoint only supports GET so far, however, we will add POST in the future so that configuration values can be changed through the API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2023-01-01 18:19:36 +01:00
DL6ER c7110ff1a7
Update ftl-build containers
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 20:18:54 +01:00
DL6ER c39a6e4080
Add /api/ftl/interfaces documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 20:18:20 +01:00
DL6ER 8f418a7788
Add /api/ftl/gateway documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 13:26:19 +01:00
DL6ER 393792b606
Remove gateway object from /api/ftl/interfaces
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 13:22:02 +01:00
DL6ER e776e0290a
Rename JSON macros
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 13:17:18 +01:00
DL6ER ffbe2bd370
Move /api/endpoints to /api/ftl/endpoints and add /api/ftl/endpoints documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 13:11:52 +01:00
DL6ER e9f5f667fa
Add /dns/port documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 12:59:24 +01:00
DL6ER 23554c4241
Onlycompile outdated documentation file to speed-up the compilation process
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 10:23:06 +01:00
DL6ER 973df98b13
Add /api/endpoints
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 10:00:10 +01:00
DL6ER a5d0a663e4
Update embedded civetweb to 1.15
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 08:44:43 +01:00
DL6ER e78f691fa0
Add civetweb patches
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-31 07:12:11 +01:00
DL6ER 7475515505
Update actions/upload-artifact
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-30 21:39:53 +01:00
DL6ER 840e73ec4c
Resolve missed merge conflict on Github workflow
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-30 16:17:08 +01:00
DL6ER a9ea62a3fc
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-12-30 15:12:14 +01:00
DL6ER 98dad054f5
Improve temperature computation and sensor name reading
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-18 18:42:16 +02:00
DL6ER bd817f54ee
Add device.model to sysinfo object
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-17 22:00:31 +02:00
DL6ER b2f4201db4
Ensure temperature sensor labels are read correctly
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-17 21:07:03 +02:00
DL6ER ebe892158c
Adjust expected 32bit struct sizes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-17 13:32:19 +02:00
DL6ER 848613488b
Re-read blocking mode from pihole-FTL.toml on receipt of SIGHUP
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-17 13:28:22 +02:00
DL6ER 0dcabb0146
Locally refused queries should not be considered an error when configured like this using regex;reply=REFUSED
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-17 13:05:15 +02:00
DL6ER 5d0559a5cb
Warn if invalid IPv4/IPv6 addresses are specified in the config file
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-17 13:02:10 +02:00
DL6ER fd12962f60
Lock shared memory before storing queries to database
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-17 12:34:24 +02:00
DL6ER b422d1b931
Add dns.reply.own_host.IPv4/6 and dns.reply.ip_blocking.IPv4/6 to pihole-FTL.toml
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-17 12:32:40 +02:00
DL6ER 20ba471ccd
pihole-FTL.log was renamed to FTL.log
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-17 12:20:28 +02:00
DL6ER 4615210b59
Change default log file path (consequence of https://github.com/pi-hole/FTL/pull/1346)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-03 15:56:03 +02:00
DL6ER fa9f9a9b8e
Remove some incompatible tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-03 14:17:25 +02:00
DL6ER a92069d945
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-07-03 14:08:26 +02:00
DL6ER e31f0766a9
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-06-29 22:47:43 +02:00
DL6ER c945053868
Merge branch 'development' into new/http 2022-06-29 20:41:08 +02:00
DL6ER f6755c3c2f
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-06-28 22:11:17 +02:00
DL6ER 8437519c24
Remove newdb and directly store in memdb to avoid database complexity and unnecessary in-memory data duplication
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-06-28 21:10:05 +02:00
DL6ER 26ccee3f4c
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-06-29 11:12:59 +02:00
DL6ER 8a5d06b618
Remove upload steps from CircleCI and add documentation upload step to Github Actions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-01-15 17:02:18 +01:00
DL6ER 0636f69bb7
Reset rate-limiting only when rate_limit.interval > 0 to reduce log noise in debug mode
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-01-15 15:39:44 +01:00
DL6ER 118e3ee280
Open database only if we are going to perform actions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2022-01-15 15:39:05 +01:00
DL6ER 6440cada53
TOML: Move check into [misc]
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-31 01:16:37 +01:00
DL6ER ed1e5a05af
Use correct pointer for regex
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-31 01:12:39 +01:00
DL6ER e52e10ab97
Change wording of routine
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-31 01:11:22 +01:00
DL6ER 2c60083af0
Print if there was a match we might have ignored due to other settings in regex debug mode
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-31 00:24:04 +01:00
DL6ER efc577a1de
Use get_query_reply_str() routine instead of reply_status_str object.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-31 00:03:43 +01:00
DL6ER e1887235b2
Fix singular/plural when reporting regex client(s)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-30 23:49:03 +01:00
DL6ER 4347d16081
Blocking mode is read when the first query is checked for blocking state. RELOAD_BLOCKINGMODE is set when reloading the DNS cache (also initially)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-30 23:46:03 +01:00
DL6ER 0cab8bea78
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-30 21:11:51 +01:00
DL6ER 1b43a44d42
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-14 10:21:54 +01:00
DL6ER 34544d0b8e
Include full dig in CI output
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-12-13 11:21:10 +01:00
DL6ER 7dbe1a09eb
Static analysis improvements
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-11-03 16:14:31 +01:00
DL6ER 285d62a4df
Update cJSON to v1.7.15
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-11-02 15:50:03 +01:00
DL6ER 22655cfdc7
Ensure HTTP server is started
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-11-02 15:09:38 +01:00
DL6ER d2db645f71
Update tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-11-02 15:09:38 +01:00
DL6ER 3f97888c1a
Ensure database permissions are correct
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-11-02 15:09:38 +01:00
DL6ER 2188fb8c0b
Extend version information by CivetWeb, cJSON and PH7
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-11-02 15:09:38 +01:00
DL6ER 86f8ab7774
Add new file src/cache_info.h
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-11-02 15:09:38 +01:00
DL6ER c0570c51c2
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-11-02 09:53:13 +01:00
DL6ER edc1583369
Fix two incorrect search-and-replace
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-07-14 20:43:32 +02:00
DL6ER 7b2770e7f6
Merge branch 'release/v5.9' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-07-13 17:04:16 +02:00
DL6ER db5c9470ba
Also upload pihole.log on CI test fail
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-07-11 11:23:55 +02:00
DL6ER 19806365c9
Tests: Include warnings and errors in the test result if we found them.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-06-11 10:07:39 +02:00
DL6ER e290e10c51
Use POSIX interfaces get/setpriority() instead of nice()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-06-11 10:05:07 +02:00
DL6ER 69e43735c9
Tests: Cannot change niceness of pihole-FTL on CircleCI
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-06-10 12:35:49 +02:00
DL6ER 018dc6788c
Report debug setting if enabled
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-06-10 12:30:02 +02:00
DL6ER 55bf825a81
Implement TOML config file reader/writer and a converter of the pre-v6.0 config file format.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-06-10 12:11:25 +02:00
DL6ER 82e9a316da
Update embedded civetweb 1.13 -> 1.14
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-06-08 09:57:18 +02:00
DL6ER a2aca98d25
Fix possible ressource deadlock in DB_read_queries()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-28 10:58:20 +02:00
DL6ER dc820e5b65
Merge pull request #1141 from yubiuser/new/http_logging
New logging style
2021-05-28 10:56:48 +02:00
DL6ER ed4c645715
Improve indentation in remaining source files.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-28 10:43:18 +02:00
DL6ER e34da9b2b0
Undefine obsolete logg() routine.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-28 10:27:49 +02:00
DL6ER 34dd54bb90
Fix indentation of refactored log routines.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-28 10:27:30 +02:00
DL6ER 8dfae4f661
Merge branch 'new/http_logging' of https://github.com/yubiuser/FTL into yubiuser-new/http_logging 2021-05-28 10:09:49 +02:00
yubiuser 498ca26fae New logging style gravity-db.c
Signed-off-by: yubiuser <ckoenig@posteo.de>
2021-05-27 21:13:57 +02:00
yubiuser b13f873353 Fix common.c
Signed-off-by: yubiuser <ckoenig@posteo.de>
2021-05-27 18:27:29 +02:00
yubiuser 8cfef60cdf Remove duplicate wording common.h
Signed-off-by: yubiuser <ckoenig@posteo.de>
2021-05-27 17:42:36 +02:00
yubiuser 5e70490697 New logging style network-table.c
Signed-off-by: yubiuser <ckoenig@posteo.de>
2021-05-27 17:40:05 +02:00
yubiuser 5e95dbf0eb New logging style common.h
Signed-off-by: yubiuser <ckoenig@posteo.de>
2021-05-27 13:52:13 +02:00
yubiuser 39753f21c7 New logging common.c
Signed-off-by: yubiuser <ckoenig@posteo.de>
2021-05-27 13:46:53 +02:00
DL6ER 078daa8ca2
Tests: Fix double-start detection test
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:42:08 +02:00
DL6ER d2f60aed08
Tests: Fix regex CLI tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:33:49 +02:00
DL6ER 700876bd6f
Tests: Improve "No ERRORS in log" test
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:25:37 +02:00
DL6ER d3b8b20bea
Use specific log routines in webserver/*.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:10:26 +02:00
DL6ER 1b05d953f9
Use specific log routines in syscalls/*.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:08:03 +02:00
DL6ER 86e07d5ae4
Use specific log routines in hooks/accept.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:04:13 +02:00
DL6ER 45812b3473
Use specific log routines in hooks/upstream_error.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:03:18 +02:00
DL6ER 5b91be4906
Use specific log routines in hooks/tcp_workers.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:01:54 +02:00
DL6ER 941fa27480
Use specific log routines in hooks/set_reply.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:00:43 +02:00
DL6ER 8176b8948b
Use specific log routines in hooks/query_in_progress.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 13:00:02 +02:00
DL6ER 184da5fdf7
Use specific log routines in hooks/new_query.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:59:38 +02:00
DL6ER 12c01e6a45
Use specific log routines in hooks/multiple_replies.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:57:43 +02:00
DL6ER b47be0801f
Use specific log routines in hooks/iface.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:57:08 +02:00
DL6ER 7cc7a84f55
Use specific log routines in hooks/header_analysis.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:56:54 +02:00
DL6ER bd94b0a26c
Use specific log routines in hooks/forwarding_failed.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:56:40 +02:00
DL6ER 1dba449708
Use specific log routines in hooks/forwarded.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:56:21 +02:00
DL6ER 47ce7020e6
Use specific log routines in hooks/fork_and_bind.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:56:03 +02:00
DL6ER e35cc1021c
Use specific log routines in hooks/extract_question_flags.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:54:33 +02:00
DL6ER eb7ac66c1b
Use specific log routines in hooks/dnssec.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:53:32 +02:00
DL6ER 815b433330
Use specific log routines in hooks/dnsmasq_reload.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:53:03 +02:00
DL6ER c45743d9b9
Use specific log routines in hooks/detect_blocked_IP.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:52:44 +02:00
DL6ER 9499cb5ac7
Use specific log routines in hooks/CNAME.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:51:40 +02:00
DL6ER 6f31f5a1c9
Use specific log routines in hooks/check_blocking.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:51:05 +02:00
DL6ER 9d0582e61d
Use specific log routines in hooks/receive_reply.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:48:52 +02:00
DL6ER baf4be08a0
Use specific log routines in hooks/cache.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:47:41 +02:00
DL6ER 80af52754b
Adjust tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:34:35 +02:00
DL6ER b541f0fe9a
Use specific log routines in database/message-table.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:30:00 +02:00
DL6ER d057c1e9fc
Use specific log routines in database/database-thread.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:27:15 +02:00
DL6ER b2f47ee846
Use specific log routines in api/stats.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:25:43 +02:00
DL6ER cafdefed01
Use specific log routines in dnsmasq code
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:23:59 +02:00
DL6ER d30fd44ff8
Use specific log routines in database/sqlite3-ext.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:22:40 +02:00
DL6ER a508120fb1
Use specific log routines in database/aliasclients.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:14:21 +02:00
DL6ER 449ed354be
Use specific log routines in api/stats_database.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:11:23 +02:00
DL6ER 150e13252d
Use specific log routines in api/queries.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:09:20 +02:00
DL6ER a0a4a7a30f
Use specific log routines in api/network.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:08:46 +02:00
DL6ER 467c4f5a17
Use specific log routines in api/api.c and api/auth.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:08:00 +02:00
DL6ER 3ab46668ca
Use specific log routines in vector.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:05:03 +02:00
DL6ER 85f6e886c3
Use specific log routines in database/query-table.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 12:03:31 +02:00
DL6ER 8a3df6b543
Use specific log routines in timers.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:51:20 +02:00
DL6ER 8e2475f04a
Use specific log routines in signals.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:50:49 +02:00
DL6ER 6eb025d97b
Use specific log routines in shmem.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:49:45 +02:00
DL6ER 049fc8c513
Use specific log routines in setupVars.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:38:54 +02:00
DL6ER f0da6f748c
Use specific log routines in resolve.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:36:38 +02:00
DL6ER da83042850
Use specific log routines in regex.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:28:33 +02:00
DL6ER f072fded33
Use specific log routines in procps.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:21:28 +02:00
DL6ER 52cf3b2338
Use specific log routines in overTime.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:20:32 +02:00
DL6ER b2ab724d83
Use specific log routines in main.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:17:35 +02:00
DL6ER 648a76ae47
Use specific log routines in log.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:15:12 +02:00
DL6ER 36fbc43f87
Include which debug option leads to a DEBUG output and skip logging if this debug option is not enabled
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 11:13:25 +02:00
DL6ER 4bf86c6795
Use specific log routines in gc.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:55:09 +02:00
DL6ER 3ea02e30b9
Use specific log routines in files.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:54:00 +02:00
DL6ER 9e5679f1be
Use specific log routines in events.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:51:26 +02:00
DL6ER 1a13a9ac79
Use specific log routines in edns0.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:50:46 +02:00
DL6ER fecec5c92c
Use printf instead of specific log routines routines in dhcp-discover.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:49:07 +02:00
DL6ER 265701f72b
Use specific log routines in datastructure.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:38:03 +02:00
DL6ER 8d47cb209e
Use specific log routines in daemon.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:33:49 +02:00
DL6ER d36a0b07b9
Tests: Check against the new priority strings.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:28:25 +02:00
DL6ER 9f8519f933
Use specific log routines in config.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:23:09 +02:00
DL6ER 9d926e12d1
Use specific log routines in capabilities.c
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-27 10:22:31 +02:00
DL6ER 1b191a5e3c
Adjust tests for new logging facility
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-26 20:20:01 +02:00
DL6ER 8f40798cbe
Add in-built syslog facility. It can be selected by specifying an empty string for LOGFILE= (pihole-FTL.conf). This changes the format of the pihole-FTL.log file.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-26 13:59:29 +02:00
DL6ER 07e17f805a
Update tests to database version 10
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-26 11:52:21 +02:00
DL6ER 58fa83b60a
Fix upstreamsData size of 32bit targets
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-26 11:46:12 +02:00
DL6ER ca84d5988d
Add option to request query data from the on-disk database instead.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-26 11:36:47 +02:00
DL6ER 94a98d6804
Finish Query Log server-side pagination implementation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-26 09:28:47 +02:00
DL6ER 494d3def68
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-25 15:43:37 +02:00
DL6ER 774bd0338a
Store in and restore from long-term database: reply type, DNSSEC type, reply delay (if applicable), client hostname (if applicable), TTL and regex ID (if applicable)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-25 15:32:38 +02:00
DL6ER 5cca7f2ebe
Split monolothic dnsmasq_interface.c file into individual hook units.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-05-25 12:32:49 +02:00
DL6ER e94914be6e
Add upstreams, types, status, replies, and dnssec to /api/queries/suggestions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-04-30 10:58:02 +02:00
DL6ER 1022ff5fa1
Rename sum property to total in /api/stats
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-04-27 09:05:53 +02:00
DL6ER 97219d45cf
Fix type handling to not offset by 1
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-04-26 19:18:38 +02:00
DL6ER 1db7c40106
Update tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-04-26 14:40:06 +02:00
DL6ER 45faefa786
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-04-26 14:09:50 +02:00
DL6ER 8e6144790f
Move queries into their own API endpoint and prepare an endpoint with search field suggestions to be used with the Query Log
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-03-06 10:59:49 +00:00
DL6ER cabffb0697
Update .gitignore and add VSCode workspace exclude-settings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-03-06 06:50:01 +00:00
DL6ER ba19cb8bbd
Add DBID to /api/history/queries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-03-06 06:34:58 +00:00
DL6ER ba0af27fb5
Implement Query Log server-side processing
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-03-02 15:17:54 +01:00
DL6ER ea509ecca1
Changes for query log implementation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-03-02 12:55:22 +01:00
DL6ER 18d45d709a
Implement new fields added to the adlist table
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-03-01 11:38:46 +01:00
DL6ER 0fa1def3bb
Add a second in-memory database to ensure non-blocking operation at all times
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-28 11:51:41 +01:00
DL6ER a273d24ce5
Upload documentation to binary bucket
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-27 08:51:45 +01:00
DL6ER e2236a2533
Remove queries from in-memory table during garbage collection
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-26 20:43:16 +01:00
DL6ER f78b15f131
Add in-memory database for queries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-26 18:26:07 +01:00
DL6ER 56f0e56b1f
Merge branch 'master' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-26 17:24:41 +01:00
DL6ER 01bb3bce23
Make domain hosting the web interface customizable through the settings (for modifying the auto-redirect to admin/)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-13 18:58:56 +01:00
DL6ER b40c2eb162
Set HttpOnly on sid cookie for XSS protection
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-04 09:49:22 +01:00
DL6ER 339749da3f
Change timestamps from integer to double for (up to) nanosecond accuracy.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-03 12:37:59 +01:00
DL6ER fcbe840804
Also allow authentication by sending SID via HEADER
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-03 09:08:23 +01:00
DL6ER 948ed62f72
Add example validation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-02 12:27:26 +01:00
DL6ER 195dc3cb9a
Rename routes.{c,g} -> api.{c,h} and reduce locking where this is not needed to gain more speed for the API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-02 12:21:36 +01:00
DL6ER 7608dd5c8d
Tests: Log intermediate steps during authentication trial
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-02 11:27:38 +01:00
DL6ER 1ae67e8fbb
Also use OpenAPI-enforcer to check the OpenAPI specs in addition
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-01 13:56:27 +01:00
DL6ER 020a8f81dd
Run GHA on every push
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-01 12:36:28 +01:00
DL6ER 4582996482
Tests: Add failed and successful login attempts
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-01 12:27:38 +01:00
DL6ER dfc9dac166
Add OpenAPI schema validation (npm test)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-02-01 12:03:20 +01:00
DL6ER 891c96a43e
Fix regex-test mode
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-31 16:51:59 +01:00
DL6ER 7a43581aeb
Update build containers to v1.9 to have xxd and jq available
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-31 16:36:55 +01:00
DL6ER ecc9486e52
API docs: Some general fixes for the specs
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-31 11:48:23 +01:00
DL6ER 5797f939e6
Add authentication support to the API documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-31 10:32:28 +01:00
DL6ER d1269c0a8a
Move /api/stats/history to /api/history/queries
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-30 16:13:31 +01:00
DL6ER be7fc02494
Add /api/history documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-30 14:00:54 +01:00
DL6ER ae5d6e2c19
Add /api/stats/summary documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-30 14:00:54 +01:00
DL6ER 0758dc9cc7
Add /api/version documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-30 14:00:54 +01:00
DL6ER afb154e402
Add /api/auth documentation, change expected payload from form to JSON for consistency
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-30 14:00:54 +01:00
DL6ER d9015367fd
Add /api/ftl documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-30 14:00:54 +01:00
DL6ER 865c4345e2
Add /api/dns documentation
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-30 14:00:52 +01:00
DL6ER e4c55ec52a
Embed API documentation into FTL. This ensures it is (a) always available locally, and (b) always corresponds to the API you have available locally.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-26 17:46:11 +01:00
DL6ER b851566372
Our JSON targets are NULL-tolerant.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-26 17:46:11 +01:00
DL6ER c99a3bc15e
Store comments for groups as well
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-26 17:46:11 +01:00
DL6ER 0271a38ce8
Groups don't have the groups property
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-26 17:46:11 +01:00
DL6ER 73b3d680ca
Improve error reporting for missing/incorrect sets of payload/URI arguments
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-26 17:46:11 +01:00
DL6ER 54cd12e895
Split type and kind into two fields for domains
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-26 17:46:11 +01:00
DL6ER c1f29ed978
Improving error reporting of the API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-26 17:46:11 +01:00
DL6ER fa4c194045
Improve URI matching algorithm
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-26 17:46:11 +01:00
DL6ER e8e4c5f180
Move id, date_added, date_modified into extra database object
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-26 17:46:09 +01:00
DL6ER efd5951968
POST should not include the target to get pushed
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-24 16:07:27 +01:00
DL6ER 0358c3b861
Test compile regex before adding to the database (we may want to reject it)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-23 08:31:21 +01:00
DL6ER f12aa58f3f
Rename /api/adlists -> /api/lists
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-23 07:49:41 +01:00
DL6ER 5e616f4ac2
Fix a bug in CivetWeb server
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-23 07:11:38 +01:00
DL6ER 8b3df9f554
Implement /api/clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-22 15:25:49 +01:00
DL6ER 9a2a2cdf10
Actually reload gravity data on list add/edit/remove
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-22 11:58:36 +01:00
DL6ER 98e74256ae
Send domains/clients/groups/adlists counts in a new ftl object
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-22 11:47:57 +01:00
DL6ER 45801543db
Remove obsolete ping/pong test
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-22 11:20:12 +01:00
DL6ER 54e206a273
Shorten history JSON keys
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-22 10:28:27 +01:00
DL6ER f67becc3e1
Introduce new ftl_conn struct that makes it easier to share stuff across processing subroutines in the same thread.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-21 16:24:16 +01:00
DL6ER 15ba45e50f
Extract payload only once
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-21 13:52:31 +01:00
DL6ER 0d91fcd55d
Rename /api/list -> /api/domains, /api/adlist -> /api/adlists, /api/group -> /api/groups, added /api/clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-21 08:28:37 +01:00
DL6ER 1fc05034ef
Set table columns comment/description to NULL if empty
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-20 17:07:36 +01:00
DL6ER 5c3a2b509c
Implement changing group assignments through the API
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-20 16:36:02 +01:00
DL6ER 7fa2dac90a
Allow domains/groups/adlists to be removed from the database
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-20 12:33:16 +01:00
DL6ER b08954aec7
http_get_payload(): Extract body payload also for PUT and PATCH
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-20 11:51:23 +01:00
DL6ER 5e1c75fb84
Include system object in /api/stats/summary to need one AJAX call less
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-20 11:12:47 +01:00
DL6ER 24aa5537a0
Merge branch 'master' into new/http 2021-01-20 10:12:09 +01:00
DL6ER 7acaeb72c7
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-19 15:35:46 +01:00
DL6ER c0505d7729
Improve format of overTime replies.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-16 16:07:26 +01:00
DL6ER b7f94c7850
Tests: No login needed when there is no password
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-16 12:39:34 +01:00
DL6ER 992fac4bde
Use SameSite=Strict as defense against some classes of cross-site request forgery (CSRF) attacks. This ensures the session cookie will only be sent in a first-party (i.e., Pi-hole) context and NOT be sent along with requests initiated by third party websites.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-16 12:35:05 +01:00
DL6ER e1922ad1ff
Get memory details directly from the kernel as we cannot rely on the information provided by sysinfo() [there sin't anything we need to compute the proper amount of used = unclaimable memory]
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-16 12:22:13 +01:00
DL6ER 4d1e9368b9
Tests: GET /api/auth results in a challange being returned to us
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-16 11:37:50 +01:00
DL6ER 9beb006006
On 2017-08-27 (after v3.3, before v3.4), nettle changed the type of destination from uint_8t* to char* in all base64 and base16 functions (armor-signedness branch). This is a breaking change as this is a change in signedness causing issues when compiling FTL against older versions of nettle.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-16 11:30:25 +01:00
DL6ER 089d25628b
Add fallback label for temperature sensors in case they don't have their own label
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:43:09 +01:00
DL6ER 262f62de2b
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:34:07 +01:00
DL6ER 8fa055122e
Use payload in form-format when adding/modifying lists
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER a8a26db9c8
Add /api/adlist endpoint to read/add/modify/delete adlists
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER a45c183e3e
Add /api/group endpoint to read/add/modify/delete groups
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER 9eb9fea198
Return type and group_id array for domains
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER 4421e88596
Rename {white,black} to {allow,deny}
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER 9966c84f54
Localhost should be able to request all ressources if this is set via a config option
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER 8e63f75a71
Add /api/ftl/system?full=true for sourceing even more information (thinking of third-party applications)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER 485642834f
Add /api/ftl/system for information about CPU, memory and temperature (if available)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER 2af933ce88
Implement login as POST to /api/auth, logout as DELETE to /api/auth
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER 2c3cefe074
Ensure every auth object has the session object
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:08 +01:00
DL6ER 5b6ad3c853
Always report success when there is no password set
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:07 +01:00
DL6ER de8d8708e6
Improve used randomness generator
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:07 +01:00
DL6ER ae713a9d69
Request challange over /api/auth/login
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:07 +01:00
DL6ER 7394722185
Improve log file locking
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-15 19:18:05 +01:00
DL6ER 5f110140c9
Improve shared memory lock mutexes
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-12 19:52:56 +01:00
DL6ER 68ee658369
Retore errno in FTL's pthread syscall
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-12 05:50:51 +01:00
DL6ER b95559bc28
Increase web service thread count to 16
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-11 20:02:30 +01:00
DL6ER a1ec17cca2
Require authentication for api_stats_query_types
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-11 20:02:06 +01:00
DL6ER c8d2e74bfb
Implement challenge-response authentication
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-11 19:47:26 +01:00
DL6ER 67db8ac608
Upload HTTP and PH7 logs to Tricorder on errors in tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-08 12:15:13 +01:00
DL6ER f498a2b4ea
Use seperate lock for web log to avoid dead-locking with FTL's main lock (e.g. when a syscalls needs to lock an error in between)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-08 12:07:20 +01:00
DL6ER ce3c81d327
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-08 11:21:51 +01:00
DL6ER 8e83082636
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2021-01-02 11:36:59 +01:00
DL6ER f71aa9467b
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-12-08 22:23:05 +01:00
DL6ER 07ea47a0e6
Merge branch 'development' into new/http
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-11-16 19:30:16 +01:00
DL6ER 92ec8e8d70
Update civetweb v1.12 -> v1.13 (and simplify auth-cookie handling)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-11-06 12:59:01 +01:00
DL6ER 81a32048c7
Restructure root redirection handler
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-11-06 12:22:19 +01:00
DL6ER 2e8e7d1feb
Add more API debugging output
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-10-27 19:57:22 +01:00
DL6ER e5ce78a9bd
Redirect pi.hole/ -> pi.hole/admin
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-10-26 17:36:31 +01:00
DL6ER 0abcc246a5
Merge branch 'development' into new/http-ph7
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-10-26 12:38:55 +01:00
DL6ER fd2f8d7926
Update cJSON from 1.7.13 to 1.7.14 (released 3 September 2020). The structure of linkedlist has been changed to improve the efficiency of adding items to arrays/objects.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-09-03 21:51:44 +02:00
DL6ER 3f918fec95
Move the API from http://pi.hole:8080/admin/api to http://pi.hole:8080/api
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 23:29:22 +02:00
DL6ER 5375ea0a62
Return null for domain comment if this is what is stored in the database.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 23:18:34 +02:00
DL6ER 12112396f0
Delete running blocking status timer when requesting the same blocking mode as already active.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 23:17:45 +02:00
DL6ER ff4b2bcfef
Zero-initialize memory on the stack to avoid picking up outdated content
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 21:50:34 +02:00
DL6ER a292c8f67f
Improve database statistics endpoint
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:31 +02:00
DL6ER 49dc3bfda0
Simplify request variable extraction
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:31 +02:00
DL6ER aa3f7443f9
Ensure we use the same reply type in /api/stats/summary as in /api/dns/status
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:31 +02:00
DL6ER b5e965e248
Improve /api/version endpoint.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:31 +02:00
DL6ER 822f8c6a28
Implement proper error handling for network table IP address sub-query
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:31 +02:00
DL6ER ae035c4ba7
Put FIFO log into its own unit (out of the API code)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:31 +02:00
DL6ER 3906490958
Return SQL error to user if sourcing data from the network table fails
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:31 +02:00
DL6ER e10d7fe27e
Avoid buffer overflow.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:31 +02:00
DL6ER e018146faa
Rename /api/ftl/clientIP to /api/ftl/client as we return more data than just the IP address (this is mostly a testing endpoint)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:31 +02:00
DL6ER 784b8ba3c9
Differentiate between POST (error on existing) and PUT, PATCH (replace if existing) for domain lists. Also return GET style reponse on success.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:30 +02:00
DL6ER fa5f6cdcdf
Use booleans instead of string for controlling the blocking status. Return the GET response after a successful PATCH request.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 20:07:30 +02:00
DL6ER be3b4045ae
Add filtering for domainlist endpoints
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-17 00:44:44 +02:00
DL6ER fc4f850259
Uniform GET and POST keys and rename /dns/status to /dns/blocking to avoid misunderstandings
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-16 23:44:48 +02:00
DL6ER 56fe849d83
Move white- and blacklist endpoints one level up
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-16 23:11:20 +02:00
DL6ER 2c78622cd2
Streamline /api/dns endpoints while writing the documentation for them
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-16 19:09:31 +02:00
DL6ER 17d3562099
Improve cmake system.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-16 19:09:25 +02:00
DL6ER 2709cdbd17
Simplify error report messages.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-15 21:07:50 +02:00
DL6ER c3d2c87800
Add request handler for directories (try /index.php)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-15 21:05:09 +02:00
DL6ER dc915c2a60
Disable directory listings.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-15 21:03:37 +02:00
DL6ER f326d2bd7d
Add two new log files for HTTP infos (incl. access logs if in API debug mode) and PH7 engine errors and messages.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-15 21:03:22 +02:00
DL6ER 73be307c77 Pass HTTP head to PH7 so it can extract header variables (, , , ...)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-02 15:19:07 +02:00
DL6ER 67a0395390 Redirect PH7 errors into pihole-FTL.log (instead of showing in the browser output) and define gethostname() PHP function.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-02 08:52:55 +02:00
DL6ER cf5f3ef464 gcc-9 optimizations.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-06-01 09:44:49 +02:00
DL6ER de09b73eb7
Use embedded PH7 instead of external PHP-CGI interpreter.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-30 15:44:16 +02:00
DL6ER c92a0e4bc4
Generally assume authentication to ease the development phase until we are actually able to add authentication.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-29 20:54:30 +02:00
DL6ER 2c6e2ae41f
Add NAPTR support for /api/stats/summary
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-29 00:42:35 +02:00
DL6ER 473a88f077
Remove React-specific index page rerouting.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-29 00:24:00 +02:00
DL6ER 308c80bc5a
Add PHP support in Pi-hole' HTTP server.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-28 23:59:52 +02:00
DL6ER 6e40e3b2dc
Finish transition to cmake on the new/http branch.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-28 20:23:51 +02:00
DL6ER 8afd0631c1
Update Civetweb server to 1.12
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:43:56 +02:00
DL6ER d878499d67
Update cJSON to 1.7.13
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:43:56 +02:00
DL6ER 0b680952a4
Add const attribute to new sqrt routines.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:43:56 +02:00
DL6ER 0aaa3237b1
Exclude database lookup time from computed forward destination delay.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:43:56 +02:00
DL6ER 5d54d3412c
Compute average response times for used upstream destinations.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:42:18 +02:00
DL6ER 4c6d94c739
Fix failed git auto-merge.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:39:23 +02:00
DL6ER 6a4040e8a5
Minor optimizations to domain list handling routines (reading, adding, and removing)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:17 +02:00
DL6ER 5bdf2b96cc
Fix forwarded queries counting logic quirk.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:17 +02:00
DL6ER 4dacd1c759
Make JSON string macros robust against being called with NULL strings.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:16 +02:00
DL6ER ef5cfe0a17
Add /api/stats/database/upstreams
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:16 +02:00
DL6ER a7500f1d58
Add /api/stats/database/query_types
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:16 +02:00
DL6ER cb8131d9ac
Add /api/stats/database/overTime/clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:16 +02:00
DL6ER 7c97cb6631
Add /api/stats/database/summary
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:16 +02:00
DL6ER 76b72bf1b7
Add /api/stats/database/top_clients by generalizing the top-domains into a top-items function that can process both.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:16 +02:00
DL6ER 429622d8e9
Add /api/stats/database/top_domains and /api/stats/database/top_blocked
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:16 +02:00
DL6ER 4516ee5186
Relock shared memory before returning an error message.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:16 +02:00
DL6ER f2412873b8
Add /admin/api/stats/database/overTime/history - this allows users to select a different time interval on the dash board than only "last 24 hours".
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:37:16 +02:00
DL6ER 0f7ead8663
A few output optimizations for /api/dns/cacheinfo
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:36:13 +02:00
DL6ER 6871dbdd04
Add /api/dns/cacheinfo
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:36:13 +02:00
DL6ER 091e656de8
Only walk known IP addresses when SELECT query succeeded
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:34:23 +02:00
DL6ER 9d24bae8be
Add /api/ftl/network
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:34:23 +02:00
DL6ER 46c622f256
Keep up to 64 lines of dnsmasq messages in the FIFO buffer.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:34:23 +02:00
DL6ER a6a7b9712e
Add API_PRETTY_JSON config option as run-time (instead of compile-time) option for human-friendy API output.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:34:23 +02:00
DL6ER fdce5481f9
Send all details we have about a domain (excluding the database ID, because we don't need it). See PR web#440 for the necessary changes in NG web.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:34:23 +02:00
DL6ER 6bf7fb782e
Read directly from domainlist table instead of using the views. This allows us to read also disabled domains plus to get all the corresponding data: date_added, date_modified, comment, enabled. They are now available in FTL, however the JSON format expected by the web interface needs to be changed to be able to send this information over.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:34:23 +02:00
DL6ER 257fcbf695
Implement transition from individual domain lists into a unified domainlist (see core PR #3015).
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:31:54 +02:00
DL6ER 5385098dc8
Reread index.html on receipt of real-time signal 1.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:31:11 +02:00
DL6ER 5a276fe1ee
Ensure shared memory is locked when reloading the DNS cache.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:29:47 +02:00
DL6ER 5d03136e39
Send 200 queries for the query log at once. With this, the clients will always have some pages of the table in their cache.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER c639cf9b6d
Actively free expired user sessions to make room for new ones.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER c38285461f
Automatically log in users when there is no (or an empty) password set in setupVars.conf
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER c389f06944
Make HTTP/API session timeout configurable through pihole-FTL.conf
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER 8742e140c0
Update user cookie when session is still running.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER 76bcef3771
Update timestamp of known client when we see them again.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER 41a1da65cd
Refresh in-memory index.html on receipt of SIGHUP. This allows replacing the content of /admin without having to restart FTL.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER 8ce1ac146e
/api/stats/history: Only use cursor when it is a numeric value, skip if "null".
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER 5d61a0c22e
Add NULL cursor in history result when privacy level is too high.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER 2ee30679f2
Reduce memory consumption of the FIFO log.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER b9857664f5
Lock SHM during API access.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER 2d8d706478
Lock SHM during FIFO buffer activity.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER 149572c1a9
Move FIFO buffer into shared memory to ensure all spawned TCP children can add entries to it.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:28:30 +02:00
DL6ER a8eb1889ea
Remove unused variable.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:55 +02:00
DL6ER 9f8152e393
Don't send ID field, it is not neded by the client. And even if it would be, it could easily be computed from nextID.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:55 +02:00
DL6ER f2aa1d25fb
Code around known gcc-arm bug.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:55 +02:00
DL6ER bdbad7ef0c
Send correct frames if FIFO is not yet completely filled.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:55 +02:00
DL6ER 12434192b8
Do not send unused slots from the FIFO buffer.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:55 +02:00
DL6ER 2a07c992ab
Add option for whether authentication is needed for localhost requests. Defaults to false. (API_AUTH_FOR_LOCALHOST)
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:55 +02:00
DL6ER e4c7cec349
Add FIFO buffer endpoint at /admin/api/ftl/dnsmasq_log. This is a FIFO buffer collecting and returning up to 32 messages from memory (compile-time option). The API endpoint can be continuously polled with the last returned ID to only show new messages that can be appended to a log on the web interface.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 50e2736ef3
Copy what dnsmasq would log even when there is no logging destination configures. This is an experimental feature at this time and can (temporarily) enabled by settting DEBUG_AP=true in pihole-FTL.conf. This feature will likely be moves into a new API endpoint /admin/api/log (TBD) when it turns out to be useful.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 637558f5aa
Rename send_http_error() to send_http_internal_error().
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 8cedaf76eb
Disable test that does not apply without a hosted web interface and add two new tests for /api/auth.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 31d0d8f75f
A test should do what is written in its description
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 59025029de
Remove dead function http_send().
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER baa4b37ddc
Add --slient to the curl commands to suppress the output of curl's progress bar during the tests. They are confusing bats.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 638ab2f933
Ensure minimum required data is sent by the API even when privacy levels are applied. Sending simply empty responses violates the expectations of the NG web interface.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER d59ac986d8
Improve additional headers as suggested by a security checker site.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER b6bf9bee20
Add Content-Security-Policy header to resolve CPS issues seen in Firefox when hosted on a machine different than localhost. The critical part is 'unsafe-inline' which is necessary for the inline Javascript found in index.html.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 91e1e99d31
Use curl instead of wget for the HTTP API tests.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER b8ec63bf7e
Directly return without buffering in the API handler.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 4a07a9ffcd
Add tests for JSON/normal error 404.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 440e4aaa84
Send JSON 404 error if an undefined path has been requested insode /api/... Elsewhere in the web interface we still send the usual 404 page.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 07285934cd
Add more convenience functions and return 400 Bad Request responses when invalid parameters are given to the /api/stats endpoints.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER fd96e3693f
Remove deprecated feature to return forward destinations without sorting. This was never offered for any other type of data so it is mroe consistent to completely remove it.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:26:54 +02:00
DL6ER 5bb6a8e82d
Add send_json_success() as yet another short convenience function.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:24:20 +02:00
DL6ER 1f78f2f276
Add new send_json_error() and send_json_unauthorized() routines everywhere we are sending an error.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:24:20 +02:00
DL6ER d9aa8bd45f
Allow password-less login if WEBPASSWORD is not set or set to an empty string in setupVars.conf.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:24:20 +02:00
DL6ER 3efb39c3af
Rename api/http.{c,h} to api/http-common.{c,h}. Separate routing function into api/routes.c.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:24:20 +02:00
DL6ER 0634bf363b
Implement correct API response for failed auth requests.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER c1b25642e0
ACL: Set default to allow all access.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER e2d72189e7
Do not consider ERROR index.html as fatal during the tests + typo fixes.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER a7bd6fea25
Move detection of API debugging mode into a dedicated subroutine to ensure DEBUG_API can also be enabled during a running FTL session.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 6e9cb2c2d3
Log HTTP server errors into pihole-FTL.log. Plus log all access to the server when DEBUG_API=true
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 4fc7554a45
Add Access Control List support for the web server.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 443d302c72
Remove additional_headers line.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 14a2df5ae2
Print reason for Auth Failure.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 224d3a5168
Improve error handling in read_indexfile(). Memory errors are worrying but not fatal at this point.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 95db7fe471
Insert <base href='...'> tag on-the-fly.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER f9e3630e0d
Load content of index.html into and serve it from memory.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER be9fac13ae
Implement rerouting from all paths without file ending onto index.html. This removes the requirment that FTL needs to know all the endpoints in advance.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER bb1554cdb5
Add support for a webhome. This defaults to serving the web interface in /admin now, not / (as before).
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 004fb93bbe
Add authentication requirement for most API endpoints. Some endpoints are special, e.g., /api/status where GET is allowed for anyone, however, POST is only allowed for authenticated users.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER d45ae84ec5
Add attribute malloc as the returned pointer cannot alias any other pointer valid when the function returns.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 34c9091c4a
Read actual password from setupVars.conf instead of always substituting 'A'.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 3d81ba7197
Remove debugging statement which worked only on 64 Bit architectures.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER ffc49a0b1e
Add rewrite-rules to support targets like "pi.hole:8080/dashboard/" which need to be handled by "pi.hole:8080/index.html".
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER 406e539511
Remove fake "api" object from /api/versions
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:22:51 +02:00
DL6ER d90a976d78
Add CORS header "Access-Control-Allow-Origin: *"
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:21:14 +02:00
DL6ER 543ebc9ff8
Directly use prepared integer value instead of trying to cast a double into an int.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:21:13 +02:00
DL6ER 4fe5193b1f
Do the same thing just without calling it 'floor' to please the arm architecture.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:21:13 +02:00
DL6ER e6d7b7e6d0
Reduce number of concurrent HTTP workers from 50 (default) to a more sane number of four. This should be totally sufficient.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:21:13 +02:00
DL6ER 32156ec17c
Add support for time-delayed blocking enable/disable actions.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:21:13 +02:00
DL6ER 8aed1cf42e
Add support for enabling/disabling blocking through the FTL HTTP API.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:21:13 +02:00
DL6ER 88d982a78f
Add /api/settings/ftldb
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:17:46 +02:00
DL6ER 7f7f41ab88
Disable automatic URI-decoding. Otherwise, we cannot delete domains or regular expressions containing "/". Although such domains/filters make no sense, they are not explicitly forbidden so users might add them. If they can add them, then it should also be possible to remove them.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:17:46 +02:00
DL6ER 978a159992
Simplify api_dns_somelist_read()
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:17:46 +02:00
DL6ER e761305779
Add support for removing domains through the FTL HTTP API.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:16:24 +02:00
DL6ER 243cfb0c81
Add support for adding domains through the FTL HTTP API.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:15:46 +02:00
DL6ER e51a069d48
Implement DELETE for /api/auth to allow users to actually log out.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:14:46 +02:00
DL6ER 411cf85ef7
Only allow login with correct password.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:14:46 +02:00
DL6ER 99cfb92d63
Add /api/auth/salt
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:14:46 +02:00
DL6ER 0cee19659e
Implement client validation in the API. Both, the IP address and the set cookie have to be correct, otherwise the authorization is denied. We also check for the validity of the cookie before permitting the user.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:14:46 +02:00
DL6ER 0a3424bcff
Add simple /api/auth implementation. Currently, all passwords are accepted and a login session is valid for 5 minutes.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:14:46 +02:00
DL6ER f500466a1e
Greatly reduce code duplication by using a common function for exact/regex white-/blacklist requests.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:14:11 +02:00
DL6ER 21a2ca1e27
Add /api/version
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:12:14 +02:00
DL6ER 6e518cc757
Add /api/dns/whitelist, /api/dns/whitelist/exact, /api/dns/whitelist/regex, /api/dns/blacklist, /api/dns/blacklist/exact, and /api/dns/blacklist/regex
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:11:51 +02:00
DL6ER 9ec5e5388a
Restructure files in /src/api
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:10:15 +02:00
DL6ER cc805ee635
Implement server-side pagination support for the Query Log. This makes browsing log even with millions of queries quick.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:07:29 +02:00
DL6ER 5e4fd9ac62
Explicitly cast to show we want to compare these numbers. The long term goal would be to move all the non-negative query->... elements to unsigned types.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:07:29 +02:00
DL6ER 242a8402b6
Remove obsolete prototypes and unused parameters.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:07:29 +02:00
DL6ER 091eaaa895
Add new /api/stats/recent_blocked
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:07:29 +02:00
DL6ER 834688ccaf
Remove legacy function getQueryTypesOverTime().
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:06:05 +02:00
DL6ER 7d18ccd947
Add parameter to request fewer than all queries for api/stats/history
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:06:05 +02:00
DL6ER 871fceff0e
Add client filtering for api/stats/history
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:06:05 +02:00
DL6ER d7aea33f4c
Add domain filtering for api/stats/history
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:06:05 +02:00
DL6ER 42eab4fafd
Add forward destination filtering for api/stats/history
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:04:44 +02:00
DL6ER a5e79bd891
Add query type filtering for api/stats/history
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:04:44 +02:00
DL6ER bc6572d371
Add time filtering for api/stats/history
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:04:44 +02:00
DL6ER 0bf6c10ca9
Prevent TypeErrors in teh NG web interface when no query/client is known to FTL (yet).
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:04:44 +02:00
DL6ER 6c057f55a0
Add /api/stats/history
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:04:44 +02:00
DL6ER 176e280c34
Implement special features of top_domains and top_clients that were also available in the telnet interface.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:04:44 +02:00
DL6ER 894bdd100f
Add ability to request an arbitrary number of results from top_domains and top_clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:04:44 +02:00
DL6ER 6ea5f372f7
Add /api/stats/top_clients and /api/stats/top_blocked_clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:04:44 +02:00
DL6ER 17260fb856
Add /api/stats/top_domains and /api/stats/top_blocked
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:04:44 +02:00
DL6ER 22f7093488
Add /api/stats/upstreams
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:03:23 +02:00
DL6ER 8ca18e6577
Comment "not found" as it causes the NG web interface to show an error when receiving something unexpected for still undefined handlers. To be reverted later.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:01:45 +02:00
DL6ER 900f265d2d
Add /api/stats/query_types
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:01:45 +02:00
DL6ER 046e3dee0e
Add /api/stats/overTime/clients
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-27 00:00:23 +02:00
DL6ER 29f9b498c8
Add /api/stats/overTime/history
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:59:38 +02:00
DL6ER 032d7892ce
Split string macro in two: One that just references a string (no copying needed) and one that actually copies a temporary string (and takes care of freeing it after having sent the JSON output).
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:59:38 +02:00
DL6ER fce40cbe77
Use cJSON macros to build JSON object. This has several benefits such as automatic character escaping and an overall clearer code structure.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:59:38 +02:00
DL6ER 6a05555d4c
Add a very simple handler for paths which are not defined.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:58:52 +02:00
DL6ER f2417b83cc
Remove two old and (not documented) debugging functions.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:58:52 +02:00
DL6ER 0fb2e95944
Fix off-by-one error in query type array.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:58:18 +02:00
DL6ER abe7623f0b
Add /api/ftl/db
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:54:36 +02:00
DL6ER 27117cbafe
Add /api/ftl/version
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:54:36 +02:00
DL6ER bc8baab07b
Added /api/dns/status and /api/stats/summary
This allows the Pi-hole NG web interface with minimal functionality.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:54:36 +02:00
DL6ER f537507c49
Remove rusty Telnet API. Note that this intermediate version of FTL has NO API at all.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:48:07 +02:00
DL6ER f5824a2aab
Remove Unix socket support
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:38:33 +02:00
DL6ER a5a35018f3
Add hook for testing sending a chunked response.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:34:29 +02:00
DL6ER 42fd4ea52e
Reduce code duplication.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:34:29 +02:00
DL6ER 4c36b8ee33
Use correct content type for JSON.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:34:29 +02:00
DL6ER d37bdd9cc7
Print local URI in response to /api requests.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:34:29 +02:00
DL6ER 15bcccdec8
Use bandwidth-friendly variant for the testing hook that does not include additional whitespace for formatting.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:34:29 +02:00
DL6ER 2b72b8a70a
Add listening on IPv6 address
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:34:29 +02:00
DL6ER 6fe4a51293
Add JSON formatting and parsing support to FTL
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:34:29 +02:00
DL6ER 6cf54db9ad
Use wget instead of curl in the tests
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:34:29 +02:00
DL6ER 8ef74a773c
Add ping callback and pong test for CI.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:34:29 +02:00
DL6ER 6245727387
Add FTL http wrapper.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:33:53 +02:00
DL6ER 6851c10fec
Change absolute vs. relative path of included file.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:33:53 +02:00
DL6ER 86f49d72a7
Add HTTP server implementation to FTL.
Signed-off-by: DL6ER <dl6er@dl6er.de>
2020-05-26 23:33:53 +02:00
323 changed files with 120425 additions and 18218 deletions

View File

@ -1,10 +1,20 @@
{
"name": "FTL x86_64 Build Env",
"image": "ghcr.io/pi-hole/ftl-build:x86_64",
"extensions": [
"jetmartin.bats",
"ms-vscode.cpptools",
"ms-vscode.cmake-tools",
"eamodio.gitlens"
],
}
"name": "FTL x86_64 Build Env",
"image": "ghcr.io/pi-hole/ftl-build:v2.5",
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
"customizations": {
"vscode": {
"extensions": [
"jetmartin.bats",
"ms-vscode.cpptools",
"ms-vscode.cmake-tools",
"eamodio.gitlens"
]
}
},
"mounts": [
"type=bind,source=/home/${localEnv:USER}/.ssh,target=/root/.ssh,readonly",
"type=bind,source=/var/www/html,target=/var/www/html,readonly"
]
}

View File

@ -2,5 +2,8 @@ ssudo
tre
ede
nd
doubleclick
requestor
requestors
requestors
punycode
bitap

1
.github/.codespellignore_lines vendored Normal file
View File

@ -0,0 +1 @@
self.errors.append("Exception when GETing from FTL: " + str(e))

32
.github/Dockerfile vendored Normal file
View File

@ -0,0 +1,32 @@
FROM ghcr.io/pi-hole/ftl-build:v2.5 AS builder
WORKDIR /app
COPY . /app
ARG CI_ARCH="linux/amd64"
ENV CI_ARCH ${CI_ARCH}
ARG GIT_BRANCH="test"
ENV GIT_BRANCH ${GIT_BRANCH}
ARG GIT_TAG="test"
ENV GIT_TAG ${GIT_TAG}
# 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 && \
# Move FTL binary to root directory
cd / &&\
mv /app/pihole-FTL . && \
# Create tarball of API docs
tar -C /app/src/api/docs/content/ -czvf /api-docs.tar.gz .
# Create final image containing only the FTL binary and API docs
FROM scratch AS result
COPY --from=builder /pihole-FTL /pihole-FTL
COPY --from=builder /api-docs.tar.gz /api-docs.tar.gz

View File

@ -0,0 +1,126 @@
name: Build and test
description: Builds and tests FTL on all supported platforms
inputs:
platform:
required: true
description: The platform to build for
git_branch:
required: true
description: The branch to build from
git_tag:
required: true
description: The tag to build from (if any)
bin_name:
required: true
description: The name of the binary to build
artifact_name:
required: true
description: The name of the artifact to upload
event_name:
required: true
description: The name of the event that triggered the workflow run
actor:
required: true
description: The name of the user or app that initiated the workflow run
target_dir:
required: true
description: The directory to deploy the artifacts to
# Secrets cannot be accessed in the action.yml file so we need to pass them as
# inputs to the action.
SSH_KEY:
required: true
description: The SSH private key to use for authentication
KNOWN_HOSTS:
required: true
description: The SSH known hosts file
SSH_USER:
required: true
description: The SSH user to use for authentication
SSH_HOST:
required: true
description: The SSH host to connect to
# Both the definition of environment variables and checking out the code
# needs to be done outside of the composite action as
# - environment variables cannot be defined using inputs
# - the checkout action needs to be the first step in the workflow, otherwise we
# cannot use the composite action as the corresponding "action.yml" isn't
# there yet
runs:
using: "composite"
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.1.0
-
name: Print directory contents
shell: bash
run: ls -l
-
name: Build and test FTL in ftl-build container (QEMU)
uses: Wandalen/wretry.action@v1.4.8
with:
attempt_limit: 3
action: docker/build-push-action@v5.0.0
with: |
platforms: ${{ inputs.platform }}
pull: true
push: false
context: .
target: result
file: .github/Dockerfile
outputs: |
type=tar,dest=build.tar
build-args: |
"CI_ARCH=${{ inputs.platform }}"
"GIT_BRANCH=${{ inputs.git_branch }}"
"GIT_TAG=${{ inputs.git_tag }}"
-
name: List files in current directory
shell: bash
run: ls -l
-
name: Extract FTL binary from container
shell: bash
run: |
tar -xf build.tar pihole-FTL
-
name: "Generate checksum file"
shell: bash
run: |
mv pihole-FTL "${{ inputs.bin_name }}"
sha1sum pihole-FTL-* > ${{ inputs.bin_name }}.sha1
-
name: Store binary artifacts for later deployoment
if: inputs.event_name != 'pull_request'
uses: actions/upload-artifact@v4.3.1
with:
name: ${{ inputs.artifact_name }}
path: '${{ inputs.bin_name }}*'
-
name: Extract documentation files from container
if: inputs.event_name != 'pull_request' && inputs.platform == 'linux/amd64'
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'
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'
uses: ./.github/actions/deploy
with:
pattern: ${{ inputs.bin_name }}-binary
target_dir: ${{ inputs.target_dir }}
event_name: ${{ inputs.event_name }}
actor: ${{ inputs.actor }}
SSH_KEY: ${{ inputs.SSH_KEY }}
KNOWN_HOSTS: ${{ inputs.KNOWN_HOSTS }}
SSH_USER: ${{ inputs.SSH_USER }}
SSH_HOST: ${{ inputs.SSH_HOST }}

96
.github/actions/deploy/action.yml vendored Normal file
View File

@ -0,0 +1,96 @@
name: Deploy
description: Deploy the FTL binary and documentation
inputs:
pattern:
required: true
description: The pattern to match the artifacts to download
target_dir:
required: true
description: The directory to deploy the artifacts to
event_name:
required: true
description: The name of the event that triggered the workflow run
actor:
required: true
description: The name of the user or app that initiated the workflow run
# Secrets cannot be accessed in the action.yml file so we need to pass them as
# inputs to the action.
SSH_KEY:
required: true
description: The SSH private key to use for authentication
KNOWN_HOSTS:
required: true
description: The SSH known hosts file
SSH_USER:
required: true
description: The SSH user to use for authentication
SSH_HOST:
required: true
description: The SSH host to connect to
runs:
using: "composite"
steps:
-
name: Get binaries built in previous jobs
uses: actions/download-artifact@v4.1.4
id: download
with:
path: ftl_builds/
pattern: ${{ inputs.pattern }}
merge-multiple: true
-
name: Get documentation files built in previous jobs
if: inputs.pattern == 'pihole-FTL-amd64-binary'
uses: actions/download-artifact@v4.1.4
with:
path: ftl_builds/
name: pihole-api-docs
-
name: Display structure of downloaded files
shell: bash
run: ls -R
working-directory: ${{steps.download.outputs.download-path}}
-
name: Install SSH Key
uses: benoitchantre/setup-ssh-authentication-action@1.0.1
with:
private-key: ${{ inputs.SSH_KEY }}
private-key-name: id_rsa
known-hosts: ${{ inputs.KNOWN_HOSTS }}
-
name: Set private key permissions
shell: bash
run: chmod 600 ~/.ssh/id_rsa
-
name: Untar documentation files
if: inputs.pattern == 'pihole-FTL-amd64-binary'
working-directory: ftl_builds/
shell: bash
run: |
mkdir docs/
tar xzvf api-docs.tar.gz -C docs/
-
name: Display structure of files ready for upload
working-directory: ftl_builds/
shell: bash
run: ls -R
-
name: Transfer Builds to Pi-hole server for pihole checkout
if: inputs.actor != 'dependabot[bot]'
env:
USER: ${{ inputs.SSH_USER }}
HOST: ${{ inputs.SSH_HOST }}
TARGET_DIR: ${{ inputs.target_dir }}
SOURCE_DIR: ftl_builds/
shell: bash
run: |
bash ./deploy.sh
-
name: Attach binaries to release
if: inputs.event_name == 'release'
uses: softprops/action-gh-release@v2
with:
files: |
ftl_builds/*

View File

@ -7,6 +7,7 @@ on:
pull_request:
release:
types: [published]
workflow_dispatch:
jobs:
smoke-tests:
@ -14,6 +15,7 @@ jobs:
github.event_name == 'push'
|| github.event_name == 'release'
|| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)
|| github.event_name == 'workflow_dispatch'
outputs:
GIT_TAG: ${{ steps.variables.outputs.GIT_TAG }}
@ -24,7 +26,7 @@ jobs:
steps:
-
name: Checkout code
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.4
-
name: "Calculate required variables"
id: variables
@ -48,123 +50,78 @@ jobs:
[[ $FAIL == 1 ]] && exit 1 || echo "Branch name depth check passed."
shell: bash
build:
gha:
runs-on: ubuntu-latest
needs: smoke-tests
container: ghcr.io/pi-hole/ftl-build:v1.26-${{ matrix.arch }}
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
bin_name: pihole-FTL-linux-x86_64
- arch: x86_64
arch_extra: _full
bin_name: pihole-FTL-linux-x86_64_full
- arch: x86_64-musl
bin_name: pihole-FTL-musl-linux-x86_64
- arch: x86_32
bin_name: pihole-FTL-linux-x86_32
- arch: armv4t
bin_name: pihole-FTL-armv4-linux-gnueabi
- arch: armv5te
bin_name: pihole-FTL-armv5-linux-gnueabi
- arch: armv6hf
bin_name: pihole-FTL-armv6-linux-gnueabihf
- arch: armv7hf
bin_name: pihole-FTL-armv7-linux-gnueabihf
- arch: armv8a
bin_name: pihole-FTL-armv8-linux-gnueabihf
- arch: aarch64
bin_name: pihole-FTL-aarch64-linux-gnu
- arch: riscv64
bin_name: pihole-FTL-riscv64-linux-gnu
- platform: linux/amd64
bin_name: pihole-FTL-amd64
- platform: linux/386
bin_name: pihole-FTL-386
- platform: linux/riscv64
bin_name: pihole-FTL-riscv64
env:
CI_ARCH: ${{ matrix.arch }}${{ matrix.arch_extra }}
CI_ARCH: ${{ matrix.platform }}
GIT_BRANCH: ${{ needs.smoke-tests.outputs.GIT_BRANCH }}
GIT_TAG: ${{ needs.smoke-tests.outputs.GIT_TAG }}
steps:
-
name: Checkout code
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.4
-
name: "Fix ownership of repository"
run: chown -R root .
-
name: "Fix ownership of repository"
run: chown -R root .
-
name: "Build"
env:
GIT_BRANCH: ${{ needs.smoke-tests.outputs.GIT_BRANCH }}
GIT_TAG: ${{ needs.smoke-tests.outputs.GIT_TAG }}
run: |
bash build.sh "-DSTATIC=${STATIC}"
-
name: "Binary checks"
run: |
bash test/arch_test.sh
-
name: "Test x86_32/64 binaries"
if: matrix.arch == 'x86_64' || matrix.arch == 'x86_64-musl' || matrix.arch == 'x86_32'
run: |
bash test/run.sh
-
name: "Generate checksum file"
run: |
mv pihole-FTL "${{ matrix.bin_name }}"
sha1sum pihole-FTL-* > ${{ matrix.bin_name }}.sha1
-
name: Upload artifacts to job for later processing
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v3.1.3
name: Build and test and deploy FTL
uses: ./.github/actions/build-and-test
with:
name: tmp-binary-storage
path: '${{ matrix.bin_name }}*'
platform: ${{ matrix.platform }}
bin_name: ${{ matrix.bin_name }}
artifact_name: ${{ matrix.bin_name }}-binary
target_dir: ${{ needs.smoke-tests.outputs.OUTPUT_DIR }}
git_branch: ${{ needs.smoke-tests.outputs.GIT_BRANCH }}
git_tag: ${{ needs.smoke-tests.outputs.GIT_TAG }}
event_name: ${{ github.event_name }}
actor: ${{ github.actor }}
SSH_KEY: ${{ secrets.SSH_KEY }}
KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }}
SSH_USER: ${{ secrets.SSH_USER }}
SSH_HOST: ${{ secrets.SSH_HOST }}
deploy:
if: github.event_name != 'pull_request'
needs: [smoke-tests, build]
runs-on: ubuntu-latest
self-hosted:
runs-on: self-hosted
needs: smoke-tests
strategy:
fail-fast: false
matrix:
include:
- platform: linux/arm/v6
bin_name: pihole-FTL-armv6
- platform: linux/arm/v7
bin_name: pihole-FTL-armv7
- platform: linux/arm64/v8
bin_name: pihole-FTL-arm64
env:
CI_ARCH: ${{ matrix.platform }}
GIT_BRANCH: ${{ needs.smoke-tests.outputs.GIT_BRANCH }}
GIT_TAG: ${{ needs.smoke-tests.outputs.GIT_TAG }}
steps:
-
name: Checkout code
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.4
-
name: Get Binaries built in previous jobs
uses: actions/download-artifact@v3.0.2
id: download
name: Build and test and deploy FTL
uses: ./.github/actions/build-and-test
with:
name: tmp-binary-storage
path: ftl-builds/
-
name: Display structure of downloaded files
run: ls -R
working-directory: ${{steps.download.outputs.download-path}}
-
name: Install SSH Key
uses: benoitchantre/setup-ssh-authentication-action@1.0.1
with:
private-key: ${{ secrets.SSH_KEY }}
known-hosts: ${{ secrets.KNOWN_HOSTS }}
-
name: Transfer Builds to Pi-hole server for pihole checkout
if: github.actor != 'dependabot[bot]'
env:
USER: ${{ secrets.SSH_USER }}
HOST: ${{ secrets.SSH_HOST }}
TARGET_DIR: ${{ needs.smoke-tests.outputs.OUTPUT_DIR }}
SOURCE_DIR: ${{ steps.download.outputs.download-path }}
run: |
bash ./deploy.sh
-
name: Attach binaries to release
if: github.event_name == 'release'
uses: softprops/action-gh-release@v1
with:
files: |
${{ steps.download.outputs.download-path }}/*
platform: ${{ matrix.platform }}
bin_name: ${{ matrix.bin_name }}
artifact_name: ${{ matrix.bin_name }}-binary
target_dir: ${{ needs.smoke-tests.outputs.OUTPUT_DIR }}
git_branch: ${{ needs.smoke-tests.outputs.GIT_BRANCH }}
git_tag: ${{ needs.smoke-tests.outputs.GIT_TAG }}
event_name: ${{ github.event_name }}
actor: ${{ github.actor }}
SSH_KEY: ${{ secrets.SSH_KEY }}
KNOWN_HOSTS: ${{ secrets.KNOWN_HOSTS }}
SSH_USER: ${{ secrets.SSH_USER }}
SSH_HOST: ${{ secrets.SSH_HOST }}

View File

@ -1,5 +1,8 @@
name: Codespell
on:
push:
branches:
- '**'
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
@ -10,10 +13,11 @@ jobs:
steps:
-
name: Checkout repository
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.4
-
name: Spell-Checking
uses: codespell-project/actions-codespell@master
with:
ignore_words_file: .github/.codespellignore
skip: ./src/database/sqlite3.c,./src/database/sqlite3.h,./src/database/shell.c,./src/lua,./src/dnsmasq,./src/tre-regex,./.git,./test/libs
skip: ./src/database/sqlite3.c,./src/database/sqlite3.h,./src/database/shell.c,./src/lua,./src/dnsmasq,./src/tre-regex,./.git,./test/libs,./src/webserver/civetweb,./src/zip/miniz,./src/api/docs/content/external
exclude_file: .github/.codespellignore_lines

View File

@ -9,11 +9,11 @@ on:
types: [synchronize]
jobs:
main:
merge-conflict:
runs-on: ubuntu-latest
steps:
- name: Check if PRs are have merge conflicts
uses: eps1lon/actions-label-merge-conflict@v2.1.0
uses: eps1lon/actions-label-merge-conflict@v3.0.0
with:
dirtyLabel: "Merge conflicts"
repoToken: "${{ secrets.GITHUB_TOKEN }}"

26
.github/workflows/openapi-validator.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: API validation
on: [push]
env:
CI: true
jobs:
openapi-validator:
name: Node
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4.1.4
- name: Set Node.js version
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install npm dependencies
run: npm ci
- name: Run tests
run: npm test

View File

@ -40,7 +40,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.4
- name: Remove 'stale' label
run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }}
env:

View File

@ -11,7 +11,7 @@ jobs:
name: Syncing branches
steps:
- name: Checkout
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.4
- 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:

19
.gitignore vendored
View File

@ -1,25 +1,40 @@
# Generated binary
pihole-FTL
pihole-FTL*
# Versioning files (generated by Makefile)
version*
version.h
version~
# CMake files generated during compilation
/cmake/
/cmake_ci/
/cmake-build-debug/
/cmake-build-release/
# IDE files
.idea/
*.sw*
/.vscode
.vscode/
/.vscode/
/build/
# __pycache__ files (API tests)
__pycache__/
# When patch fails to apply a patch segment to the original file, it saves the
# temporary original file copy out durably as *.orig, dumps the rejected segment
# to *.rej, and continues trying to apply patch segments.
*.orig
*.rej
# MAC->Vendor database files
tools/manuf.data
tools/macvendor.db
# Documentation files generated by cmake
src/api/docs/hex
# Test dependencies
/node_modules/

View File

@ -9,8 +9,9 @@
# Please see LICENSE file for your rights under this license.
cmake_minimum_required(VERSION 2.8.12)
project(PIHOLE_FTL C)
set(DNSMASQ_VERSION pi-hole-v2.89-9461807)
set(DNSMASQ_VERSION pi-hole-v2.90+1)
add_subdirectory(src)

View File

@ -12,6 +12,10 @@
# Abort script if one command returns a non-zero value
set -e
# Set builddir
builddir="cmake/"
# Parse arguments
for var in "$@"
do
case "${var}" in
@ -19,22 +23,39 @@ do
"-C" | "CLEAN" ) clean=1 && nobuild=1;;
"-i" | "install" ) install=1;;
"-t" | "test" ) test=1;;
"ci" ) builddir="cmake_ci/";;
esac
done
# Prepare build environment
if [[ -n "${clean}" ]]; then
echo "Cleaning build environment"
rm -rf cmake/
# Remove build directory
rm -rf "${builddir}"
if [[ -n ${nobuild} ]]; then
exit 0
fi
fi
# Remove possibly outdated api/docs elements
for filename in src/api/docs/hex/* src/api/docs/hex/**/*; do
# Skip if not a file
if [ ! -f "${filename}" ]; then
continue
fi
# Get the original filename
original_filename="${filename/"src/api/docs/hex/"/"src/api/docs/content/"}"
# Remove the file if it is outdated
if [ "${filename}" -ot "${original_filename}" ]; then
rm "${filename}"
fi
done
# Remove compiled LUA scripts if older than the plain ones
for scriptname in src/lua/scripts/*.lua; do
if [ -f "${scriptname}.hex" ] && [ "${scriptname}.hex" -ot "${scriptname}" ]; then
echo "INFO: ${scriptname} is outdated and will be recompiled"
rm "${scriptname}.hex"
fi
done
@ -42,18 +63,18 @@ done
# Configure build, pass CMake CACHE entries if present
# Wrap multiple options in "" as first argument to ./build.sh:
# ./build.sh "-DA=1 -DB=2" install
mkdir -p cmake
cd cmake
mkdir -p "${builddir}"
cd "${builddir}"
if [[ "${1}" == "-D"* ]]; then
cmake "${1}" ..
else
cmake ..
fi
# Build the sources
# Build the sources with the number of available cores
cmake --build . -- -j $(nproc)
# If we are asked to install, we do this here
# If we are asked to install, we do this here (requires root privileges)
# Otherwise, we simply copy the binary one level up
if [[ -n "${install}" ]]; then
echo "Installing pihole-FTL"
@ -64,6 +85,7 @@ else
cp pihole-FTL ../
fi
# If we are asked to run tests, we do this here
if [[ -n "${test}" ]]; then
cd ..
./test/run.sh

View File

@ -62,4 +62,8 @@ for dir in "${path[@]}"; do
done
sftp -r -b - "${USER}"@"${HOST}" <<< "cd ${old_path}
-mkdir ./docs
-mkdir ./docs/external
-mkdir ./docs/images
-mkdir ./docs/specs
put ${SOURCE_DIR}/* ./"

851
package-lock.json generated Normal file
View File

@ -0,0 +1,851 @@
{
"name": "pihole-ftl",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pihole-ftl",
"version": "1.0.0",
"license": "EUPL-1.2",
"devDependencies": {
"openapi-enforcer": "^1.13.1",
"openapi-examples-validator": "^4.2.1"
}
},
"node_modules/@apidevtools/json-schema-ref-parser": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz",
"integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==",
"dev": true,
"dependencies": {
"@jsdevtools/ono": "^7.1.3",
"@types/json-schema": "^7.0.6",
"call-me-maybe": "^1.0.1",
"js-yaml": "^4.1.0"
}
},
"node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/call-me-maybe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
"dev": true
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"node_modules/decimal.js": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz",
"integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==",
"dev": true
},
"node_modules/errno": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz",
"integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==",
"dev": true,
"dependencies": {
"prr": "~1.0.1"
},
"bin": {
"errno": "cli.js"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreach": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
"integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==",
"dev": true
},
"node_modules/format-util": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz",
"integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==",
"dev": true
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-pointer": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz",
"integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==",
"dev": true,
"dependencies": {
"foreach": "^2.0.4"
}
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
"dev": true
},
"node_modules/lodash.flatmap": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz",
"integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=",
"dev": true
},
"node_modules/lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
"dev": true
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/ono": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz",
"integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==",
"dev": true,
"dependencies": {
"format-util": "^1.0.3"
}
},
"node_modules/openapi-enforcer": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/openapi-enforcer/-/openapi-enforcer-1.13.1.tgz",
"integrity": "sha512-NDDmyonl3bgxP+RabJsTPxn8EEza4Aet5zEocfX6nP9LL0J52aNFMYxNHoAuqoY3zQcZG2pnm3DMi2gYiRYbQg==",
"dev": true,
"dependencies": {
"axios": "^0.21.1",
"json-schema-ref-parser": "^6.1.0"
}
},
"node_modules/openapi-enforcer/node_modules/json-schema-ref-parser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz",
"integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==",
"deprecated": "Please switch to @apidevtools/json-schema-ref-parser",
"dev": true,
"dependencies": {
"call-me-maybe": "^1.0.1",
"js-yaml": "^3.12.1",
"ono": "^4.0.11"
}
},
"node_modules/openapi-examples-validator": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/openapi-examples-validator/-/openapi-examples-validator-4.7.1.tgz",
"integrity": "sha512-/OZZHhJkiaMQhkVfD0vFNOrRCBhkOL+X4/uhC55CJH0giuLUPQgFB5/iU26DCq1sZo2j9L70XYPcdHoM4PS+Xw==",
"dev": true,
"dependencies": {
"ajv": "^6.12.6",
"ajv-oai": "1.2.1",
"commander": "^6.2.1",
"errno": "^1.0.0",
"glob": "^7.2.0",
"json-pointer": "^0.6.2",
"json-schema-ref-parser": "^9.0.9",
"jsonpath-plus": "^6.0.1",
"lodash.clonedeep": "^4.5.0",
"lodash.flatmap": "^4.5.0",
"lodash.flatten": "^4.4.0",
"lodash.merge": "^4.6.2",
"yaml": "^1.10.2"
},
"bin": {
"openapi-examples-validator": "dist/cli.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/openapi-examples-validator/node_modules/ajv-oai": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ajv-oai/-/ajv-oai-1.2.1.tgz",
"integrity": "sha512-gj7dnSdLyjWKid3uQI16u5wQNpkyqivjtCuvI4BWezeOzYTj5YHt4otH9GOBCaXY3FEbzQeWsp6C2qc18+BXDA==",
"dev": true,
"dependencies": {
"decimal.js": "^10.2.0"
},
"peerDependencies": {
"ajv": "6.x"
}
},
"node_modules/openapi-examples-validator/node_modules/commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"dev": true,
"engines": {
"node": ">= 6"
}
},
"node_modules/openapi-examples-validator/node_modules/json-schema-ref-parser": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz",
"integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==",
"deprecated": "Please switch to @apidevtools/json-schema-ref-parser",
"dev": true,
"dependencies": {
"@apidevtools/json-schema-ref-parser": "9.0.9"
},
"engines": {
"node": ">=10"
}
},
"node_modules/openapi-examples-validator/node_modules/jsonpath-plus": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-6.0.1.tgz",
"integrity": "sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==",
"dev": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
"dev": true
},
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"engines": {
"node": ">= 6"
}
}
},
"dependencies": {
"@apidevtools/json-schema-ref-parser": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz",
"integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==",
"dev": true,
"requires": {
"@jsdevtools/ono": "^7.1.3",
"@types/json-schema": "^7.0.6",
"call-me-maybe": "^1.0.1",
"js-yaml": "^4.1.0"
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
}
}
}
},
"@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dev": true,
"requires": {
"follow-redirects": "^1.14.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"call-me-maybe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
"integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"decimal.js": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz",
"integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==",
"dev": true
},
"errno": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz",
"integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==",
"dev": true,
"requires": {
"prr": "~1.0.1"
}
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"dev": true
},
"foreach": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
"integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==",
"dev": true
},
"format-util": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz",
"integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"json-pointer": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz",
"integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==",
"dev": true,
"requires": {
"foreach": "^2.0.4"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
"dev": true
},
"lodash.flatmap": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz",
"integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=",
"dev": true
},
"lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
"dev": true
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"ono": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz",
"integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==",
"dev": true,
"requires": {
"format-util": "^1.0.3"
}
},
"openapi-enforcer": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/openapi-enforcer/-/openapi-enforcer-1.13.1.tgz",
"integrity": "sha512-NDDmyonl3bgxP+RabJsTPxn8EEza4Aet5zEocfX6nP9LL0J52aNFMYxNHoAuqoY3zQcZG2pnm3DMi2gYiRYbQg==",
"dev": true,
"requires": {
"axios": "^0.21.1",
"json-schema-ref-parser": "^6.1.0"
},
"dependencies": {
"json-schema-ref-parser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz",
"integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==",
"dev": true,
"requires": {
"call-me-maybe": "^1.0.1",
"js-yaml": "^3.12.1",
"ono": "^4.0.11"
}
}
}
},
"openapi-examples-validator": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/openapi-examples-validator/-/openapi-examples-validator-4.7.1.tgz",
"integrity": "sha512-/OZZHhJkiaMQhkVfD0vFNOrRCBhkOL+X4/uhC55CJH0giuLUPQgFB5/iU26DCq1sZo2j9L70XYPcdHoM4PS+Xw==",
"dev": true,
"requires": {
"ajv": "^6.12.6",
"ajv-oai": "1.2.1",
"commander": "^6.2.1",
"errno": "^1.0.0",
"glob": "^7.2.0",
"json-pointer": "^0.6.2",
"json-schema-ref-parser": "^9.0.9",
"jsonpath-plus": "^6.0.1",
"lodash.clonedeep": "^4.5.0",
"lodash.flatmap": "^4.5.0",
"lodash.flatten": "^4.4.0",
"lodash.merge": "^4.6.2",
"yaml": "^1.10.2"
},
"dependencies": {
"ajv-oai": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ajv-oai/-/ajv-oai-1.2.1.tgz",
"integrity": "sha512-gj7dnSdLyjWKid3uQI16u5wQNpkyqivjtCuvI4BWezeOzYTj5YHt4otH9GOBCaXY3FEbzQeWsp6C2qc18+BXDA==",
"dev": true,
"requires": {
"decimal.js": "^10.2.0"
}
},
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"dev": true
},
"json-schema-ref-parser": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz",
"integrity": "sha512-qcP2lmGy+JUoQJ4DOQeLaZDqH9qSkeGCK3suKWxJXS82dg728Mn3j97azDMaOUmJAN4uCq91LdPx4K7E8F1a7Q==",
"dev": true,
"requires": {
"@apidevtools/json-schema-ref-parser": "9.0.9"
}
},
"jsonpath-plus": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-6.0.1.tgz",
"integrity": "sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==",
"dev": true
}
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
}
}
}

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "pihole-ftl",
"private": true,
"version": "1.0.0",
"description": "Source of the Pi-hole FTL daemon",
"main": "",
"repository": {
"type": "git",
"url": "git+https://github.com/pi-hole/FTL.git"
},
"keywords": [],
"author": "",
"license": "EUPL-1.2",
"bugs": {
"url": "https://github.com/pi-hole/FTL/issues"
},
"scripts": {
"openapi-enforcer": "node test/api/openapi-enforcer.js",
"validate-examples": "openapi-examples-validator src/api/docs/content/specs/main.yaml",
"test": "npm run openapi-enforcer && npm run validate-examples"
},
"devDependencies": {
"openapi-enforcer": "^1.13.1",
"openapi-examples-validator": "^4.2.1"
}
}

13
patch/civetweb.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/sh
set -e
patch -p1 < patch/civetweb/0001-add-pihole-mods.patch
patch -p1 < patch/civetweb/0001-Add-NO_DLOPEN-option-to-civetweb-s-LUA-routines.patch
patch -p1 < patch/civetweb/0001-Always-Kepler-syntax-for-Lua-server-pages.patch
patch -p1 < patch/civetweb/0001-Add-FTL-URI-rewriting-changes-to-CivetWeb.patch
patch -p1 < patch/civetweb/0001-Add-mbedTLS-debug-logging-hook.patch
patch -p1 < patch/civetweb/0001-Register-CSRF-token-in-conn-request_info.patch
patch -p1 < patch/civetweb/0001-Log-debug-messages-to-webserver.log-when-debug.webse.patch
patch -p1 < patch/civetweb/0001-Allow-extended-ASCII-characters-in-URIs.patch
echo "ALL PATCHES APPLIED OKAY"

View File

@ -0,0 +1,41 @@
From 44a0a3a27731bb8577f39743eb034c024be48df2 Mon Sep 17 00:00:00 2001
From: DL6ER <dl6er@dl6er.de>
Date: Thu, 25 May 2023 18:02:13 +0200
Subject: [PATCH] Add FTL URI rewriting changes to CivetWeb
Signed-off-by: DL6ER <dl6er@dl6er.de>
---
src/webserver/civetweb/civetweb.c | 2 ++
src/webserver/civetweb/civetweb.h | 3 +++
2 files changed, 5 insertions(+)
diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c
index 0d293f1f..44f6cf3d 100644
--- a/src/webserver/civetweb/civetweb.c
+++ b/src/webserver/civetweb/civetweb.c
@@ -7754,6 +7754,8 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
mg_snprintf(
conn, &truncated, filename, filename_buf_len - 1, "%s%s", root, uri);
+ FTL_rewrite_pattern(filename, filename_buf_len - 1, root, uri);
+
if (truncated) {
goto interpret_cleanup;
}
diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h
index e71dfedc..2ad76693 100644
--- a/src/webserver/civetweb/civetweb.h
+++ b/src/webserver/civetweb/civetweb.h
@@ -935,6 +935,9 @@ int my_send_http_error_headers(struct mg_connection *conn,
int status, const char* mime_type,
long long content_length);
+void FTL_rewrite_pattern(char *filename, size_t filename_buf_len,
+ const char *root, const char *uri);
+
// Buffer used for additional "Set-Cookie" headers
#define PIHOLE_HEADERS_MAXLEN 1024
extern char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN];
--
2.34.1

View File

@ -0,0 +1,35 @@
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

View File

@ -0,0 +1,44 @@
From f785e181f8b43fa9f77bf7dcc6711f16206c9e89 Mon Sep 17 00:00:00 2001
From: DL6ER <dl6er@dl6er.de>
Date: Thu, 25 May 2023 18:26:45 +0200
Subject: [PATCH] Add mbedTLS debug logging hook
Signed-off-by: DL6ER <dl6er@dl6er.de>
---
src/webserver/civetweb/civetweb.h | 4 ++++
src/webserver/civetweb/mod_mbedtls.inl | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h
index 2ad76693..52724199 100644
--- a/src/webserver/civetweb/civetweb.h
+++ b/src/webserver/civetweb/civetweb.h
@@ -938,6 +938,10 @@ int my_send_http_error_headers(struct mg_connection *conn,
void FTL_rewrite_pattern(char *filename, size_t filename_buf_len,
const char *root, const char *uri);
+#define MG_CONFIG_MBEDTLS_DEBUG 3
+void FTL_mbed_debug(void *user_param, int level, const char *file,
+ int line, const char *message);
+
// Buffer used for additional "Set-Cookie" headers
#define PIHOLE_HEADERS_MAXLEN 1024
extern char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN];
diff --git a/src/webserver/civetweb/mod_mbedtls.inl b/src/webserver/civetweb/mod_mbedtls.inl
index e72685f4..00b9280a 100644
--- a/src/webserver/civetweb/mod_mbedtls.inl
+++ b/src/webserver/civetweb/mod_mbedtls.inl
@@ -83,6 +83,10 @@ mbed_sslctx_init(SSL_CTX *ctx, const char *crt)
mbedtls_ssl_conf_dbg(conf, mbed_debug, (void *)ctx);
#endif
+ /****************** Pi-hole change ******************/
+ mbedtls_ssl_conf_dbg(conf, FTL_mbed_debug, NULL);
+ /****************************************************/
+
#ifdef MBEDTLS_SSL_PROTO_TLS1_3
psa_status_t status = psa_crypto_init();
if (status != PSA_SUCCESS) {
--
2.34.1

View File

@ -0,0 +1,35 @@
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

View File

@ -0,0 +1,31 @@
From 19efd3e2e10858b549404e6cae97c20337aafb0b Mon Sep 17 00:00:00 2001
From: DL6ER <dl6er@dl6er.de>
Date: Mon, 22 May 2023 19:11:44 +0200
Subject: [PATCH] Always Kepler syntax for Lua server pages
Signed-off-by: DL6ER <dl6er@dl6er.de>
---
src/webserver/civetweb/mod_lua.inl | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/webserver/civetweb/mod_lua.inl b/src/webserver/civetweb/mod_lua.inl
index 26f281ee..e9a13835 100644
--- a/src/webserver/civetweb/mod_lua.inl
+++ b/src/webserver/civetweb/mod_lua.inl
@@ -3208,10 +3208,9 @@ handle_lsp_request(struct mg_connection *conn,
* "<?" which means "classic CivetWeb Syntax".
*
*/
- run_lsp = run_lsp_civetweb;
- if ((addr[0] == '<') && (addr[1] != '?')) {
- run_lsp = run_lsp_kepler;
- }
+
+ // Pi-hole change: Always use Kepler syntax, ignore rules above
+ run_lsp = run_lsp_kepler;
/* We're not sending HTTP headers here, Lua page must do it. */
error =
--
2.34.1

View File

@ -0,0 +1,54 @@
From 445aed4fd5ef53a52ac974aa5123ba56b11d8a1f Mon Sep 17 00:00:00 2001
From: DL6ER <dl6er@dl6er.de>
Date: Sat, 14 Oct 2023 15:39:21 +0200
Subject: [PATCH] Log debug messages to webserver.log when debug.webserver is
true
Signed-off-by: DL6ER <dl6er@dl6er.de>
---
src/webserver/civetweb/civetweb.c | 5 +++--
src/webserver/civetweb/mod_mbedtls.inl | 4 ++--
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c
index 3df8eab9..9b0c6308 100644
--- a/src/webserver/civetweb/civetweb.c
+++ b/src/webserver/civetweb/civetweb.c
@@ -239,9 +239,10 @@ static void DEBUG_TRACE_FUNC(const char *func,
#endif
#else
+#include "log.h"
#define DEBUG_TRACE(fmt, ...) \
- do { \
- } while (0)
+ if(debug_flags[DEBUG_WEBSERVER]) {\
+ log_web("DEBUG: " fmt " (%s:%d)", ##__VA_ARGS__, short_path(__FILE__), __LINE__); }
#endif /* DEBUG */
#endif /* DEBUG_TRACE */
diff --git a/src/webserver/civetweb/mod_mbedtls.inl b/src/webserver/civetweb/mod_mbedtls.inl
index 00b9280a..6a450ba3 100644
--- a/src/webserver/civetweb/mod_mbedtls.inl
+++ b/src/webserver/civetweb/mod_mbedtls.inl
@@ -213,7 +213,7 @@ mbed_ssl_accept(mbedtls_ssl_context **ssl,
return -1;
}
- DEBUG_TRACE("TLS connection %p accepted, state: %d", ssl, (*ssl)->state);
+ DEBUG_TRACE("TLS connection %p accepted, state: %d", ssl, (*ssl)->MBEDTLS_PRIVATE(state));
return 0;
}
@@ -239,7 +239,7 @@ mbed_ssl_handshake(mbedtls_ssl_context *ssl)
}
}
- DEBUG_TRACE("TLS handshake rc: %d, state: %d", rc, ssl->state);
+ DEBUG_TRACE("TLS handshake rc: %d, state: %d", rc, ssl->MBEDTLS_PRIVATE(state));
return rc;
}
--
2.34.1

View File

@ -0,0 +1,58 @@
From 4890e1c2586d4a74a3925ede8cfeb1805672a42e Mon Sep 17 00:00:00 2001
From: DL6ER <dl6er@dl6er.de>
Date: Sat, 3 Jun 2023 20:52:02 +0200
Subject: [PATCH] Register CSRF token and is_authenticated boolean in conn->request_info
Signed-off-by: DL6ER <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(+)
diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c
index 233b342a..f44b17ba 100644
--- a/src/webserver/civetweb/civetweb.c
+++ b/src/webserver/civetweb/civetweb.c
@@ -17760,6 +17760,9 @@ reset_per_request_attributes(struct mg_connection *conn)
}
conn->request_info.local_uri = NULL;
+ /* Pi-hole addition */
+ memset(conn->request_info.csrf_token, 0, sizeof(conn->request_info.csrf_token));
+ reg_boolean(L, "is_authenticated", conn->request_info.is_authenticated != 0);
+
#if defined(USE_SERVER_STATS)
conn->processing_time = 0;
#endif
diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h
index 5b3d596b..291ef683 100644
--- a/src/webserver/civetweb/civetweb.h
+++ b/src/webserver/civetweb/civetweb.h
@@ -183,6 +183,8 @@ struct mg_request_info {
const char *acceptedWebSocketSubprotocol; /* websocket subprotocol,
* accepted during handshake */
+ /* Pi-hole modification */
+ char csrf_token[32];
+ int is_authenticated;
};
diff --git a/src/webserver/civetweb/mod_lua.inl b/src/webserver/civetweb/mod_lua.inl
index e9a13835..92066b3f 100644
--- a/src/webserver/civetweb/mod_lua.inl
+++ b/src/webserver/civetweb/mod_lua.inl
@@ -2603,6 +2603,9 @@ prepare_lua_request_info_inner(const struct mg_connection *conn, lua_State *L)
reg_string(L, "finger", conn->request_info.client_cert->finger);
lua_rawset(L, -3);
}
+
+ /* Pi-hole addition */
+ reg_string(L, "csrf_token", conn->request_info.csrf_token);
}
--
2.34.1

View File

@ -0,0 +1,103 @@
From f59235a1973d13d1271b50546c28cf4d9d2f921a Mon Sep 17 00:00:00 2001
From: DL6ER <dl6er@dl6er.de>
Date: Sat, 31 Dec 2022 07:08:23 +0100
Subject: [PATCH] Add Pi-hole specific method to send HTTP headers
Signed-off-by: DL6ER <dl6er@dl6er.de>
---
src/webserver/civetweb/civetweb.c | 53 +++++++++++++++++++++++++++++++++++++++++
src/webserver/civetweb/civetweb.h | 11 +++++++++
2 files changed, 64 insertions(+)
diff --git a/src/webserver/civetweb/civetweb.c b/src/webserver/civetweb/civetweb.c
index 81f642be..ed360a76 100644
--- a/src/webserver/civetweb/civetweb.c
+++ b/src/webserver/civetweb/civetweb.c
@@ -4135,6 +4135,14 @@ send_additional_header(struct mg_connection *conn)
if (header && header[0]) {
mg_response_header_add_lines(conn, header);
}
+
+ /*************** Pi-hole modification ****************/
+ if (pi_hole_extra_headers[0] != '\0') {
+ mg_response_header_add_lines(conn, pi_hole_extra_headers);
+ // Invalidate extra headers after having sent them to avoid repetitions
+ pi_hole_extra_headers[0] = '\0';
+ }
+ /*****************************************************/
}
@@ -4530,6 +4538,48 @@ mg_send_http_error_impl(struct mg_connection *conn,
}
+/************************************** Pi-hole method **************************************/
+CIVETWEB_API int
+my_send_http_error_headers(struct mg_connection *conn,
+ int status, const char* mime_type,
+ long long content_length)
+{
+ if ((mime_type == NULL) || (*mime_type == 0)) {
+ /* No content type defined: default to text/html */
+ mime_type = "text/html";
+ }
+
+ mg_response_header_start(conn, status);
+ send_no_cache_header(conn);
+ send_additional_header(conn);
+ mg_response_header_add(conn, "Content-Type", mime_type, -1);
+ if (content_length < 0) {
+ /* Size not known. Use chunked encoding (HTTP/1.x) */
+ if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) {
+ /* Only HTTP/1.x defines "chunked" encoding, HTTP/2 does not*/
+ mg_response_header_add(conn, "Transfer-Encoding", "chunked", -1);
+ }
+ } else {
+ char len[32];
+ int trunc = 0;
+ mg_snprintf(conn,
+ &trunc,
+ len,
+ sizeof(len),
+ "%" UINT64_FMT,
+ (uint64_t)content_length);
+ if (!trunc) {
+ /* Since 32 bytes is enough to hold any 64 bit decimal number,
+ * !trunc is always true */
+ mg_response_header_add(conn, "Content-Length", len, -1);
+ }
+ }
+ mg_response_header_send(conn);
+
+ return 0;
+}
+/********************************************************************************************/
+
CIVETWEB_API int
mg_send_http_error(struct mg_connection *conn, int status, const char *fmt, ...)
{
diff --git a/src/webserver/civetweb/civetweb.h b/src/webserver/civetweb/civetweb.h
index 7ea45fb2..f879ff3e 100644
--- a/src/webserver/civetweb/civetweb.h
+++ b/src/webserver/civetweb/civetweb.h
@@ -963,6 +964,16 @@ CIVETWEB_API int mg_send_http_error(struct mg_connection *conn,
PRINTF_FORMAT_STRING(const char *fmt),
...) PRINTF_ARGS(3, 4);
+/************************************** Pi-hole method **************************************/
+int my_send_http_error_headers(struct mg_connection *conn,
+ int status, const char* mime_type,
+ long long content_length);
+
+// Buffer used for additional "Set-Cookie" headers
+#define PIHOLE_HEADERS_MAXLEN 1024
+extern char pi_hole_extra_headers[PIHOLE_HEADERS_MAXLEN];
+/********************************************************************************************/
+
/* Send "HTTP 200 OK" response header.
* After calling this function, use mg_write or mg_send_chunk to send the
--
2.34.1

View File

@ -2,3 +2,5 @@
set -e
patch -p1 < patch/lua/0001-add-pihole-library.patch
echo "ALL PATCHES APPLIED OKAY"

View File

@ -3,3 +3,5 @@ set -e
patch -p1 < patch/sqlite3/0001-print-FTL-version-in-interactive-shell.patch
patch -p1 < patch/sqlite3/0002-make-sqlite3ErrName-public.patch
echo "ALL PATCHES APPLIED OKAY"

View File

@ -25,6 +25,6 @@ index 6280ebf6..a5e82f70 100644
char *zHistory;
int nHistory;
+ print_FTL_version();
printf(
"SQLite version %s %.19s\n" /*extra-version-info*/
"Enter \".help\" for usage hints.\n",
#if CIO_WIN_WC_XLATE
# define SHELL_CIO_CHAR_SET (stdout_is_console? " (UTF-16 console I/O)" : "")
#else

View File

@ -24,13 +24,20 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
# SQLITE_DEFAULT_MEMSTATUS=0: This setting causes the sqlite3_status() interfaces that track memory usage to be disabled. This helps the sqlite3_malloc() routines run much faster, and since SQLite uses sqlite3_malloc() internally, this helps to make the entire library faster.
# SQLITE_OMIT_DEPRECATED: Omitting deprecated interfaces and features will not help SQLite to run any faster. It will reduce the library footprint, however. And it is the right thing to do.
# SQLITE_OMIT_PROGRESS_CALLBACK: The progress handler callback counter must be checked in the inner loop of the bytecode engine. By omitting this interface, a single conditional is removed from the inner loop of the bytecode engine, helping SQL statements to run slightly faster.
# SQLITE_OMIT_SHARED_CACHE: This option builds SQLite without support for shared cache mode. The sqlite3_enable_shared_cache() is omitted along with a fair amount of logic within the B-Tree subsystem associated with shared cache management. This compile-time option is recommended most applications as it results in improved performance and reduced library footprint.
# SQLITE_DEFAULT_FOREIGN_KEYS=1: This macro determines whether enforcement of foreign key constraints is enabled or disabled by default for new database connections.
# SQLITE_DQS=0: This setting disables the double-quoted string literal misfeature.
# SQLITE_ENABLE_DBPAGE_VTAB: Enables the SQLITE_DBPAGE virtual table. Warning: writing to the SQLITE_DBPAGE virtual table can very easily cause unrecoverably database corruption.
# SQLITE_OMIT_DESERIALIZE: This option causes the the sqlite3_serialize() and sqlite3_deserialize() interfaces to be omitted from the build (was the default before 3.36.0)
# 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)
set(SQLITE_DEFINES "-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_DEFAULT_FOREIGN_KEYS=1 -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_OMIT_DESERIALIZE -DHAVE_READLINE -DSQLITE_DEFAULT_CACHE_SIZE=-16384")
# SQLITE_DEFAULT_SYNCHRONOUS=1: Use normal synchronous mode (default is 2)
# SQLITE_LIKE_DOESNT_MATCH_BLOBS: This option causes the LIKE operator to only match BLOB values against BLOB values and TEXT values against TEXT values. This compile-time option makes SQLite run more efficiently when processing queries that use the LIKE operator.
# HAVE_MALLOC_USABLE_SIZE: This option causes SQLite to try to use the malloc_usable_size() function to obtain the actual size of memory allocations from the underlying malloc() system interface. Applications are encouraged to use HAVE_MALLOC_USABLE_SIZE whenever possible.
# HAVE_FDATASYNC: This option causes SQLite to try to use the fdatasync() system call to sync the database file to disk when committing a transaction. Syncing using fdatasync() is faster than syncing using fsync() as fdatasync() does not wait for the file metadata to be written to disk.
# SQLITE_DEFAULT_WORKER_THREADS=4: This option sets the default number of worker threads to use when doing parallel sorting and indexing. The default is 0 which means to use a single thread. The default for SQLITE_MAX_WORKER_THREADS is 8.
# SQLITE_MAX_PREPARE_RETRY=200: This option sets the maximum number of automatic re-preparation attempts that can occur after encountering a schema change. This can be caused by running ANALYZE which is done periodically by FTL.
set(SQLITE_DEFINES "-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_DEFAULT_FOREIGN_KEYS=1 -DSQLITE_DQS=0 -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TEMP_STORE=2 -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")
# Code hardening and debugging improvements
# -fstack-protector-strong: The program will be resistant to having its stack overflowed
@ -55,43 +62,127 @@ set(DEBUG_FLAGS "-rdynamic -fno-omit-frame-pointer")
set(WARN_FLAGS "-Wall -Wextra -Wno-unused-parameter")
# Extra warning flags we apply only to the FTL part of the code (used not for foreign code such as dnsmasq and SQLite3)
# -Werror: Halt on any warnings, useful for enforcing clean code without any warnings (we use it only for our code part)
# -Waddress: Warn about suspicious uses of memory addresses
# -Wlogical-op: Warn about suspicious uses of logical operators in expressions
# -Wmissing-field-initializers: Warn if a structure's initializer has some fields missing
# -Woverlength-strings: Warn about string constants that are longer than the "minimum maximum length specified in the C standard
# -Wformat: Check calls to printf and scanf, etc., to make sure that the arguments supplied have types appropriate to the format string specified, and that the conversions specified in the format string make sense.
# -Wformat-nonliteral: If -Wformat is specified, also warn if the format string is not a string literal and so cannot be checked, unless the format function takes its format arguments as a va_list.
# -Wuninitialized: Warn if an automatic variable is used without first being initialized
# -Wswitch-enum: Warn whenever a switch statement has an index of enumerated type and lacks a case for one or more of the named codes of that enumeration.
# -Wshadow: Warn whenever a local variable or type declaration shadows another variable, parameter, type, class member, or whenever a built-in function is shadowed.
# -Wfloat-equal: Warn if floating-point values are used in equality comparisons
# -Wpointer-arith: Warn about anything that depends on the "size of" a function type or of "void". GNU C assigns these types a size of 1
# -Wundef: Warn if an undefined identifier is evaluated in an "#if" directive
# -Wbad-function-cast: Warn when a function call is cast to a non-matching type
# -Wwrite-strings: When compiling C, give string constants the type "const char[length]" so that copying the address of one into a non-"const" "char *" pointer produces a warning
# -Wparentheses: Warn if parentheses are omitted in certain contexts, such as when there is an assignment in a context where a truth value is expected, or when operators are nested whose precedence people often get confused about
# -Wlogical-op: Warn about suspicious uses of logical operators in expressions
# -Wstrict-prototypes: Warn if a function is declared or defined without specifying the argument types
# -Wmissing-prototypes: Warn if a global function is defined without a previous prototype declaration
# -Wredundant-decls: Warn if anything is declared more than once in the same scope
# -Winline: Warn if a function that is declared as inline cannot be inlined
set(EXTRAWARN_GCC6 "-Werror \
-Waddress \
-Wlogical-op \
-Wmissing-field-initializers \
-Woverlength-strings \
-Wformat=2 \
-Wformat-signedness \
-Wuninitialized \
-Wnull-dereference \
-Wshift-overflow=2 \
-Wunused-const-variable=2 \
-Wstrict-aliasing \
-Warray-bounds=2 \
-Wno-aggressive-loop-optimizations \
-Wswitch-enum \
-Wshadow \
-Wfloat-equal \
-Wbad-function-cast \
-Wwrite-strings \
-Wparentheses \
-Wlogical-op \
-Wstrict-prototypes \
-Wmissing-prototypes \
-Wredundant-decls \
-Wmissing-field-initializers \
-Wnormalized=nfkc \
-Woverride-init \
-Wpacked \
-Winline \
-Wpacked \
-Wredundant-decls \
-Wnested-externs \
-Wvla \
-Wvector-operation-performance \
-Wvolatile-register-var \
-Wdisabled-optimization \
-Wpointer-sign \
-Wstack-protector \
-Woverlength-strings")
# Extra warnings flags available only in GCC 7 and higher
if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 7 OR CMAKE_C_COMPILER_VERSION VERSION_GREATER 7)
set(EXTRAWARN_GCC7 "-Wformat-overflow=2 \
-Wformat-truncation=2 \
-Wstringop-overflow=4 \
-Walloc-zero \
-Wint-in-bool-context")
else()
set(EXTRAWARN_GCC7 "")
endif()
# Extra warnings flags available only in GCC 8 and higher
if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 8 OR CMAKE_C_COMPILER_VERSION VERSION_GREATER 8)
# -Wduplicated-cond: Warn about duplicated conditions in an if-else-if chain
# -Wduplicated-branches: Warn when an if-else has identical branches
# -Wcast-align=strict: Warn whenever a pointer is cast such that the required alignment of the target is increased. For example, warn if a "char *" is cast to an "int *" regardless of the target machine.
# -Wlogical-not-parentheses: Warn about logical not used on the left hand side operand of a comparison
set(EXTRAWARN_GCC8 "-Wduplicated-cond -Wduplicated-branches -Wcast-align=strict -Wlogical-not-parentheses -Wsuggest-attribute=pure -Wsuggest-attribute=const -Wsuggest-attribute=malloc -Wsuggest-attribute=format -Wsuggest-attribute=cold")
set(EXTRAWARN_GCC8 "-Wduplicated-cond \
-Wduplicated-branches \
-Wcast-align=strict \
-Wlogical-not-parentheses \
-Wmultistatement-macros \
-Wmissing-attributes \
-Wsuggest-attribute=pure \
-Wsuggest-attribute=const \
-Wsuggest-attribute=malloc \
-Wsuggest-attribute=format \
-Wsuggest-attribute=cold")
else()
set(EXTRAWARN_GCC8 "")
endif()
set(EXTRAWARN "-Werror -Waddress -Wlogical-op -Wmissing-field-initializers -Woverlength-strings -Wformat -Wformat-nonliteral -Wuninitialized -Wswitch-enum -Wshadow -Wfloat-equal -Wbad-function-cast -Wwrite-strings -Wparentheses -Wlogical-op -Wstrict-prototypes -Wmissing-prototypes -Wredundant-decls -Winline ${EXTRAWARN_GCC8}")
# Extra warnings flags available only in GCC 9 and higher
# The only new warning -Wabsolute-value is implied by -Wextra
# Extra warnings flags available only in GCC 10 and higher
# The new option -Wstring-compare is implied by -Wextra
# The new option -Wzero-length-bounds is implied by -Warray-bounds
# Extra warnings flags available only in GCC 11 and higher
# All new options are implied by either -Wall or -Wextra \
# Extra warnings flags available only in GCC 12 and higher
if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 12 OR CMAKE_C_COMPILER_VERSION VERSION_GREATER 12)
set(EXTRAWARN_GCC12 "-Wbidi-chars \
-Warray-compare")
else()
set(EXTRAWARN_GCC12 "")
endif()
# Extra warnings flags available only in GCC 13 and higher
if(CMAKE_C_COMPILER_VERSION VERSION_EQUAL 13 OR CMAKE_C_COMPILER_VERSION VERSION_GREATER 13)
set(EXTRAWARN_GCC13 "-Wenum-int-mismatch")
else()
set(EXTRAWARN_GCC13 "")
endif()
set(EXTRAWARN "${EXTRAWARN_GCC6} \
${EXTRAWARN_GCC7} \
${EXTRAWARN_GCC8} \
${EXTRAWARN_GCC12} \
${EXTRAWARN_GCC13}")
# Remove extra spaces from EXTRAWARN
string(REGEX REPLACE " +" " " EXTRAWARN "${EXTRAWARN}")
# Separate EXTRAWARN into a list of arguments
separate_arguments(EXTRAWARN)
# Do we want to compile a statically linked musl executable?
if(STATIC STREQUAL "true")
# -Wxor-used-as-pow
# Do we want to compile a statically linked executable?
if(DEFINED ENV{STATIC})
if($ENV{STATIC} STREQUAL "true")
set(STATIC true)
else()
set(STATIC false)
endif()
endif()
if(STATIC)
message(STATUS "Compiling statically linked executable")
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
SET(BUILD_SHARED_LIBS OFF)
else()
message(STATUS "Compiling dynamically linked executable")
endif()
# -pie -fPIE: (Dynamic) position independent executable
set(HARDENING_FLAGS "${HARDENING_FLAGS} -pie -fPIE")
@ -110,8 +201,6 @@ set(sources
args.h
capabilities.c
capabilities.h
config.c
config.h
daemon.c
daemon.h
datastructure.c
@ -132,6 +221,7 @@ set(sources
log.h
main.c
main.h
metrics.h
overTime.c
overTime.h
procps.c
@ -140,8 +230,6 @@ set(sources
regex_r.h
resolve.c
resolve.h
setupVars.c
setupVars.h
shmem.c
shmem.h
signals.c
@ -172,15 +260,23 @@ add_dependencies(FTL gen_version)
add_executable(pihole-FTL
$<TARGET_OBJECTS:FTL>
$<TARGET_OBJECTS:api>
$<TARGET_OBJECTS:api_docs>
$<TARGET_OBJECTS:webserver>
$<TARGET_OBJECTS:civetweb>
$<TARGET_OBJECTS:cJSON>
$<TARGET_OBJECTS:miniz>
$<TARGET_OBJECTS:zip>
$<TARGET_OBJECTS:database>
$<TARGET_OBJECTS:dnsmasq>
$<TARGET_OBJECTS:sqlite3>
$<TARGET_OBJECTS:lua>
$<TARGET_OBJECTS:tre-regex>
$<TARGET_OBJECTS:syscalls>
$<TARGET_OBJECTS:tomlc99>
$<TARGET_OBJECTS:config>
$<TARGET_OBJECTS:tools>
)
if(STATIC STREQUAL "true")
if(STATIC)
set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_START_STATIC ON)
set_target_properties(pihole-FTL PROPERTIES LINK_SEARCH_END_STATIC ON)
target_link_libraries(pihole-FTL -static-libgcc -static -pie)
@ -193,12 +289,15 @@ set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED)
# for DNSSEC we need the nettle (+ hogweed) crypto and the gmp math libraries
find_library(LIBHOGWEED NAMES libhogweed${CMAKE_STATIC_LIBRARY_SUFFIX} hogweed)
find_library(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)
find_library(LIBIDN NAMES libidn${CMAKE_STATIC_LIBRARY_SUFFIX} idn)
find_library(LIBNETTLE NAMES libnettle${CMAKE_STATIC_LIBRARY_SUFFIX} nettle HINTS /usr/local/lib64)
target_link_libraries(pihole-FTL rt Threads::Threads ${LIBHOGWEED} ${LIBGMP} ${LIBNETTLE} ${LIBIDN})
# for IDN2 we need the idn2 library which in turn depends on the unistring library
find_library(LIBIDN2 NAMES libidn2${CMAKE_STATIC_LIBRARY_SUFFIX} idn2)
find_library(LIBUNISTRING NAMES libunistring${CMAKE_STATIC_LIBRARY_SUFFIX} unistring)
target_link_libraries(pihole-FTL rt Threads::Threads ${LIBHOGWEED} ${LIBGMP} ${LIBNETTLE} ${LIBIDN2} ${LIBUNISTRING})
if(LUA_DL STREQUAL "true")
find_library(LIBDL dl)
@ -239,17 +338,29 @@ 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+eip \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)")
install(CODE "execute_process(COMMAND ${SETCAP} CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN,CAP_SYS_NICE,CAP_CHOWN+eip \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/pihole-FTL)")
add_subdirectory(api)
add_subdirectory(webserver)
add_subdirectory(zip)
add_subdirectory(database)
add_subdirectory(dnsmasq)
add_subdirectory(lua)
add_subdirectory(lua/scripts)
add_subdirectory(tre-regex)
add_subdirectory(syscalls)
add_subdirectory(config)
add_subdirectory(tools)

View File

@ -39,8 +39,6 @@
#include <syslog.h>
// tolower()
#include <ctype.h>
// Unix socket
#include <sys/un.h>
// Interfaces
#include <ifaddrs.h>
#include <net/if.h>
@ -49,6 +47,10 @@
#define MAX(x,y) (((x) > (y)) ? (x) : (y))
// MIN(x,y) is already defined in dnsmasq.h
// Number of elements in an array
#define ArraySize(X) (sizeof(X)/sizeof(X[0]))
// Constant socket buffer length
#define SOCKETBUFFERLEN 1024
// How often do we garbage collect (to ensure we only have data fitting to the MAXLOGAGE defined above)? [seconds]
@ -56,7 +58,7 @@
#define GCinterval 600
// Delay applied to the garbage collecting [seconds]
// Default: -60 (one minute before a full hour)
// Default: -60 (one minute before the end of the interval set above)
#define GCdelay (-60)
// How many client connection do we accept at once?
@ -102,6 +104,10 @@
// Default: 1000 (one second)
#define DATABASE_BUSY_TIMEOUT 1000
// After how much time does a valid API session expire? [seconds]
// Default: 300 (five minutes)
#define API_SESSION_EXPIRE 300u
// After how many seconds do we check again if a client can be identified by other means?
// (e.g., interface, MAC address, hostname)
// Default: 60 (after one minutee)
@ -118,12 +124,30 @@
// 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
// Special exit code used to signal that FTL wants to restart
#define RESTART_FTL_CODE 22
// How often should the database be analyzed?
// Default: 604800 (once per week)
#define DATABASE_ANALYZE_INTERVAL 604800
// How often should we update client vendor's from the MAC vendor database?
// Default: 2592000 (once per month)
#define DATABASE_MACVENDOR_INTERVAL 2592000
// Use out own syscalls handling functions that will detect possible errors
// and report accordingly in the log. This will make debugging FTL crash
// caused by insufficient memory or by code bugs (not properly dealing
// with NULL pointers) much easier.
#undef strdup // strdup() is a macro in itself, it needs special handling
#define free(ptr) FTLfree(ptr, __FILE__, __FUNCTION__, __LINE__)
#define free(ptr) FTLfree((void**)&ptr, __FILE__, __FUNCTION__, __LINE__)
#define strdup(str_in) FTLstrdup(str_in, __FILE__, __FUNCTION__, __LINE__)
#define calloc(numer_of_elements, element_size) FTLcalloc(numer_of_elements, element_size, __FILE__, __FUNCTION__, __LINE__)
#define realloc(ptr, new_size) FTLrealloc(ptr, new_size, __FILE__, __FUNCTION__, __LINE__)
@ -146,10 +170,32 @@
#define pthread_mutex_lock(mutex) FTLpthread_mutex_lock(mutex, __FILE__, __FUNCTION__, __LINE__)
#define fopen(pathname, mode) FTLfopen(pathname, mode, __FILE__, __FUNCTION__, __LINE__)
#define ftlallocate(fd, offset, len) FTLfallocate(fd, offset, len, __FILE__, __FUNCTION__, __LINE__)
#define strlen(str) FTLstrlen(str, __FILE__, __FUNCTION__, __LINE__)
#define strnlen(str, maxlen) FTLstrnlen(str, maxlen, __FILE__, __FUNCTION__, __LINE__)
#define strcpy(dest, src) FTLstrcpy(dest, src, __FILE__, __FUNCTION__, __LINE__)
#define strncpy(dest, src, n) FTLstrncpy(dest, src, n, __FILE__, __FUNCTION__, __LINE__)
#define memset(s, c, n) FTLmemset(s, c, n, __FILE__, __FUNCTION__, __LINE__)
#define memcpy(dest, src, n) FTLmemcpy(dest, src, n, __FILE__, __FUNCTION__, __LINE__)
#define memmove(dest, src, n) FTLmemmove(dest, src, n, __FILE__, __FUNCTION__, __LINE__)
#define strstr(haystack, needle) FTLstrstr(haystack, needle, __FILE__, __FUNCTION__, __LINE__)
#define strcmp(s1, s2) FTLstrcmp(s1, s2, __FILE__, __FUNCTION__, __LINE__)
#define strncmp(s1, s2, n) FTLstrncmp(s1, s2, n, __FILE__, __FUNCTION__, __LINE__)
#define strcasecmp(s1, s2) FTLstrcasecmp(s1, s2, __FILE__, __FUNCTION__, __LINE__)
#define strncasecmp(s1, s2, n) FTLstrncasecmp(s1, s2, n, __FILE__, __FUNCTION__, __LINE__)
#define strcat(dest, src) FTLstrcat(dest, src, __FILE__, __FUNCTION__, __LINE__)
#define strncat(dest, src, n) FTLstrncat(dest, src, n, __FILE__, __FUNCTION__, __LINE__)
#define memcmp(s1, s2, n) FTLmemcmp(s1, s2, n, __FILE__, __FUNCTION__, __LINE__)
#define memmem(haystack, haystacklen, needle, needlelen) FTLmemmem(haystack, haystacklen, needle, needlelen, __FILE__, __FUNCTION__, __LINE__)
#include "syscalls/syscalls.h"
// Preprocessor help functions
#define str(x) # x
#define str(x) #x
#define xstr(x) str(x)
// Intentionally ignore result of function declared warn_unused_result
#define igr(x) {__typeof__(x) __attribute__((unused)) d=(x);}
#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; })
#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
#endif // FTL_H

376
src/api/2fa.c Normal file
View File

@ -0,0 +1,376 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation 2FA methods
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "FTL.h"
#include "api/api.h"
#include "webserver/json_macros.h"
#include "log.h"
#include "config/config.h"
// getrandom()
#include "daemon.h"
// generate_app_password()
#include "config/password.h"
// TOTP+HMAC
#include <nettle/hmac.h>
#include <nettle/sha1.h>
static uint32_t hotp(const uint8_t *key, size_t key_len, const uint64_t counter, const uint8_t digits)
{
// Initialize HMAC-SHA1 (RFC 2104)
// TOTP uses HMAC-SHA1 (RFC 6238, section 5.1)
struct hmac_sha1_ctx ctx;
hmac_sha1_set_key(&ctx, key_len, key);
// Convert counter to big endian
const uint64_t counter_be = htobe64(counter);
// Compute HMAC-SHA1
hmac_sha1_update(&ctx, sizeof(counter_be), (uint8_t*)&counter_be);
uint8_t out[SHA1_DIGEST_SIZE];
hmac_sha1_digest(&ctx, SHA1_DIGEST_SIZE, out);
// Truncate HMAC-SHA1 for ease of use
// RFC 6238 (section 5.3): offset = last nibble of hash
const uint8_t offset = out[SHA1_DIGEST_SIZE-1] & 0x0F;
// RFC 6238 (section 5.3): binary = (hash[offset] & 0x7F) << 24 |
// (hash[offset+1] & 0xFF) << 16 |
// (hash[offset+2] & 0xFF) << 8 |
// (hash[offset+3] & 0xFF)
const uint32_t binary = (out[offset] & 0x7F) << 24 |
(out[offset+1] & 0xFF) << 16 |
(out[offset+2] & 0xFF) << 8 |
(out[offset+3] & 0xFF);
// RFC 6238 (section 5.3): HOTP = binary mod 10^digits
uint32_t mask = 10;
for(unsigned int i = 1; i < digits; i++)
mask *= 10;
return binary % mask;
}
// RFC 6238 (section 4.1): T0 is the Unix time to start counting time steps
// (default value is 0, i.e., the Unix epoch) and is also a system parameter.
#define RFC6238_T0 0
// RFC 6238 (section 5.2): We RECOMMEND a default time-step size of 30 seconds.
// This default value of 30 seconds is selected as a balance between security
// and usability.
#define RFC6238_X 30
// RFC 6238 (section 4, R6): The algorithm MUST use a strong shared secret. The
// length of the shared secret MUST be at least 128 bits (16 Byte). This
// document RECOMMENDs a shared secret length of 160 bits (20 Byte).
#define RFC6238_SECRET_LEN 160/8
// The number of digits to truncate to is not specified in RFC 6238. RFC 4226
// (section 5.3) specifies that the default is 6 (up to 8) digits, however, the
// example given in RFC 6238 uses 8 digits.
#define RFC6238_DIGITS 6
static uint32_t totp(const uint8_t *key, const size_t key_len, const time_t now)
{
// Get time
// RFC 6238 (section 4.2): T = (Current Unix time - T0) / X
// T is an integer and represents the number of time steps between the
// initial time T0 and the current time. T needs to be big endian
const uint64_t T = (now - RFC6238_T0) / RFC6238_X;
// RFC 6238 (section 4.2): TOTP(K, T) = HOTP(K,C) with C = T
return hotp(key, key_len, T, RFC6238_DIGITS);
}
static bool decode_base32_to_uint8_array(const char *base32, uint8_t *out, const size_t out_len)
{
// Base32 alphabet
const char *b32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Check input for validity
if(out_len == 0 || out_len*8/5 < strlen(base32) || out_len*8%5 != 0)
{
log_err("Decoding base32 2FA secret failed, invalid length (%zu)", out_len);
return false;
}
// Initialize output array
memset(out, 0, out_len);
// Iterate over input string
size_t out_pos = 0u;
for(size_t i = 0; i < strlen(base32); i++)
{
// Get current character
const char c = base32[i];
// Get position of current character in base32 alphabet
const char *c_pos = strchr(b32, toupper(c));
if(c_pos == NULL)
{
log_err("Decoding base32 2FA secret failed, invalid character '%c'", c);
return false;
}
// Get value of current character
const uint8_t c_val = (uint8_t)(c_pos-b32);
// Iterate over 5 bits of the current character
for(unsigned int j = 0; j < 5; j++)
{
// Current bit position
const unsigned int bit = 4-j;
// Get current bit in the current character
const uint8_t c_bit = (c_val >> bit) & 1;
// Get current byte position in the output array
out_pos = (i*5+j)/8;
// If we are out of bounds, return false
if(out_pos >= out_len)
{
log_err("Decoding base32 2FA secret failed, out of bounds (%zu >= %zu)", out_pos, out_len);
return false;
}
// Set current bit in the output array
out[out_pos] |= c_bit << (7-((i*5+j)%8));
}
}
return true;
}
static bool encode_uint8_t_array_to_base32(const uint8_t *in, const size_t in_len, char *base32, size_t base32_len)
{
// Base32 alphabet
const char *b32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// Check input for validity
if(in_len == 0 || in_len > base32_len*5/8 || in_len%5 != 0)
{
log_err("Encoding base32 2FA secret failed, invalid input length");
return false;
}
// Initialize base32 output array
memset(base32, 0, base32_len);
// Iterate over input string
size_t base32_pos = 0u;
for(size_t i = 0; i < in_len; i++)
{
// Get current byte
const uint8_t b = in[i];
// Iterate over 8 bits of the current byte
for(unsigned int j = 0; j < 8; j++)
{
// Current bit position
const unsigned int bit = 7-j;
// Get current bit in the current byte
const uint8_t b_bit = (b >> bit) & 1;
// Get current byte position in the base32 output array
base32_pos = (i*8+j)/5;
// If we are out of bounds, return false
if(base32_pos >= base32_len)
{
log_err("Decoding base32 2FA secret failed, base32 output array is too small");
return false;
}
// Set current bit in the base32 output array
base32[base32_pos] |= b_bit << (4-((i*8+j)%5));
}
}
// Iterate over base32 output array and replace each byte with its
// corresponding character in the base32 alphabet
for(size_t i = 0; i <= base32_pos; i++)
base32[i] = b32[(uint8_t)base32[i]];
return true;
}
static uint32_t last_code = 0;
enum totp_status verifyTOTP(const uint32_t incode)
{
// Decode base32 secret
uint8_t decoded_secret[RFC6238_SECRET_LEN];
if(!decode_base32_to_uint8_array(config.webserver.api.totp_secret.v.s, decoded_secret, sizeof(decoded_secret)))
return false;
// Get current time
const time_t now = time(NULL);
// Verify code for the previous, the current and the next time step
for(int i = -1; i <= 1; i++)
{
const uint32_t gencode = totp(decoded_secret, sizeof(decoded_secret), now + i*RFC6238_X);
// Verify code
// RFC 6238 (section 4.2): If the calculated value matches the value
// provided by the user, then the user is authenticated
// RFC 6238 (section 4.3): The server MUST NOT accept a TOTP value
// generated more than 30 seconds in the future
// RFC 6238 (section 4.3): The server MUST NOT accept a TOTP value
// generated more than 30 seconds in the past
// RFC 6238 (section 4.3): The server MUST NOT accept a TOTP value
// it accepted previously
if(gencode == incode)
{
if(gencode == last_code)
{
log_warn("2FA code has already been used (%i, %u), please wait %lu seconds",
i, gencode, (unsigned long)(RFC6238_X - (now % RFC6238_X)));
return TOTP_REUSED;
}
const char *which = i == -1 ? "previous" : i == 0 ? "current" : "next";
log_debug(DEBUG_API, "2FA code from %s time step is valid", which);
last_code = gencode;
return TOTP_CORRECT;
}
}
return TOTP_INVALID;
}
// Print TOTP code to stdout (for CLI use)
int printTOTP(void)
{
if(strlen(config.webserver.api.totp_secret.v.s) == 0)
{
puts("0");
return EXIT_SUCCESS;
}
// Decode base32 secret
uint8_t decoded_secret[RFC6238_SECRET_LEN];
if(!decode_base32_to_uint8_array(config.webserver.api.totp_secret.v.s, decoded_secret, sizeof(decoded_secret)))
return EXIT_FAILURE;
// Get current time
const time_t now = time(NULL);
const uint32_t code = totp(decoded_secret, sizeof(decoded_secret), now);
printf("%u\n", code);
return EXIT_SUCCESS;
}
// A QR code may be generated from the data using
// otpauth://totp/<label>?secret=<secret>&issuer=<issuer>&algorithm=<algorithm>&digits=<digits>&period=<period>
int generateTOTP(struct ftl_conn *api)
{
// Generate random secret using the system's random number generator
uint8_t random_secret[RFC6238_SECRET_LEN];
if(getrandom(random_secret, sizeof(random_secret), 0) < (ssize_t)sizeof(random_secret))
{
return send_json_error(api, 500, "internal_error", "Failed to generate random secret", strerror(errno));
}
// Encode base32 secret
const size_t base32_len = sizeof(random_secret)*8/5+1;
char *base32 = calloc(base32_len, sizeof(char));
if(!encode_uint8_t_array_to_base32(random_secret, sizeof(random_secret), base32, base32_len))
return send_json_error(api, 500, "internal_error", "Failed to encode secret", "Check FTL.log for details");
// Create JSON object
cJSON *tjson = cJSON_CreateObject();
JSON_REF_STR_IN_OBJECT(tjson, "type", "totp");
JSON_REF_STR_IN_OBJECT(tjson, "account", config.webserver.domain.v.s);
JSON_REF_STR_IN_OBJECT(tjson, "issuer", "Pi-hole%20API");
JSON_REF_STR_IN_OBJECT(tjson, "algorithm", "SHA1");
JSON_ADD_NUMBER_TO_OBJECT(tjson, "digits", RFC6238_DIGITS);
JSON_ADD_NUMBER_TO_OBJECT(tjson, "period", RFC6238_X);
JSON_ADD_NUMBER_TO_OBJECT(tjson, "offset", RFC6238_T0);
JSON_COPY_STR_TO_OBJECT(tjson, "secret", base32);
free(base32);
base32 = NULL;
// Generate a few codes to show the user how to use the secret
cJSON *codes = cJSON_CreateArray();
for(int i = 0; i < 5; i++)
{
const time_t now = time(NULL) + (i-1)*RFC6238_X;
const uint32_t code = totp(random_secret, sizeof(random_secret), now);
JSON_ADD_NUMBER_TO_ARRAY(codes, code);
}
JSON_ADD_ITEM_TO_OBJECT(tjson, "codes", codes);
// Send JSON response
cJSON *json = cJSON_CreateObject();
JSON_ADD_ITEM_TO_OBJECT(json, "totp", tjson);
JSON_SEND_OBJECT(json);
}
int generateAppPw(struct ftl_conn *api)
{
// Generate and set app password
char *password = NULL, *pwhash = NULL;
if(!generate_app_password(&password, &pwhash))
{
return send_json_error(api,
500,
"internal_error",
"Failed to generate app password",
"Check FTL.log for details");
}
// Create JSON object
cJSON *tjson = cJSON_CreateObject();
JSON_COPY_STR_TO_OBJECT(tjson, "password", password);
JSON_COPY_STR_TO_OBJECT(tjson, "hash", pwhash);
free(password);
password = NULL;
free(pwhash);
pwhash = NULL;
// Send JSON response
cJSON *json = cJSON_CreateObject();
JSON_ADD_ITEM_TO_OBJECT(json, "app", tjson);
JSON_SEND_OBJECT(json);
}
#if 0
#define RFC6238_TESTKEY "12345678901234567890"
#define RFC6238_TESTTIME 59
#define RFC6238_TESTTOTP 94287082
int test_totp(struct ftl_conn *api)
{
// Generate base32 secret
uint8_t secret[sizeof(RFC6238_TESTKEY)-1];
for(size_t i = 0; i < sizeof(secret); i++)
secret[i] = RFC6238_TESTKEY[i];
// Encode base32 secret
char base32_secret[sizeof(secret)*8/5+1];
if(!encode_uint8_t_array_to_base32(secret, sizeof(secret), base32_secret, sizeof(base32_secret)))
return false;
// Decode base32 secret
uint8_t decoded_secret[sizeof(RFC6238_TESTKEY)-1];
if(!decode_base32_to_uint8_array(base32_secret, decoded_secret, sizeof(decoded_secret)))
return false;
// Get test time
const time_t now = RFC6238_TESTTIME;
// Verify code for the current time and the previous and next time step
for(int i = -1; i <= 1; i++)
{
// Verify code
const time_t t = now + i*RFC6238_X;
if(totp(decoded_secret, sizeof(decoded_secret), t) == RFC6238_TESTTOTP)
log_info("Code is valid for time %ld", t);
}
return 200;
}
#endif

View File

@ -9,16 +9,32 @@
# Please see LICENSE file for your rights under this license.
set(sources
api.c
2fa.c
action.c
api_helper.h
api.h
msgpack.c
request.c
request.h
socket.c
socket.h
api.c
auth.c
auth.h
config.c
dhcp.c
dns.c
network.c
history.c
info.c
list.c
logs.c
queries.c
search.c
stats_database.c
stats.c
teleporter.c
theme.c
theme.h
)
add_library(api OBJECT ${sources})
add_dependencies(api gen_version)
target_compile_options(api PRIVATE ${EXTRAWARN})
target_include_directories(api PRIVATE ${PROJECT_SOURCE_DIR}/src)
add_subdirectory(docs)

177
src/api/action.c Normal file
View File

@ -0,0 +1,177 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/action
*
* 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/api.h"
// wait()
#include <sys/wait.h>
// reboot()
#include <sys/reboot.h>
#include <unistd.h>
// exit_code
#include "signals.h"
// flush_network_table()
#include "database/network-table.h"
#include "config/config.h"
static int run_and_stream_command(struct ftl_conn *api, const char *path, const char *const args[])
{
// Create a pipe for communication with our child
int pipefd[2];
if(pipe(pipefd) !=0)
{
log_err("Cannot create pipe while running gravity action: %s", strerror(errno));
return false;
}
// Fork!
pid_t cpid = fork();
int code = -1;
bool crashed = false;
if (cpid == 0)
{
/*** CHILD ***/
// Close the reading end of the pipe
close(pipefd[0]);
// Disable logging
log_ctrl(false, false);
// Flush STDERR
fflush(stderr);
// Redirect STDERR into our pipe
dup2(pipefd[1], STDERR_FILENO);
dup2(pipefd[1], STDOUT_FILENO);
// Run pihole -g
execv(path, (char *const *)args);
// Exit the fork
exit(EXIT_SUCCESS);
}
else
{
/*** PARENT ***/
// Close the writing end of the pipe
close(pipefd[1]);
// Send 200 OK with chunked size (-1)
mg_send_http_ok(api->conn, "text/plain", -1);
// Read readirected STDOUT/STDERR until EOF
// We are only interested in the last pipe line
char errbuf[1024] = "";
while(read(pipefd[0], errbuf, sizeof(errbuf)) > 0)
{
// Send chunked data
// The chunked size is the length of the string in hex and has to be
// transferred in advance, followed by \r\n as line separator and
// followed by a chunk of data (the string itself) of the specified
// size
mg_printf(api->conn, "%zX\r\n%s\r\n", strlen(errbuf), errbuf);
// Reset buffer
memset(errbuf, 0, sizeof(errbuf));
}
// Wait until child has exited to get its return code
int status;
waitpid(cpid, &status, 0);
code = WEXITSTATUS(status);
if(WIFSIGNALED(status))
{
crashed = true;
log_err("gravity failed with signal %d %s",
WTERMSIG(status),
WCOREDUMP(status) ? "(core dumped)" : "");
}
log_debug(DEBUG_API, "Gravity return code: %d", code);
// Close the reading end of the pipe
close(pipefd[0]);
}
// Send final chunk of size 0 showing end of data
mg_printf(api->conn, "0\r\n\r\n");
if(code == EXIT_SUCCESS && !crashed)
return send_json_success(api);
else
return send_json_error(api, 500,
"server_error",
"Gravity failed",
NULL);
}
int api_action_gravity(struct ftl_conn *api)
{
return run_and_stream_command(api, "/usr/local/bin/pihole", (const char *const []){ "pihole", "-g", NULL });
}
int api_action_restartDNS(struct ftl_conn *api)
{
if(!config.webserver.api.allow_destructive.v.b)
return send_json_error(api, 403,
"forbidden",
"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);
return send_json_success(api);
}
int api_action_flush_logs(struct ftl_conn *api)
{
if(!config.webserver.api.allow_destructive.v.b)
return send_json_error(api, 403,
"forbidden",
"Flushing the logs is not allowed",
"Check setting webserver.api.allow_destructive");
log_info("Received API request to flush the logs");
// Flush the logs
if(flush_dnsmasq_log())
return send_json_success(api);
else
return send_json_error(api, 500,
"server_error",
"Cannot flush the logs",
NULL);
}
int api_action_flush_arp(struct ftl_conn *api)
{
if(!config.webserver.api.allow_destructive.v.b)
return send_json_error(api, 403,
"forbidden",
"Flushing the ARP tables is not allowed",
"Check setting webserver.api.allow_destructive");
log_info("Received API request to flush the ARP tables");
// Flush the ARP tables
if(flush_network_table())
return send_json_success(api);
else
return send_json_error(api, 500,
"server_error",
"Cannot flush the ARP tables",
NULL);
}

File diff suppressed because it is too large Load Diff

View File

@ -3,51 +3,126 @@
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API commands and MessagePack helpers
* API route prototypes
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef API_H
#define API_H
#ifndef ROUTES_H
#define ROUTES_H
// struct mg_connection
#include "webserver/civetweb/civetweb.h"
// type cJSON
#include "webserver/cJSON/cJSON.h"
#include "webserver/http-common.h"
// regex_t
#include "regex_r.h"
// Common definitions
#define LOCALHOSTv4 "127.0.0.1"
#define LOCALHOSTv6 "::1"
// API router
int api_handler(struct mg_connection *conn, void *ignored);
// Statistic methods
void getStats(const int sock, const bool istelnet);
void getOverTime(const int sock, const bool istelnet);
void getTopDomains(const char *client_message, const int sock, const bool istelnet);
void getTopClients(const char *client_message, const int sock, const bool istelnet);
void getUpstreamDestinations(const char *client_message, const int sock, const bool istelnet);
void getQueryTypes(const int sock, const bool istelnet);
void getAllQueries(const char *client_message, const int sock, const bool istelnet);
void getRecentBlocked(const char *client_message, const int sock, const bool istelnet);
void getClientsOverTime(const int sock, const bool istelnet);
void getClientNames(const int sock, const bool istelnet);
int __attribute__((pure)) cmpdesc(const void *a, const void *b);
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);
// FTL methods
void getClientID(const int sock, const bool istelnet);
void getVersion(const int sock, const bool istelnet);
void getDBstats(const int sock, const bool istelnet);
void getUnknownQueries(const int sock, const bool istelnet);
void getMAXLOGAGE(const int sock);
void getGateway(const int sock);
void getInterfaces(const int sock);
// History methods
int api_history(struct ftl_conn *api);
int api_history_clients(struct ftl_conn *api);
// DNS resolver methods (dnsmasq_interface.c)
void getCacheInformation(const int sock);
void getDNSport(const int sock);
// History methods (database)
int api_history_database(struct ftl_conn *api);
int api_history_database_clients(struct ftl_conn *api);
// MessagePack serialization helpers
void pack_eom(const int sock);
void pack_bool(const int sock, const bool value);
void pack_uint8(const int sock, const uint8_t value);
void pack_uint64(const int sock, const uint64_t value);
void pack_int32(const int sock, const int32_t value);
void pack_int64(const int sock, const int64_t value);
void pack_float(const int sock, const float value);
bool pack_fixstr(const int sock, const char *string);
bool pack_str32(const int sock, const char *string);
void pack_map16_start(const int sock, const uint16_t length);
// Query methods
int api_queries(struct ftl_conn *api);
int api_queries_suggestions(struct ftl_conn *api);
bool compile_filter_regex(struct ftl_conn *api, const char *path, cJSON *json, regex_t **regex, unsigned int *N_regex);
// DHCP lease management
void delete_lease(const char *client_message, const int sock);
// Statistics methods (database)
int api_stats_database_top_items(struct ftl_conn *api);
int api_stats_database_summary(struct ftl_conn *api);
int api_stats_database_query_types(struct ftl_conn *api);
int api_stats_database_upstreams(struct ftl_conn *api);
#endif // API_H
// Info methods
int api_info_client(struct ftl_conn *api);
int api_info_database(struct ftl_conn *api);
int api_info_system(struct ftl_conn *api);
int api_info_ftl(struct ftl_conn *api);
int api_info_host(struct ftl_conn *api);
int api_info_sensors(struct ftl_conn *api);
int api_info_version(struct ftl_conn *api);
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);
// Config methods
int api_config(struct ftl_conn *api);
// Log methods
int api_logs(struct ftl_conn *api);
// Network methods
int api_network_gateway(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);
// DNS methods
int api_dns_blocking(struct ftl_conn *api);
// List methods
int api_list(struct ftl_conn *api);
int api_group(struct ftl_conn *api);
// Auth method
void init_api(void);
void free_api(void);
int check_client_auth(struct ftl_conn *api, const bool is_api);
int api_auth(struct ftl_conn *api);
void delete_all_sessions(void);
int api_auth_sessions(struct ftl_conn *api);
int api_auth_session_delete(struct ftl_conn *api);
bool is_local_api_user(const char *remote_addr) __attribute__((pure));
// 2FA methods
enum totp_status {
TOTP_INVALID,
TOTP_CORRECT,
TOTP_REUSED,
} __attribute__ ((packed));
enum totp_status verifyTOTP(const uint32_t code);
int generateTOTP(struct ftl_conn *api);
int printTOTP(void);
int generateAppPw(struct ftl_conn *api);
// Documentation methods
int api_docs(struct ftl_conn *api);
// Teleporter methods
int api_teleporter(struct ftl_conn *api);
// Action methods
int api_action_gravity(struct ftl_conn *api);
int api_action_restartDNS(struct ftl_conn *api);
int api_action_flush_logs(struct ftl_conn *api);
int api_action_flush_arp(struct ftl_conn *api);
// Search methods
int api_search(struct ftl_conn *api);
// DHCP methods
int api_dhcp_leases_GET(struct ftl_conn *api);
int api_dhcp_leases_DELETE(struct ftl_conn *api);
#endif // ROUTES_H

706
src/api/auth.c Normal file
View File

@ -0,0 +1,706 @@
/* 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/auth
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "FTL.h"
#include "api/auth.h"
#include "webserver/http-common.h"
#include "webserver/json_macros.h"
#include "api/api.h"
#include "log.h"
#include "config/config.h"
// get_password_hash()
#include "config/setupVars.h"
// (un)lock_shm()
#include "shmem.h"
// getrandom()
#include "daemon.h"
// sha256_raw_to_hex()
#include "config/password.h"
// database session functions
#include "database/session-table.h"
static uint16_t max_sessions = 0;
static struct session *auth_data = NULL;
static void add_request_info(struct ftl_conn *api, const char *csrf)
{
// Copy CSRF token into request
if(csrf != NULL)
strncpy((char*)api->request->csrf_token, csrf, sizeof(api->request->csrf_token) - 1);
// Store that this client is authenticated
// We use memset() with the size of an int here to avoid a
// compiler warning about modifying a variable in a const struct
memset((int*)&api->request->is_authenticated, 1, sizeof(api->request->is_authenticated));
}
void init_api(void)
{
// Restore sessions from database
max_sessions = config.webserver.api.max_sessions.v.u16;
auth_data = calloc(max_sessions, sizeof(struct session));
if(auth_data == NULL)
{
log_crit("Could not allocate memory for API sessions, check config value of webserver.api.max_sessions");
exit(EXIT_FAILURE);
}
restore_db_sessions(auth_data, max_sessions);
}
void free_api(void)
{
if(auth_data == NULL)
return;
// Store sessions in database
backup_db_sessions(auth_data, max_sessions);
max_sessions = 0;
free(auth_data);
auth_data = NULL;
}
// Is this client connecting from localhost?
bool __attribute__((pure)) is_local_api_user(const char *remote_addr)
{
return strcmp(remote_addr, LOCALHOSTv4) == 0 ||
strcmp(remote_addr, LOCALHOSTv6) == 0;
}
// Can we validate this client?
// Returns -1 if not authenticated or expired
// 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))
{
api->message = "no auth required for local user";
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;
}
// Does the client provide a session ID?
char sid[SID_SIZE];
const char *sid_source = "-";
// Try to extract SID from cookie
bool sid_avail = false;
// If not, does the client provide a session ID via GET/POST?
if(api->payload.avail)
{
// Try to extract SID from form-encoded payload
if(GET_VAR("sid", sid, api->payload.raw) > 0)
{
// "+" may have been replaced by " ", undo this here
for(unsigned int i = 0; i < SID_SIZE; i++)
if(sid[i] == ' ')
sid[i] = '+';
// Zero terminate SID string
sid[SID_SIZE-1] = '\0';
// Mention source of SID
sid_source = "payload (form-data)";
// Mark SID as available
sid_avail = true;
}
// Try to extract SID from root of a possibly included JSON payload
else if(api->payload.json != NULL)
{
cJSON *sid_obj = cJSON_GetObjectItem(api->payload.json, "sid");
if(cJSON_IsString(sid_obj))
{
// Copy SID string
strncpy(sid, sid_obj->valuestring, SID_SIZE - 1u);
// Zero terminate SID string
sid[SID_SIZE-1] = '\0';
// Mention source of SID
sid_source = "payload (JSON)";
// Mark SID as available
sid_avail = true;
}
}
}
// If not, does the client provide a session ID via HEADER?
if(!sid_avail)
{
const char *sid_header = NULL;
// Try to extract SID from header
if((sid_header = mg_get_header(api->conn, "sid")) != NULL ||
(sid_header = mg_get_header(api->conn, "X-FTL-SID")) != NULL)
{
// Copy SID string
strncpy(sid, sid_header, SID_SIZE - 1u);
// Zero terminate SID string
sid[SID_SIZE-1] = '\0';
// Mention source of SID
sid_source = "header";
// Mark SID as available
sid_avail = true;
}
}
// If not, does the client provide a session ID via COOKIE?
bool cookie_auth = false;
if(!sid_avail)
{
cookie_auth = http_get_cookie_str(api, "sid", sid, SID_SIZE);
if(cookie_auth)
{
// Mention source of SID
sid_source = "cookie";
// Mark SID as available
sid_avail = true;
}
}
// If not, does the client provide a session ID via URI?
if(!sid_avail && api->request->query_string && GET_VAR("sid", sid, api->request->query_string) > 0)
{
// "+" may have been replaced by " ", undo this here
for(unsigned int i = 0; i < SID_SIZE; i++)
if(sid[i] == ' ')
sid[i] = '+';
// Zero terminate SID string
sid[SID_SIZE-1] = '\0';
// Mention source of SID
sid_source = "URI";
// Mark SID as available
sid_avail = true;
}
if(!sid_avail)
{
api->message = "no SID provided";
log_debug(DEBUG_API, "API Authentication: FAIL (%s)", api->message);
return API_AUTH_UNAUTHORIZED;
}
// else: Analyze SID
int user_id = API_AUTH_UNAUTHORIZED;
const time_t now = time(NULL);
log_debug(DEBUG_API, "Read sid=\"%s\" from %s", sid, sid_source);
// If the SID has been sent through a cookie, we require a CSRF token in
// the header to be sent along with the request for any API requests
char csrf[SID_SIZE];
const bool need_csrf = cookie_auth && is_api;
if(need_csrf)
{
const char *csrf_header = NULL;
// Try to extract CSRF token from header
if((csrf_header = mg_get_header(api->conn, "X-CSRF-TOKEN")) != NULL)
{
// Copy CSRF string
strncpy(csrf, csrf_header, SID_SIZE - 1u);
// Zero terminate CSRF string
csrf[SID_SIZE-1] = '\0';
}
else
{
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 &&
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)
{
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;
break;
}
}
if(user_id > API_AUTH_UNAUTHORIZED)
{
// Authentication successful: valid session
// Update timestamp of this client to extend
// the validity of their API authentication
auth_data[user_id].valid_until = now + config.webserver.session.timeout.v.ui;
// Set strict_tls permanently to false if the client connected via HTTP
auth_data[user_id].tls.mixed |= api->request->is_ssl != auth_data[user_id].tls.login;
// Update user cookie
if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers),
FTL_SET_COOKIE,
auth_data[user_id].sid, config.webserver.session.timeout.v.ui) < 0)
{
return send_json_error(api, 500, "internal_error", "Internal server error", NULL);
}
// Add CSRF token to request
add_request_info(api, auth_data[user_id].csrf);
// Debug logging
if(config.debug.api.v.b)
{
char timestr[128];
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);
}
}
else
{
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->message = "correct password";
return user_id;
}
static int get_all_sessions(struct ftl_conn *api, cJSON *json)
{
const time_t now = time(NULL);
cJSON *sessions = JSON_NEW_ARRAY();
for(int i = 0; i < max_sessions; i++)
{
if(!auth_data[i].used)
continue;
cJSON *session = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(session, "id", i);
JSON_ADD_BOOL_TO_OBJECT(session, "current_session", i == api->user_id);
JSON_ADD_BOOL_TO_OBJECT(session, "valid", auth_data[i].valid_until >= now);
cJSON *tls = JSON_NEW_OBJECT();
JSON_ADD_BOOL_TO_OBJECT(tls, "login", auth_data[i].tls.login);
JSON_ADD_BOOL_TO_OBJECT(tls, "mixed", auth_data[i].tls.mixed);
JSON_ADD_ITEM_TO_OBJECT(session, "tls", tls);
JSON_ADD_NUMBER_TO_OBJECT(session, "login_at", auth_data[i].login_at);
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);
JSON_ADD_BOOL_TO_OBJECT(session, "app", auth_data[i].app);
JSON_ADD_ITEM_TO_ARRAY(sessions, session);
}
JSON_ADD_ITEM_TO_OBJECT(json, "sessions", sessions);
return 0;
}
static int get_session_object(struct ftl_conn *api, cJSON *json, const int user_id, const time_t now)
{
cJSON *session = JSON_NEW_OBJECT();
// Authentication not needed
if(user_id == API_AUTH_LOCALHOST || 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;
}
// Valid session
if(user_id > API_AUTH_UNAUTHORIZED && auth_data[user_id].used)
{
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_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;
}
// No valid session
JSON_ADD_BOOL_TO_OBJECT(session, "valid", false);
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;
}
static bool delete_session(const int user_id)
{
// Skip if nothing to be done here
if(user_id < 0 || user_id >= max_sessions)
return false;
const bool was_valid = auth_data[user_id].used;
// Zero out this session (also sets valid to false == 0)
memset(&auth_data[user_id], 0, sizeof(auth_data[user_id]));
return was_valid;
}
void delete_all_sessions(void)
{
// Zero out all sessions without looping
memset(auth_data, 0, max_sessions*sizeof(*auth_data));
}
static int send_api_auth_status(struct ftl_conn *api, const int user_id, const time_t now)
{
if(user_id > API_AUTH_UNAUTHORIZED && (api->method == HTTP_GET || api->method == HTTP_POST))
{
log_debug(DEBUG_API, "API Auth status: OK");
// Ten minutes validity
if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers),
FTL_SET_COOKIE,
auth_data[user_id].sid, config.webserver.session.timeout.d.ui) < 0)
{
return send_json_error(api, 500, "internal_error", "Internal server error", NULL);
}
cJSON *json = JSON_NEW_OBJECT();
get_session_object(api, json, user_id, now);
JSON_SEND_OBJECT(json);
}
else if(api->method == HTTP_DELETE)
{
if(user_id > API_AUTH_UNAUTHORIZED)
{
log_debug(DEBUG_API, "API Auth status: Logout, asking to delete cookie");
strncpy(pi_hole_extra_headers, FTL_DELETE_COOKIE, sizeof(pi_hole_extra_headers));
// Revoke client authentication. This slot can be used by a new client afterwards.
const int code = delete_session(user_id) ? 204 : 404;
// Send empty reply with appropriate HTTP status code
send_http_code(api, "application/json; charset=utf-8", code, "");
return code;
}
else
{
log_debug(DEBUG_API, "API Auth status: Logout, but not authenticated");
cJSON *json = JSON_NEW_OBJECT();
get_session_object(api, json, user_id, now);
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)");
cJSON *json = JSON_NEW_OBJECT();
get_session_object(api, json, user_id, now);
JSON_SEND_OBJECT(json);
}
else
{
log_debug(DEBUG_API, "API Auth status: Invalid, asking to delete cookie");
strncpy(pi_hole_extra_headers, FTL_DELETE_COOKIE, sizeof(pi_hole_extra_headers));
cJSON *json = JSON_NEW_OBJECT();
get_session_object(api, json, user_id, now);
JSON_SEND_OBJECT_CODE(json, 401); // 401 Unauthorized
}
}
static void generateSID(char *sid)
{
uint8_t raw_sid[SID_SIZE];
if(getrandom(raw_sid, sizeof(raw_sid), 0) < 0)
{
log_err("getrandom() failed in generateSID()");
return;
}
base64_encode_raw(NETTLE_SIGN sid, SID_BITSIZE/8, raw_sid);
sid[SID_SIZE-1] = '\0';
}
// api/auth
// GET: Check authentication
// POST: Login
// DELETE: Logout
int api_auth(struct ftl_conn *api)
{
// Check HTTP method
char *password = NULL;
const time_t now = time(NULL);
const bool empty_password = config.webserver.api.pwhash.v.s[0] == '\0';
if(api->item != NULL && strlen(api->item) > 0)
{
// Sub-paths are not allowed
return 0;
}
// Login attempt, check password
if(api->method == HTTP_POST)
{
// Try to extract response from payload
const int ret = check_json_payload(api);
if(ret != 0)
return ret;
// Check if password is available
cJSON *json_password;
if((json_password = cJSON_GetObjectItemCaseSensitive(api->payload.json, "password")) == NULL)
{
const char *message = "No password found in JSON payload";
log_debug(DEBUG_API, "API auth error: %s", message);
return send_json_error(api, 400,
"bad_request",
message,
NULL);
}
// Check password type
if(!cJSON_IsString(json_password))
{
const char *message = "Field password has to be of type 'string'";
log_debug(DEBUG_API, "API auth error: %s", message);
return send_json_error(api, 400,
"bad_request",
message,
NULL);
}
// password is already null-terminated
password = json_password->valuestring;
}
// Did the client authenticate before and we can validate this?
int user_id = check_client_auth(api, false);
// If this is a valid session, we can exit early at this point if no password is supplied
if(user_id != API_AUTH_UNAUTHORIZED && (password == NULL || strlen(password) == 0))
return send_api_auth_status(api, user_id, now);
// Logout attempt
if(api->method == HTTP_DELETE)
{
log_debug(DEBUG_API, "API Auth: User with ID %i wants to log out", user_id);
return send_api_auth_status(api, user_id, now);
}
// If this is not a login attempt, we can exit early at this point
if(password == NULL && !empty_password)
return send_api_auth_status(api, user_id, now);
// else: Login attempt
// - Client tries to authenticate using a password, or
// - There no password on this machine
enum password_result result = PASSWORD_INCORRECT;
// If there is no password (or empty), check if there is any password at all
if(empty_password && (password == NULL || strlen(password) == 0))
result = PASSWORD_CORRECT;
else
result = verify_login(password);
if(result == PASSWORD_CORRECT || result == APPPASSWORD_CORRECT)
{
// Accepted
// Zero-out password in memory to avoid leaking it when it is
// freed at the end of the current API request
if(password != NULL)
memset(password, 0, strlen(password));
// 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)
{
// Get 2FA token from payload
cJSON *json_totp;
if((json_totp = cJSON_GetObjectItemCaseSensitive(api->payload.json, "totp")) == NULL)
{
const char *message = "No 2FA token found in JSON payload";
log_debug(DEBUG_API, "API auth error: %s", message);
return send_json_error(api, 400,
"bad_request",
message,
NULL);
}
enum totp_status totp = verifyTOTP(json_totp->valueint);
if(totp == TOTP_REUSED)
{
// 2FA token has been reused
return send_json_error(api, 401,
"unauthorized",
"Reused 2FA token",
"wait for new token");
}
else if(totp != TOTP_CORRECT)
{
// 2FA token is invalid
return send_json_error(api, 401,
"unauthorized",
"Invalid 2FA token",
NULL);
}
}
// Find unused authentication slot
for(unsigned int i = 0; i < max_sessions; i++)
{
// Expired slow, mark as unused
if(auth_data[i].used &&
auth_data[i].valid_until < now)
{
log_debug(DEBUG_API, "API: Session of client %u (%s) expired, freeing...",
i, auth_data[i].remote_addr);
delete_session(i);
}
// Found unused authentication slot (might have been freed before)
if(!auth_data[i].used)
{
// Mark as used
auth_data[i].used = true;
// Set validitiy to now + timeout
auth_data[i].login_at = now;
auth_data[i].valid_until = now + config.webserver.session.timeout.v.ui;
// Set remote address
strncpy(auth_data[i].remote_addr, api->request->remote_addr, sizeof(auth_data[i].remote_addr));
auth_data[i].remote_addr[sizeof(auth_data[i].remote_addr)-1] = '\0';
// Store user-agent (if available)
const char *user_agent = mg_get_header(api->conn, "user-agent");
if(user_agent != NULL)
{
strncpy(auth_data[i].user_agent, user_agent, sizeof(auth_data[i].user_agent));
auth_data[i].user_agent[sizeof(auth_data[i].user_agent)-1] = '\0';
}
else
{
auth_data[i].user_agent[0] = '\0';
}
auth_data[i].tls.login = api->request->is_ssl;
auth_data[i].tls.mixed = false;
auth_data[i].app = result == APPPASSWORD_CORRECT;
// Generate new SID and CSRF token
generateSID(auth_data[i].sid);
generateSID(auth_data[i].csrf);
user_id = i;
break;
}
}
// Debug logging
if(config.debug.api.v.b && user_id > API_AUTH_UNAUTHORIZED)
{
char timestr[128];
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,
empty_password ? "empty password" : "correct response");
}
if(user_id == API_AUTH_UNAUTHORIZED)
{
log_warn("No free API seats available (webserver.api.max_sessions = %u), not authenticating client",
max_sessions);
return send_json_error(api, 429,
"api_seats_exceeded",
"API seats exceeded",
"increase webserver.api.max_sessions");
}
api->message = result == APPPASSWORD_CORRECT ? "app-password correct" : "password correct";
}
else if(result == PASSWORD_RATE_LIMITED)
{
// Rate limited
return send_json_error(api, 429,
"rate_limiting",
"Rate-limiting login attempts",
NULL);
}
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);
}
// Free allocated memory
return send_api_auth_status(api, user_id, now);
}
int api_auth_sessions(struct ftl_conn *api)
{
// Get session object
cJSON *json = JSON_NEW_OBJECT();
get_all_sessions(api, json);
JSON_SEND_OBJECT(json);
}
int api_auth_session_delete(struct ftl_conn *api)
{
// Get user ID
int uid;
if(sscanf(api->item, "%i", &uid) != 1)
return send_json_error(api, 400, "bad_request", "Missing or invalid session ID", NULL);
// Check if session ID is valid
if(uid <= API_AUTH_UNAUTHORIZED || uid >= max_sessions)
return send_json_error(api, 400, "bad_request", "Session ID out of bounds", NULL);
// Check if session is used
if(!auth_data[uid].used)
return send_json_error(api, 400, "bad_request", "Session ID not in use", NULL);
// Delete session
const int code = delete_session(uid) ? 204 : 404;
// Send empty reply with appropriate HTTP status code
send_http_code(api, "application/json; charset=utf-8", code, "");
return code;
}

63
src/api/auth.h Normal file
View File

@ -0,0 +1,63 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API authentication prototypes
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef AUTH_H
#define AUTH_H
// crypto library
#include <nettle/sha2.h>
#include <nettle/base64.h>
#include <nettle/version.h>
// On 2017-08-27 (after v3.3, before v3.4), nettle changed the type of
// destination from uint_8t* to char* in all base64 and base16 functions
// (armor-signedness branch). This is a breaking change as this is a change in
// signedness causing issues when compiling FTL against older versions of
// nettle. We create this constant here to have a conversion if necessary.
// See https://github.com/gnutls/nettle/commit/f2da403135e2b2f641cf0f8219ad5b72083b7dfd
#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR < 4
#define NETTLE_SIGN (uint8_t*)
#else
#define NETTLE_SIGN
#endif
// How many bits should the SID and CSRF token use?
#define SID_BITSIZE 128
#define SID_SIZE BASE64_ENCODE_RAW_LENGTH(SID_BITSIZE/8)
// SameSite=Strict: Defense against some classes of cross-site request forgery
// (CSRF) attacks. This ensures the session cookie will only be sent in a
// first-party (i.e., Pi-hole) context and NOT be sent along with requests
// initiated by third party websites.
//
// HttpOnly: the cookie cannot be accessed through client side script (if the
// browser supports this flag). As a result, even if a cross-site scripting
// (XSS) flaw exists, and a user accidentally accesses a link that exploits this
// flaw, the browser (primarily Internet Explorer) will not reveal the cookie to
// a third party.
#define FTL_SET_COOKIE "Set-Cookie: sid=%s; SameSite=Strict; Path=/; Max-Age=%u; HttpOnly\r\n"
#define FTL_DELETE_COOKIE "Set-Cookie: sid=deleted; SameSite=Strict; Path=/; Max-Age=-1\r\n"
struct session {
bool used;
bool app;
struct {
bool login;
bool mixed;
} tls;
time_t login_at;
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 sid[SID_SIZE];
char csrf[SID_SIZE];
};
#endif // AUTH_H

1051
src/api/config.c Normal file

File diff suppressed because it is too large Load Diff

113
src/api/dhcp.c Normal file
View File

@ -0,0 +1,113 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/dhcp
*
* 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"
#include "config/dnsmasq_config.h"
// rotate_files()
#include "files.h"
int api_dhcp_leases_GET(struct ftl_conn *api)
{
// Get DHCP leases
cJSON *leases = JSON_NEW_ARRAY();
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "leases", leases);
FILE *fp = fopen(DHCPLEASESFILE, "r");
if(fp == NULL)
{
// File does not exist or not readable, send empty array
JSON_SEND_OBJECT(json);
}
char *line = NULL;
size_t len = 0;
ssize_t read;
while((read = getline(&line, &len, fp)) != -1)
{
// Skip empty lines
if(read == 0)
continue;
// Skip duid line
if(strncmp(line, "duid", 4) == 0)
continue;
// Parse line
unsigned long expires = 0;
char hwaddr[18] = { 0 };
char ip[INET_ADDRSTRLEN] = { 0 };
char name[65] = { 0 };
char clientid[765] = { 0 };
const int ret = sscanf(line, "%lu %17s %15s %64s %764s", &expires, hwaddr, ip, name, clientid);
// Skip invalid lines
if(ret != 5)
continue;
// Create JSON object for this lease
cJSON *lease = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(lease, "expires", expires);
JSON_COPY_STR_TO_OBJECT(lease, "hwaddr", hwaddr);
JSON_COPY_STR_TO_OBJECT(lease, "ip", ip);
JSON_COPY_STR_TO_OBJECT(lease, "name", name);
JSON_COPY_STR_TO_OBJECT(lease, "clientid", clientid);
// Add lease to array
JSON_ADD_ITEM_TO_ARRAY(leases, lease);
}
free(line);
fclose(fp);
JSON_SEND_OBJECT(json);
}
// defined in dnsmasq_interface.c
extern bool FTL_unlink_DHCP_lease(const char *ipaddr, const char **hint);
// Delete DHCP leases
int api_dhcp_leases_DELETE(struct ftl_conn *api)
{
// Validate input (must be a valid IPv4 address)
struct sockaddr_in sa;
if(api->item == NULL || strlen(api->item) == 0 || inet_pton(AF_INET, api->item, &(sa.sin_addr)) == 0)
{
// Send empty reply with code 204 No Content
return send_json_error(api,
400,
"bad_request",
"The provided IPv4 address is invalid",
api->item);
}
// Delete lease
log_debug(DEBUG_API, "Deleting DHCP lease for address %s", api->item);
const char *hint = NULL;
const bool found = FTL_unlink_DHCP_lease(api->item, &hint);
if(!found && hint != NULL)
{
// Send error when something went wrong (hint is not NULL)
return send_json_error(api,
400,
"bad_request",
"Failed to delete DHCP lease",
hint);
}
// Send empty reply with codes:
// - 204 No Content (if a lease was deleted)
// - 404 Not Found (if no lease was found)
cJSON *json = JSON_NEW_OBJECT();
JSON_SEND_OBJECT_CODE(json, found ? 204 : 404);
}

143
src/api/dns.c Normal file
View File

@ -0,0 +1,143 @@
/* 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"
// {s,g}et_blockingstatus()
#include "config/setupVars.h"
// set_blockingmode_timer()
#include "timers.h"
#include "shmem.h"
// config struct
#include "config/config.h"
// Location of custom.list
#include "config/dnsmasq_config.h"
#define DOMAIN_VALIDATION_REGEX "^((-|_)*[a-z0-9]((-|_)*[a-z0-9])*(-|_)*)(\\.(-|_)*([a-z0-9]((-|_)*[a-z0-9])*))*$"
#define LABEL_VALIDATION_REGEX "^[^\\.]{1,63}(\\.[^\\.]{1,63})*$"
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;
}
// Get timer information (if applicable)
double delay;
bool target_status;
get_blockingmode_timer(&delay, &target_status);
if(delay > -1)
{
JSON_ADD_NUMBER_TO_OBJECT(json, "timer", delay);
}
else
{
JSON_ADD_NULL_TO_OBJECT(json, "timer");
}
// Send object (HTTP 200 OK)
JSON_SEND_OBJECT(json);
}
static int set_blocking(struct ftl_conn *api)
{
if(get_blockingstatus() == DNS_FAILED)
{
return send_json_error(api, 500,
"dns_failure",
"DNS resolver is not running",
NULL);
}
// 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))
{
return send_json_error(api, 400,
"body_error",
"No \"blocking\" boolean in body data",
NULL);
}
const enum blocking_status target_status = cJSON_IsTrue(elem) ? BLOCKING_ENABLED : BLOCKING_DISABLED;
// Get (optional) timer
double timer = -1;
elem = cJSON_GetObjectItemCaseSensitive(api->payload.json, "timer");
if (cJSON_IsNumber(elem) && elem->valuedouble > 0.0)
timer = elem->valuedouble;
if(target_status == get_blockingstatus())
{
// The blocking status does not need to be changed
// Delete a possibly running timer
set_blockingmode_timer(-1.0, true);
log_debug(DEBUG_API, "No change in blocking mode, resetting timer");
}
else
{
// Activate requested status
set_blockingstatus(target_status);
// Start timer (-1 disables all running timers)
set_blockingmode_timer(timer, !target_status);
log_debug(DEBUG_API, "%sd Pi-hole, timer set to %f seconds", target_status ? "Enable" : "Disable", timer);
}
// Return GET property as result of POST/PUT/PATCH action
// if no error happened above
return get_blocking(api);
}
int api_dns_blocking(struct ftl_conn *api)
{
if(api->method == HTTP_GET)
{
lock_shm();
const int ret = get_blocking(api);
unlock_shm();
return ret;
}
else if(api->method == HTTP_POST)
{
lock_shm();
const int ret = set_blocking(api);
unlock_shm();
return ret;
}
else
{
return send_json_error(api, 405, "method_not_allowed", "Method not allowed", NULL);
}
}

View File

@ -0,0 +1,66 @@
# Pi-hole: A black hole for Internet advertisements
# (c) 2021 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# FTL Engine
# /src/api/docs/CMakeList.txt
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
set(sources
hex/index.html
hex/index.css
hex/pi-hole.js
hex/external/rapidoc-min.js
hex/external/rapidoc-min.js.map
hex/external/highlight.min.js
hex/external/highlight-default.min.css
hex/images/logo.svg
hex/specs/action.yaml
hex/specs/auth.yaml
hex/specs/clients.yaml
hex/specs/config.yaml
hex/specs/common.yaml
hex/specs/dhcp.yaml
hex/specs/dns.yaml
hex/specs/docs.yaml
hex/specs/domains.yaml
hex/specs/endpoints.yaml
hex/specs/groups.yaml
hex/specs/history.yaml
hex/specs/info.yaml
hex/specs/lists.yaml
hex/specs/logs.yaml
hex/specs/main.yaml
hex/specs/network.yaml
hex/specs/queries.yaml
hex/specs/search.yaml
hex/specs/stats.yaml
hex/specs/teleporter.yaml
docs.c
)
# Create relevant directories for processed content
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex/specs)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex/images)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/hex/external)
# Compile files from content/ into hex/
find_program(RESOURCE_COMPILER xxd)
file(GLOB_RECURSE COMPILED_RESOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/content" "content/*")
foreach(INPUT_FILE ${COMPILED_RESOURCES})
set(IN ${CMAKE_CURRENT_SOURCE_DIR}/content/${INPUT_FILE})
set(OUTPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/hex/${INPUT_FILE})
add_custom_command(
OUTPUT hex/${INPUT_FILE}
COMMAND ${RESOURCE_COMPILER} -i < ${IN} > ${OUTPUT_FILE}
COMMENT "Compiling ${INPUT_FILE}"
VERBATIM)
list(APPEND COMPILED_RESOURCES ${OUTPUT_FILE})
endforeach()
add_library(api_docs OBJECT ${sources})
target_compile_options(api_docs PRIVATE ${EXTRAWARN})
target_include_directories(api_docs PRIVATE ${PROJECT_SOURCE_DIR}/src)

View File

@ -0,0 +1 @@
.hljs{display:block;overflow-x:auto;padding:.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 88.32 129.93"><defs><linearGradient id="New_Gradient_Swatch_1" x1="2.71" x2="69.77" y1="20.04" y2="20.04" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#12b212"/><stop offset="1" stop-color="#0f0"/></linearGradient><style>.cls-2{fill:#980200}.cls-3{fill:red}</style></defs><title>NewVortex</title><path fill="url(#New_Gradient_Swatch_1)" d="M36.56 39.93C20.34 38.2 4 25.94 2.71 0c25.17 0 38.63 14.9 39.93 38.51 4.76-28.32 27.07-25 27.07-25 1.06 16.05-12.12 25.78-27.07 26.59-4.2-8.85-29.36-30.56-29.36-30.56a.07.07 0 00-.11.08s24.28 21.15 23.39 30.31"/><path d="M44.16 129.93c-1.57-.09-16.22-.65-17.11-17.11-.72-10 7.18-17.37 7.18-27.08C32.44 61.53 0 64.53 0 85.74a19.94 19.94 0 005.83 14.14L30 124.06a19.94 19.94 0 0014.14 5.83" class="cls-2"/><path d="M88.32 85.75c-.09 1.57-.65 16.22-17.11 17.11-10 .72-17.38-7.18-27.08-7.18-24.21 1.79-21.21 34.22 0 34.22a19.94 19.94 0 0014.14-5.83L82.46 99.9a19.94 19.94 0 005.83-14.14" class="cls-3"/><path d="M44.16 41.59c1.57.09 16.22.65 17.11 17.11.72 10-7.18 17.37-7.18 27.08 1.79 24.21 34.22 21.21 34.22 0a19.94 19.94 0 00-5.83-14.14L58.3 47.45a19.94 19.94 0 00-14.14-5.83" class="cls-2"/><path d="M.08 85.75c.09-1.57.65-16.22 17.11-17.11 10-.72 17.38 7.18 27.08 7.18 24.21-1.82 21.21-34.22 0-34.22a19.94 19.94 0 00-14.14 5.83L5.94 71.61A19.94 19.94 0 00.11 85.75" class="cls-3"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,73 @@
.btn {
padding: 6px 12px;
font-size:13px;
background-color: var(--primary-color);
color: #fff;
border: none;
margin: 0 2px;
border-radius: 2px;
cursor:pointer;
}
.btn.large {
width: 120px;
height: 24px
}
.btn.medium {
width: 75px;
height: 24px
}
.btn.small {
width: 60px;
height: 24px
}
.btn.green {
background-color: #228b22;
}
.btn.red {
background-color: #ff4500;
}
.btn:hover {
background: #fff;
color: var(--primary-color);
}
.logo-slot {
padding: 10px 0 10px 16px;
width: 50px;
}
.header-slot {
margin: 0 15px 0 0;
width: 100%;
display: flex;
gap: 6px 32px;
flex-wrap: wrap;
justify-content: start;
}
.header-title {
font-weight: 700;
font-size: 32px;
line-height: 1;
}
.password-controls {
display: flex;
align-items: stretch;
}
.footer-slot {
margin:0;
padding:16px;
display: flex;
gap: 16px 36px;
flex-wrap: wrap;
justify-content: space-between;
color:#fff;
text-align:center;
background-color:#222;
}
.footer-slot > div {
flex: 0 0 auto;
}

View File

@ -0,0 +1,67 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1,user-scalable=yes">
<title>Pi-hole API documentation</title>
<!--<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,600&display=swap" rel="stylesheet">-->
<link rel="stylesheet" href="external/highlight-default.min.css">
<script src="external/highlight.min.js"></script>
<script type='text/javascript' src='external/rapidoc-min.js'></script>
<script type="text/javascript" src="pi-hole.js"></script>
<link href='index.css' rel='stylesheet'>
<link rel="apple-touch-icon" href="/admin/img/favicons/apple-touch-icon.png" sizes="180x180">
<link rel="icon" href="/admin/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="/admin/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="manifest" href="/admin/img/favicons/manifest.json">
<link rel="mask-icon" href="/admin/img/favicons/safari-pinned-tab.svg" color="#367fa9">
<link rel="shortcut icon" href="/admin/img/favicons/favicon.ico">
<meta name="msapplication-TileColor" content="#367fa9">
<meta name="msapplication-TileImage" content="/admin/img/favicons/mstile-150x150.png">
<meta name="theme-color" content="#367fa9">
</head>
<body>
<rapi-doc id = "thedoc"
spec-url = "specs/main.yaml"
allow-server-selection = "true"
allow-authentication = "false"
allow-spec-url-load = "false"
allow-spec-file-load = "false"
show-header = "true"
show-info = "true"
theme = "dark"
allow-try = "true"
sort-endpoints-by = "path"
default-schema-tab = "model"
schema-style = "tree"
render-style = "view"
primary-color = "#2d87e2"
header-color = "#222"
api-key-name = "sid"
api-key-location = "header"
api-key-value = "-"
show-method-in-nav-bar="as-colored-block"
allow-search = "false">
<img slot="logo" class="logo-slot" src="images/logo.svg">
<div slot="header" class="header-slot">
<div class="header-title">Pi-hole API Documentation</div>
<div class="password-controls">
<input id="loginpw" type="password" placeholder="password">
<button class="btn" id="loginbtn" onclick="loginout()">Login</button>
</div>
</div>
<!-- content at the bottom -->
<div slot="footer" class="footer-slot">
<div>
<button onclick="document.getElementById('thedoc').setAttribute('theme', 'dark')" class="btn">Dark Theme</button>
<button onclick="document.getElementById('thedoc').setAttribute('theme', 'light')" class="btn">Light Theme</button>
</div>
<div>
<button onclick="setStyle('view')" class="btn">Default</button>
<button onclick="setStyle('read')" class="btn">Reader</button>
<button onclick="setStyle('focused')" class="btn">Focused reader</button>
</div>
</div>
</rapi-doc>
</body>
</html>

View File

@ -0,0 +1,113 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2021 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
// GET implementation
async function getData(url = '') {
const docEl = document.getElementById('thedoc');
const sid = docEl.attributes["api-key-value"].value;
const response = await fetch(url, {
method: 'GET',
headers: {'Content-Type': 'application/json', 'X-FTL-SID': sid}
});
return response.json();
}
// DELETE implementation
async function deleteData(url = '') {
const docEl = document.getElementById('thedoc');
const sid = docEl.attributes["api-key-value"].value;
const response = await fetch(url, {
method: 'DELETE',
headers: {'X-FTL-SID': sid}
});
return response;
}
// POST implementation
async function postData(url = '', data = {}) {
const docEl = document.getElementById('thedoc');
const sid = docEl.attributes["api-key-value"].value;
const response = await fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-FTL-SID': sid},
body: JSON.stringify(data)
});
return response.json();
}
// Mark login as OK
function loginOk(sid) {
const docEl = document.getElementById('thedoc');
docEl.setAttribute('api-key-value', sid);
const btn = document.getElementById('loginbtn');
btn.classList.add('green');
btn.classList.remove('red');
btn.textContent = 'Logout';
}
// Mark login as FAIL
function loginFAIL() {
const docEl = document.getElementById('thedoc');
docEl.setAttribute('api-key-value', '-');
const btn = document.getElementById('loginbtn');
btn.classList.remove('green');
btn.classList.add('red');
btn.textContent = 'Login';
}
// Mark logout as OK
function logoutOk() {
const docEl = document.getElementById('thedoc');
docEl.setAttribute('api-key-value', "-");
const btn = document.getElementById('loginbtn');
btn.classList.remove('green');
btn.classList.remove('red');
btn.textContent = 'Login';
}
// Login using password
function loginout(){
const docEl = document.getElementById('thedoc');
if(docEl.attributes["api-key-value"].value === '-') {
var pw = document.getElementById('loginpw').value;
postData('/api/auth', {password: pw})
.then(data => {
if(data.session.valid === true) {
loginOk(data.session.sid);
} else {
loginFAIL();
}
})
.catch((error) => {
loginFAIL();
console.error('Error:', error);
});
} else {
deleteData('/api/auth')
.then(logoutOk())
.catch((error) => {
loginFAIL();
console.error('Error:', error);
});
}
}
function setStyle(style) {
const docEl = document.getElementById('thedoc');
docEl.setAttribute('render-style', style);
docEl.setAttribute('allow-search', style !== 'view');
}
document.addEventListener('DOMContentLoaded', (event) => {
let docEl = document.getElementById("thedoc");
docEl.addEventListener('after-try', (e) => {
console.log(e.detail.response);
if(e.detail.response.status === 401) {
loginFAIL();
}
});
});

View File

@ -0,0 +1,170 @@
openapi: 3.0.2
components:
paths:
gravity:
post:
summary: Run gravity
tags:
- Actions
operationId: "action_gravity"
description: |
Update Pi-hole's adlists by running `pihole -g`. The output of the process is streamed with chunked encoding.
responses:
'200':
description: OK
content:
text/plain:
schema:
type: string
example: |
[i] Neutrino emissions detected...
[✓] Pulling blocklist source list into range
[i] Preparing new gravity database...
[✓] Preparing new gravity database
[i] Using libz compression
[i] Target: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
[✓] Status: Retrieval successful
[i] Imported 172502 domains, ignoring 3 non-domain entries
Sample of non-domain entries:
- 0.0.0.0
- fe
- ff
[i] List stayed unchanged
[i] Target: https://v.firebog.net/hosts/AdguardDNS.txt
[✓] Status: No changes detected
[i] Imported 47225 domains
[✓] Creating new gravity databases
[✓] Storing downloaded domains in new gravity database
[✓] Building tree
[✓] Swapping databases
[✓] The old database remains available.
[i] Number of gravity domains: 219727 (215440 unique domains)
[i] Number of exact blacklisted domains: 0
[i] Number of regex blacklist filters: 2
[i] Number of exact whitelisted domains: 0
[i] Number of regex whitelist filters: 0
[✓] Cleaning up stray matter
[✓] FTL is listening on port
[✓] UDP (IPv4)
[✓] TCP (IPv4)
[✓] UDP (IPv6)
[✓] TCP (IPv6)
[✓] Pi-hole blocking is enabled
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: 'common.yaml#/components/errors/unauthorized'
restartdns:
post:
summary: Restart pihole-FTL
tags:
- Actions
operationId: "action_restartdns"
description: |
Restarts the pihole-FTL service
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/schemas/success'
- $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'
flush_logs:
post:
summary: Flush the DNS logs
tags:
- Actions
operationId: "action_flushlogs"
description: |
Flushes the DNS logs. This includes emptying the DNS log file and purging the most recent 24 hours from both the database and FTL's internal memory.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/schemas/success'
- $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'
flush_arp:
post:
summary: Flush the network table
tags:
- Actions
operationId: "action_flusharp"
description: |
Flushes the network table. This includes emptying the ARP table and removing both all known devices and their associated addresses.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/schemas/success'
- $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'
'403':
description: Forbidden
content:
application/json:
schema:
allOf:
- $ref: 'action.yaml#/components/errors/forbidden'
errors:
forbidden:
description: |
The request was valid, but the server is refusing the action.
type: object
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "forbidden"
message:
type: string
description: "Human-readable error message"
example: "<Action name> is not allowed"
hint:
type: string
nullable: true
description: "No additional data available"
example: "Check setting webserver.api.allow_destructive"

View File

@ -0,0 +1,584 @@
openapi: 3.0.2
components:
paths:
auth:
get:
summary: Check if authentication is required
tags:
- Authentication
operationId: "get_auth"
security: []
description: |
The API may chose to reply with a valid session if no authentication is needed for this server.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'auth.yaml#/components/schemas/session'
- $ref: 'common.yaml#/components/schemas/took'
examples:
auth_okay:
$ref: 'auth.yaml#/components/examples/auth_okay'
no_login_required:
$ref: 'auth.yaml#/components/examples/no_login_required'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'auth.yaml#/components/schemas/session'
- $ref: 'common.yaml#/components/schemas/took'
examples:
login_required:
$ref: 'auth.yaml#/components/examples/login_required'
login_required_2fa:
$ref: 'auth.yaml#/components/examples/login_required_2fa'
post:
summary: Submit password for login
tags:
- Authentication
operationId: "add_auth"
security: []
description: |
Authenticate using a password. The password isn't stored in the session nor used to create the session token. Instead, the session token is produced using a cryptographically secure random number generator. A CSRF token is utilized to guard against CSRF attacks and is necessary when using Cookie-based authentication. However, it's not needed with other authentication methods.
Both the Session ID (SID) and CSRF token remain valid for the session's duration. The session can be extended before its expiration by performing any authenticated action. By default, the session lasts for 5 minutes. It can be invalidated by either logging out or deleting the session. Additionally, the session becomes invalid when the password is altered or a new application password is created.
If two-factor authentication (2FA) is activated, the Time-based One-Time Password (TOTP) token must be included in the request body. Be aware that the TOTP token, generated by your authenticator app, is only valid for 30 seconds. If the TOTP token is missing, invalid, or has been used previously, the login attempt will be unsuccessful.
requestBody:
description: Callback payload
content:
'application/json':
schema:
$ref: 'auth.yaml#/components/schemas/password'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'auth.yaml#/components/schemas/session'
- $ref: 'common.yaml#/components/schemas/took'
examples:
login_okay:
$ref: 'auth.yaml#/components/examples/login_okay'
no_login_required:
$ref: 'auth.yaml#/components/examples/no_login_required'
'400':
description: Bad Request
content:
application/json:
schema:
allOf:
- $ref: 'auth.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
no_payload:
$ref: 'auth.yaml#/components/examples/errors/no_payload'
no_password:
$ref: 'auth.yaml#/components/examples/errors/no_password'
password_inval:
$ref: 'auth.yaml#/components/examples/errors/password_inval'
totp_missing:
$ref: 'auth.yaml#/components/examples/errors/totp_missing'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
examples:
totp_invalid:
$ref: 'auth.yaml#/components/examples/errors/totp_invalid'
totp_reused:
$ref: 'auth.yaml#/components/examples/errors/totp_reused'
'429':
description: Too Many Requests
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/too_many_requests'
- $ref: 'common.yaml#/components/schemas/took'
examples:
rate_limit:
$ref: 'auth.yaml#/components/examples/errors/rate_limit'
no_seats:
$ref: 'auth.yaml#/components/examples/errors/no_seats'
delete:
summary: Delete session
tags:
- Authentication
operationId: "delete_groups"
description: |
This endpoint can be used to delete the current session. It will
invalidate the session token and the CSRF token. The session can be
extended before its expiration by performing any authenticated action.
By default, the session lasts for 5 minutes. It can be invalidated by
either logging out or deleting the session. Additionally, the session
becomes invalid when the password is altered or a new application
password is created.
You can also delete a session by its ID using the `DELETE /auth/session/{id}` endpoint.
Note that you cannot delete the current session if you have not
authenticated (e.g., no password has been set on your Pi-hole).
responses:
'204':
description: No Content (deleted)
'404':
description: Not Found (no session active)
content:
application/json:
schema:
$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'
session_list:
get:
summary: List of all current sessions
tags:
- Authentication
operationId: "get_auth_sessions"
description: List of all current sessions including their validity and further information about the client such as the IP address and user agent.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'auth.yaml#/components/schemas/sessions_list'
- $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'
totp:
get:
summary: Suggest new TOTP credentials
tags:
- Authentication
operationId: "get_auth_totp"
description: Suggest new TOTP credentials for two-factor authentication (2FA)
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'auth.yaml#/components/schemas/totp'
- $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'
session:
delete:
summary: Delete session by ID
parameters:
- $ref: 'auth.yaml#/components/parameters/id'
tags:
- Authentication
operationId: "delete_auth_session"
description: |
Using this endpoint, a session can be deleted by its ID.
responses:
'204':
description: No Content (deleted)
'404':
description: Not Found (session not found)
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad Request
content:
application/json:
schema:
allOf:
- $ref: 'auth.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
missing_session_id:
$ref: 'auth.yaml#/components/examples/errors/missing_session_id'
session_id_oob:
$ref: 'auth.yaml#/components/examples/errors/no_password'
session_not_in_use:
$ref: 'auth.yaml#/components/examples/errors/session_not_in_use'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
app:
get:
summary: Create new application password
tags:
- Authentication
operationId: "add_app"
description: |
Create a new application password. The generated password is shown only once and cannot be retrieved later - make sure to store it in a safe place. The application password can be used to authenticate against the API instead of the regular password.
It does not require 2FA verification. Generating a new application password will invalidate all currently active sessions.
Note that this endpoint only generates an application password accompanied by its hash. To make this new password effective, the returned `hash` has to be set as `webserver.api.app_password` in the Pi-hole configuration in a follow-up step. This can be done in various ways, e.g. via the API (`PATCH /api/config/webserver/api/app_pwhash`), the graphical web interface (Settings -> All Settings) or by editing the configuration file directly.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'auth.yaml#/components/schemas/app'
- $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:
session:
type: object
required:
- session
properties:
session:
type: object
description: Session object
required:
- valid
- sid
- csrf
- validity
- message
- totp
properties:
valid:
type: boolean
description: Valid session indicator (client is authenticated)
totp:
type: boolean
description: Whether 2FA (TOTP) is enabled on this Pi-hole
sid:
type: string
description: Session ID
nullable: true
csrf:
type: string
description: CSRF token
nullable: true
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
description: Password to be sent to the API
properties:
password:
type: string
description: Password to be used for login
example: abcdef
sessions_list:
type: object
description: List of all current sessions
properties:
sessions:
type: array
items:
type: object
description: Session object
properties:
id:
type: number
description: Session ID
current_session:
type: boolean
description: Indicator if this is the current session
valid:
type: boolean
description: Valid session indicator (existing sessions may be invalid due to timeout)
tls:
type: object
description: TLS (end-to-end encryption) information
properties:
login:
type: boolean
description: Indicator if TLS (end-to-end encryption) has been used during login for this session
mixed:
type: boolean
description: Indicator if TLS (end-to-end encryption) is used only partially for this session
app:
type: boolean
description: Indicator if this session was initiated using an application password
login_at:
type: integer
description: Timestamp of login (seconds since epoch)
last_active:
type: integer
description: Timestamp of last activity (seconds since epoch)
valid_until:
type: integer
description: Timestamp of session expiration (seconds since epoch)
remote_addr:
type: string
description: IP address of the client
user_agent:
type: string
description: User agent of the client
example:
- id: 1
current_session: true
valid: true
tls:
login: true
mixed: false
app: 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"
totp:
type: object
description: TOTP secret suggestion
properties:
totp:
type: object
properties:
type:
type: string
account:
type: string
issuer:
type: string
algorithm:
type: string
digits:
type: integer
period:
type: integer
offset:
type: integer
secret:
type: string
codes:
type: array
items:
type: integer
app:
type: object
description: Application password
properties:
app:
type: object
properties:
password:
type: string
hash:
type: string
example:
password: "7ua0rHPZixX6B3bTa5o4Dv08iyEIm/2q9qgLrF9MNVw="
hash: "$BALLOON-SHA256$v=1$s=1024,t=32$4ERwJD4XucRP4PcDHNLiAg==$kSy0Eou8RUVtK2UTbc5MCpItV8YC3VRuoGhoENxSQ2I="
errors:
bad_request:
type: object
description: Bad request
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
message:
type: string
description: "Human-readable error message"
hint:
type: string
description: "Additional data (if available)"
nullable: true
examples:
auth_okay:
summary: Session valid
value:
session:
valid: true
totp: false
sid: null
csrf: null
validity: 300
message: null
login_okay:
summary: Login successful
value:
session:
valid: true
totp: false
sid: "vFA+EP4MQ5JJvJg+3Q2Jnw="
csrf: "Ux87YTIiMOf/GKCefVIOMw="
validity: 300
message: correct password
no_login_required:
summary: No login required for this client
value:
session:
valid: true
totp: false
sid: null
csrf: null
validity: -1
message: no auth for local user
login_required:
summary: Login required, 2FA disabled
value:
session:
valid: false
totp: false
sid: null
csrf: null
validity: -1
message: password incorrect
login_required_2fa:
summary: Login required, 2FA enabled
value:
session:
valid: false
totp: true
sid: null
csrf: null
validity: -1
message: password incorrect
login_failed:
summary: Login failed
value:
session:
valid: false
totp: false
sid: null
csrf: null
validity: -1
message: no SID provided
errors:
no_payload:
summary: Bad request (no valid JSON payload)
value:
error:
key: "bad_request"
message: "No valid JSON payload found"
hint: null
no_password:
summary: Bad request (no password)
value:
error:
key: "bad_request"
message: "No password found in JSON payload"
hint: null
password_inval:
summary: Bad request (password is not a string)
value:
error:
key: "bad_request"
message: "Field password has to be of type 'string'"
hint: null
totp_missing:
summary: Bad request (2FA token missing)
value:
error:
key: "bad_request"
message: "No 2FA token found in JSON payload"
hint: null
missing_session_id:
summary: Bad request (missing session ID)
value:
error:
key: "bad_request"
message: "Missing or invalid session ID"
hint: null
session_id_oob:
summary: Bad request (session ID out of bounds)
value:
error:
key: "bad_request"
message: "Session ID out of bounds"
hint: null
session_not_in_use:
summary: Bad request (session not in use)
value:
error:
key: "bad_request"
message: "Session ID not in use"
hint: null
totp_invalid:
summary: 2FA token invalid
value:
error:
key: "unauthorized"
message: "Invalid 2FA token"
hint: null
totp_reused:
summary: 2FA token reused
value:
error:
key: "unauthorized"
message: "Reused 2FA token"
hint: "wait for new token"
rate_limit:
summary: Rate limit exceeded
value:
error:
key: "rate_limiting"
message: "Rate-limiting login attempts"
hint: null
no_seats:
summary: No free API seats available
value:
error:
key: "api_seats_exceeded"
message: "API seats exceeded"
hint: "increase webserver.api.max_sessions"
parameters:
id:
in: path
name: id
schema:
type: integer
required: true
description: Session ID
example: 0

View File

@ -0,0 +1,489 @@
openapi: 3.0.2
components:
paths:
client:
summary: Modify client
parameters:
- $ref: 'clients.yaml#/components/parameters/client'
get:
summary: Get clients
tags:
- "Client management"
operationId: "get_clients"
description: |
`{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)
- `/api/clients/my_client` (client identical to `my_client`)
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'clients.yaml#/components/schemas/clients/get'
- $ref: 'common.yaml#/components/schemas/took'
examples:
clients:
$ref: 'clients.yaml#/components/examples/clients'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
put:
summary: Replace client
tags:
- "Client management"
operationId: "replace_client"
description: |
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.
requestBody:
description: Callback payload
content:
application/json:
schema:
allOf:
- $ref: 'clients.yaml#/components/schemas/clients/put'
- $ref: 'common.yaml#/components/schemas/took'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: 'clients.yaml#/components/schemas/clients/get' # identical to GET
- $ref: 'clients.yaml#/components/schemas/lists_processed'
- $ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
item_missing:
$ref: 'clients.yaml#/components/examples/errors/uri_error/item_missing'
no_payload:
$ref: 'clients.yaml#/components/examples/errors/bad_request/no_payload'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
delete:
summary: Delete client
tags:
- "Client management"
operationId: "delete_client"
description: |
*Note:* There will be no content on success. `{client}` is required and needs to be URI-encoded.
responses:
'204':
description: Item deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
item_missing:
$ref: 'clients.yaml#/components/examples/errors/uri_error/item_missing'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
suggestions:
get:
summary: Get client suggestions
tags:
- "Client management"
operationId: "get_client_suggestions"
description: |
Returns a list of unconfigured clients that have been seen by Pi-hole.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'clients.yaml#/components/schemas/suggestions'
- $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'
direct:
post:
summary: Add new client
tags:
- "Client management"
operationId: "add_client"
description: |
Creates a new client in the `clients` object. The `{client}` itself is specified in the request body (POST JSON).
Clients may be described either by their IP addresses (IPv4 and IPv6 are supported),
IP subnets (CIDR notation, like `192.168.2.0/24`), their MAC addresses (like `12:34:56:78:9A:BC`), by their hostnames (like `localhost`), or by the interface they are connected to (prefaced with a colon, like `:eth0`).
Note that client recognition by IP addresses (incl. subnet ranges) is preferred over MAC address, host name or interface recognition as the two latter will only be available after some time.
Furthermore, MAC address recognition only works for devices at most one networking hop away from your Pi-hole.
On success, a new resource is created at `/clients/{client}`.
The `database_error` with message `UNIQUE constraint failed` error indicates that this client already exists.
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: 'clients.yaml#/components/schemas/clients/post'
- $ref: 'common.yaml#/components/schemas/took'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: 'clients.yaml#/components/schemas/clients/get' # identical to GET
- $ref: 'clients.yaml#/components/schemas/lists_processed'
- $ref: 'common.yaml#/components/schemas/took'
headers:
Location:
$ref: 'common.yaml#/components/headers/Location'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/schemas/took'
- $ref: 'common.yaml#/components/errors/bad_request'
examples:
no_payload:
$ref: 'clients.yaml#/components/examples/errors/bad_request/no_payload'
duplicate:
$ref: 'clients.yaml#/components/examples/errors/database_error/duplicate'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/schemas/took'
- $ref: 'common.yaml#/components/errors/unauthorized'
batchDelete:
post:
summary: Delete multiple clients
tags:
- "Client management"
operationId: "batchDelete_clients"
description: |
Deletes multiple clients in the `clients` object. The `{client}`s themselves are specified in the request body (POST JSON).
Clients may be described either by their IP addresses (IPv4 and IPv6 are supported),
IP subnets (CIDR notation, like `192.168.2.0/24`), their MAC addresses (like `12:34:56:78:9A:BC`), by their hostnames (like `localhost`), or by the interface they are connected to (prefaced with a colon, like `:eth0`).</p>
*Note:* There will be no content on success.
requestBody:
description: Callback payload
content:
application/json:
schema:
type: array
items:
type: object
properties:
item:
type: string
description: client IP / MAC / hostname / interface
example:
- "item": "192.168.2.5"
- "item": "::1"
- "item": "12:34:56:78:9A:BC"
- "item": "localhost"
- "item": ":eth0"
responses:
'204':
description: Items deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
no_payload:
$ref: 'clients.yaml#/components/examples/errors/bad_request/no_payload'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
schemas:
clients:
get:
type: object
properties:
clients:
type: array
description: Array of clients
items:
allOf:
- $ref: 'clients.yaml#/components/schemas/client_object'
- $ref: 'clients.yaml#/components/schemas/comment'
- $ref: 'clients.yaml#/components/schemas/groups'
- $ref: 'clients.yaml#/components/schemas/readonly'
post:
allOf:
- $ref: 'clients.yaml#/components/schemas/client_maybe_array'
- $ref: 'clients.yaml#/components/schemas/comment'
- $ref: 'clients.yaml#/components/schemas/groups'
put:
allOf:
- $ref: 'clients.yaml#/components/schemas/comment'
- $ref: 'clients.yaml#/components/schemas/groups'
suggestions:
type: object
properties:
clients:
type: array
items:
type: object
properties:
hwaddr:
type: string
nullable: true
example: "12:34:56:78:9A:BC"
macVendor:
type: string
nullable: true
example: "Espressif Inc."
lastQuery:
type: integer
example: 1683305917
addresses:
type: string
nullable: true
description: Comma-separated list of IP addresses
example: "127.0.0.1,::1"
names:
type: string
nullable: true
description: Comma-separated list of hostnames (if available)
example: "localhost,ip6-localhost"
client:
description: client IP / MAC / hostname / interface
type: string
example: 127.0.0.1
client_array:
description: array of client IPs / MACs / hostnames / interfaces
type: array
items:
type: string
example: ["127.0.0.1", "192.168.2.12"]
client_maybe_array:
type: object
properties:
client:
oneOf:
- $ref: 'clients.yaml#/components/schemas/client'
- $ref: 'clients.yaml#/components/schemas/client_array'
client_object:
type: object
properties:
client:
$ref: 'clients.yaml#/components/schemas/client'
comment:
type: object
properties:
comment:
description: User-provided free-text comment for this client
type: string
nullable: true
default: null
example: Some comment for this client
name:
type: object
properties:
name:
description: hostname (only available when {client} is an IP address)
type: string
readOnly: true
nullable: true
example: localhost
groups:
type: object
properties:
groups:
description: Array of group IDs
type: array
default: [0]
items:
type: integer
readonly:
type: object
properties:
id:
description: Database ID
type: integer
readOnly: true
example: 1
date_added:
description: Unix timestamp of item addition
type: integer
readOnly: true
example: 1611239095
date_modified:
description: Unix timestamp of last item modification
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:
processed:
type: object
nullable: true
description: |
Object containing the number of clients that were successfully
added to the database and the number of clients that could not be
added to the database.
properties:
success:
description: |
Array of clients that were successfully added to the database.
type: array
items:
type: object
properties:
item:
description: Client that was added to the database
type: string
errors:
description: |
Array of errors that occurred during processing.
type: array
items:
type: object
properties:
item:
description: Client that could not be added to the database
type: string
error:
description: Error message
type: string
example:
success:
- item: "127.0.0.1"
- item: "::1"
errors:
- item: "192.168.2.5"
error: "UNIQUE constraint failed: client.ip"
examples:
clients:
value:
clients:
- client: "127.0.0.1"
name: "localhost"
comment: "comment"
id: 1
date_added: 1604871899
date_modified: 1604871899
- client: "::1"
name: "ip6-localhost"
comment: null
id: 2
date_added: 1611322675
date_modified: 1611325497
took: 0.012
processed:
success:
- item: "127.0.0.1"
failed:
- item: "127.0.0.2"
error: "UNIQUE constraint failed: clientlist.client"
errors:
uri_error:
item_missing:
summary: Client to be modified is missing
value:
error:
key: "uri_error"
message: "Invalid request: Specify item in URI"
hint: null
bad_request:
no_payload:
summary: No JSON payload found
value:
error:
key: "bad_request"
message: "Invalid request body data (no valid JSON)"
hint: null
database_error:
duplicate:
summary: Database error
value:
error:
key: "database_error"
message: "Could not add to gravity database"
hint: "UNIQUE constraint failed: clientlist.client"
parameters:
client:
in: path
name: client
schema:
type: string
required: true
description: client IP / MAC / hostname / interface
example: 127.0.0.1

View File

@ -0,0 +1,106 @@
openapi: 3.0.2
components:
schemas:
took:
type: object
properties:
took:
type: number
description: Time in seconds it took to process the request
example: 0.003
success:
type: object
properties:
status:
type: string
description: Key indicating the status of the request
example: "success"
errors:
bad_request:
type: object
description: Bad request
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
message:
type: string
description: "Human-readable error message"
hint:
type: string
description: "Further details"
nullable: true
unauthorized:
type: object
description: Authentication required
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "unauthorized"
message:
type: string
description: "Human-readable error message"
example: "Unauthorized"
hint:
type: string
nullable: true
description: "No additional data available"
example: null
too_many_requests:
type: object
description: "Too many requests (rate limiting)"
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "too_many_requests"
message:
type: string
description: "Human-readable error message"
example: "Too many requests"
hint:
type: string
nullable: true
description: "No additional data available"
example: null
headers:
Location:
description: Location of created resource
schema:
type: string
parameters:
database:
from:
in: query
description: Unix timestamp from when the data should be requested
name: from
schema:
type: integer
required: true
example: 1672580025
until:
in: query
description: Unix timestamp from when the data should be requested
name: until
schema:
type: integer
required: true
example: 1672666425
blocked:
in: query
description: Should this query return only blocked queries?
schema:
type: boolean
required: false
example: false

View File

@ -0,0 +1,835 @@
openapi: 3.0.2
components:
paths:
config:
get:
summary: Get current configuration of your Pi-hole
tags:
- "Pi-hole Configuration"
operationId: "get_config"
description: |
This API hook returns infos about the config of your Pi-hole.
parameters:
- $ref: 'config.yaml#/components/parameters/detailed'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'config.yaml#/components/schemas/config'
- $ref: 'common.yaml#/components/schemas/took'
examples:
config:
$ref: 'config.yaml#/components/examples/config'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
patch:
summary: Change configuration of your Pi-hole
tags:
- "Pi-hole Configuration"
operationId: "patch_config"
description: |
This API hook allows to modify the config of your Pi-hole. This endpoint supports changing multiple properties at once when you specify several in the payload. See examples below.
requestBody:
description: Callback payload
content:
application/json:
schema:
allOf:
- $ref: 'config.yaml#/components/schemas/config'
- $ref: 'common.yaml#/components/schemas/took'
examples:
config_one:
$ref: 'config.yaml#/components/examples/config_one'
config_two:
$ref: 'config.yaml#/components/examples/config_two'
config:
$ref: 'config.yaml#/components/examples/config'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'config.yaml#/components/schemas/config'
- $ref: 'common.yaml#/components/schemas/took'
examples:
config:
$ref: 'config.yaml#/components/examples/config'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
config_elem:
get:
summary: Get specific part of current configuration of your Pi-hole
tags:
- "Pi-hole Configuration"
operationId: "get_config_elem"
description: |
This API hook returns infos about the requested subset of your Pi-hole's configuration.
The response will be a filtered JSON object and a subset of the full `GET /config` response.
parameters:
- $ref: 'config.yaml#/components/parameters/detailed'
- $ref: 'config.yaml#/components/parameters/element'
responses:
'200':
description: OK
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
config_elem_value:
put:
summary: Add config array item
tags:
- "Pi-hole Configuration"
operationId: "add_array_item"
description: |
*Note:* There will be no content on success.
parameters:
- $ref: 'config.yaml#/components/parameters/element'
- $ref: 'config.yaml#/components/parameters/value'
responses:
'201':
description: Item created
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
invalid_path_depth:
$ref: 'config.yaml#/components/examples/errors/bad_request/invalid_path_depth'
item_already_present:
$ref: 'config.yaml#/components/examples/errors/bad_request/item_already_present'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
delete:
summary: Delete config array item
tags:
- "Pi-hole Configuration"
operationId: "delete_array_item"
description: |
*Note:* There will be no content on success.
parameters:
- $ref: 'config.yaml#/components/parameters/element'
- $ref: 'config.yaml#/components/parameters/value'
responses:
'204':
description: Item deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
invalid_path_depth:
$ref: 'config.yaml#/components/examples/errors/bad_request/invalid_path_depth'
item_already_present:
$ref: 'config.yaml#/components/examples/errors/bad_request/item_already_present'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
schemas:
config:
type: object
properties:
config:
type: object
properties:
dns:
type: object
properties:
upstreams:
type: array
items:
type: string
CNAMEdeepInspect:
type: boolean
blockESNI:
type: boolean
EDNS0ECS:
type: boolean
ignoreLocalhost:
type: boolean
showDNSSEC:
type: boolean
analyzeOnlyAandAAAA:
type: boolean
piholePTR:
type: string
replyWhenBusy:
type: string
blockTTL:
type: integer
hosts:
type: array
items:
type: string
domainNeeded:
type: boolean
expandHosts:
type: boolean
domain:
type: string
bogusPriv:
type: boolean
dnssec:
type: boolean
interface:
type: string
hostRecord:
type: string
listeningMode:
type: string
queryLogging:
type: boolean
cnameRecords:
type: array
items:
type: string
port:
type: integer
cache:
type: object
properties:
size:
type: integer
optimizer:
type: integer
revServers:
type: array
items:
type: string
blocking:
type: object
properties:
active:
type: boolean
mode:
type: string
specialDomains:
type: object
properties:
mozillaCanary:
type: boolean
iCloudPrivateRelay:
type: boolean
reply:
type: object
properties:
host:
type: object
properties:
force4:
type: boolean
force6:
type: boolean
IPv4:
type: string
x-format: ipv4
IPv6:
type: string
x-format: ipv6
blocking:
type: object
properties:
force4:
type: boolean
force6:
type: boolean
IPv4:
type: string
x-format: ipv4
IPv6:
type: string
x-format: ipv6
rateLimit:
type: object
properties:
count:
type: integer
interval:
type: integer
dhcp:
type: object
properties:
active:
type: boolean
start:
type: string
x-format: ipv4
end:
type: string
x-format: ipv4
router:
type: string
x-format: ipv4
netmask:
type: string
x-format: ipv4
leaseTime:
type: string
ipv6:
type: boolean
rapidCommit:
type: boolean
multiDNS:
type: boolean
logging:
type: boolean
hosts:
type: array
items:
type: string
resolver:
type: object
properties:
resolveIPv4:
type: boolean
resolveIPv6:
type: boolean
networkNames:
type: boolean
refreshNames:
type: string
database:
type: object
properties:
DBimport:
type: boolean
maxDBdays:
type: integer
DBinterval:
type: integer
useWAL:
type: boolean
network:
type: object
properties:
parseARPcache:
type: boolean
expire:
type: integer
webserver:
type: object
properties:
domain:
type: string
acl:
type: string
port:
type: string
session:
type: object
properties:
timeout:
type: integer
restore:
type: boolean
tls:
type: object
properties:
rev_proxy:
type: boolean
cert:
type: string
paths:
type: object
properties:
webroot:
type: string
webhome:
type: string
interface:
type: object
properties:
boxed:
type: boolean
theme:
type: string
api:
type: object
properties:
localAPIauth:
type: boolean
searchAPIauth:
type: boolean
max_sessions:
type: integer
prettyJSON:
type: boolean
password:
description: |
*Note:* Special write-only property used to change the password via the API.
type: string
pwhash:
type: string
totp_secret:
type: string
app_pwhash:
type: string
excludeClients:
type: array
items:
type: string
excludeDomains:
type: array
items:
type: string
maxHistory:
type: integer
maxClients:
type: integer
client_history_global_max:
type: boolean
allow_destructive:
type: boolean
temp:
type: object
properties:
limit:
type: number
unit:
type: string
files:
type: object
properties:
pid:
type: string
database:
type: string
gravity:
type: string
gravity_tmp:
type: string
macvendor:
type: string
setupVars:
type: string
pcap:
type: string
log:
type: object
properties:
ftl:
type: string
dnsmasq:
type: string
webserver:
type: string
misc:
type: object
properties:
nice:
type: integer
delay_startup:
type: integer
addr2line:
type: boolean
etc_dnsmasq_d:
type: boolean
privacylevel:
type: integer
dnsmasq_lines:
type: array
items:
type: string
extraLogging:
type: boolean
check:
type: object
properties:
load:
type: boolean
shmem:
type: integer
disk:
type: integer
debug:
type: object
properties:
database:
type: boolean
networking:
type: boolean
locks:
type: boolean
queries:
type: boolean
flags:
type: boolean
shmem:
type: boolean
gc:
type: boolean
arp:
type: boolean
regex:
type: boolean
api:
type: boolean
tls:
type: boolean
overtime:
type: boolean
status:
type: boolean
caps:
type: boolean
dnssec:
type: boolean
vectors:
type: boolean
resolver:
type: boolean
edns0:
type: boolean
clients:
type: boolean
aliasclients:
type: boolean
events:
type: boolean
helper:
type: boolean
config:
type: boolean
inotify:
type: boolean
webserver:
type: boolean
extra:
type: boolean
reserved:
type: boolean
all:
type: boolean
topics:
type: object
properties:
topics:
type: array
items:
type: object
properties:
name:
type: string
description: The name of the topic
title:
type: string
description: The tab title of the topic
description:
type: string
description: A human-readable description of the topic
server:
type: object
properties:
server:
type: array
items:
type: object
properties:
name:
type: string
description: Human-readable name of this server
v4:
type: array
description: Array of IPv4 addresses (if any)
items:
type: string
v6:
type: array
description: Array of IPv6 addresses (if any)
items:
type: string
examples:
config:
summary: The entire configuration
value:
config:
dns:
upstreams: [ "127.0.0.1#5353", "8.8.8.8" ]
CNAMEdeepInspect: true
blockESNI: true
EDNS0ECS: true
ignoreLocalhost: false
showDNSSEC: true
analyzeOnlyAandAAAA: false
piholePTR: PI.HOLE
replyWhenBusy: ALLOW
blockTTL: 2
hosts:
- "192.168.2.123 mymusicbox"
domainNeeded: true
expandHosts: true
domain: "lan"
bogusPriv: true
dnssec: true
interface: "eth0"
hostRecord: ""
listeningMode: "local"
queryLogging: true
cnameRecords:
- "*.example.com,default.example.com"
- "hourly.yetanother.com,yetanother.com,3600"
port: 53
cache:
size: 10000
optimizer: 3600
revServers:
- "true,192.168.0.0/24,192.168.0.1,lan"
blocking:
active: true
mode: 'NULL'
specialDomains:
mozillaCanary: true
iCloudPrivateRelay: true
reply:
host:
force4: false
force6: false
IPv4: 0.0.0.0
IPv6: "::"
blocking:
force4: false
force6: false
IPv4: 0.0.0.0
IPv6: "::"
rateLimit:
count: 0
interval: 0
dhcp:
active: false
start: "192.168.0.10"
end: "192.168.0.250"
router: "192.168.0.1"
netmask: "0.0.0.0"
leaseTime: "24h"
ipv6: false
rapidCommit: false
multiDNS: false
logging: false
hosts:
- "11:22:33:44:55:66,192.168.1.123"
- "11:22:33:44:55:67,192.168.1.124,hostname"
resolver:
resolveIPv4: true
resolveIPv6: true
networkNames: true
refreshNames: IPV4_ONLY
database:
DBimport: true
maxDBdays: 365
DBinterval: 60
useWAL: true
network:
parseARPcache: true
expire: 365
webserver:
domain: pi.hole
acl: "+0.0.0.0/0,::/0"
port: 80,[::]:80
session:
timeout: 300
restore: true
tls:
rev_proxy: false
cert: "/etc/pihole/tls.pem"
paths:
webroot: "/var/www/html"
webhome: "/admin/"
interface:
boxed: true
theme: "default-darker"
api:
localAPIauth: false
searchAPIauth: false
max_sessions: 16
prettyJSON: false
password: "********"
pwhash: ''
totp_secret: ''
app_pwhash: ''
excludeClients: [ '1\.2\.3\.4', 'localhost', 'fe80::345' ]
excludeDomains: [ 'google\\.de', 'pi-hole\.net' ]
maxHistory: 86400
maxClients: 10
client_history_global_max: true
allow_destructive: true
temp:
limit: 60.0
unit: "C"
files:
pid: "/run/pihole-FTL.pid"
database: "/etc/pihole/pihole-FTL.db"
gravity: "/etc/pihole/gravity.db"
gravity_tmp: "/tmp"
macvendor: "/etc/pihole/macvendor.db"
setupVars: "/etc/pihole/setupVars.conf"
pcap: ""
log:
ftl: "/var/log/pihole/FTL.log"
dnsmasq: "/var/log/pihole/pihole.log"
webserver: "/var/log/pihole/webserver.log"
misc:
nice: -10
delay_startup: 10
addr2line: true
privacylevel: 0
etc_dnsmasq_d: false
dnsmasq_lines: [ ]
extraLogging: false
check:
load: true
shmem: 90
disk: 90
debug:
database: false
networking: false
locks: false
queries: false
flags: false
shmem: false
gc: false
arp: false
regex: false
api: false
tls: false
overtime: false
status: false
caps: false
dnssec: false
vectors: false
resolver: false
edns0: false
clients: false
aliasclients: false
events: false
helper: false
config: false
inotify: false
webserver: false
extra: false
reserved: false
all: false
config_one:
summary: One option
value:
config:
dns:
CNAMEdeepInspect: true
config_two:
summary: Two options
value:
config:
dns:
specialDomains:
mozillaCanary: true
misc:
nice: -10
topics:
summary: All topics
value:
topics:
- name: dns
title: DNS
description: DNS settings
- name: dhcp
title: DHCP
description: DHCP settings
server:
summary: Several servers being suggested
value:
server:
- name: Google (ECS, DNSSEC)
v4: ["8.8.8.8", "8.8.4.4"]
v6: ["2001:4860:4860:0:0:0:0:8888","2001:4860:4860:0:0:0:0:8844"]
- name: OpenDNS (ECS, DNSSEC)
v4: ["208.67.222.222", "208.67.220.220"]
v6: ["2620:119:35::35","2620:119:53::53"]
errors:
bad_request:
invalid_path_depth:
summary: Invalid path
value:
error:
key: "bad_request"
message: "Invalid path depth"
hint: "Use, e.g., DELETE /config/dnsmasq/upstreams/127.0.0.1 to remove \"127.0.0.1\" from config.dns.upstreams"
item_already_present:
summary: Item to be added exists already
value:
error:
key: "bad_request"
message: "Item already present"
hint: "Uniqueness of items is enforced"
parameters:
detailed:
name: detailed
in: query
description: Return detailed information about the configuration
required: false
schema:
type: boolean
default: false
example: false
element:
in: path
name: element
schema:
type: string
required: true
description: config element
example: "dnsmasq/upstreams"
value:
in: path
name: value
schema:
type: string
required: true
description: config value
example: "8.8.8.8"

View File

@ -0,0 +1,115 @@
openapi: 3.0.2
components:
paths:
leases:
get:
summary: Get currently active DHCP leases
tags:
- "DHCP"
operationId: "get_dhcp"
description: |
This API hook returns the currently active DHCP leases.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'dhcp.yaml#/components/schemas/leases'
- $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'
leases_ip:
parameters:
- $ref: 'dhcp.yaml#/components/parameters/ip'
delete:
summary: Remove DHCP lease
tags:
- "DHCP"
operationId: "delete_dhcp"
description: |
This API hook removes a currently active DHCP lease.
Managing DHCP leases is only possible when the DHCP server is enabled.
*Note:* There will be no content on success.
responses:
'204':
description: Item deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
item_missing:
$ref: 'dhcp.yaml#/components/examples/errors/uri_error/item_missing'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: 'common.yaml#/components/errors/unauthorized'
schemas:
leases:
type: object
properties:
leases:
type: array
description: DHCP leases
items:
type: object
properties:
expires:
type: integer
description: Expiration time (0 = infinite lease, never expires)
example: 1675671991
name:
type: string
description: Hostname
example: "raspberrypi"
hwaddr:
type: string
description: Hardware (MAC) address
example: "00:00:00:00:00:00"
ip:
type: string
description: IP address
example: "192.168.2.111"
clientid:
type: string
description: Client ID
example: "00:00:00:00:00:00"
examples:
errors:
uri_error:
item_missing:
summary: DHCP lease to be deleted not specified accurately enough
value:
error:
key: "uri_error"
message: "No ip in URI"
hint: null
parameters:
ip:
in: path
name: ip
schema:
type: string
required: true
description: IP address of lease to be modified
example: 192.168.2.222

View File

@ -0,0 +1,142 @@
openapi: 3.0.2
components:
paths:
blocking:
summary: Modify blocking status
get:
summary: Get current blocking status
tags:
- DNS control
operationId: "get_blocking"
description: |
The property `timer` may contain additional details concerning a temporary en-/disabling.
It is `null` when no timer is active (the current status is permanent).
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'dns.yaml#/components/schemas/blocking'
- $ref: 'dns.yaml#/components/schemas/timer'
- $ref: 'common.yaml#/components/schemas/took'
post:
summary: Change current blocking status
tags:
- DNS control
operationId: "set_blocking"
description: |
Change the current blocking mode by setting `blocking` to the desired value.
The optional `timer` object may used to set a timer. Once this timer elapsed, the opposite blocking mode is automatically set.
For instance, you can request `{blocking: false, timer: 60}` to disable Pi-hole for one minute.
Blocking will be automatically resumed afterwards.
You can terminate a possibly running timer by setting `timer` to `null` (the set mode becomes permanent).
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: 'dns.yaml#/components/schemas/blocking_bool'
- $ref: 'dns.yaml#/components/schemas/timer'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'dns.yaml#/components/schemas/blocking'
- $ref: 'dns.yaml#/components/schemas/timer'
- $ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- oneOf:
- $ref: 'dns.yaml#/components/schemas/errors/no_payload'
- $ref: 'dns.yaml#/components/schemas/errors/item_missing'
- $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:
blocking:
type: object
properties:
blocking:
type: string
description: Blocking status
enum:
- "enabled"
- "disabled"
- "failed"
- "unknown"
example: "enabled"
blocking_bool:
type: object
properties:
blocking:
type: boolean
description: Blocking status
default: true
example: true
timer:
type: object
properties:
timer:
type: number
description: Remaining seconds until blocking mode is automatically changed
nullable: true
example: 15.0
errors:
item_missing:
type: object
description: Item to be modified is missing
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "body_error"
message:
type: string
description: "Human-readable error message"
example: "No \\\"blocking\\\" boolean in body data"
hint:
type: string
nullable: true
description: "Additional data (if available)"
example: null
no_payload:
type: object
description: No JSON payload found
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "bad_request"
message:
type: string
description: "Human-readable error message"
example: "Invalid request body data (no valid JSON)"
hint:
type: string
nullable: true
description: "Additional data (if available)"
example: null

View File

@ -0,0 +1,29 @@
openapi: 3.0.2
components:
paths:
docs:
summary: Pi-hole's API documentation
get:
summary: Get the embedded API documentation rendered as HTML
tags:
- "Documentation"
operationId: "get_docs"
security: []
description: |
This API hook returns the embedded API documentation rendered as HTML.
responses:
'200':
description: OK
content:
text/html:
schema:
description: HTML document
type: string
example: |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>API Documentation</title>
</body>
</html>

View File

@ -0,0 +1,545 @@
openapi: 3.0.2
components:
paths:
type_kind_domain:
summary: Modify domains and regular expressions
parameters:
- $ref: 'domains.yaml#/components/parameters/type'
- $ref: 'domains.yaml#/components/parameters/kind'
- $ref: 'domains.yaml#/components/parameters/domain'
get:
summary: Get domain
tags:
- "Domain management"
operationId: "get_domains"
description: |
`{type}`, `{kind}`, and `{domain}` are optional. Specifying any of these may result in only a subset of the available data being returned.
Valid combinations are:
- `/api/domains` (all domains)
- `/api/domains/abc.com` (all domains identical to `abc.com`)
- `/api/domains/allow` (only allowed domains)
- `/api/domains/allow/abc.com` (only allowed domains identical to `abc.com`)
- `/api/domains/allow/exact` (only exactly allowed domains)
- `/api/domains/allow/exact/abc.com` (only exactly allowed domain identical to `abc.com`)
- `/api/domains/allow/regex` (only allowed regex domains)
- `/api/domains/allow/regex/abc.com` (only allowed regex domains identical to `abc.com`)
- `/api/domains/deny` (only denied domains)
- `/api/domains/deny/abc.com` (only denied domains identical to `abc.com`)
- `/api/domains/deny/exact` (only exactly denied domains)
- `/api/domains/deny/exact/abc.com` (only exactly denied domain identical to `abc.com`)
- `/api/domains/deny/regex` (only denied regex domains)
- `/api/domains/deny/regex/abc.com` (only denied regex domains identical to `abc.com`)
- `/api/domains/exact` (allowed and denied exact domains)
- `/api/domains/exact/abc.com` (allowed and denied exact domains identical to `abc.com`)
- `/api/domains/regex` (allowed and denied regex domains)
- `/api/domains/regex/abc.com` (allowed and denied regex domains identical to `abc.com`)
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'domains.yaml#/components/schemas/domains/get'
- $ref: 'common.yaml#/components/schemas/took'
examples:
domains:
$ref: 'domains.yaml#/components/examples/domains'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
put:
summary: Replace domain
tags:
- "Domain management"
operationId: "replace_domain"
description: |
Items may be updated by replacing them. `{type}`, `{kind}`, and `{domain}` are required.
Ensure to send all the required parameters (such as `comment`) to ensure these properties are retained.
The read-only fields `id` and `date_added` are preserved, `date_modified` is automatically updated on success.
You can move existing domains to another list type/kind by `PUT`ting the domain to the new destination by specifying the optional fields `type` and `kind`.
Example:
Use `PUT allow/exact/abc.com` with `type="deny", kind="exact"` to change `abc.com` from exact denied to exact allowed. Make sure to always specify *both* values.
When adding/replacing a regular expression, ensure that `{domain}` is properly URI-escaped.
requestBody:
description: Callback payload
content:
'application/json':
schema:
allOf:
- $ref: 'domains.yaml#/components/schemas/domains/put'
- $ref: 'common.yaml#/components/schemas/took'
responses:
'201':
description: Created domain
content:
application/json:
schema:
allOf:
- $ref: 'domains.yaml#/components/schemas/domains/get' # identical to GET
- $ref: 'domains.yaml#/components/schemas/lists_processed'
- $ref: 'common.yaml#/components/schemas/took'
examples:
domains:
$ref: 'domains.yaml#/components/examples/domains'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
list_imprecise:
$ref: 'domains.yaml#/components/examples/errors/uri_error/list_imprecise'
item_missing:
$ref: 'domains.yaml#/components/examples/errors/uri_error/item_missing'
no_payload:
$ref: 'domains.yaml#/components/examples/errors/bad_request/no_payload'
duplicate:
$ref: 'domains.yaml#/components/examples/errors/database_error/duplicate'
invalid_regex:
$ref: 'domains.yaml#/components/examples/errors/regex_error/invalid_regex'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
delete:
summary: Delete domain
tags:
- "Domain management"
operationId: "delete_domain"
description: |
*Note:* There will be no content on success.
responses:
'204':
description: Item deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
list_imprecise:
$ref: 'domains.yaml#/components/examples/errors/uri_error/list_imprecise'
item_missing:
$ref: 'domains.yaml#/components/examples/errors/uri_error/item_missing'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
type_kind:
parameters:
- $ref: 'domains.yaml#/components/parameters/type'
- $ref: 'domains.yaml#/components/parameters/kind'
post:
summary: Add new domain
tags:
- "Domain management"
operationId: "add_domain"
description: |
Creates a new domain in the `domains` object. This may be either an exact domain or a regex, depending on `{kind}`.
Both `{type}` and `{kind}` are mandatory for this endpoint.
The `{domain}` itself is specified in the request body (POST JSON).
On success, a new resource is created at `/domains/{type}/{kind}/{domain}`.
The `database_error` with message `UNIQUE constraint failed` error indicates that the same entry (`domain`, `type`, `kind`) already exists.
When adding a regular expression, ensure the request body is properly JSON-escaped.
requestBody:
description: Callback payload
content:
application/json:
schema:
$ref: 'domains.yaml#/components/schemas/domains/post'
responses:
'201':
description: Created domain
content:
application/json:
schema:
allOf:
- $ref: 'domains.yaml#/components/schemas/domains/get' # identical to GET
- $ref: 'domains.yaml#/components/schemas/lists_processed'
- $ref: 'common.yaml#/components/schemas/took'
headers:
Location:
$ref: 'common.yaml#/components/headers/Location'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
list_imprecise:
$ref: 'domains.yaml#/components/examples/errors/uri_error/list_imprecise'
no_payload:
$ref: 'domains.yaml#/components/examples/errors/bad_request/no_payload'
duplicate:
$ref: 'domains.yaml#/components/examples/errors/database_error/duplicate'
invalid_regex:
$ref: 'domains.yaml#/components/examples/errors/regex_error/invalid_regex'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
batchDelete:
summary: Delete multiple domains
post:
summary: Delete multiple domains
tags:
- "Domain management"
operationId: "batchDelete_domains"
description: |
*Note:* There will be no content on success.
requestBody:
description: Callback payload
content:
application/json:
schema:
type: array
items:
type: object
properties:
item:
type: string
description: Domain to delete
example: "example.com"
type:
type: string
description: Type of domain to delete
enum:
- "allow"
- "deny"
example: "allow"
kind:
type: string
description: Kind of domain to delete
enum:
- "exact"
- "regex"
example: "exact"
responses:
'204':
description: Items deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
no_payload:
$ref: 'domains.yaml#/components/examples/errors/bad_request/no_payload'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
schemas:
domains:
get:
type: object
properties:
domains:
type: array
description: Array of domains
items:
allOf:
- $ref: 'domains.yaml#/components/schemas/domain_object'
- $ref: 'domains.yaml#/components/schemas/unicode'
- $ref: 'domains.yaml#/components/schemas/type'
- $ref: 'domains.yaml#/components/schemas/kind'
- $ref: 'domains.yaml#/components/schemas/comment'
- $ref: 'domains.yaml#/components/schemas/groups'
- $ref: 'domains.yaml#/components/schemas/enabled'
- $ref: 'domains.yaml#/components/schemas/readonly'
put:
allOf:
- $ref: 'domains.yaml#/components/schemas/type'
- $ref: 'domains.yaml#/components/schemas/kind'
- $ref: 'domains.yaml#/components/schemas/comment'
- $ref: 'domains.yaml#/components/schemas/groups'
- $ref: 'domains.yaml#/components/schemas/enabled'
post:
allOf:
- $ref: 'domains.yaml#/components/schemas/domain_maybe_array'
- $ref: 'domains.yaml#/components/schemas/comment'
- $ref: 'domains.yaml#/components/schemas/groups'
- $ref: 'domains.yaml#/components/schemas/enabled'
domain:
description: Domain
type: string
example: testdomain.com
unicode:
type: object
properties:
unicode:
description: Unicode domain (may be different from `domain` if punycode-encoding is used)
type: string
example: "äbc.com"
domain_array:
description: array of domains
type: array
items:
type: string
example: ["testdomain.com", "otherdomain.de"]
domain_maybe_array:
type: object
properties:
domain:
oneOf:
- $ref: 'domains.yaml#/components/schemas/domain'
- $ref: 'domains.yaml#/components/schemas/domain_array'
domain_object:
type: object
properties:
domain:
$ref: 'domains.yaml#/components/schemas/domain'
type:
type: object
properties:
type:
description: String specifying domain type
type: string
enum:
- "allow"
- "deny"
example: "allow"
kind:
type: object
properties:
kind:
description: String specifying domain kind
type: string
enum:
- "exact"
- "regex"
example: "exact"
comment:
type: object
properties:
comment:
description: User-provided free-text comment for this domain
type: string
nullable: true
default: null
example: Some comment describing this domain
groups:
type: object
properties:
groups:
description: Array of group IDs
type: array
items:
type: integer
enabled:
type: object
properties:
enabled:
description: Status of domain
type: boolean
default: true
readonly:
type: object
properties:
id:
description: Database ID
type: integer
readOnly: true
date_added:
description: Unix timestamp of domain addition
type: integer
readOnly: true
date_modified:
description: Unix timestamp of last domain modification
type: integer
readOnly: true
lists_processed:
type: object
properties:
processed:
type: object
nullable: true
description: |
Object containing the number of domains that were successfully
added to the database and the number of domains that could not be
added to the database.
properties:
success:
description: |
Array of domains that were successfully added to the database.
type: array
items:
type: object
properties:
item:
description: Domain that was added to the database
type: string
errors:
description: |
Array of errors that occurred during processing.
type: array
items:
type: object
properties:
item:
description: Domain that could not be added to the database
type: string
error:
description: Error message
type: string
example:
success:
- item: "example.com"
- item: "example3.com"
errors:
- item: "example2.com"
error: "UNIQUE constraint failed: domainlist.domain"
examples:
domains:
summary: Example domains
value:
domains:
- domain: "allowed.com"
unicode: "allowed.com"
type: allow
kind: exact
comment: null
groups:
- 0
enabled: true
id: 299
date_added: 1611239095
date_modified: 1612163756
- domain: "xn--4ca.com"
unicode: "ä.com"
type: allow
kind: regex
comment: "Some text"
groups:
- 0
enabled: true
id: 305
date_added: 1611240635
date_modified: 1611241276
took: 0.012
processed: null
errors:
uri_error:
list_imprecise:
summary: List not specified precisely enough
value:
error:
key: "uri_error"
message: "Invalid request: Specify list to modify more precisely"
hint: null
item_missing:
summary: Domain to be modified is missing
value:
error:
key: "uri_error"
message: "Invalid request: Specify item in URI"
hint: null
bad_request:
no_payload:
summary: No JSON payload found
value:
error:
key: "bad_request"
message: "Invalid request body data (no valid JSON)"
hint: null
database_error:
duplicate:
summary: Database error
value:
error:
key: "database_error"
message: "Could not add to gravity database"
hint: "UNIQUE constraint failed: domainlist.domain, domainlist.type"
regex_error:
invalid_regex:
summary: Invalid regex rejected
value:
error:
key: "regex_error"
message: "Regex validation failed"
hint: "Missing ']'"
parameters:
type:
in: path
name: type
description: Type (allowed or denied domain)
schema:
type: string
enum:
- allow
- deny
required: true
example: allow
kind:
in: path
name: kind
description: Kind (exact domain or regular expression)
schema:
type: string
enum:
- exact
- regex
required: true
example: exact
domain:
in: path
name: domain
description: Domain
schema:
type: string
required: true
example: testdomain.com

View File

@ -0,0 +1,154 @@
openapi: 3.0.2
components:
paths:
endpoints:
get:
summary: Get list of available API endpoints
tags:
- "FTL information"
operationId: "get_endpoints"
description: |
This API hook returns the list of all available API endpoints.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'endpoints.yaml#/components/schemas/endpoints'
- $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:
endpoints:
type: object
properties:
endpoints:
type: object
description: Endpoints
properties:
get:
type: array
items:
type: object
properties:
uri:
type: string
description: URI
parameters:
type: string
description: Parameters
example:
- uri: "/api/dns/blocking"
parameters: ''
- uri: "/api/dns/cache"
parameters: ''
- uri: "/api/dns/port"
parameters: ''
- uri: "/api/domains"
parameters: ''
- uri: "/api/groups"
parameters: ''
- uri: "/api/lists"
parameters: ''
post:
type: array
items:
type: object
properties:
uri:
type: string
description: URI
parameters:
type: string
description: Parameters
example:
- uri: "/api/dns/blocking"
parameters: ''
- uri: "/api/dns/cache"
parameters: ''
- uri: "/api/dns/port"
parameters: ''
- uri: "/api/domains"
parameters: ''
- uri: "/api/groups"
parameters: ''
- uri: "/api/lists"
parameters: ''
put:
type: array
items:
type: object
properties:
uri:
type: string
description: URI
parameters:
type: string
description: Parameters
example:
- uri: "/api/dns/blocking"
parameters: ''
- uri: "/api/dns/cache"
parameters: ''
- uri: "/api/dns/port"
parameters: ''
- uri: "/api/domains"
parameters: ''
- uri: "/api/groups"
parameters: ''
- uri: "/api/lists"
parameters: ''
patch:
type: array
items:
properties:
uri:
type: string
description: URI
parameters:
type: string
description: Parameters
example:
- uri: "/api/dns/blocking"
parameters: ''
- uri: "/api/dns/cache"
parameters: ''
- uri: "/api/dns/port"
parameters: ''
- uri: "/api/domains"
parameters: ''
- uri: "/api/groups"
parameters: ''
- uri: "/api/lists"
parameters: ''
delete:
type: array
items:
properties:
uri:
type: string
description: URI
parameters:
type: string
description: Parameters
example:
- uri: "/api/dns/blocking"
parameters: ''
- uri: "/api/dns/cache"
parameters: ''
- uri: "/api/dns/port"
parameters: ''
- uri: "/api/domains"
parameters: ''
- uri: "/api/groups"
parameters: ''
- uri: "/api/lists"
parameters: ''

View File

@ -0,0 +1,408 @@
openapi: 3.0.2
components:
paths:
name:
summary: Modify group
parameters:
- $ref: 'groups.yaml#/components/parameters/name'
get:
summary: Get groups
tags:
- "Group management"
operationId: "get_groups"
description: |
`{name}` is optional. Specifying it will result in only the requested group being returned.
Valid combinations are:
- `/api/groups` (all groups)
- `/api/groups/my_group` (group identical to `my_group`)
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'groups.yaml#/components/schemas/groups/get'
- $ref: 'common.yaml#/components/schemas/took'
examples:
groups:
$ref: 'groups.yaml#/components/examples/groups'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
put:
summary: Replace group
tags:
- "Group management"
operationId: "replace_group"
description: |
Items may be updated by replacing them. `{name}` is required.
Ensure to send all the required parameters (such as `comment`) to ensure these properties are retained.
By specifying a different `name`, the group with the former name as specified in the URI will be renamed.
The read-only fields `id` and `date_added` are preserved, `date_modified` is automatically updated on success.
requestBody:
description: Callback payload
content:
application/json:
schema:
$ref: 'groups.yaml#/components/schemas/groups/put'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: 'groups.yaml#/components/schemas/groups/get' # identical to GET
- $ref: 'groups.yaml#/components/schemas/lists_processed'
- $ref: 'common.yaml#/components/schemas/took'
headers:
Location:
$ref: 'common.yaml#/components/headers/Location'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
item_missing:
$ref: 'groups.yaml#/components/examples/errors/uri_error/item_missing'
no_payload:
$ref: 'groups.yaml#/components/examples/errors/bad_request/no_payload'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
delete:
summary: Delete group
tags:
- "Group management"
operationId: "delete_group"
description: |
*Note:* There will be no content on success.
responses:
'204':
description: Item deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
item_missing:
$ref: 'groups.yaml#/components/examples/errors/uri_error/item_missing'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
direct:
post:
summary: Add new group
tags:
- "Group management"
operationId: "add_group"
description: |
Creates a new group in the `groups` object. The `{group}` itself is specified in the request body (POST JSON).
On success, a new resource is created at `/groups/{name}`.
The `database_error` with message `UNIQUE constraint failed` error indicates that a group with the same name already exists.
requestBody:
description: Callback payload
content:
application/json:
schema:
$ref: 'groups.yaml#/components/schemas/groups/post'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: 'groups.yaml#/components/schemas/groups/get' # identical to GET
- $ref: 'groups.yaml#/components/schemas/lists_processed'
- $ref: 'common.yaml#/components/schemas/took'
headers:
Location:
$ref: 'common.yaml#/components/headers/Location'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
no_payload:
$ref: 'groups.yaml#/components/examples/errors/bad_request/no_payload'
duplicate:
$ref: 'groups.yaml#/components/examples/errors/database_error/duplicate'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
batchDelete:
post:
summary: Delete multiple groups
tags:
- "Group management"
operationId: "batchDelete_groups"
description: |
Deletes multiple groups in the `groups` object. The `{groups}` themselves are specified in the request body (POST JSON).
On success, a new resource is created at `/groups/{name}`.
The `database_error` with message `UNIQUE constraint failed` error indicates that a group with the same name already exists.
requestBody:
description: Callback payload
content:
application/json:
schema:
type: array
items:
type: object
properties:
item:
type: string
description: group name
example:
- "item": "test1"
- "item": "test2"
responses:
'204':
description: Items deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
no_payload:
$ref: 'groups.yaml#/components/examples/errors/bad_request/no_payload'
duplicate:
$ref: 'groups.yaml#/components/examples/errors/database_error/duplicate'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
schemas:
groups:
get:
type: object
properties:
groups:
type: array
items:
allOf:
- $ref: 'groups.yaml#/components/schemas/name_object'
- $ref: 'groups.yaml#/components/schemas/comment'
- $ref: 'groups.yaml#/components/schemas/enabled'
- $ref: 'groups.yaml#/components/schemas/readonly'
put:
allOf:
# Can rename group
- $ref: 'groups.yaml#/components/schemas/name_object'
- $ref: 'groups.yaml#/components/schemas/comment'
- $ref: 'groups.yaml#/components/schemas/enabled'
post:
allOf:
- $ref: 'groups.yaml#/components/schemas/name_maybe_array'
- $ref: 'groups.yaml#/components/schemas/comment'
- $ref: 'groups.yaml#/components/schemas/enabled'
name:
description: Group name
type: string
example: test_group
name_array:
description: array of group names
type: array
items:
type: string
example: ["test1", "test2", "test3"]
name_maybe_array:
type: object
properties:
name:
oneOf:
- $ref: 'groups.yaml#/components/schemas/name'
- $ref: 'groups.yaml#/components/schemas/name_array'
name_object:
type: object
properties:
name:
$ref: 'groups.yaml#/components/schemas/name'
comment:
type: object
properties:
comment:
description: User-provided free-text comment for this group
type: string
nullable: true
default: null
example: Some comment for this group
enabled:
type: object
properties:
enabled:
description: Status of item
type: boolean
default: true
example: true
readonly:
type: object
properties:
id:
description: Database ID
type: integer
readOnly: true
example: 1
date_added:
description: Unix timestamp of item addition
type: integer
readOnly: true
example: 1611239095
date_modified:
description: Unix timestamp of last item modification
type: integer
readOnly: true
example: 1611239099
lists_processed:
type: object
properties:
processed:
type: object
nullable: true
description: |
Object containing the number of groups that were successfully
added to the database and the number of groups that could not be
added to the database.
properties:
success:
description: |
Array of groups that were successfully added to the database.
type: array
items:
type: object
properties:
item:
description: Group that was added to the database
type: string
errors:
description: |
Array of errors that occurred during processing.
type: array
items:
type: object
properties:
item:
description: Group that could not be added to the database
type: string
error:
description: Error message
type: string
example:
success:
- item: "Home-Automation"
- item: "Children"
errors:
- item: "Garden"
error: "UNIQUE constraint failed: group.name"
examples:
groups:
value:
groups:
- name: "Default"
comment: "The default group"
enabled: true
id: 0
date_added: 1594670974
date_modified: 1611157897
- name: "a"
comment: null
enabled: true
id: 5
date_added: 1604871899
date_modified: 1604871899
took: 0.003
processed: null
errors:
uri_error:
item_missing:
summary: Group to be modified is missing
value:
error:
key: "uri_error"
message: "Invalid request: Specify item in URI"
hint: null
bad_request:
no_payload:
summary: No JSON payload found
value:
error:
key: "bad_request"
message: "Invalid request body data (no valid JSON)"
hint: null
database_error:
duplicate:
summary: Database error
value:
error:
key: "database_error"
message: "Could not add to gravity database"
hint: "UNIQUE constraint failed: grouplist.group"
parameters:
name:
in: path
name: name
schema:
type: string
required: true
description: Group name
example: test_group

View File

@ -0,0 +1,226 @@
openapi: 3.0.2
components:
paths:
history:
get:
summary: Get activity graph data
tags:
- Metrics
operationId: "get_activity_metrics"
description: |
Request data needed to generate the \"Query over last 24 hours\" graph. The sum of the values in the individual data arrays may be smaller than the total number of queries for the corresponding timestamp. The remaining queries are queries that do not fit into the shown categories (e.g. database busy, unknown status queries, etc.).
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'history.yaml#/components/schemas/total_history'
- $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'
database_history:
get:
summary: Get activity graph data (long-term data)
tags:
- Metrics
operationId: "get_activity_metrics_database"
description: |
Request long-term data needed to generate the activity graph
parameters:
- $ref: 'common.yaml#/components/parameters/database/from'
- $ref: 'common.yaml#/components/parameters/database/until'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'history.yaml#/components/schemas/total_history'
- $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'
clients:
get:
summary: Get per-client activity graph data
tags:
- Metrics
operationId: "get_client_metrics"
description: |
Request data needed to generate the \"Client activity over last 24 hours\" graph.
This endpoint returns the top N clients, sorted by total number of queries within 24 hours. If N is set to 0, all clients will be returned.
The client name is only available if the client's IP address can be resolved to a hostname.
The last client returned is a special client that contains the total number of queries that were not sent by any of the other shown clients , i.e. queries that were sent by clients that are not in the top N. This client is always present, even if it has 0 queries and can be identified by the special name "other clients" (mind the space in the hostname) and the IP address "0.0.0.0".
Note that, due to privacy settings, the returned data may also be empty.
parameters:
- $ref: 'history.yaml#/components/parameters/clients/N'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'history.yaml#/components/schemas/client_history'
- $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'
database_clients:
get:
summary: Get per-client activity graph data (long-term data)
tags:
- Metrics
operationId: "get_client_metrics_database"
description: |
Request long-term data needed to generate the client activity graph
parameters:
- $ref: 'common.yaml#/components/parameters/database/from'
- $ref: 'common.yaml#/components/parameters/database/until'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'history.yaml#/components/schemas/client_history'
- $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:
total_history:
type: object
properties:
history:
type: array
description: Data array
items:
type: object
properties:
timestamp:
type: number
description: Timestamp
total:
type: integer
description: Total number of queries
cached:
type: integer
description: Number of cached queries
blocked:
type: integer
description: Number of blocked queries
forwarded:
type: integer
description: Number of forwarded queries
example:
- timestamp: 1511819900.539157
total: 2134
cached: 525
blocked: 413
forwarded: 1196
- timestamp: 1511820500.583821
total: 2014
cached: 52
blocked: 43
forwarded: 1910
client_history:
type: object
properties:
clients:
type: object
description: Data corresponding to the individual clients
additionalProperties:
type: object
properties:
name:
type: string
nullable: true
description: Client name
total:
type: integer
description: Total number of queries
example:
"127.0.0.1":
name: localhost
total: 13428
"::1":
name: ip6-localnet
total: 2100
"192.168.1.1":
name: null
total: 254
"::":
name: "pi.hole"
total: 29
"0.0.0.0":
name: "other clients"
total: 14
history:
type: array
description: Data array
items:
type: object
properties:
timestamp:
type: number
description: Timestamp
data:
type: object
description: Data corresponding to the individual clients
additionalProperties:
type: integer
description: Number of queries for the corresponding client
example:
- timestamp: 1511819900.539157
data:
"127.0.0.1": 35
"::1": 63
"192.168.1.1": 20
"::": 9
"0.0.0.0": 0
- timestamp: 1511820500.583821
data:
"127.0.0.1": 10
"::1": 44
"192.168.1.1": 56
"::": 52
parameters:
clients:
N:
in: query
description: Maximum number of clients to return, setting this to 0 will return all clients
name: N
schema:
type: integer
required: false
example: 20

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,463 @@
openapi: 3.0.2
components:
paths:
list:
summary: Modify list
parameters:
- $ref: 'lists.yaml#/components/parameters/list'
- $ref: 'lists.yaml#/components/parameters/listtype'
get:
summary: Get lists
tags:
- "List management"
operationId: "get_lists"
description: |
`{list}` is optional. Specifying it will result in only the requested list being returned.
Valid combinations are:
- `/api/lists` (all lists)
- `/api/lists/my_list` (list identical to `my_list`)
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'lists.yaml#/components/schemas/lists/get'
- $ref: 'common.yaml#/components/schemas/took'
examples:
lists:
$ref: 'lists.yaml#/components/examples/lists'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
put:
summary: Replace list
tags:
- "List management"
operationId: "replace_lists"
description: |
Items may be updated by replacing them. `{list}` is required.
Ensure to send all the required parameters (such as `comment`) to ensure these properties are retained.
The read-only fields `id` and `date_added` are preserved, `date_modified` is automatically updated on success.
requestBody:
description: Callback payload
content:
application/json:
schema:
$ref: 'lists.yaml#/components/schemas/lists/put'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: 'lists.yaml#/components/schemas/lists/get' # identical to GET
- $ref: 'lists.yaml#/components/schemas/lists_processed'
- $ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
item_missing:
$ref: 'lists.yaml#/components/examples/errors/uri_error/item_missing'
no_payload:
$ref: 'lists.yaml#/components/examples/errors/bad_request/no_payload'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
delete:
summary: Delete list
tags:
- "List management"
operationId: "delete_lists"
description: |
*Note:* There will be no content on success.
responses:
'204':
description: Item deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
item_missing:
$ref: 'lists.yaml#/components/examples/errors/uri_error/item_missing'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
direct:
post:
summary: Add new list
tags:
- "List management"
operationId: "add_list"
description: |
Creates a new list in the `lists` object. The `{list}` itself is specified in the request body (POST JSON).
lists may be described either by their IP addresses (IPv4 and IPv6 are supported),
IP subnets (CIDR notation, like `192.168.2.0/24`), their MAC addresses (like `12:34:56:78:9A:BC`), by their hostnames (like `localhost`), or by the interface they are connected to (prefaced with a colon, like `:eth0`).</p>
Note that list recognition by IP addresses (incl. subnet ranges) is preferred over MAC address, host name or interface recognition as the two latter will only be available after some time.
Furthermore, MAC address recognition only works for devices at most one networking hop away from your Pi-hole.
On success, a new resource is created at `/lists/{list}`.
The `database_error` with message `UNIQUE constraint failed` error indicates that this list already exists.
requestBody:
description: Callback payload
content:
application/json:
schema:
$ref: 'lists.yaml#/components/schemas/lists/post'
responses:
'201':
description: Created item
content:
application/json:
schema:
allOf:
- $ref: 'lists.yaml#/components/schemas/lists/get'
- $ref: 'lists.yaml#/components/schemas/lists_processed'
- $ref: 'common.yaml#/components/schemas/took'
headers:
Location:
$ref: 'common.yaml#/components/headers/Location'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
no_payload:
$ref: 'lists.yaml#/components/examples/errors/bad_request/no_payload'
duplicate:
$ref: 'lists.yaml#/components/examples/errors/database_error/duplicate'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
batchDelete:
post:
summary: Delete lists
tags:
- "List management"
operationId: "batchDelete_lists"
description: |
Deletes multiple lists in the `lists` object. The `{list}`s themselves are specified in the request body (POST JSON).
On success, a new resource is created at `/lists/{list}`.
The `database_error` with message `UNIQUE constraint failed` error indicates that this list already exists.
requestBody:
description: Callback payload
content:
application/json:
schema:
$ref: 'lists.yaml#/components/schemas/lists/post'
responses:
'204':
description: Items deleted
'404':
description: Item not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $ref: 'common.yaml#/components/schemas/took'
examples:
no_payload:
$ref: 'lists.yaml#/components/examples/errors/bad_request/no_payload'
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
schemas:
lists:
get:
type: object
properties:
lists:
type: array
description: Array of lists
items:
allOf:
- $ref: 'lists.yaml#/components/schemas/address_object'
- $ref: 'lists.yaml#/components/schemas/type'
- $ref: 'lists.yaml#/components/schemas/comment'
- $ref: 'lists.yaml#/components/schemas/groups'
- $ref: 'lists.yaml#/components/schemas/enabled'
- $ref: 'lists.yaml#/components/schemas/readonly'
put:
allOf:
- $ref: 'lists.yaml#/components/schemas/comment'
- $ref: 'lists.yaml#/components/schemas/type'
- $ref: 'lists.yaml#/components/schemas/groups'
- $ref: 'lists.yaml#/components/schemas/enabled'
post:
allOf:
- $ref: 'lists.yaml#/components/schemas/address_maybe_array'
- $ref: 'lists.yaml#/components/schemas/type'
- $ref: 'lists.yaml#/components/schemas/comment'
- $ref: 'lists.yaml#/components/schemas/groups'
- $ref: 'lists.yaml#/components/schemas/enabled'
address:
description: Address of the list
type: string
example: https://hosts-file.net/ad_servers.txt
address_array:
description: array of list addresses
type: array
items:
type: string
example: ["https://hosts-file.net/ad_servers.txt"]
address_maybe_array:
type: object
properties:
address:
oneOf:
- $ref: 'lists.yaml#/components/schemas/address'
- $ref: 'lists.yaml#/components/schemas/address_array'
address_object:
type: object
properties:
address:
$ref: 'lists.yaml#/components/schemas/address'
type:
type: object
properties:
type:
description: Type of list
type: string
enum:
- "allow"
- "block"
example: "block"
comment:
type: object
properties:
comment:
description: User-provided free-text comment for this list
type: string
nullable: true
default: null
example: Some comment for this list
groups:
type: object
properties:
groups:
description: Array of group IDs
type: array
default: [0]
items:
type: integer
enabled:
type: object
properties:
enabled:
description: Status of domain
type: boolean
default: true
example: true
readonly:
type: object
properties:
id:
description: Database ID
type: integer
readOnly: true
example: 1
date_added:
description: Unix timestamp of item addition
type: integer
readOnly: true
example: 1611239095
date_modified:
description: Unix timestamp of last item modification
type: integer
readOnly: true
example: 1611239099
date_updated:
description: Unix timestamp of last update of list content
type: integer
readOnly: true
example: 1608435667
number:
description: Number of VALID domains on this list
type: integer
readOnly: true
example: 20566
invalid_domains:
description: Number of INVALID domains on this list
type: integer
readOnly: true
example: 0
abp_entries:
description: Number of ABP entries on this list
type: integer
readOnly: true
example: 0
status:
description: List status
type: integer
readOnly: true
example: 2
lists_processed:
type: object
properties:
processed:
type: object
nullable: true
description: |
Object containing the number of lists that were successfully
added to the database and the number of lists that could not be
added to the database.
properties:
success:
description: |
Array of lists that were successfully added to the database.
type: array
items:
type: object
properties:
item:
description: List that was added to the database
type: string
errors:
description: |
Array of errors that occurred during processing.
type: array
items:
type: object
properties:
item:
description: List that could not be added to the database
type: string
error:
description: Error message
type: string
example:
success:
- item: "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
- item: "https://mirror1.malwaredomains.com/files/justdomains"
errors:
- item: "https://raw.githubusercontent.com/abc/def/master/hosts"
error: "UNIQUE constraint failed: adlist.address"
examples:
lists:
value:
lists:
- address: "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
comment: "Migrated from /etc/pihole/adlists.list"
groups:
- 0
enabled: true
id: 1
date_added: 1594670974
date_modified: 1595279300
- address: "https://mirror1.malwaredomains.com/files/justdomains"
comment: "Migrated from /etc/pihole/adlists.list"
groups:
- 0
enabled: true
id: 2
date_added: 1594670974
date_modified: 1594670974
took: 0.012
processed: null
errors:
uri_error:
item_missing:
summary: List to be modified is missing
value:
error:
key: "uri_error"
message: "Invalid request: Specify item in URI"
hint: null
bad_request:
no_payload:
summary: No JSON payload found
value:
error:
key: "bad_request"
message: "Invalid request body data (no valid JSON)"
hint: null
database_error:
duplicate:
summary: Database error
value:
error:
key: "database_error"
message: "Could not add to gravity database"
hint: "UNIQUE constraint failed: listlist.list"
parameters:
list:
in: path
name: list
schema:
type: string
required: true
description: Address of the list
example: https://hosts-file.net/ad_servers.txt
listtype:
in: query
name: type
schema:
type: string
enum:
- "allow"
- "block"
required: false
description: Type of list, optional
example: block

View File

@ -0,0 +1,158 @@
openapi: 3.0.2
components:
paths:
logs:
dnsmasq:
get:
summary: Get DNS log content
tags:
- "FTL information"
operationId: "get_dns_log"
description: |
This API hook returns content from the log of the embedded DNS resolver `dnsmasq`.
Every successful request will return a `nextID`.
This ID can be used on the next request to only get lines which were added *after* the last request.
This makes periodic polling for new log lines easy as no check for duplicated log lines is necessary.
The expected behavior for an immediate re-request of a log line with the same ID is an empty response.
As soon as the next message arrived, this will be included in your request and `nextID` is incremented by one.
parameters:
- $ref: 'logs.yaml#/components/parameters/logs/dnsmasq/nextID'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'logs.yaml#/components/schemas/logs/dnsmasq/log'
- $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'
ftl:
get:
summary: Get DNS log content
tags:
- "FTL information"
operationId: "get_ftl_log"
description: |
This API hook returns content from the log of FTL.
Every successful request will return a `nextID`.
This ID can be used on the next request to only get lines which were added *after* the last request.
This makes periodic polling for new log lines easy as no check for duplicated log lines is necessary.
The expected behavior for an immediate re-request of a log line with the same ID is an empty response.
As soon as the next message arrived, this will be included in your request and `nextID` is incremented by one.
parameters:
- $ref: 'logs.yaml#/components/parameters/logs/dnsmasq/nextID'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'logs.yaml#/components/schemas/logs/dnsmasq/log'
- $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'
webserver:
get:
summary: Get DNS log content
tags:
- "FTL information"
operationId: "get_webserver_log"
description: |
This API hook returns content from the log of the embedded CivetWeb HTTP server.
Every successful request will return a `nextID`.
This ID can be used on the next request to only get lines which were added *after* the last request.
This makes periodic polling for new log lines easy as no check for duplicated log lines is necessary.
The expected behavior for an immediate re-request of a log line with the same ID is an empty response.
As soon as the next message arrived, this will be included in your request and `nextID` is incremented by one.
parameters:
- $ref: 'logs.yaml#/components/parameters/logs/dnsmasq/nextID'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'logs.yaml#/components/schemas/logs/dnsmasq/log'
- $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:
logs:
dnsmasq:
log:
type: object
properties:
log:
type: array
description: Request headers
items:
type: object
properties:
timestamp:
type: number
description: Unix timestamp of log line creation (server time)
message:
type: string
description: Log line content
prio:
type: string
nullable: true
description: Log line priority (if available)
example:
- timestamp: 1611729969.0
message: "started, version pi-hole-2.84 cachesize 10000"
- timestamp: 1611729969.0
message: "reading /etc/resolv.conf"
- timestamp: 1611729969.0
message: "read /etc/hosts - 7 addresses"
- timestamp: 1611729969.0
message: "query[A] connectivity-check.ubuntu.com from 127.0.0.1"
nextID:
type: integer
description: Next ID to query if checking for new log lines
example: 229
pid:
type: integer
description: Process ID of FTL. When this changes, FTL was restarted and nextID should be reset to 0.
example: 2258
file:
type: string
description: Path to respective log file on disk
example: /var/log/pihole/pihole.log
parameters:
logs:
dnsmasq:
nextID:
in: query
description: (Optional) ID of next line to return
name: nextID
schema:
type: integer
required: false
example: 219

View File

@ -0,0 +1,301 @@
openapi: 3.0.2
info:
title: Pi-hole API
version: "6.0"
contact:
name: Pi-hole API Support
url: https://discourse.pi-hole.net
license:
name: European Union Public Licence (EUPL)
url: https://joinup.ec.europa.eu/community/eupl/og_page/eupl
description: >
The Pi-hole API is organized around [REST](http://en.wikipedia.org/wiki/Representational_State_Transfer).
Our API has predictable resource-oriented URLs, accepts and returns reliable UTF-8 [JavaScript Object Notation (JSON)-encoded](http://www.json.org/) data for all API responses, and uses standard HTTP response codes and verbs.
Most (but not all) endpoints require authentication.
API endpoints requiring authentication will fail with code `401 Unauthorized` when used outside a valid session.
servers:
- url: 'https://{url}:{port}/{path}'
variables:
url:
description: URL or address of your Pi-hole
default: pi.hole
port:
description: Port of your Pi-hole's API (HTTPS)
default: "443"
path:
description: Path where your Pi-hole's API is hosted
default: api
- url: 'http://{url}:{port}/{path}'
variables:
url:
description: URL or address of your Pi-hole
default: pi.hole
port:
description: Port of your Pi-hole's API
default: "80"
path:
description: Path where your Pi-hole's API is hosted
default: api
tags:
- name: "Authentication"
description: Methods used to authentaticate with the API
- name: "Metrics"
description: Methods used to get usage data from your Pi-hole
- name: "DNS control"
description: Methods used to control the behavior of your Pi-hole
- name: "DHCP server control"
description: Methods used to control the DHCP server of your Pi-hole
- name: "Group management"
description: Methods used to manage groups on your Pi-hole
- name: "Domain management"
description: Methods used to manage domains on your Pi-hole
- name: "Client management"
description: Methods used to manage clients on your Pi-hole
- name: "List management"
description: Methods used to manage lists on your Pi-hole
- name: "FTL information"
description: Methods used to gather advanced data from your Pi-hole
- name: "Pi-hole configuration"
description: Methods used to configure your Pi-hole
- name: "Network information"
description: Methods used to gather advanced information about your network
- name: "Actions"
description: Methods used to trigger certain actions on your Pi-hole
paths:
/auth:
$ref: 'auth.yaml#/components/paths/auth'
/auth/session/{id}:
$ref: 'auth.yaml#/components/paths/session'
/auth/sessions:
$ref: 'auth.yaml#/components/paths/session_list'
/auth/totp:
$ref: 'auth.yaml#/components/paths/totp'
/auth/app:
$ref: 'auth.yaml#/components/paths/app'
/stats/summary:
$ref: 'stats.yaml#/components/paths/summary'
/stats/database/summary:
$ref: 'stats.yaml#/components/paths/database_summary'
/stats/upstreams:
$ref: 'stats.yaml#/components/paths/upstreams'
/stats/database/upstreams:
$ref: 'stats.yaml#/components/paths/database_upstreams'
/stats/top_domains:
$ref: 'stats.yaml#/components/paths/top_domains'
/stats/database/top_domains:
$ref: 'stats.yaml#/components/paths/database_top_domains'
/stats/top_clients:
$ref: 'stats.yaml#/components/paths/top_clients'
/stats/database/top_clients:
$ref: 'stats.yaml#/components/paths/database_top_clients'
/stats/query_types:
$ref: 'stats.yaml#/components/paths/query_types'
/stats/database/query_types:
$ref: 'stats.yaml#/components/paths/database_query_types'
/stats/recent_blocked:
$ref: 'stats.yaml#/components/paths/recent_blocked'
/history:
$ref: 'history.yaml#/components/paths/history'
/history/clients:
$ref: 'history.yaml#/components/paths/clients'
/history/database:
$ref: 'history.yaml#/components/paths/database_history'
/history/database/clients:
$ref: 'history.yaml#/components/paths/database_clients'
/queries:
$ref: 'queries.yaml#/components/paths/queries'
/queries/suggestions:
$ref: 'queries.yaml#/components/paths/suggestions'
/dns/blocking:
$ref: 'dns.yaml#/components/paths/blocking'
/domains/{type}/{kind}/{domain}:
$ref: 'domains.yaml#/components/paths/type_kind_domain'
/domains/{type}/{kind}:
$ref: 'domains.yaml#/components/paths/type_kind'
/domains:batchDelete:
$ref: 'domains.yaml#/components/paths/batchDelete'
/groups/{name}:
$ref: 'groups.yaml#/components/paths/name'
/groups:
$ref: 'groups.yaml#/components/paths/direct'
/groups:batchDelete:
$ref: 'groups.yaml#/components/paths/batchDelete'
/clients/{client}:
$ref: 'clients.yaml#/components/paths/client'
/clients:
$ref: 'clients.yaml#/components/paths/direct'
/clients:batchDelete:
$ref: 'clients.yaml#/components/paths/batchDelete'
/clients/_suggestions:
$ref: 'clients.yaml#/components/paths/suggestions'
/lists/{list}:
$ref: 'lists.yaml#/components/paths/list'
/lists:
$ref: 'lists.yaml#/components/paths/direct'
/lists:batchDelete:
$ref: 'lists.yaml#/components/paths/batchDelete'
/info/client:
$ref: 'info.yaml#/components/paths/client'
/info/system:
$ref: 'info.yaml#/components/paths/system'
/info/database:
$ref: 'info.yaml#/components/paths/database'
/info/ftl:
$ref: 'info.yaml#/components/paths/ftl'
/info/host:
$ref: 'info.yaml#/components/paths/host'
/info/sensors:
$ref: 'info.yaml#/components/paths/sensors'
/info/version:
$ref: 'info.yaml#/components/paths/version'
/info/messages:
$ref: 'info.yaml#/components/paths/messages'
/info/messages/{message_id}:
$ref: 'info.yaml#/components/paths/messages_with_id'
/info/messages/count:
$ref: 'info.yaml#/components/paths/messages_count'
/info/metrics:
$ref: 'info.yaml#/components/paths/metrics'
/info/login:
$ref: 'info.yaml#/components/paths/login'
/logs/dnsmasq:
$ref: 'logs.yaml#/components/paths/logs/dnsmasq'
/logs/ftl:
$ref: 'logs.yaml#/components/paths/logs/ftl'
/logs/webserver:
$ref: 'logs.yaml#/components/paths/logs/webserver'
/endpoints:
$ref: 'endpoints.yaml#/components/paths/endpoints'
/config:
$ref: 'config.yaml#/components/paths/config'
/config/{element}:
$ref: 'config.yaml#/components/paths/config_elem'
/config/{element}/{value}:
$ref: 'config.yaml#/components/paths/config_elem_value'
/network/devices:
$ref: 'network.yaml#/components/paths/devices'
/network/devices/{device_id}:
$ref: 'network.yaml#/components/paths/devices_id'
/network/gateway:
$ref: 'network.yaml#/components/paths/gateway'
/network/interfaces:
$ref: 'network.yaml#/components/paths/interfaces'
/teleporter:
$ref: 'teleporter.yaml#/components/paths/teleporter'
/action/gravity:
$ref: 'action.yaml#/components/paths/gravity'
/action/restartdns:
$ref: 'action.yaml#/components/paths/restartdns'
/action/flush/logs:
$ref: 'action.yaml#/components/paths/flush_logs'
/action/flush/arp:
$ref: 'action.yaml#/components/paths/flush_arp'
/dhcp/leases:
$ref: 'dhcp.yaml#/components/paths/leases'
/dhcp/leases/{ip}:
$ref: 'dhcp.yaml#/components/paths/leases_ip'
/search/{domain}:
$ref: 'search.yaml#/components/paths/search'
/docs:
$ref: 'docs.yaml#/components/paths/docs'
components:
securitySchemes:
query_sid:
type: apiKey
in: query
name: sid
cookie_sid:
type: apiKey
in: cookie
name: sid
header_sid:
type: apiKey
in: header
name: sid
x_header_sid:
type: apiKey
in: header
name: X-FTL-SID
query_password:
type: apiKey
in: query
name: password
security:
- query_sid: []
- cookie_sid: []
- header_sid: []
- x_header_sid: []

View File

@ -0,0 +1,306 @@
openapi: 3.0.2
components:
paths:
gateway:
get:
summary: Get info about the gateway of your Pi-hole
tags:
- "Network information"
operationId: "get_gateway"
description: |
This API hook returns infos about the gateway of your Pi-hole.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'network.yaml#/components/schemas/gateway'
- $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"
description: |
This API hook returns infos about the networking interfaces of your Pi-hole.
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'network.yaml#/components/schemas/interfaces'
- $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'
devices:
get:
summary: Get info about the devices in your local network as seen by your Pi-hole
tags:
- "Network information"
operationId: "get_network"
description: |
This API hook returns infos about the devices in your local network as seen by your Pi-hole. By default, this number of shown devices is limited to 10. Devices are ordered by when your Pi-hole has received the last query from this device (most recent first)
parameters:
- $ref: 'network.yaml#/components/parameters/devices/max_devices'
- $ref: 'network.yaml#/components/parameters/devices/max_addresses'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'network.yaml#/components/schemas/devices'
- $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'
devices_id:
delete:
summary: Delete a device from the network table
tags:
- "Network information"
operationId: "delete_device"
description: |
This API hook deletes a device from the network table. This will also remove all associated IP addresses and hostnames.
parameters:
- $ref: 'network.yaml#/components/parameters/devices/device_id'
responses:
'204':
description: No Content (deleted)
'404':
description: Not found
content:
application/json:
schema:
$ref: 'common.yaml#/components/schemas/took'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/bad_request'
- $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:
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"
interfaces:
type: object
properties:
interfaces:
type: array
description: Interface information
items:
type: object
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
items:
type: string
ipv6:
type: array
nullable: true
description: Array of associated IPv6 addresses
items:
type: string
example:
- 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
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"]
devices:
type: object
properties:
devices:
type: array
description: Array of devices
items:
type: object
properties:
id:
type: integer
description: Device network table ID
example: 1
hwaddr:
type: string
description: MAC address of this device
example: 00:11:22:33:44:55
interface:
type: string
description: Interface this device is connected to
example: enp2s0
firstSeen:
type: integer
description: Unix timestamp when this device was first seen by your Pi-hole
example: 1664623620
lastQuery:
type: integer
description: Unix timestamp when your Pi-hole received the last query from this device
example: 1664688620
numQueries:
type: integer
description: Total number of queries your Pi-hole has received from this device
example: 585462
macVendor:
type: string
nullable: true
description: Vendor name associated with the device's MAC address (if available)
example: "Digital Data Communications Asia Co.,Ltd"
ips:
type: array
items:
type: object
properties:
ip:
type: string
description: Associated IP address (can be IPv4 or IPv6)
example: "192.168.1.51"
name:
type: string
description: Associated hostname (can be null)
nullable: true
example: ubuntu-server
lastSeen:
type: integer
description: Unix timestamp when your Pi-hole has seen this address the last time
example: 1664688620
nameUpdated:
type: integer
description: Unix timestamp when device updated its hostname the last time
example: 1664688620
parameters:
devices:
max_devices:
in: query
description: (Optional) Maximum number of devices to show
name: max_devices
schema:
type: integer
required: false
example: 10
max_addresses:
in: query
description: (Optional) Maximum number of addresses to show per device
name: max_addresses
schema:
type: integer
required: false
example: 3
device_id:
in: path
description: Device ID
name: device_id
schema:
type: integer
required: true
example: 1

View File

@ -0,0 +1,320 @@
openapi: 3.0.2
components:
paths:
queries:
get:
summary: Get queries
tags:
- Metrics
operationId: "get_queries"
description: |
Request query details.
Query parameters may be used to limit the number of results.
By default, this API callback returns the most recent 100 queries.
This can be changed using the parameter `n`.
This callback allows for fine-grained filtering by various parameters.
All query parameters are all optional and can be combined in any way:
- Only show queries *from* a given timestamp on: Use parameter `from`
- Only show queries *until* a given timestamp: Use parameter `until`
- Only show queries sent to a specific upstream destination (may also be `cache` or `blocklist`): Use parameter `upstream`
- Only show queries for specific domains: Use parameter `domain`
- Only show queries for specific clients: Use parameter `client`
By default, the returned queries always start at the most recent query.
This can be changed by supplying the parameter `cursor`.
Each result of this API callback contains a `cursor` pointing the beginning of the next `n` queries chunk.
This provides a very fast and lightweight server-side pagination implementation.
If wildcards are supported for a parameter, you may specify `*` at any position in the parameter to match any number of characters.
parameters:
- in: query
name: from
description: Get queries from...
required: false
schema:
type: number
- in: query
name: until
description: Get queries until...
required: false
schema:
type: number
- in: query
name: length
description: Number of results to return
required: false
schema:
type: integer
- in: query
name: start
description: Offset from first record
required: false
schema:
type: integer
- in: query
name: cursor
description: |
Database ID of the most recent query to be shown
required: false
schema:
type: integer
- in: query
name: domain
description: Filter by specific domain (wildcards supported)
required: false
schema:
type: string
- in: query
name: client_ip
description: Filter by specific client IP address (wildcards supported)
required: false
schema:
type: string
- in: query
name: client_name
description: Filter by specific client hostname (wildcards supported)
required: false
schema:
type: string
- in: query
name: upstream
description: Filter by specific upstream (wildcards supported)
required: false
schema:
type: string
- in: query
name: type
description: Filter by specific query type (A, AAAA, ...)
required: false
schema:
type: string
- in: query
name: status
description: Filter by specific query status (GRAVITY, FORWARDED, ...)
required: false
schema:
type: string
- in: query
name: reply
description: Filter by specific reply type (NODATA, NXDOMAIN, ...)
required: false
schema:
type: string
- in: query
name: dnssec
description: Filter by specific DNSSEC status (SECURE, INSECURE, ...)
required: false
schema:
type: string
- in: query
name: disk
description: Load queries from on-disk database rather than from in-memory
required: false
schema:
enum:
- true
- false
default: false
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'queries.yaml#/components/schemas/queries'
- $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'
suggestions:
get:
summary: Get query filter suggestions
tags:
- Metrics
operationId: "get_suggestions"
description: |
This endpoint provides suggestions for filters suitable to be used with /queries
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'queries.yaml#/components/schemas/suggestions'
- $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:
queries:
type: object
properties:
queries:
type: array
description: Data array
items:
type: object
properties:
id:
type: integer
description: Query ID in the long-term database
time:
type: number
description: Timestamp
type:
type: string
description: Query type
domain:
type: string
description: Queried domain
cname:
type: string
description: Domain blocked during deep CNAME inspection
nullable: true
status:
type: string
description: Query status
nullable: true
client:
type: object
properties:
ip:
type: string
description: Requesting client's IP address
name:
type: string
description: Requesting client's hostname (if available)
nullable: true
dnssec:
type: string
description: DNSSEC status
nullable: true
reply:
type: object
properties:
type:
type: string
description: Reply type
nullable: true
time:
type: number
description: Time until the response was received (ms, negative if N/A)
list_id:
type: integer
description: ID of corresponding database table (adlist for anti-/gravity, else domainlist) (`NULL` if N/A)
nullable: true
upstream:
type: string
description: IP or name + port of upstream server
nullable: true
example:
- time: 1581907991.539157
type: "A"
domain: "community.stoplight.io"
cname: null
status: "FORWARDED"
client:
ip: "192.168.0.14"
name: "desktop.lan"
dnssec: "INSECURE"
reply:
type: "IP"
time: 19
list_id: NULL
upstream: "localhost#5353"
dbid: 112421354
- time: 1581907871.583821
type: "AAAA"
domain: "api.github.com"
cname: null
status: "FORWARDED"
client:
ip: "127.0.0.1"
name: "localhost"
dnssec: "UNKNOWN"
reply:
type: "IP"
time: 12.3
list_id: NULL
upstream: "localhost#5353"
dbid: 112421355
cursor:
type: integer
description: Database ID of most recent query to show
example: 175881
recordsTotal:
type: integer
description: Total number of available queries
example: 1234
recordsFiltered:
type: integer
description: Number of available queries after filtering
example: 1234
draw:
type: integer
description: DataTables-specific integer (echos input value)
example: 1
suggestions:
type: object
properties:
suggestions:
type: object
properties:
domain:
type: array
description: Array of suggested domains
items:
type: string
example: ["pi-hole.net","amazon.com"]
client_ip:
type: array
description: Array of suggested client IP addresses
items:
type: string
client_name:
type: array
description: Array of suggested client names
items:
type: string
upstream:
type: array
description: Array of suggested upstreams
items:
type: string
type:
type: array
description: Array of suggested query types
items:
type: string
status:
type: array
description: Array of suggested query statuses
items:
type: string
reply:
type: array
description: Array of suggested query replies
items:
type: string
dnssec:
type: array
description: Array of suggested DNSSEC statuses
items:
type: string

View File

@ -0,0 +1,246 @@
openapi: 3.0.2
components:
paths:
search:
parameters:
- $ref: 'search.yaml#/components/parameters/domain'
get:
summary: Search domains in Pi-hole's lists
tags:
- "List management"
operationId: "get_search"
parameters:
- $ref: 'search.yaml#/components/parameters/partial'
- $ref: 'search.yaml#/components/parameters/N'
- $ref: 'search.yaml#/components/parameters/debug'
description: |
Search for domains in Pi-hole's list. The specified domain is automatically converted to lowercase.
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':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'search.yaml#/components/schemas/search'
- $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:
search:
type: object
properties:
search:
type: object
properties:
domains:
type: array
items:
type: object
properties:
domain:
type: string
example: "blockeddomain.com"
comment:
type: string
description: Optional comment
nullable: true
example: "I needed to block this because of XYZ"
enabled:
type: boolean
description: Whether this entry is enabled
example: true
type:
description: String specifying domain type
type: string
enum:
- "allow"
- "deny"
example: "allow"
kind:
description: String specifying domain kind
type: string
enum:
- "exact"
- "regex"
example: "exact"
id:
type: integer
description: Database ID
example: 7
date_added:
type: integer
description: Unix timestamp of addition
example: 1664624500
date_modified:
type: integer
description: Unix timestamp of last modification
example: 1664624500
groups:
type: array
description: Array of IDs corresponding to associated groups
items:
type: integer
example: [0,1,2]
gravity:
type: array
items:
type: object
properties:
domain:
type: string
example: "doubleclick.net"
address:
type: string
description: Address of the list this domain was found on
example: "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
comment:
type: string
description: Optional comment of the list
nullable: true
example: "I needed to block this because of XYZ"
enabled:
type: boolean
description: Whether this list is enabled
example: true
id:
type: integer
description: Database ID of the associated list
example: 0
type:
type: string
description: String specifying list type
enum:
- "allow"
- "block"
date_added:
type: integer
description: Unix timestamp of list addition
example: 1664624500
date_modified:
type: integer
description: Unix timestamp of last list modification
example: 1664624500
date_updated:
type: integer
description: Unix timestamp of last local update of this list
example: 1664624500
number:
type: integer
description: Number of entries in the list
example: 7
invalid_domains:
type: integer
description: Number of invalid domains in the list
example: 0
abp_entries:
type: integer
description: Number of ABP entries in the list
example: 0
status:
type: integer
description: Status of the list
example: 1
groups:
type: array
description: Array of IDs corresponding to associated groups
items:
type: integer
example: [0,1,2]
parameters:
type: object
properties:
partial:
type: boolean
description: Whether partial matching was requested
example: false
N:
type: integer
description: Maximum number of results to be returned (per type)
example: 20
domain:
type: string
description: (Part of) domain to be searched for
example: "doubleclick.net"
debug:
type: boolean
description: Whether debug information was requested
example: false
results:
type: object
properties:
domains:
type: object
properties:
exact:
type: integer
description: Number of exactly matching domains
example: 1
regex:
type: integer
description: Number of regex matching domains
example: 2
gravity:
type: object
properties:
allow:
type: integer
description: Number of allow matches (antigravity)
example: 0
block:
type: integer
description: Number of block matches (gravity)
example: 1
total:
type: integer
description: Total number of matches
example: 4
parameters:
domain:
name: domain
in: path
description: (Part of) domain to be searched for
schema:
type: string
default: ""
example: doubleclick.net
required: true
partial:
name: partial
in: query
description: Is partial matching requested?
required: false
schema:
type: boolean
default: false
example: false
N:
name: N
in: query
description: Maximum number of results to be returned
required: false
schema:
type: integer
default: 20
example: 20
debug:
name: debug
in: query
description: Add debug information to the response
required: false
schema:
type: boolean
default: false
example: false

View File

@ -0,0 +1,789 @@
openapi: 3.0.2
components:
paths:
summary:
get:
summary: Get overview of Pi-hole activity
tags:
- Metrics
operationId: "get_metrics_summary"
description: |
Request various query, system, and FTL properties
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/queries'
- $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'
database_summary:
get:
summary: Get database content details
tags:
- Metrics
operationId: "get_metrics_database_summary"
description: |
Request various database content details
parameters:
- $ref: 'common.yaml#/components/parameters/database/from'
- $ref: 'common.yaml#/components/parameters/database/until'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/database_summary'
- $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'
upstreams:
get:
summary: Get metrics about Pi-hole's upstream destinations
tags:
- Metrics
operationId: "get_metrics_upstreams"
description: |
Request upstream metrics
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/upstreams'
- $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'
database_upstreams:
get:
summary: Get metrics about Pi-hole's upstream destinations (long-term database)
tags:
- Metrics
operationId: "get_metrics_upstreams_database"
description: |
Request upstream metrics (long-term database)
parameters:
- $ref: 'common.yaml#/components/parameters/database/from'
- $ref: 'common.yaml#/components/parameters/database/until'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/upstreams'
- $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'
recent_blocked:
get:
summary: Get most recently blocked domain
tags:
- Metrics
operationId: "get_metrics_recent_blocked"
description: |
Request most recently blocked domain
parameters:
- $ref: 'stats.yaml#/components/parameters/recent_blocked/count'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/recent_blocked'
- $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'
top_domains:
get:
summary: Get top domains
tags:
- Metrics
operationId: "get_metrics_top_domains"
description: |
Request top domains
parameters:
- $ref: 'stats.yaml#/components/parameters/top_items/blocked'
- $ref: 'stats.yaml#/components/parameters/top_items/count'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/top_domains'
- $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'
database_top_domains:
get:
summary: Get top domains (long-term database)
tags:
- Metrics
operationId: "get_metrics_database_top_domains"
description: |
Request top domains
parameters:
- $ref: 'common.yaml#/components/parameters/database/from'
- $ref: 'common.yaml#/components/parameters/database/until'
- $ref: 'stats.yaml#/components/parameters/top_items/blocked'
- $ref: 'stats.yaml#/components/parameters/top_items/count'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/top_domains'
- $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'
top_clients:
get:
summary: Get top clients
tags:
- Metrics
operationId: "get_metrics_top_clients"
description: |
Request top clients
parameters:
- $ref: 'stats.yaml#/components/parameters/top_items/blocked'
- $ref: 'stats.yaml#/components/parameters/top_items/count'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/top_clients'
- $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'
database_top_clients:
get:
summary: Get top clients (long-term database)
tags:
- Metrics
operationId: "get_metrics_database_top_clients"
description: |
Request top clients
parameters:
- $ref: 'common.yaml#/components/parameters/database/from'
- $ref: 'common.yaml#/components/parameters/database/until'
- $ref: 'stats.yaml#/components/parameters/top_items/blocked'
- $ref: 'stats.yaml#/components/parameters/top_items/count'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/top_clients'
- $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'
query_types:
get:
summary: Get query types
tags:
- Metrics
operationId: "get_metrics_query_types"
description: |
Request query types
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/query_types'
- $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'
database_query_types:
get:
summary: Get query types (long-term database)
tags:
- Metrics
operationId: "get_metrics_database_query_types"
description: |
Request query types
parameters:
- $ref: 'common.yaml#/components/parameters/database/from'
- $ref: 'common.yaml#/components/parameters/database/until'
responses:
'200':
description: OK
content:
application/json:
schema:
allOf:
- $ref: 'stats.yaml#/components/schemas/query_types'
- $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:
queries:
type: object
properties:
queries:
type: object
properties:
total:
type: integer
description: Total number of queries
example: 7497
blocked:
type: integer
description: Number of blocked queries
example: 3465
percent_blocked:
type: number
description: Percent of blocked queries
example: 34.5
unique_domains:
type: integer
description: Number of unique domains FTL knows
example: 445
forwarded:
type: integer
description: Number of queries that have been forwarded upstream
example: 4574
cached:
type: integer
description: Number of queries replied to from cache or local configuration
example: 9765
types:
type: object
description: Number of individual queries
properties:
A:
type: integer
description: Type A queries
example: 3643
AAAA:
type: integer
description: Type AAAA queries
example: 123
ANY:
type: integer
description: Type ANY queries
example: 3423
SRV:
type: integer
description: Type SRV queries
example: 345
SOA:
type: integer
description: Type SOA queries
example: 7567
PTR:
type: integer
description: Type PTR queries
example: 456
TXT:
type: integer
description: Type TXT queries
example: 85
NAPTR:
type: integer
description: Type NAPTR queries
example: 346
MX:
type: integer
description: Type MX queries
example: 457
DS:
type: integer
description: Type DS queries
example: 456
RRSIG:
type: integer
description: Type RRSIG queries
example: 345
DNSKEY:
type: integer
description: Type DNSKEY queries
example: 55
NS:
type: integer
description: Type NS queries
example: 868
SVCB:
type: integer
description: Type SVCB queries
example: 645
HTTPS:
type: integer
description: Type HTTPS queries
example: 4
OTHER:
type: integer
description: Queries of remaining types
example: 845
status:
type: object
description: Number of individual queries (by status)
properties:
UNKNOWN:
type: integer
description: Type UNKNOWN queries
example: 3
GRAVITY:
type: integer
description: Type GRAVITY queries
example: 72
FORWARDED:
type: integer
description: Type FORWARDED queries
example: 533
CACHE:
type: integer
description: Type CACHE queries
example: 32
REGEX:
type: integer
description: Type REGEX queries
example: 84
DENYLIST:
type: integer
description: Type DENYLIST queries
example: 31
EXTERNAL_BLOCKED_IP:
type: integer
description: Type EXTERNAL_BLOCKED_IP queries
example: 0
EXTERNAL_BLOCKED_NULL:
type: integer
description: Type EXTERNAL_BLOCKED_NULL queries
example: 0
EXTERNAL_BLOCKED_NXRA:
type: integer
description: Type EXTERNAL_BLOCKED_NXRA queries
example: 0
GRAVITY_CNAME:
type: integer
description: Type GRAVITY_CNAME queries
example: 0
REGEX_CNAME:
type: integer
description: Type REGEX_CNAME queries
example: 0
DENYLIST_CNAME:
type: integer
description: Type DENYLIST_CNAME queries
example: 0
RETRIED:
type: integer
description: Type RETRIED queries
example: 0
RETRIED_DNSSEC:
type: integer
description: Type RETRIED_DNSSEC queries
example: 0
IN_PROGRESS:
type: integer
description: Type IN_PROGRESS queries
example: 0
DBBUSY:
type: integer
description: Type DBBUSY queries
example: 0
SPECIAL_DOMAIN:
type: integer
description: Type SPECIAL_DOMAIN queries
example: 0
CACHE_STALE:
type: integer
description: Type CACHE_STALE queries
example: 0
replies:
type: object
description: Number of individual replies
properties:
UNKNOWN:
type: integer
description: Type UNKNOWN replies
example: 3
NODATA:
type: integer
description: Type NODATA replies
example: 72
NXDOMAIN:
type: integer
description: Type NXDOMAIN replies
example: 533
CNAME:
type: integer
description: Type CNAME replies
example: 32
IP:
type: integer
description: Type IP replies
example: 84
DOMAIN:
type: integer
description: Type DOMAIN replies
example: 31
RRNAME:
type: integer
description: Type RRNAME replies
example: 0
SERVFAIL:
type: integer
description: Type SERVFAIL replies
example: 0
REFUSED:
type: integer
description: Type REFUSED replies
example: 0
NOTIMP:
type: integer
description: Type NOTIMP replies
example: 0
OTHER:
type: integer
description: Type OTHER replies
example: 0
DNSSEC:
type: integer
description: Type DNSSEC replies
example: 31
NONE:
type: integer
description: Type NONE replies
example: 0
BLOB:
type: integer
description: Type BLOB replies
example: 0
clients:
type: object
properties:
active:
type: integer
description: Number of active clients (seen in the last 24 hours)
example: 10
total:
type: integer
description: Total number of clients seen by FTL
example: 22
gravity:
type: object
properties:
domains_being_blocked:
type: integer
description: Number of domain on your Pi-hole's gravity list
example: 104756
upstreams:
type: object
properties:
upstreams:
type: array
description: Array of upstream destinations
items:
type: object
properties:
ip:
type: string
description: Upstream destination's IP address (can be either IPv4 or IPv6)
nullable: true
example: "127.0.0.1"
name:
type: string
description: Upstream destination's hostname (if available)
nullable: true
example: "localhost"
port:
type: integer
description: Upstream destination's destination port (-1 if not applicable, e.g., for the local cache)
example: 53
count:
type: integer
description: Number of queries this upstream destination has been used for
example: 65445
statistics:
type: object
properties:
response:
type: number
description: Average response time of this upstream destination in seconds (0 if not applicable)
example: 0.0254856
variance:
type: number
description: Standard deviation of the average response time (0 if not applicable)
example: 0.02058
forwarded_queries:
type: integer
description: Number of forwarded queries
example: 6379
total_queries:
type: integer
description: Total number of queries
example: 29160
top_domains:
type: object
properties:
domains:
type: array
description: Array of domains
items:
type: object
properties:
domain:
type: string
description: Requested domain
example: "pi-hole.net"
count:
type: integer
description: Number of times this domain has been requested
example: 8516
total_queries:
type: integer
description: Total number of queries
example: 29160
blocked_queries:
type: integer
description: Number of blocked queries
example: 6379
top_clients:
type: object
properties:
clients:
type: array
description: Array of clients
items:
type: object
properties:
ip:
type: string
description: Client IP address (can be either IPv4 or IPv6)
example: "192.168.0.44"
name:
type: string
description: Client hostname (if available)
example: "raspberrypi.lan"
count:
type: integer
description: Number of queries this client has made
example: 5896
total_queries:
type: integer
description: Total number of queries
example: 29160
blocked_queries:
type: integer
description: Number of blocked queries
example: 6379
query_types:
type: object
properties:
types:
type: object
description: Number of individual query types
properties:
A:
type: integer
description: Type A queries
example: 18268
AAAA:
type: integer
description: Type AAAA queries
example: 2332
ANY:
type: integer
description: Type ANY queries
example: 0
SRV:
type: integer
description: Type SRV queries
example: 6
SOA:
type: integer
description: Type SOA queries
example: 44
PTR:
type: integer
description: Type PTR queries
example: 389
TXT:
type: integer
description: Type TXT queries
example: 0
NAPTR:
type: integer
description: Type NAPTR queries
example: 1
MX:
type: integer
description: Type MX queries
example: 109
DS:
type: integer
description: Type DS queries
example: 596
RRSIG:
type: integer
description: Type RRSIG queries
example: 25
DNSKEY:
type: integer
description: Type DNSKEY queries
example: 240
NS:
type: integer
description: Type NS queries
example: 18
SVCB:
type: integer
description: Type SVCB queries
example: 0
HTTPS:
type: integer
description: Type HTTPS queries
example: 11
OTHER:
type: integer
description: Type OTHER queries
example: 0
database_summary:
type: object
properties:
sum_queries:
type: integer
description: Total number of queries
example: 29160
sum_blocked:
type: integer
description: Total number of blocked queries
example: 6379
percent_blocked:
type: number
description: Percentage of blocked queries
example: 21.9
total_clients:
type: integer
description: Total number of clients
example: 10
recent_blocked:
type: object
properties:
blocked:
type: array
description: List of blocked domains
items:
type: string
example: ["doubleclick.net"]
parameters:
recent_blocked:
count:
in: query
description: Number of requested blocked domains
name: count
schema:
type: integer
required: false
example: 1
top_items:
blocked:
in: query
description: Return information about permitted or blocked queries
name: blocked
schema:
type: boolean
required: false
example: false
count:
in: query
description: Number of requested items
name: count
schema:
type: integer
required: false
example: 10

View File

@ -0,0 +1,155 @@
openapi: 3.0.2
components:
paths:
teleporter:
get:
summary: Export Pi-hole settings
tags:
- "Pi-hole configuration"
operationId: "get_teleporter"
description: |
Request an archived copy of your Pi-hole's current configuration.
Authentication via header or cookie is required for the endpoint.
responses:
'200':
description: OK
content:
application/zip:
schema:
type: string
format: binary
'401':
description: Unauthorized
content:
application/json:
schema:
allOf:
- $ref: 'common.yaml#/components/errors/unauthorized'
- $ref: 'common.yaml#/components/schemas/took'
post:
summary: Import Pi-hole settings
tags:
- "Pi-hole configuration"
operationId: "post_teleporter"
description: |
Upload a Pi-hole Teleporter archive to (partially) restore from it. Note that this will overwrite your current configuration.
Authentication via header or cookie is required for the endpoint.
requestBody:
content:
multipart/form-data:
schema:
properties:
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
content:
application/json:
schema:
allOf:
- $ref: 'teleporter.yaml#/components/schemas/teleporter/post'
- $ref: 'common.yaml#/components/schemas/took'
examples:
teleporter:
$ref: 'teleporter.yaml#/components/examples/teleporter'
'400':
description: Bad request
content:
application/json:
schema:
allOf:
- $ref: 'teleporter.yaml#/components/errors/invalid_zip'
- $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:
teleporter:
post:
type: object
properties:
processed:
type: array
items:
type: string
errors:
invalid_zip:
type: object
description: Invalid ZIP archive uploaded
properties:
error:
type: object
properties:
key:
type: string
description: "Machine-readable error type"
example: "invalid_zip"
message:
type: string
description: "Human-readable error message"
example: "Invalid ZIP file uploaded"
hint:
type: string
nullable: true
description: "No additional data available"
example: null
examples:
teleporter:
value:
processed:
- etc/pihole/pihole.toml
- etc/pihole/gravity.db->group
- etc/pihole/gravity.db->adlist
- etc/pihole/gravity.db->adlist_by_group

44
src/api/docs/docs.c Normal file
View File

@ -0,0 +1,44 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2021 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/docs
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "docs.h"
int api_docs(struct ftl_conn *api)
{
// Handle resource request by redirecting to "/"
if(strcmp(api->request->request_uri, "/api/docs") == 0)
{
log_debug(DEBUG_API, "Redirecting /api/docs --301--> /api/docs/");
mg_send_http_redirect(api->conn, "/api/docs/", 301);
}
// Handle root request by redirecting to "/"
bool serve_index = false;
if(strcmp(api->request->request_uri, "/api/docs/") == 0)
{
serve_index = true;
}
// Loop over all available files and see if we can serve this request
for(unsigned int i = 0; i < (sizeof(docs_files)/sizeof(docs_files[0])); i++)
{
// Check if this is the requested file
if(strcmp(docs_files[i].path, api->item) == 0 ||
(serve_index && strcmp(docs_files[i].path, "index.html") == 0))
{
// Send the file
mg_send_http_ok(api->conn, docs_files[i].mime_type, (long long)docs_files[i].content_size);
return mg_write(api->conn, docs_files[i].content, docs_files[i].content_size);
}
}
// Requested path was not found
return 0;
}

173
src/api/docs/docs.h Normal file
View File

@ -0,0 +1,173 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2021 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/docs (helper)
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef API_DOCS_H
#define API_DOCS_H
#include "FTL.h"
#include "webserver/civetweb/civetweb.h"
#include "webserver/http-common.h"
#include "webserver/json_macros.h"
#include "api/api.h"
static const unsigned char index_html[] = {
#include "hex/index.html"
};
static const unsigned char index_css[] = {
#include "hex/index.css"
};
static const unsigned char pi_hole_js[] = {
#include "hex/pi-hole.js"
};
static const unsigned char rapidoc_min_js[] = {
#include "hex/external/rapidoc-min.js"
};
static const unsigned char rapidoc_min_js_map[] = {
#include "hex/external/rapidoc-min.js.map"
};
static const unsigned char highlight_default_min_css[] = {
#include "hex/external/highlight-default.min.css"
};
static const unsigned char highlight_min_js[] = {
#include "hex/external/highlight.min.js"
};
static const unsigned char images_logo_svg[] = {
#include "hex/images/logo.svg"
};
static const unsigned char specs_auth_yaml[] = {
#include "hex/specs/auth.yaml"
};
static const unsigned char specs_clients_yaml[] = {
#include "hex/specs/clients.yaml"
};
static const unsigned char specs_common_yaml[] = {
#include "hex/specs/common.yaml"
};
static const unsigned char specs_dhcp_yaml[] = {
#include "hex/specs/dhcp.yaml"
};
static const unsigned char specs_dns_yaml[] = {
#include "hex/specs/dns.yaml"
};
static const unsigned char specs_docs_yaml[] = {
#include "hex/specs/docs.yaml"
};
static const unsigned char specs_domains_yaml[] = {
#include "hex/specs/domains.yaml"
};
static const unsigned char specs_info_yaml[] = {
#include "hex/specs/info.yaml"
};
static const unsigned char specs_groups_yaml[] = {
#include "hex/specs/groups.yaml"
};
static const unsigned char specs_history_yaml[] = {
#include "hex/specs/history.yaml"
};
static const unsigned char specs_lists_yaml[] = {
#include "hex/specs/lists.yaml"
};
static const unsigned char specs_main_yaml[] = {
#include "hex/specs/main.yaml"
};
static const unsigned char specs_queries_yaml[] = {
#include "hex/specs/queries.yaml"
};
static const unsigned char specs_stats_yaml[] = {
#include "hex/specs/stats.yaml"
};
static const unsigned char specs_config_yaml[] = {
#include "hex/specs/config.yaml"
};
static const unsigned char specs_network_yaml[] = {
#include "hex/specs/network.yaml"
};
static const unsigned char specs_logs_yaml[] = {
#include "hex/specs/logs.yaml"
};
static const unsigned char specs_endpoints_yaml[] = {
#include "hex/specs/endpoints.yaml"
};
static const unsigned char specs_teleporter_yaml[] = {
#include "hex/specs/teleporter.yaml"
};
static const unsigned char specs_search_yaml[] = {
#include "hex/specs/search.yaml"
};
static const unsigned char specs_action_yaml[] = {
#include "hex/specs/action.yaml"
};
struct {
const char *path;
const char *mime_type;
const char *content;
const size_t content_size;
} docs_files[] =
{
{"index.html", "text/html", (const char*)index_html, sizeof(index_html)},
{"index.css", "text/css", (const char*)index_css, sizeof(index_css)},
{"pi-hole.js", "application/javascript", (const char*)pi_hole_js, sizeof(pi_hole_js)},
{"external/rapidoc-min.js", "application/javascript", (const char*)rapidoc_min_js, sizeof(rapidoc_min_js)},
{"external/rapidoc-min.js.map", "text/plain", (const char*)rapidoc_min_js_map, sizeof(rapidoc_min_js_map)},
{"external/highlight-default.min.css", "text/css", (const char*)highlight_default_min_css, sizeof(highlight_default_min_css)},
{"external/highlight.min.js", "application/javascript", (const char*)highlight_min_js, sizeof(highlight_min_js)},
{"images/logo.svg", "image/svg+xml", (const char*)images_logo_svg, sizeof(images_logo_svg)},
{"specs/auth.yaml", "text/plain", (const char*)specs_auth_yaml, sizeof(specs_auth_yaml)},
{"specs/clients.yaml", "text/plain", (const char*)specs_clients_yaml, sizeof(specs_clients_yaml)},
{"specs/config.yaml", "text/plain", (const char*)specs_config_yaml, sizeof(specs_config_yaml)},
{"specs/common.yaml", "text/plain", (const char*)specs_common_yaml, sizeof(specs_common_yaml)},
{"specs/dhcp.yaml", "text/plain", (const char*)specs_dhcp_yaml, sizeof(specs_dhcp_yaml)},
{"specs/dns.yaml", "text/plain", (const char*)specs_dns_yaml, sizeof(specs_dns_yaml)},
{"specs/domains.yaml", "text/plain", (const char*)specs_domains_yaml, sizeof(specs_domains_yaml)},
{"specs/docs.yaml", "text/plain", (const char*)specs_docs_yaml, sizeof(specs_docs_yaml)},
{"specs/endpoints.yaml", "text/plain", (const char*)specs_endpoints_yaml, sizeof(specs_endpoints_yaml)},
{"specs/groups.yaml", "text/plain", (const char*)specs_groups_yaml, sizeof(specs_groups_yaml)},
{"specs/history.yaml", "text/plain", (const char*)specs_history_yaml, sizeof(specs_history_yaml)},
{"specs/info.yaml", "text/plain", (const char*)specs_info_yaml, sizeof(specs_info_yaml)},
{"specs/lists.yaml", "text/plain", (const char*)specs_lists_yaml, sizeof(specs_lists_yaml)},
{"specs/logs.yaml", "text/plain", (const char*)specs_logs_yaml, sizeof(specs_logs_yaml)},
{"specs/main.yaml", "text/plain", (const char*)specs_main_yaml, sizeof(specs_main_yaml)},
{"specs/network.yaml", "text/plain", (const char*)specs_network_yaml, sizeof(specs_network_yaml)},
{"specs/queries.yaml", "text/plain", (const char*)specs_queries_yaml, sizeof(specs_queries_yaml)},
{"specs/search.yaml", "text/plain", (const char*)specs_search_yaml, sizeof(specs_search_yaml)},
{"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)},
};
#endif // API_DOCS_H

264
src/api/history.c Normal file
View File

@ -0,0 +1,264 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2021 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation
*
* 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"
#include "shmem.h"
#include "datastructure.h"
// overTime data
#include "overTime.h"
// config struct
#include "config/config.h"
// read_setupVarsconf()
#include "config/setupVars.h"
// get_aliasclient_list()
#include "database/aliasclients.h"
int api_history(struct ftl_conn *api)
{
lock_shm();
// Loop over all overTime slots and add them to the array
cJSON *history = JSON_NEW_ARRAY();
for(unsigned int slot = 0; slot < OVERTIME_SLOTS; slot++)
{
cJSON *item = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(item, "timestamp", overTime[slot].timestamp);
JSON_ADD_NUMBER_TO_OBJECT(item, "total", overTime[slot].total);
JSON_ADD_NUMBER_TO_OBJECT(item, "cached", overTime[slot].cached);
JSON_ADD_NUMBER_TO_OBJECT(item, "blocked", overTime[slot].blocked);
JSON_ADD_NUMBER_TO_OBJECT(item, "forwarded", overTime[slot].forwarded);
JSON_ADD_ITEM_TO_ARRAY(history, item);
}
// Unlock already here to avoid keeping the lock during JSON generation
// This is safe because we don't access any shared memory after this
// point. All numbers in the JSON are copied
unlock_shm();
// Minimum structure is
// {"history":[]}
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "history", history);
JSON_SEND_OBJECT(json);
}
static unsigned int build_client_temparray(int *temparray, const int slot)
{
// Clear temporary array
memset(temparray, 0, 2 * counters->clients * sizeof(int));
unsigned int num_clients = 0;
for(int clientID = 0; clientID < counters->clients; clientID++)
{
// Get client pointer
const clientsData* client = getClient(clientID, true);
// Skip invalid (recycled) clients
if(client == NULL)
continue;
// If this client is managed by an alias-client, we substitute
// -1 for the total count
if(!client->flags.aliasclient && client->aliasclient_id > -1)
{
log_debug(DEBUG_API, "Skipping client (ID %d) contained in alias-client with ID %d",
clientID, client->aliasclient_id);
continue;
}
else
// Store clientID and number of queries in temporary array
// If the slot is -1, we return the total number of queries.
// Otherwise, we return the number of queries in the given time
// slot
temparray[2*num_clients + 0] = clientID;
temparray[2*num_clients + 1] = slot < 0 ? client->count : client->overTime[slot];
// Increase number of clients by one
num_clients++;
}
return num_clients;
}
int api_history_clients(struct ftl_conn *api)
{
// Exit before processing any data if requested via config setting
if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS_CLIENTS)
{
// Minimum structure is
// {"history":[], "clients":[]}
cJSON *json = JSON_NEW_OBJECT();
cJSON *history = JSON_NEW_ARRAY();
JSON_ADD_ITEM_TO_OBJECT(json, "history", history);
cJSON *clients = JSON_NEW_ARRAY();
JSON_ADD_ITEM_TO_OBJECT(json, "clients", clients);
JSON_SEND_OBJECT_UNLOCK(json);
}
// Get number of clients to return
unsigned int Nc = min(counters->clients, config.webserver.api.maxClients.v.u16);
if(api->request->query_string != NULL)
{
// Does the user request a non-default number of clients
get_uint_var(api->request->query_string, "N", &Nc);
}
// Limit the number of clients to the maximum number of clients
if(Nc == 0 || Nc > (unsigned int)counters->clients)
{
// Return all clients
Nc = counters->clients;
}
// Lock shared memory
lock_shm();
// Allocate memory for the temporary buffer for ranking our clients
int *temparray = calloc(counters->clients, 2 * sizeof(int));
if(temparray == NULL)
{
unlock_shm();
return send_json_error(api, 500,
"internal_error",
"Failed to allocate memory for temporary array",
NULL);
}
// Get MAX_CLIENTS clients with the highest number of queries
// Skip clients included in others (in alias-clients)
unsigned int num_clients = build_client_temparray(temparray, -1);
if(config.webserver.api.client_history_global_max.v.b)
{
// Sort temporary array. Even when the array itself has <counters.clients>
// elements, we only sort the first <clients> elements to avoid sorting
// the whole array (the final elements are not used when clients have been
// skipped above, e.g. alias-clients or recycled clients)
qsort(temparray, num_clients, sizeof(int[2]), cmpdesc);
}
// Main return loop
int others_total = 0;
cJSON *history = JSON_NEW_ARRAY();
for(unsigned int slot = 0; slot < OVERTIME_SLOTS; slot++)
{
cJSON *item = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(item, "timestamp", overTime[slot].timestamp);
// If we are not in global-max mode, we need to build the temporary
// client array for each slot individually
if(!config.webserver.api.client_history_global_max.v.b)
{
// Collect global client data
num_clients = build_client_temparray(temparray, slot);
// Sort temporary array. Even when the array itself has <counters.clients>
// elements, we only sort the first <clients> elements to avoid sorting
// the whole array (the final elements are not used when clients have been
// skipped above, e.g. alias-clients or recycled clients)
qsort(temparray, num_clients, sizeof(int[2]), cmpdesc);
}
// Loop over clients to generate output to be sent to the client
int others = 0;
cJSON *data = JSON_NEW_OBJECT();
for(unsigned int arrayID = 0; arrayID < num_clients; arrayID++)
{
// Get client pointer
const int clientID = temparray[2*arrayID + 0];
// All clientIDs will be valid because we only added
// valid clients to the temparray
const clientsData* client = getClient(clientID, true);
// Skip further clients when we reached the maximum
// number of clients to return They are summed together
// under the special "other" client
// -1 because of the special "other" client we add below
// This is disabled when Nc is 0, which means we want to return
// all clients
if(arrayID >= Nc - 1)
{
others += client->overTime[slot];
continue;
}
// Add client to the array
cJSON_AddNumberToObject(data, getstr(client->ippos), client->overTime[slot]);
}
// Add others as last element in the array
others_total += others;
JSON_ADD_NUMBER_TO_OBJECT(data, "others", others);
JSON_ADD_ITEM_TO_OBJECT(item, "data", data);
JSON_ADD_ITEM_TO_ARRAY(history, item);
}
// Loop over clients to generate output to be sent to the client
cJSON *clients = JSON_NEW_OBJECT();
for(unsigned int arrayID = 0; arrayID < num_clients; arrayID++)
{
// Get client pointer
const int clientID = temparray[2*arrayID + 0];
// All clientIDs will be valid because we only added
// valid clients to the temparray
const clientsData* client = getClient(clientID, true);
// Break once we reached the maximum number of clients to return
// -1 because of the special "other" client we add below
// This is disabled when
// - N is 0, which means we want to return all clients, or
// - when we are NOT in global-max mode as we need to return all
// clients in that case
if(config.webserver.api.client_history_global_max.v.b && arrayID >= Nc - 1)
break;
// Get client name and IP address
const char *client_ip = getstr(client->ippos);
const char *client_name = client->namepos != 0 ? getstr(client->namepos) : NULL;
// Create JSON object for this client
cJSON *item = JSON_NEW_OBJECT();
JSON_REF_STR_IN_OBJECT(item, "name", client_name);
JSON_ADD_NUMBER_TO_OBJECT(item, "total", client->count);
JSON_ADD_ITEM_TO_OBJECT(clients, client_ip, item);
}
// Unlock already here to avoid keeping the lock during JSON generation
// This is safe because we don't access any shared memory after this
// point and all strings in the JSON are references to idempotent shared
// memory and can, thus, be accessed at any time without locking
unlock_shm();
// Add "others" client only if there are more clients than we return
// and if we are not returning all clients
if(num_clients > Nc)
{
cJSON *item = JSON_NEW_OBJECT();
JSON_REF_STR_IN_OBJECT(item, "name", "other clients");
JSON_ADD_NUMBER_TO_OBJECT(item, "total", others_total);
JSON_ADD_ITEM_TO_OBJECT(clients, "others", item);
}
// Free memory
free(temparray);
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "history", history);
JSON_ADD_ITEM_TO_OBJECT(json, "clients", clients);
JSON_SEND_OBJECT(json);
}

1074
src/api/info.c Normal file

File diff suppressed because it is too large Load Diff

936
src/api/list.c Normal file
View File

@ -0,0 +1,936 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2020 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/{allow,deny}list
*
* 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"
#include "database/gravity-db.h"
#include "events.h"
#include "shmem.h"
// getNameFromIP()
#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,
const int code,
const enum gravity_list_type listtype,
const char *item,
cJSON *processed)
{
const char *sql_msg = NULL;
if(!gravityDB_readTable(listtype, item, &sql_msg, true, NULL))
{
return send_json_error(api, 400, // 400 Bad Request
"database_error",
"Could not read domains from database table",
sql_msg);
}
tablerow table = { 0 };
cJSON *rows = JSON_NEW_ARRAY();
while(gravityDB_readTableGetRow(listtype, &table, &sql_msg))
{
cJSON *row = JSON_NEW_OBJECT();
// Special fields
if(listtype == GRAVITY_GROUPS)
{
JSON_COPY_STR_TO_OBJECT(row, "name", table.name);
JSON_COPY_STR_TO_OBJECT(row, "comment", table.comment);
}
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
JSON_COPY_STR_TO_OBJECT(row, "address", table.address);
JSON_COPY_STR_TO_OBJECT(row, "comment", table.comment);
}
else if(listtype == GRAVITY_CLIENTS)
{
char *name = NULL;
if(table.client != NULL)
{
// Try to obtain hostname
if(isValidIPv4(table.client) || isValidIPv6(table.client))
name = getNameFromIP(NULL, table.client);
else if(isMAC(table.client))
name = getNameFromMAC(table.client);
}
JSON_COPY_STR_TO_OBJECT(row, "client", table.client);
JSON_COPY_STR_TO_OBJECT(row, "name", name);
JSON_COPY_STR_TO_OBJECT(row, "comment", table.comment);
// Free allocated memory (if applicable)
if(name != NULL)
free(name);
}
else // domainlists
{
char *unicode = NULL;
const int rc = idn2_to_unicode_lzlz(table.domain, &unicode, IDN2_NONTRANSITIONAL);
JSON_COPY_STR_TO_OBJECT(row, "domain", table.domain);
if(rc == IDN2_OK)
JSON_COPY_STR_TO_OBJECT(row, "unicode", unicode);
else
JSON_COPY_STR_TO_OBJECT(row, "unicode", table.domain);
JSON_REF_STR_IN_OBJECT(row, "type", table.type);
JSON_REF_STR_IN_OBJECT(row, "kind", table.kind);
JSON_COPY_STR_TO_OBJECT(row, "comment", table.comment);
if(unicode != NULL)
free(unicode);
}
// Groups don't have the groups property
if(listtype != GRAVITY_GROUPS)
{
if(table.group_ids != NULL)
{
const int ret = parse_groupIDs(api, &table, row);
if(ret != 0)
{
JSON_DELETE(rows);
return ret;
}
}
else
{
// Empty group set
cJSON *group_ids = JSON_NEW_ARRAY();
JSON_ADD_ITEM_TO_OBJECT(row, "groups", group_ids);
}
}
// Clients don't have the enabled property
if(listtype != GRAVITY_CLIENTS)
JSON_ADD_BOOL_TO_OBJECT(row, "enabled", table.enabled);
// Add read-only database parameters
JSON_ADD_NUMBER_TO_OBJECT(row, "id", table.id);
JSON_ADD_NUMBER_TO_OBJECT(row, "date_added", table.date_added);
JSON_ADD_NUMBER_TO_OBJECT(row, "date_modified", table.date_modified);
// Properties added in https://github.com/pi-hole/pi-hole/pull/3951
if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
JSON_REF_STR_IN_OBJECT(row, "type", table.type);
JSON_ADD_NUMBER_TO_OBJECT(row, "date_updated", table.date_updated);
JSON_ADD_NUMBER_TO_OBJECT(row, "number", table.number);
JSON_ADD_NUMBER_TO_OBJECT(row, "invalid_domains", table.invalid_domains);
JSON_ADD_NUMBER_TO_OBJECT(row, "abp_entries", table.abp_entries);
JSON_ADD_NUMBER_TO_OBJECT(row, "status", table.status);
}
JSON_ADD_ITEM_TO_ARRAY(rows, row);
}
gravityDB_readTableFinalize();
if(sql_msg == NULL)
{
// No error, send domains array
const char *objname;
cJSON *json = JSON_NEW_OBJECT();
if(listtype == GRAVITY_GROUPS)
objname = "groups";
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
objname = "lists";
else if(listtype == GRAVITY_CLIENTS)
objname = "clients";
else // domainlists
objname = "domains";
JSON_ADD_ITEM_TO_OBJECT(json, objname, rows);
// Add processed count (if applicable)
if(processed != NULL)
JSON_ADD_ITEM_TO_OBJECT(json, "processed", processed);
JSON_SEND_OBJECT_CODE(json, code);
}
else
{
JSON_DELETE(rows);
return send_json_error(api, 400, // 400 Bad Request
"database_error",
"Could not read from gravity database",
sql_msg);
}
}
static int api_list_write(struct ftl_conn *api,
const enum gravity_list_type listtype,
const char *item)
{
tablerow row = { 0 };
// Check if valid JSON payload is available
const int json_ret = check_json_payload(api);
if(json_ret != 0)
return json_ret;
bool spaces_allowed = false;
bool allocated_json = false;
if(api->method == HTTP_POST)
{
// Extract domain/name/client/address from payload when using POST, all
// others specify it as URI-component
switch(listtype)
{
case GRAVITY_DOMAINLIST_ALLOW_EXACT:
case GRAVITY_DOMAINLIST_ALLOW_REGEX:
case GRAVITY_DOMAINLIST_DENY_EXACT:
case GRAVITY_DOMAINLIST_DENY_REGEX:
{
cJSON* json_domain = cJSON_GetObjectItemCaseSensitive(api->payload.json, "domain");
if(cJSON_IsString(json_domain) && strlen(json_domain->valuestring) > 0)
{
row.items = cJSON_CreateArray();
cJSON_AddItemToArray(row.items, cJSON_CreateStringReference(json_domain->valuestring));
allocated_json = true;
}
else if(cJSON_IsArray(json_domain) && cJSON_GetArraySize(json_domain) > 0)
row.items = json_domain;
else
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: No valid \"domain\" in payload (must be either string or array)",
NULL);
}
break;
}
case GRAVITY_GROUPS:
{
spaces_allowed = true;
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(api->payload.json, "name");
if(cJSON_IsString(json_name) && strlen(json_name->valuestring) > 0)
{
row.items = cJSON_CreateArray();
cJSON_AddItemToArray(row.items, cJSON_CreateStringReference(json_name->valuestring));
allocated_json = true;
}
else if(cJSON_IsArray(json_name) && cJSON_GetArraySize(json_name) > 0)
row.items = json_name;
else
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: No valid \"name\" in payload (must be either string or array)",
NULL);
}
break;
}
case GRAVITY_CLIENTS:
{
cJSON *json_client = cJSON_GetObjectItemCaseSensitive(api->payload.json, "client");
if(cJSON_IsString(json_client) && strlen(json_client->valuestring) > 0)
{
row.items = cJSON_CreateArray();
cJSON_AddItemToArray(row.items, cJSON_CreateStringReference(json_client->valuestring));
allocated_json = true;
}
else if(cJSON_IsArray(json_client) && cJSON_GetArraySize(json_client) > 0)
row.items = json_client;
else
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: No valid \"client\" in payload (must be either string or array)",
NULL);
}
break;
}
case GRAVITY_ADLISTS:
case GRAVITY_ADLISTS_BLOCK:
case GRAVITY_ADLISTS_ALLOW:
{
cJSON *json_address = cJSON_GetObjectItemCaseSensitive(api->payload.json, "address");
if(cJSON_IsString(json_address) && strlen(json_address->valuestring) > 0)
{
row.items = cJSON_CreateArray();
cJSON_AddItemToArray(row.items, cJSON_CreateStringReference(json_address->valuestring));
allocated_json = true;
}
else if(cJSON_IsArray(json_address) && cJSON_GetArraySize(json_address) > 0)
row.items = json_address;
else
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: No valid \"address\" in payload (must be either string or array)",
NULL);
}
break;
}
// Aggregate types (and gravity) are not handled by this routine
case GRAVITY_DOMAINLIST_ALL_ALL:
case GRAVITY_DOMAINLIST_ALL_EXACT:
case GRAVITY_DOMAINLIST_ALL_REGEX:
case GRAVITY_DOMAINLIST_ALLOW_ALL:
case GRAVITY_DOMAINLIST_DENY_ALL:
case GRAVITY_GRAVITY:
case GRAVITY_ANTIGRAVITY:
return send_json_error(api, 400, // 400 Bad Request
"bad_request",
"Aggregate types (and gravity) are not handled by this routine",
NULL);
}
}
else
{
// PUT = Use URI item
row.items = cJSON_CreateArray();
cJSON_AddItemToArray(row.items, cJSON_CreateStringReference(item));
allocated_json = true;
}
cJSON *json_comment = cJSON_GetObjectItemCaseSensitive(api->payload.json, "comment");
if(cJSON_IsString(json_comment) && strlen(json_comment->valuestring) > 0)
row.comment = json_comment->valuestring;
else
row.comment = NULL; // Default value
// Check if there is a type field in the payload (only for lists)
if(listtype == GRAVITY_ADLISTS)
{
cJSON *json_type = cJSON_GetObjectItemCaseSensitive(api->payload.json, "type");
if(cJSON_IsString(json_type) && strlen(json_type->valuestring) > 0)
row.type_int = strcasecmp(json_type->valuestring, "allow") == 0 ? ADLIST_ALLOW : ADLIST_BLOCK;
else
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: No valid item \"type\" in payload",
NULL);
}
}
else if(listtype == GRAVITY_ADLISTS_BLOCK)
row.type_int = ADLIST_BLOCK;
else if(listtype == GRAVITY_ADLISTS_ALLOW)
row.type_int = ADLIST_ALLOW;
else
{
cJSON *json_type = cJSON_GetObjectItemCaseSensitive(api->payload.json, "type");
if(cJSON_IsString(json_type) && strlen(json_type->valuestring) > 0)
row.type = json_type->valuestring;
else
row.type = NULL; // Default value
}
cJSON *json_kind = cJSON_GetObjectItemCaseSensitive(api->payload.json, "kind");
if(cJSON_IsString(json_kind) && strlen(json_kind->valuestring) > 0)
row.kind = json_kind->valuestring;
else
row.kind = NULL; // Default value
cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(api->payload.json, "enabled");
if (cJSON_IsBool(json_enabled))
row.enabled = cJSON_IsTrue(json_enabled);
else
row.enabled = true; // Default value
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(api->payload.json, "name");
if(cJSON_IsString(json_name) && strlen(json_name->valuestring) > 0)
row.name = json_name->valuestring;
else
row.name = NULL; // Default value
bool okay = true;
char *regex_msg = NULL;
if(listtype == GRAVITY_DOMAINLIST_ALLOW_REGEX || listtype == GRAVITY_DOMAINLIST_DENY_REGEX)
{
// Test validity of this regex
regexData regex = { 0 };
cJSON *it = NULL;
cJSON_ArrayForEach(it, row.items)
{
// If any element isn't a string, break early
if(!cJSON_IsString(it))
{
okay = false;
break;
}
// Check every array element for its validity
okay = compile_regex(it->valuestring, &regex, &regex_msg);
// Free regex after successful compilation
if(regex.available)
{
regfree(&regex.regex);
free(regex.string);
}
// Fail fast if any regex in the passed array is invalid
if(!okay)
break;
}
}
else if(!spaces_allowed)
{
cJSON *it = NULL;
cJSON_ArrayForEach(it, row.items)
{
// If any element isn't a string, break early
if(!cJSON_IsString(it))
{
okay = false;
break;
}
// Check validity: Spaces are not allowed in any domain/URL
if(strchr(it->valuestring, ' ') != NULL ||
strchr(it->valuestring, '\t') != NULL ||
strchr(it->valuestring, '\n') != NULL)
{
if(allocated_json)
cJSON_free(row.items);
return send_json_error(api, 400, // 400 Bad Request
"bad_request",
"Spaces, newlines and tabs are not allowed in domains and URLs",
it->valuestring);
}
if(listtype == GRAVITY_DOMAINLIST_ALLOW_EXACT ||
listtype == GRAVITY_DOMAINLIST_DENY_EXACT)
{
char *punycode = NULL;
const int rc = idn2_to_ascii_lz(it->valuestring, &punycode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
if (rc != IDN2_OK)
{
// Invalid domain name
return send_json_error(api, 400,
"bad_request",
"Invalid request: Invalid domain name",
idn2_strerror(rc));
}
// Convert punycode domain to lowercase
for(unsigned int i = 0u; i < strlen(punycode); i++)
punycode[i] = tolower(punycode[i]);
// Validate punycode domain
// This will reject domains like äöü{{{.com
// which convert to xn--{{{-pla4gpb.com
if(!valid_domain(punycode, strlen(punycode), false))
{
if(allocated_json)
cJSON_free(row.items);
return send_json_error(api, 400, // 400 Bad Request
"bad_request",
"Invalid domain",
it->valuestring);
}
// Replace domain with punycode version
if(!(it->type & cJSON_IsReference))
free(it->valuestring);
it->valuestring = punycode;
// Remove reference flag
it->type &= ~cJSON_IsReference;
}
}
}
// Fail fast if any regex in the passed array is invalid
if(!okay)
{
// Send error reply
if(allocated_json)
cJSON_free(row.items);
return send_json_error_free(api, 400, // 400 Bad Request
"regex_error",
"Regex validation failed",
regex_msg, true);
}
// Try to add item(s) to table
const char *sql_msg = NULL;
cJSON *elem = NULL;
cJSON *processed = JSON_NEW_OBJECT();
cJSON *errors = JSON_NEW_ARRAY();
cJSON *success = JSON_NEW_ARRAY();
cJSON_AddItemToObject(processed, "errors", errors);
cJSON_AddItemToObject(processed, "success", success);
cJSON_ArrayForEach(elem, row.items)
{
row.item = elem->valuestring;
if((okay = gravityDB_addToTable(listtype, &row, &sql_msg, api->method)))
{
if(listtype != GRAVITY_GROUPS)
{
cJSON *groups = cJSON_GetObjectItemCaseSensitive(api->payload.json, "groups");
if(groups != NULL)
okay = gravityDB_edit_groups(listtype, groups, &row, &sql_msg);
else
// The groups array is optional, we still succeed if it
// is omitted (groups stay as they are)
okay = true;
}
else
{
// Groups cannot be assigned to groups
okay = true;
}
}
cJSON *details = JSON_NEW_OBJECT();
JSON_COPY_STR_TO_OBJECT(details, "item", row.item);
if(!okay)
JSON_COPY_STR_TO_OBJECT(details, "error", sql_msg);
cJSON_AddItemToArray(okay ? success : errors, details);
}
// Inform the resolver that it needs to reload gravity
set_event(RELOAD_GRAVITY);
int response_code = 201; // 201 - Created
if(api->method == HTTP_PUT)
response_code = 200; // 200 - OK
// Add "Location" header to response
if(snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers), "Location: %s/%s", api->action_path, row.item) >= (int)sizeof(pi_hole_extra_headers))
{
// This may happen for *extremely* long URLs but is not issue in
// itself. Merely add a warning to the log file
log_warn("Could not add Location header to response: URL too long");
// Truncate location by replacing the last characters with "...\0"
pi_hole_extra_headers[sizeof(pi_hole_extra_headers)-4] = '.';
pi_hole_extra_headers[sizeof(pi_hole_extra_headers)-3] = '.';
pi_hole_extra_headers[sizeof(pi_hole_extra_headers)-2] = '.';
pi_hole_extra_headers[sizeof(pi_hole_extra_headers)-1] = '\0';
}
// Send GET style reply
const int ret = api_list_read(api, response_code, listtype, row.item, processed);
// Free allocated memory
if(allocated_json)
cJSON_free(row.items);
return ret;
}
static int api_list_remove(struct ftl_conn *api,
const enum gravity_list_type listtype,
const char *item)
{
const char *sql_msg = NULL;
cJSON *array = api->payload.json;
bool allocated_json = false;
// If this is not a :batchDelete call, then the item is specified in the
// URI, not in the payload. Create a JSON array with the item and use
// that instead
const bool isBatchDelete = api->opts.flags & API_BATCHDELETE;
// If this is a domain callback, we need to translate type/kind into an
// integer for use in the database
if(listtype == GRAVITY_DOMAINLIST_ALLOW_EXACT ||
listtype == GRAVITY_DOMAINLIST_DENY_EXACT ||
listtype == GRAVITY_DOMAINLIST_ALLOW_REGEX ||
listtype == GRAVITY_DOMAINLIST_DENY_REGEX ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
int type = -1;
switch (listtype)
{
case GRAVITY_DOMAINLIST_ALLOW_EXACT:
type = 0;
break;
case GRAVITY_DOMAINLIST_DENY_EXACT:
type = 1;
break;
case GRAVITY_DOMAINLIST_ALLOW_REGEX:
type = 2;
break;
case GRAVITY_DOMAINLIST_DENY_REGEX:
type = 3;
break;
case GRAVITY_ADLISTS_BLOCK:
type = ADLIST_BLOCK;
break;
case GRAVITY_ADLISTS_ALLOW:
type = ADLIST_ALLOW;
break;
// Not handled herein
case GRAVITY_GROUPS:
case GRAVITY_ADLISTS:
case GRAVITY_CLIENTS:
case GRAVITY_GRAVITY:
case GRAVITY_ANTIGRAVITY:
case GRAVITY_DOMAINLIST_ALLOW_ALL:
case GRAVITY_DOMAINLIST_DENY_ALL:
case GRAVITY_DOMAINLIST_ALL_EXACT:
case GRAVITY_DOMAINLIST_ALL_REGEX:
case GRAVITY_DOMAINLIST_ALL_ALL:
default:
break;
}
// Create new JSON array with the item and type:
// array = [{"item": "example.com", "type": 0}]
array = cJSON_CreateArray();
cJSON *obj = cJSON_CreateObject();
cJSON_AddItemToObject(obj, "item", cJSON_CreateStringReference(item));
cJSON_AddItemToObject(obj, "type", cJSON_CreateNumber(type));
cJSON_AddItemToArray(array, obj);
allocated_json = true;
}
else if(isBatchDelete && listtype == GRAVITY_DOMAINLIST_ALL_ALL)
{
// Loop over all items and parse type/kind for each item
cJSON *it = NULL;
cJSON_ArrayForEach(it, array)
{
if(!cJSON_IsObject(it))
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: Batch delete requires an array of objects",
NULL);
}
// Check if item is a string
cJSON *json_item = cJSON_GetObjectItemCaseSensitive(it, "item");
if(!cJSON_IsString(json_item))
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: Batch delete requires an array of objects with \"item\" as string",
NULL);
}
// Check if type and kind are both present and strings
cJSON *json_type = cJSON_GetObjectItemCaseSensitive(it, "type");
cJSON *json_kind = cJSON_GetObjectItemCaseSensitive(it, "kind");
if(!cJSON_IsString(json_type) || !cJSON_IsString(json_kind))
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: Batch delete requires an array of objects with \"type\" and \"kind\" as string",
NULL);
}
// Parse type and kind
// 0 = allow exact
// 1 = deny exact
// 2 = allow regex
// 3 = deny regex
int type = -1;
if(strcasecmp(json_type->valuestring, "allow") == 0)
{
if(strcasecmp(json_kind->valuestring, "exact") == 0)
type = 0;
else if(strcasecmp(json_kind->valuestring, "regex") == 0)
type = 2;
}
else if(strcasecmp(json_type->valuestring, "deny") == 0)
{
if(strcasecmp(json_kind->valuestring, "exact") == 0)
type = 1;
else if(strcasecmp(json_kind->valuestring, "regex") == 0)
type = 3;
}
// Check if type/kind combination is valid
if(type == -1)
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: Batch delete requires an valid combination of \"type\" and \"kind\" for each object",
NULL);
}
// Replace type/kind with integer type
// array = [{"item": "example.com", "type": 0}]
cJSON_DeleteItemFromObject(it, "type");
cJSON_DeleteItemFromObject(it, "kind");
cJSON_AddNumberToObject(it, "type", type);
}
}
else if(!isBatchDelete)
{
// Create array with object (used for clients, groups, lists)
// array = [{"item": <item>}]
array = cJSON_CreateArray();
cJSON *obj = cJSON_CreateObject();
cJSON_AddItemToObject(obj, "item", cJSON_CreateStringReference(item));
cJSON_AddItemToArray(array, obj);
allocated_json = true;
}
// Verify that the payload is an array of objects each containing an
// item
if(isBatchDelete)
{
cJSON *it = NULL;
cJSON_ArrayForEach(it, array)
{
if(!cJSON_IsObject(it))
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: Batch delete requires an array of objects",
NULL);
}
// Check if item is a string
cJSON *json_item = cJSON_GetObjectItemCaseSensitive(it, "item");
if(!cJSON_IsString(json_item))
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: Batch delete requires an array of objects with \"item\" as string",
NULL);
}
}
}
// From here on, we can assume the JSON payload is valid
unsigned int deleted = 0u;
if(gravityDB_delFromTable(listtype, array, &deleted, &sql_msg))
{
// Inform the resolver that it needs to reload gravity
set_event(RELOAD_GRAVITY);
// Free memory allocated above
if(allocated_json)
cJSON_free(array);
// Send empty reply with codes:
// - 204 No Content (if any items were deleted)
// - 404 Not Found (if no items were deleted)
cJSON *json = JSON_NEW_OBJECT();
JSON_SEND_OBJECT_CODE(json, deleted > 0u ? 204 : 404);
}
else
{
// Free memory allocated above
if(allocated_json)
cJSON_free(array);
// Send error reply
return send_json_error(api, 400,
"database_error",
"Could not remove entries from table",
sql_msg);
}
}
int api_list(struct ftl_conn *api)
{
enum gravity_list_type listtype;
bool can_modify = false;
bool batchDelete = false;
if((api->item = startsWith("/api/groups", api)) != NULL)
{
listtype = GRAVITY_GROUPS;
can_modify = true;
}
else if((api->item = startsWith("/api/groups:batchDelete", api)) != NULL)
{
listtype = GRAVITY_GROUPS;
can_modify = true;
batchDelete = true;
}
else if((api->item = startsWith("/api/lists", api)) != NULL)
{
listtype = GRAVITY_ADLISTS;
can_modify = true;
}
else if((api->item = startsWith("/api/lists:batchDelete", api)) != NULL)
{
listtype = GRAVITY_ADLISTS;
can_modify = true;
batchDelete = true;
}
else if((api->item = startsWith("/api/clients", api)) != NULL)
{
listtype = GRAVITY_CLIENTS;
can_modify = true;
}
else if((api->item = startsWith("/api/clients:batchDelete", api)) != NULL)
{
listtype = GRAVITY_CLIENTS;
can_modify = true;
batchDelete = true;
}
else if((api->item = startsWith("/api/domains/allow/exact", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_ALLOW_EXACT;
can_modify = true;
}
else if((api->item = startsWith("/api/domains/allow/regex", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_ALLOW_REGEX;
can_modify = true;
}
else if((api->item = startsWith("/api/domains/allow", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_ALLOW_ALL;
}
else if((api->item = startsWith("/api/domains/deny/exact", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_DENY_EXACT;
can_modify = true;
}
else if((api->item = startsWith("/api/domains/deny/regex", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_DENY_REGEX;
can_modify = true;
}
else if((api->item = startsWith("/api/domains/deny", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_DENY_ALL;
}
else if((api->item = startsWith("/api/domains/exact", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_ALL_EXACT;
}
else if((api->item = startsWith("/api/domains/regex", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_ALL_REGEX;
}
else if((api->item = startsWith("/api/domains", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_ALL_ALL;
}
else if((api->item = startsWith("/api/domains:batchDelete", api)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_ALL_ALL;
can_modify = true;
batchDelete = true;
}
else
{
return send_json_error(api, 400,
"bad_request",
"Invalid request: Specified endpoint not available",
api->request->local_uri_raw);
}
// If this is a request for a list, we check if there is a request
// parameter narrowing down which kind of list. If so, we modify the
// list type accordingly
if(listtype == GRAVITY_ADLISTS && api->request->query_string != NULL)
{
// Check if there is a type parameter
char typestr[16] = { 0 };
if(get_string_var(api->request->query_string, "type", typestr, sizeof(typestr)) > 0)
{
if(strcasecmp(typestr, "allow") == 0)
listtype = GRAVITY_ADLISTS_ALLOW;
else if(strcasecmp(typestr, "block") == 0)
listtype = GRAVITY_ADLISTS_BLOCK;
else
{
// Invalid type parameter
return send_json_error(api, 400,
"bad_request",
"Invalid request: Invalid type parameter (should be either \"allow\" or \"block\")",
api->request->query_string);
}
}
}
if(api->method == HTTP_GET)
{
// Read list item identified by URI (or read them all)
// We would not actually need the SHM lock here, however, we do
// this for simplicity to ensure nobody else is editing the
// lists while we're doing this here
lock_shm();
const int ret = api_list_read(api, 200, listtype, api->item, NULL);
unlock_shm();
return ret;
}
else if(can_modify && api->method == HTTP_PUT)
{
// Add/update item identified by URI
if(api->item != NULL && strlen(api->item) == 0)
{
return send_json_error(api, 400,
"uri_error",
"Invalid request: Specify item in URI",
NULL);
}
else
{
// We would not actually need the SHM lock here,
// however, we do this for simplicity to ensure nobody
// else is editing the lists while we're doing this here
lock_shm();
const int ret = api_list_write(api, listtype, api->item);
unlock_shm();
return ret;
}
}
else if(can_modify && api->method == HTTP_POST && !batchDelete)
{
// Add item to list identified by payload
if(api->item != NULL && strlen(api->item) != 0)
{
return send_json_error(api, 400,
"uri_error",
"Invalid request: Specify item in payload, not as URI parameter",
api->item);
}
else
{
// We would not actually need the SHM lock here,
// however, we do this for simplicity to ensure nobody
// else is editing the lists while we're doing this here
lock_shm();
const int ret = api_list_write(api, listtype, api->item);
unlock_shm();
return ret;
}
}
else if(can_modify && (api->method == HTTP_DELETE || (api->method == HTTP_POST && batchDelete)))
{
// Delete item from list
// We would not actually need the SHM lock here, however, we do
// this for simplicity to ensure nobody else is editing the
// lists while we're doing this here
lock_shm();
const int ret = api_list_remove(api, listtype, api->item);
unlock_shm();
return ret;
}
else if(!can_modify)
{
// This list type cannot be modified (e.g., ALL_ALL)
return send_json_error(api, 400,
"uri_error",
"Invalid request: Specify list to modify more precisely",
api->request->local_uri_raw);
}
else
{
// This results in error 404
return 0;
}
}

99
src/api/logs.c Normal file
View File

@ -0,0 +1,99 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/logs
*
* 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/api.h"
// struct fifologData
#include "log.h"
#include "config/config.h"
// main_pid()
#include "signals.h"
// fifologData is allocated in shared memory for cross-fork compatibility
int api_logs(struct ftl_conn *api)
{
unsigned int start = 0u;
if(api->request->query_string != NULL)
{
// Does the user request an ID to sent from?
unsigned int nextID;
if(get_uint_var(api->request->query_string, "nextID", &nextID))
{
if(nextID >= fifo_log->logs[api->opts.which].next_id)
{
// Do not return any data
start = LOG_SIZE;
}
else if((fifo_log->logs[api->opts.which].next_id > LOG_SIZE) && nextID < (fifo_log->logs[api->opts.which].next_id) - LOG_SIZE)
{
// Requested an ID smaller than the lowest one we have
// We return the entire buffer
start = 0u;
}
else if(fifo_log->logs[api->opts.which].next_id >= LOG_SIZE)
{
// Reply with partial buffer, measure from the end
// (the log is full)
start = LOG_SIZE - (fifo_log->logs[api->opts.which].next_id - nextID);
}
else
{
// Reply with partial buffer, measure from the start
// (the log is not yet full)
start = nextID;
}
}
}
// Process data
cJSON *json = JSON_NEW_OBJECT();
cJSON *log = JSON_NEW_ARRAY();
for(unsigned int i = start; i < LOG_SIZE; i++)
{
if(fifo_log->logs[api->opts.which].timestamp[i] < 1.0)
{
// Uninitialized buffer entry
break;
}
cJSON *entry = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(entry, "timestamp", fifo_log->logs[api->opts.which].timestamp[i]);
JSON_REF_STR_IN_OBJECT(entry, "message", fifo_log->logs[api->opts.which].message[i]);
JSON_REF_STR_IN_OBJECT(entry, "prio", fifo_log->logs[api->opts.which].prio[i]);
JSON_ADD_ITEM_TO_ARRAY(log, entry);
}
JSON_ADD_ITEM_TO_OBJECT(json, "log", log);
JSON_ADD_NUMBER_TO_OBJECT(json, "nextID", fifo_log->logs[api->opts.which].next_id);
JSON_ADD_NUMBER_TO_OBJECT(json, "pid", main_pid());
// Add file name
const char *logfile = NULL;
switch(api->opts.which)
{
case FIFO_FTL:
logfile = config.files.log.ftl.v.s;
break;
case FIFO_DNSMASQ:
logfile = config.files.log.dnsmasq.v.s;
break;
case FIFO_WEBSERVER:
logfile = config.files.log.webserver.v.s;
break;
case FIFO_MAX:
// This should never happen
break;
}
JSON_REF_STR_IN_OBJECT(json, "file", logfile);
// Send data
JSON_SEND_OBJECT(json);
}

View File

@ -1,120 +0,0 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* MessagePack serialization
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "FTL.h"
#include "api.h"
#include "socket.h"
#include "log.h"
void pack_eom(const int sock) {
// This byte is explicitly never used in the MessagePack spec, so it is perfect to use as an EOM for this API.
uint8_t eom = 0xc1;
write(sock, &eom, sizeof(eom));
}
static void pack_basic(const int sock, const uint8_t format, const void *value, const size_t size) {
write(sock, &format, sizeof(format));
write(sock, value, size);
}
static uint64_t __attribute__((const)) leToBe64(const uint64_t value) {
const char *ptr = (char *) &value;
uint32_t part1, part2;
// Copy the two halves of the 64 bit input into uint32_t's so we can use htonl
memcpy(&part1, ptr, 4);
memcpy(&part2, ptr + 4, 4);
// Flip each half around
part1 = htonl(part1);
part2 = htonl(part2);
// Arrange them to form the big-endian version of the original input
return (uint64_t) part1 << 32 | part2;
}
void pack_bool(const int sock, const bool value) {
uint8_t packed = (uint8_t) (value ? 0xc3 : 0xc2);
write(sock, &packed, sizeof(packed));
}
void pack_uint8(const int sock, const uint8_t value) {
pack_basic(sock, 0xcc, &value, sizeof(value));
}
void pack_uint64(const int sock, const uint64_t value) {
const uint64_t bigEValue = leToBe64(value);
pack_basic(sock, 0xcf, &bigEValue, sizeof(bigEValue));
}
void pack_int32(const int sock, const int32_t value) {
const uint32_t bigEValue = htonl((uint32_t) value);
pack_basic(sock, 0xd2, &bigEValue, sizeof(bigEValue));
}
void pack_int64(const int sock, const int64_t value) {
// Need to use memcpy to do a direct copy without reinterpreting the bytes (making negatives into positives).
// It should get optimized away.
uint64_t bigEValue;
memcpy(&bigEValue, &value, sizeof(bigEValue));
bigEValue = leToBe64(bigEValue);
pack_basic(sock, 0xd3, &bigEValue, sizeof(bigEValue));
}
void pack_float(const int sock, const float value) {
// Need to use memcpy to do a direct copy without reinterpreting the bytes. It should get optimized away.
uint32_t bigEValue;
memcpy(&bigEValue, &value, sizeof(bigEValue));
bigEValue = htonl(bigEValue);
pack_basic(sock, 0xca, &bigEValue, sizeof(bigEValue));
}
// Return true if successful
bool pack_fixstr(const int sock, const char *string) {
// Make sure that the length is less than 32
const size_t length = strlen(string);
if(length >= 32) {
logg("Tried to send a fixstr longer than 31 bytes!");
return false;
}
const uint8_t format = (uint8_t) (0xA0 | length);
write(sock, &format, sizeof(format));
write(sock, string, length);
return true;
}
// Return true if successful
bool pack_str32(const int sock, const char *string) {
// Make sure that the length is less than 4294967296
const size_t length = strlen(string);
if(length >= 2147483648u) {
logg("Tried to send a str32 longer than 2147483647 bytes!");
return false;
}
const uint8_t format = 0xdb;
write(sock, &format, sizeof(format));
const uint32_t bigELength = htonl((uint32_t) length);
write(sock, &bigELength, sizeof(bigELength));
write(sock, string, length);
return true;
}
void pack_map16_start(const int sock, const uint16_t length) {
const uint8_t format = 0xde;
write(sock, &format, sizeof(format));
const uint16_t bigELength = htons(length);
write(sock, &bigELength, sizeof(bigELength));
}

582
src/api/network.c Normal file
View File

@ -0,0 +1,582 @@
/* 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/network
*
* 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/api.h"
// Routing information and flags
#include <net/route.h>
// Iterate through directories
#include <dirent.h>
// networkrecord
#include "database/network-table.h"
// dbopen(false, )
#include "database/common.h"
// attach_database()
#include "database/query-table.h"
// config struct
#include "config/config.h"
static bool getDefaultInterface(char iface[IF_NAMESIZE], in_addr_t *gw)
{
// 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")))
{
// Parse /proc/net/route - the kernel's IPv4 routing table
char buf[1024] = { 0 };
while(fgets(buf, sizeof(buf), file))
{
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;
// Only analyze routes which are UP and whose
// destinations are a gateway
if(!(flags & RTF_UP) || !(flags & RTF_GATEWAY))
continue;
// Only analyze "catch all" routes (destination 0.0.0.0)
if(dest_r != 0)
continue;
// Store default gateway, overwrite if we find a route with
// a lower metric
if(metric < minmetric)
{
minmetric = metric;
*gw = gw_r;
strcpy(iface, iface_r);
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);
}
}
fclose(file);
}
else
log_err("Cannot read /proc/net/route: %s", strerror(errno));
// Return success based on having found the default gateway's address
return gw != 0;
}
int api_network_gateway(struct ftl_conn *api)
{
in_addr_t gw = 0;
char iface[IF_NAMESIZE] = { 0 };
// 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);
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));
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;
// 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);
JSON_ADD_ITEM_TO_OBJECT(json, "interfaces", interfaces);
JSON_SEND_OBJECT(json);
}
static int api_network_devices_GET(struct ftl_conn *api)
{
// Does the user request a custom number of devices to be included?
unsigned int device_count = 10;
get_uint_var(api->request->query_string, "max_devices", &device_count);
// Does the user request a custom number of addresses per device to be included?
unsigned int address_count = 3;
get_uint_var(api->request->query_string, "max_addresses", &address_count);
// Open pihole-FTL.db database file
sqlite3_stmt *device_stmt = NULL, *ip_stmt = NULL;
sqlite3 *db = dbopen(true, false);
if(db == NULL)
{
log_warn("Failed to open database in networkTable_readDevices()");
return false;
}
const char *sql_msg = NULL;
if(!networkTable_readDevices(db, &device_stmt, &sql_msg))
{
// Add SQL message (may be NULL = not available)
return send_json_error(api, 500,
"database_error",
"Could not read network details from database table",
sql_msg);
}
// Read record for a single device
cJSON *devices = JSON_NEW_ARRAY();
network_record network;
unsigned int device_counter = 0;
while(networkTable_readDevicesGetRecord(device_stmt, &network, &sql_msg) &&
device_counter++ < device_count)
{
cJSON *item = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(item, "id", network.id);
JSON_COPY_STR_TO_OBJECT(item, "hwaddr", network.hwaddr);
JSON_COPY_STR_TO_OBJECT(item, "interface", network.iface);
JSON_ADD_NUMBER_TO_OBJECT(item, "firstSeen", network.firstSeen);
JSON_ADD_NUMBER_TO_OBJECT(item, "lastQuery", network.lastQuery);
JSON_ADD_NUMBER_TO_OBJECT(item, "numQueries", network.numQueries);
JSON_COPY_STR_TO_OBJECT(item, "macVendor", network.macVendor);
// Build array of all IP addresses known associated to this client
cJSON *ips = JSON_NEW_ARRAY();
if(networkTable_readIPs(db, &ip_stmt, network.id, &sql_msg))
{
// Walk known IP addresses + names
network_addresses_record network_address;
unsigned int address_counter = 0;
while(networkTable_readIPsGetRecord(ip_stmt, &network_address, &sql_msg) &&
address_counter++ < address_count)
{
cJSON *ip = JSON_NEW_OBJECT();
JSON_COPY_STR_TO_OBJECT(ip, "ip", network_address.ip);
JSON_COPY_STR_TO_OBJECT(ip, "name", network_address.name);
JSON_ADD_NUMBER_TO_OBJECT(ip, "lastSeen", network_address.lastSeen);
JSON_ADD_NUMBER_TO_OBJECT(ip, "nameUpdated", network_address.nameUpdated);
JSON_ADD_ITEM_TO_ARRAY(ips, ip);
}
// Possible error handling
if(sql_msg != NULL)
{
cJSON_Delete(ips);
cJSON_Delete(devices);
return send_json_error(api, 500,
"database_error",
"Could not read network details from database table (getting IP records)",
sql_msg);
}
// Finalize sub-query
networkTable_readIPsFinalize(ip_stmt);
}
// Add array of IP addresses to device
JSON_ADD_ITEM_TO_OBJECT(item, "ips", ips);
// Add device to array of all devices
JSON_ADD_ITEM_TO_ARRAY(devices, item);
}
if(sql_msg != NULL)
{
cJSON_Delete(devices);
return send_json_error(api, 500,
"database_error",
"Could not read network details from database table (step)",
sql_msg);
}
// Finalize query
networkTable_readDevicesFinalize(device_stmt);
dbclose(&db);
// Return data to user
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "devices", devices);
JSON_SEND_OBJECT(json);
}
static int api_network_devices_DELETE(struct ftl_conn *api)
{
// Get device ID
int device_id = 0;
if(sscanf(api->item, "%i", &device_id) != 1)
{
return send_json_error(api, 400,
"invalid_request",
"Missing or invalid {id} parameter",
NULL);
}
// Open pihole-FTL.db database file
sqlite3 *db = dbopen(false, false);
if(db == NULL)
{
log_warn("Failed to open database in networkTable_readDevices()");
return false;
}
// Delete row from network table by ID
const char *sql_msg = NULL;
int deleted = 0;
if(!networkTable_deleteDevice(db, device_id, &deleted, &sql_msg))
{
// Add SQL message (may be NULL = not available)
return send_json_error(api, 500,
"database_error",
"Could not delete network details from database table",
sql_msg);
}
// Close database
dbclose(&db);
// Send empty reply with codes:
// - 204 No Content (if any items were deleted)
// - 404 Not Found (if no items were deleted)
cJSON *json = JSON_NEW_OBJECT();
JSON_SEND_OBJECT_CODE(json, deleted > 0 ? 204 : 404);
}
int api_network_devices(struct ftl_conn *api)
{
if(api->method == HTTP_GET)
{
return api_network_devices_GET(api);
}
else if(api->method == HTTP_DELETE)
{
return api_network_devices_DELETE(api);
}
else
{
return send_json_error(api, 405,
"method_not_allowed",
"Method not allowed",
NULL);
}
}
int api_client_suggestions(struct ftl_conn *api)
{
// Get client suggestions
if(api->method != HTTP_GET)
{
// This results in error 404
return 0;
}
// Does the user request a custom number of addresses per device to be included?
unsigned int count = 50;
get_uint_var(api->request->query_string, "count", &count);
bool ipv4_only = true;
get_bool_var(api->request->query_string, "ipv4_only", &ipv4_only);
// Open pihole-FTL.db database file connection
sqlite3 *db = dbopen(true, false);
// Attach gravity database
const char *message = "";
if(!attach_database(db, &message, config.files.gravity.v.s, "g"))
{
log_err("Failed to attach gravity database: %s", message);
dbclose(&db);
return send_json_error(api, 500,
"database_error",
"Could not attach gravity database",
message);
}
// Prepare SQL statement
sqlite3_stmt *stmt = NULL;
const char *sql = "SELECT n.hwaddr,n.macVendor,n.lastQuery,"
"(SELECT GROUP_CONCAT(DISTINCT na.ip) "
"FROM network_addresses na "
"WHERE na.network_id = n.id),"
"(SELECT GROUP_CONCAT(DISTINCT na.name) "
"FROM network_addresses na "
"WHERE na.network_id = n.id) "
"FROM network n "
"ORDER BY lastQuery DESC LIMIT ?";
if(sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
{
log_err("Failed to prepare SQL statement: %s", sqlite3_errmsg(db));
dbclose(&db);
return send_json_error(api, 500,
"database_error",
"Could not prepare SQL statement",
sqlite3_errmsg(db));
}
// Bind parameters
if(sqlite3_bind_int(stmt, 1, count) != SQLITE_OK)
{
log_err("Failed to bind parameter: %s", sqlite3_errmsg(db));
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"database_error",
"Could not bind parameter",
sqlite3_errmsg(db));
}
// Execute SQL statement
cJSON *clients = JSON_NEW_ARRAY();
while(sqlite3_step(stmt) == SQLITE_ROW)
{
cJSON *client = JSON_NEW_OBJECT();
JSON_COPY_STR_TO_OBJECT(client, "hwaddr", sqlite3_column_text(stmt, 0));
JSON_COPY_STR_TO_OBJECT(client, "macVendor", sqlite3_column_text(stmt, 1));
JSON_ADD_NUMBER_TO_OBJECT(client, "lastQuery", sqlite3_column_int(stmt, 2));
JSON_COPY_STR_TO_OBJECT(client, "addresses", sqlite3_column_text(stmt, 3));
JSON_COPY_STR_TO_OBJECT(client, "names", sqlite3_column_text(stmt, 4));
JSON_ADD_ITEM_TO_ARRAY(clients, client);
}
// Finalize query
sqlite3_finalize(stmt);
// Detach gravity database
if(!detach_database(db, &message, "g"))
{
log_err("Failed to detach gravity database: %s", message);
dbclose(&db);
return send_json_error(api, 500,
"database_error",
"Could not detach gravity database",
message);
}
// Close database connection
dbclose(&db);
// Return data to user
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "clients", clients);
JSON_SEND_OBJECT(json);
}

1137
src/api/queries.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,205 +0,0 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* Socket request handling routines
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "../FTL.h"
#include "api.h"
#include "../shmem.h"
#include "../timers.h"
#include "request.h"
#include "socket.h"
#include "../resolve.h"
#include "../regex_r.h"
#include "../database/network-table.h"
#include "../log.h"
// Eventqueue routines
#include "../events.h"
#include "../config.h"
bool __attribute__((pure)) command(const char *client_message, const char* cmd) {
return strstr(client_message, cmd) != NULL;
}
bool process_request(const char *client_message, const int sock, const bool istelnet)
{
char EOT[2];
EOT[0] = 0x04;
EOT[1] = 0x00;
bool processed = false;
if(command(client_message, ">stats"))
{
processed = true;
lock_shm();
getStats(sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">overTime"))
{
processed = true;
lock_shm();
getOverTime(sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">top-domains") || command(client_message, ">top-ads"))
{
processed = true;
lock_shm();
getTopDomains(client_message, sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">top-clients"))
{
processed = true;
lock_shm();
getTopClients(client_message, sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">forward-dest"))
{
processed = true;
lock_shm();
getUpstreamDestinations(client_message, sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">forward-names"))
{
processed = true;
lock_shm();
getUpstreamDestinations(">forward-dest unsorted", sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">querytypes"))
{
processed = true;
lock_shm();
getQueryTypes(sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">getallqueries"))
{
processed = true;
lock_shm();
getAllQueries(client_message, sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">recentBlocked"))
{
processed = true;
lock_shm();
getRecentBlocked(client_message, sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">clientID"))
{
processed = true;
lock_shm();
getClientID(sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">version"))
{
processed = true;
// No lock required
getVersion(sock, istelnet);
}
else if(command(client_message, ">dbstats"))
{
processed = true;
// No lock required. Access to the database
// is guaranteed to be atomic
getDBstats(sock, istelnet);
}
else if(command(client_message, ">ClientsoverTime"))
{
processed = true;
lock_shm();
getClientsOverTime(sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">client-names"))
{
processed = true;
lock_shm();
getClientNames(sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">unknown"))
{
processed = true;
lock_shm();
getUnknownQueries(sock, istelnet);
unlock_shm();
}
else if(command(client_message, ">cacheinfo"))
{
processed = true;
lock_shm();
getCacheInformation(sock);
unlock_shm();
}
else if(command(client_message, ">reresolve"))
{
processed = true;
logg("Received API request to re-resolve host names");
set_event(RELOAD_PRIVACY_LEVEL);
}
else if(command(client_message, ">recompile-regex"))
{
processed = true;
logg("Received API request to recompile regex");
lock_shm();
// Reread regex.list
// Read and compile possible regex filters
read_regex_from_database();
unlock_shm();
}
else if(command(client_message, ">delete-lease"))
{
processed = true;
delete_lease(client_message, sock);
}
else if(command(client_message, ">dns-port"))
{
processed = true;
getDNSport(sock);
}
else if(command(client_message, ">maxlogage"))
{
processed = true;
getMAXLOGAGE(sock);
}
else if(command(client_message, ">gateway"))
{
processed = true;
getGateway(sock);
}
else if(command(client_message, ">interfaces"))
{
processed = true;
getInterfaces(sock);
}
// Test only at the end if we want to quit or kill
// so things can be processed before
if(command(client_message, ">quit") || command(client_message, EOT))
{
if(config.debug & DEBUG_API)
logg("Received >quit or EOT on socket %d", sock);
return true;
}
if(!processed)
ssend(sock, "unknown command: %s\n", client_message);
// End of queryable commands: Send EOM
seom(sock, istelnet);
return false;
}

324
src/api/search.c Normal file
View File

@ -0,0 +1,324 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/search
*
* 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/api.h"
#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
static int search_table(struct ftl_conn *api, const char *item,
const enum gravity_list_type listtype,
char *ids, const unsigned int limit,
unsigned int *N, const bool partial, cJSON* json)
{
if(ids != NULL)
{
// Set item to NULL to indicate that we are searching for IDs
item = NULL;
// Strip "[" and "]" from ids
ids[strlen(ids)-1] = '\0';
ids++;
}
// Check domain against lists table
const char *sql_msg = NULL;
if(!gravityDB_readTable(listtype, item, &sql_msg, !partial, ids))
{
return send_json_error(api, 400, // 400 Bad Request
"database_error",
"Could not read domains from database table",
sql_msg);
}
tablerow table;
while(gravityDB_readTableGetRow(listtype, &table, &sql_msg))
{
if(++(*N) > limit)
continue;
cJSON *row = JSON_NEW_OBJECT();
JSON_COPY_STR_TO_OBJECT(row, "domain", table.domain);
if(table.type != NULL)
JSON_REF_STR_IN_OBJECT(row, "type", table.type);
if(table.kind != NULL)
JSON_REF_STR_IN_OBJECT(row, "kind", table.kind);
if(table.address != NULL)
JSON_COPY_STR_TO_OBJECT(row, "address", table.address);
JSON_COPY_STR_TO_OBJECT(row, "comment", table.comment);
JSON_ADD_BOOL_TO_OBJECT(row, "enabled", table.enabled);
// Add read-only database parameters
JSON_ADD_NUMBER_TO_OBJECT(row, "id", table.id);
JSON_ADD_NUMBER_TO_OBJECT(row, "date_added", table.date_added);
JSON_ADD_NUMBER_TO_OBJECT(row, "date_modified", table.date_modified);
if(listtype == GRAVITY_GRAVITY || listtype == GRAVITY_ANTIGRAVITY)
{
// Add gravity specific parameters
JSON_REF_STR_IN_OBJECT(row, "type", table.type);
JSON_ADD_NUMBER_TO_OBJECT(row, "date_updated", table.date_updated);
JSON_ADD_NUMBER_TO_OBJECT(row, "number", table.number);
JSON_ADD_NUMBER_TO_OBJECT(row, "invalid_domains", table.invalid_domains);
JSON_ADD_NUMBER_TO_OBJECT(row, "abp_entries", table.abp_entries);
JSON_ADD_NUMBER_TO_OBJECT(row, "status", table.status);
}
if(table.group_ids != NULL)
{
const int ret = parse_groupIDs(api, &table, row);
if(ret != 0)
return ret;
}
else
{
// Empty group set
cJSON *group_ids = JSON_NEW_ARRAY();
JSON_ADD_ITEM_TO_OBJECT(row, "groups", group_ids);
}
JSON_ADD_ITEM_TO_ARRAY(json, row);
}
gravityDB_readTableFinalize();
return 200;
}
static int search_gravity(struct ftl_conn *api, const char *punycode, cJSON *array,
cJSON **abp_patterns, const unsigned int limit, unsigned int *N,
const bool partial, const bool antigravity)
{
enum gravity_list_type table = antigravity ? GRAVITY_ANTIGRAVITY : GRAVITY_GRAVITY;
if(partial)
{
// Search for partial matches in (anti/)gravity
const int ret = search_table(api, punycode, table, NULL, limit, N, partial, array);
if(ret != 200)
return ret;
}
else
{
// Search for exact matches in (anti/)gravity
int ret = search_table(api, punycode, table, NULL, limit, N, false, array);
if(ret != 200)
return ret;
// Search for ABP matches in (anti/)gravity
*abp_patterns = gen_abp_patterns(punycode, antigravity);
cJSON *abp_pattern = NULL;
cJSON_ArrayForEach(abp_pattern, *abp_patterns)
{
const char *pattern = cJSON_GetStringValue(abp_pattern);
if(pattern == NULL)
continue;
ret = search_table(api, pattern, table, NULL, limit, N, partial, array);
if(ret != 200)
return ret;
}
}
return 200;
}
int api_search(struct ftl_conn *api)
{
int ret = 0;
const char *domain = api->item;
if(domain == NULL || strlen(domain) == 0)
{
// No search term provided
return send_json_error(api, 400,
"bad_request",
"Invalid request: No search term provided",
api->request->local_uri_raw);
}
// Parse query string parameters
bool partial = false, debug = false;
unsigned int limit = 20u;
if(api->request->query_string != NULL)
{
// Check if we should perform a partial search
get_bool_var(api->request->query_string, "partial", &partial);
get_bool_var(api->request->query_string, "debug", &debug);
get_uint_var(api->request->query_string, "N", &limit);
// Check validity of limit
if(limit > MAX_SEARCH_RESULTS)
{
// Too many results requested
char hint[100];
sprintf(hint, "Requested %u number of results but hard upper limit is %u", limit, MAX_SEARCH_RESULTS);
return send_json_error(api, 400,
"bad_request",
"Invalid request: Requested too many results",
hint);
}
}
// Convert domain to punycode
// The IDNA document defines internationalized domain names (IDNs) and a
// mechanism called IDNA for handling them in a standard fashion. IDNs
// use characters drawn from a large repertoire (Unicode), but IDNA
// allows the non-ASCII characters to be represented using only the
// ASCII characters already allowed in so-called host names today.
// idn2_to_ascii_lz() convert domain name in the locales encoding to an
// ASCII string. The domain name may contain several labels, separated
// by dots. The output buffer must be deallocated by the caller.
// Used flags:
// - IDN2_NFC_INPUT: Input is in Unicode Normalization Form C (NFC)
// - IDN2_NONTRANSITIONAL: Use Unicode TR46 non-transitional processing
char *punycode = NULL;
const int rc = idn2_to_ascii_lz(domain, &punycode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
if (rc != IDN2_OK)
{
// Invalid domain name
return send_json_error(api, 400,
"bad_request",
"Invalid request: Invalid domain name",
idn2_strerror(rc));
}
// Convert punycode domain to lowercase
for(unsigned int i = 0u; i < strlen(punycode); i++)
punycode[i] = tolower(punycode[i]);
// Search through all exact domains
cJSON *domains = JSON_NEW_ARRAY();
unsigned int Nexact = 0u;
ret = search_table(api, punycode, GRAVITY_DOMAINLIST_ALL_EXACT, NULL, limit, &Nexact, partial, domains);
if(ret != 200)
{
free(punycode);
return ret;
}
// Search through gravity
cJSON *gravity = JSON_NEW_ARRAY();
cJSON *gravity_patterns = NULL;
unsigned int Ngravity = 0u;
ret = search_gravity(api, punycode, gravity, &gravity_patterns, limit, &Ngravity, partial, false);
if(ret != 200)
{
free(punycode);
return ret;
}
// Search through antigravity
cJSON *antigravity_patterns = NULL;
unsigned int Nantigravity = 0u;
ret = search_gravity(api, punycode, gravity, &antigravity_patterns, limit, &Nantigravity, partial, true);
if(ret != 200)
{
free(punycode);
return ret;
}
// Search through all regex filters
cJSON *regex_ids = JSON_NEW_OBJECT();
check_all_regex(punycode, regex_ids);
cJSON *deny_ids = cJSON_GetObjectItem(regex_ids, "deny");
cJSON *allow_ids = cJSON_GetObjectItem(regex_ids, "allow");
// Get allow regex filters
unsigned int Nregex = 0u;
if(cJSON_GetArraySize(allow_ids) > 0)
{
char *allow_list = cJSON_PrintUnformatted(allow_ids);
ret = search_table(api,punycode, GRAVITY_DOMAINLIST_ALLOW_REGEX, allow_list, limit, &Nregex, false, domains);
free(allow_list);
if(ret != 200)
{
free(punycode);
return ret;
}
}
if(cJSON_GetArraySize(deny_ids) > 0)
{
char *deny_list = cJSON_PrintUnformatted(deny_ids);
ret = search_table(api, punycode, GRAVITY_DOMAINLIST_DENY_REGEX, deny_list, limit, &Nregex, false, domains);
free(deny_list);
if(ret != 200)
{
free(punycode);
return ret;
}
}
cJSON *search = JSON_NEW_OBJECT();
// .domains.{}
JSON_ADD_ITEM_TO_OBJECT(search, "domains", domains);
// .gravity.{}
JSON_ADD_ITEM_TO_OBJECT(search, "gravity", gravity);
// .results.{}
cJSON *results = JSON_NEW_OBJECT();
// .results.domains.{}
cJSON *jdomains = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(jdomains, "exact", Nexact);
JSON_ADD_NUMBER_TO_OBJECT(jdomains, "regex", Nregex);
JSON_ADD_ITEM_TO_OBJECT(results, "domains", jdomains);
// .results.gravity.{}
cJSON *jgravity = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(jgravity, "allow", Nantigravity);
JSON_ADD_NUMBER_TO_OBJECT(jgravity, "block", Ngravity);
JSON_ADD_ITEM_TO_OBJECT(results, "gravity", jgravity);
// .results.total
JSON_ADD_NUMBER_TO_OBJECT(results, "total", Nexact+Nregex+Ngravity+Nantigravity);
JSON_ADD_ITEM_TO_OBJECT(search, "results", results);
// .parameters.{}
cJSON *parameters = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(parameters, "N", limit);
JSON_ADD_BOOL_TO_OBJECT(parameters, "partial", partial);
JSON_REF_STR_IN_OBJECT(parameters, "domain", api->item);
JSON_ADD_BOOL_TO_OBJECT(parameters, "debug", debug);
JSON_ADD_ITEM_TO_OBJECT(search, "parameters", parameters);
// .debug.{}
if(debug)
{
// Add debug information
cJSON *abp_pattern = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(abp_pattern, "gravity", gravity_patterns);
JSON_ADD_ITEM_TO_OBJECT(abp_pattern, "antigravity", antigravity_patterns);
cJSON *jdebug = JSON_NEW_OBJECT();
JSON_COPY_STR_TO_OBJECT(jdebug, "domain", domain);
JSON_COPY_STR_TO_OBJECT(jdebug, "punycode", punycode);
JSON_ADD_ITEM_TO_OBJECT(jdebug, "abp_pattern", abp_pattern);
JSON_ADD_ITEM_TO_OBJECT(jdebug, "regex_ids", regex_ids);
JSON_ADD_ITEM_TO_OBJECT(search, "debug", jdebug);
}
else
{
// Free intermediate JSON objects containing ABP patterns
cJSON_Delete(gravity_patterns);
cJSON_Delete(antigravity_patterns);
// Free intermediate JSON objects containing list of regex IDs
cJSON_Delete(regex_ids);
}
// Free punycode
free(punycode);
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "search", search);
JSON_SEND_OBJECT(json);
}

View File

@ -1,271 +0,0 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* Socket connection routines
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "FTL.h"
#include "api.h"
#include "../log.h"
#include "socket.h"
#include "request.h"
#include "../config.h"
// sleepms()
#include "../timers.h"
// global variable killed
#include "../signals.h"
// API thread storage
#include "../daemon.h"
#include "../shmem.h"
// The backlog argument defines the maximum length
// to which the queue of pending connections for
// telnetfd may grow. If a connection request arrives
// when the queue is full, the client may receive an
// error with an indication of ECONNREFUSED or, if
// the underlying protocol supports retransmission,
// the request may be ignored so that a later
// reattempt at connection succeeds.
#define BACKLOG 5
static int bind_to_telnet_socket(const enum telnet_type type, const char *stype)
{
const int socketdescriptor = socket(type == TELNET_SOCK ? AF_LOCAL : (type == TELNETv4 ? AF_INET : AF_INET6), SOCK_STREAM, 0);
if(socketdescriptor < 0)
{
logg("Error opening %s telnet socket: %s (%i)", stype, strerror(errno), errno);
return -1;
}
const size_t addrlen = MAX(sizeof(struct sockaddr_un), MAX(sizeof(struct sockaddr_in), sizeof(struct sockaddr_in6)));
void *address = calloc(1, addrlen);
if(type == TELNETv4 || type == TELNETv6)
{
// Set SO_REUSEADDR to allow re-binding to the port that has been used
// previously by FTL. A common pattern is that you change FTL's
// configuration file and need to restart that server to make it reload
// its configuration. Without SO_REUSEADDR, the bind() call in the restarted
// new instance will fail if there were connections open to the previous
// instance when you killed it. Those connections will hold the TCP port in
// the TIME_WAIT state for 30-120 seconds, so you fall into case 1 above.
if(setsockopt(socketdescriptor, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)) != 0)
logg("WARN: allowing re-binding (%s) failed: %s", stype, strerror(errno));
if(type == TELNETv6)
{
// If this flag is set to true (nonzero), then the socket is re
// stricted to sending and receiving IPv6 packets only. In this
// case, an IPv4 and an IPv6 application can bind to a single port
// at the same time.
if(setsockopt(socketdescriptor, IPPROTO_IPV6, IPV6_V6ONLY, &(int){ 1 }, sizeof(int)) != 0)
logg("WARN: setting socket to IPv6-only failed: %s", strerror(errno));
if(config.socket_listenlocal)
((struct sockaddr_in6*) address)->sin6_addr = in6addr_loopback;
else
((struct sockaddr_in6*) address)->sin6_addr = in6addr_any;
// The bind() system call binds a socket to an address,
// in this case the address of the current host and
// port number on which the server will run.
// convert this to network byte order using the function htons()
// which converts a port number in host byte order to a port number
// in network byte order
// Bind to IPv6 socket
((struct sockaddr_in6*) address)->sin6_family = AF_INET6;
((struct sockaddr_in6*) address)->sin6_port = htons(config.port);
}
else // IPv4
{
if(config.socket_listenlocal)
((struct sockaddr_in*) address)->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
else
((struct sockaddr_in*) address)->sin_addr.s_addr = INADDR_ANY;
// Bind to IPv4 port
((struct sockaddr_in*) address)->sin_family = AF_INET;
((struct sockaddr_in*) address)->sin_port = htons(config.port);
}
}
else // socket
{
// Make sure unix socket file handle does not exist, if it exists, remove it
unlink(FTLfiles.socketfile);
((struct sockaddr_un*) address)->sun_family = AF_LOCAL;
// The sockaddr_un.sum_path may be shorter than the size of the FTLfiles.socketfile
// buffer. Ensure that the string is null-terminated even when the string is too large.
// In case strlen(FTLfiles.socketfile) < sizeof(address.sun_path) [this will virtually
// always be the case], the explicit setting of the last byte to zero is a no-op as
// strncpy() writes additional null bytes to ensure that a total of n bytes are written.
strncpy(((struct sockaddr_un*) address)->sun_path, FTLfiles.socketfile, sizeof(((struct sockaddr_un*) address)->sun_path));
((struct sockaddr_un*) address)->sun_path[sizeof(((struct sockaddr_un*) address)->sun_path)-1] = '\0';
}
// Bind to socket
if(bind(socketdescriptor, (struct sockaddr *) address, addrlen) < 0)
{
logg("Error binding to %s telnet socket: %s (%i)", stype, strerror(errno), errno);
return -1;
}
// The listen system call allows the process to listen on the socket for connections
if(listen(socketdescriptor, BACKLOG) == -1)
{
logg("Error listening on %s telnet socket: %s (%i)", stype, strerror(errno), errno);
return -1;
}
logg("Listening on port %i for incoming %s telnet connections", config.port, stype);
return socketdescriptor;
}
static void *telnet_connection_handler_thread(void *args)
{
struct thread_info *tinfo = args;
// Set thread name
char threadname[16] = { 0 };
snprintf(threadname, sizeof(threadname), "telnet-%s-%i", tinfo->stype, tinfo->tid);
prctl(PR_SET_NAME, threadname, 0, 0, 0);
// Ensure this thread can be canceled at any time (not only at
// cancellation points)
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
if(config.debug & DEBUG_API)
logg("Started telnet thread %s", threadname);
// Listen as long as this thread is not canceled
int errors = 0;
while(!killed)
{
// Look for new clients that want to connect
const int csck = accept(tinfo->fd, NULL, NULL);
if(csck == -1)
{
logg("Telnet error in %s: %s (%i, fd: %d)", threadname, strerror(errno), errno, tinfo->fd);
if(errors++ > 20)
break;
sleepms(100);
continue;
}
// Define buffer for client's message
char client_message[SOCKETBUFFERLEN] ={ 0 };
// Receive from client
ssize_t n;
while((n = recv(csck, client_message, SOCKETBUFFERLEN-1, 0)))
{
if (n > 0 && n < SOCKETBUFFERLEN)
{
// Null-terminate client string
client_message[n] = '\0';
char *message = strdup(client_message);
if(message == NULL)
{
if(config.debug & DEBUG_API)
logg("Break in telnet thread for socket %d/%d: Memory error", tinfo->fd, csck);
break;
}
// Clear client message receive buffer
memset(client_message, 0, sizeof client_message);
// Process received message
const bool eom = process_request(message, csck, tinfo->istelnet);
free(message);
if(eom) break;
}
else if(n == -1)
{
if(config.debug & DEBUG_API)
logg("Break in telnet thread for socket %d/%d: No data received", tinfo->fd, csck);
break;
}
}
// Close client socket
close(csck);
}
if(config.debug & DEBUG_API)
logg("Terminating telnet thread %s (%d errors)", threadname, errors);
// Free thread-private memory
free(tinfo);
return NULL;
}
void listen_telnet(const enum telnet_type type)
{
// We will use the attributes object later to start all threads in detached mode
pthread_attr_t attr;
// Initialize thread attributes object with default attribute values
pthread_attr_init(&attr);
// When a detached thread terminates, its resources are automatically released back to
// the system without the need for another thread to join with the terminated thread
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// Initialize telnet socket
const char *stype = type == TELNET_SOCK ? "socket" : (type == TELNETv4 ? "IPv4" : "IPv6");
const int fd = bind_to_telnet_socket(type, stype);
if(fd < 0)
{
logg("WARN: Cannot bind to %s telnet socket", stype);
return;
}
if(config.debug & DEBUG_API)
logg("Telnet-%s listener accepting on fd %d", stype, fd);
for(unsigned int i = 0; i < MAX_API_THREADS; i++)
{
// Spawn telnet thread
// Create a private copy of the socket fd for the child thread
struct thread_info *tinfo = calloc(1, sizeof(struct thread_info));
if(!tinfo)
continue;
tinfo->fd = fd;
tinfo->tid = i;
tinfo->istelnet = (type == TELNETv4 || type == TELNETv6);
tinfo->stype = stype;
if(pthread_create(&api_threads[i], &attr, telnet_connection_handler_thread, (void*) tinfo) != 0)
{
// Log the error code description
logg("WARNING: Unable to open telnet processing thread: %s", strerror(errno));
}
}
}
void seom(const int sock, const bool istelnet)
{
if(istelnet)
ssend(sock, "---EOM---\n\n");
else
pack_eom(sock);
}
bool __attribute__ ((format (gnu_printf, 5, 6))) _ssend(const int sock, const char *file, const char *func, const int line, const char *format, ...)
{
char *buffer;
va_list args;
va_start(args, format);
int bytes = vasprintf(&buffer, format, args);
va_end(args);
if(bytes > 0 && buffer != NULL)
{
FTLwrite(sock, buffer, bytes, short_path(file), func, line);
free(buffer);
}
return errno == 0;
}

View File

@ -1,29 +0,0 @@
/* 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
* Socket prototypes
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef SOCKET_H
#define SOCKET_H
// enum telnet_type
#include "../enums.h"
struct thread_info {
int fd;
int tid;
bool istelnet;
const char *stype;
};
void close_unix_socket(bool unlink_file);
void seom(const int sock, const bool istelnet);
#define ssend(sock, format, ...) _ssend(sock, __FILE__, __FUNCTION__, __LINE__, format, ##__VA_ARGS__)
bool _ssend(const int sock, const char *file, const char *func, const int line, const char *format, ...) __attribute__ ((format (gnu_printf, 5, 6)));
void listen_telnet(const enum telnet_type type);
#endif //SOCKET_H

649
src/api/stats.c Normal file
View File

@ -0,0 +1,649 @@
/* 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
*
* 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/api.h"
#include "shmem.h"
#include "datastructure.h"
// read_setupVarsconf()
#include "config/setupVars.h"
// logging routines
#include "log.h"
// config struct
#include "config/config.h"
// overTime data
#include "overTime.h"
// enum REGEX
#include "regex_r.h"
// sqrt()
#include <math.h>
/* qsort comparison function (count field), sort ASC
static int __attribute__((pure)) cmpasc(const void *a, const void *b)
{
const int *elem1 = (int*)a;
const int *elem2 = (int*)b;
if (elem1[1] < elem2[1])
return -1;
else if (elem1[1] > elem2[1])
return 1;
else
return 0;
} */
// qsort subroutine, sort DESC
int __attribute__((pure)) cmpdesc(const void *a, const void *b)
{
const int *elem1 = (int*)a;
const int *elem2 = (int*)b;
if (elem1[1] > elem2[1])
return -1;
else if (elem1[1] < elem2[1])
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++)
{
// We add the collective OTHER type at the end
if(i == TYPE_OTHER)
continue;
JSON_ADD_NUMBER_TO_OBJECT(types, get_query_type_str(i, NULL, NULL), counters->querytype[i]);
}
JSON_ADD_NUMBER_TO_OBJECT(types, "OTHER", counters->querytype[TYPE_OTHER]);
return 0;
}
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;
// 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, "forwarded", forwarded);
JSON_ADD_NUMBER_TO_OBJECT(queries, "cached", cached);
cJSON *types = JSON_NEW_OBJECT();
int ret = get_query_types_obj(api, types);
if(ret != 0)
return ret;
JSON_ADD_ITEM_TO_OBJECT(queries, "types", types);
cJSON *statuses = JSON_NEW_OBJECT();
for(enum query_status status = 0; status < QUERY_STATUS_MAX; status++)
JSON_ADD_NUMBER_TO_OBJECT(statuses, get_query_status_str(status), counters->status[status]);
JSON_ADD_ITEM_TO_OBJECT(queries, "status", statuses);
cJSON *replies = JSON_NEW_OBJECT();
for(enum reply_type reply = 0; reply <QUERY_REPLY_MAX; reply++)
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);
cJSON *gravity = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(gravity, "domains_being_blocked", counters->database.gravity);
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);
}
int api_stats_top_domains(struct ftl_conn *api)
{
// Exit before processing any data if requested via config setting
if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS)
{
log_debug(DEBUG_API, "Not returning top domains: Privacy level is set to %i",
config.misc.privacylevel.v.privacy_level);
// 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);
}
// Lock shared memory
lock_shm();
// Allocate memory
const int domains = counters->domains;
int *temparray = calloc(2*domains, sizeof(int));
if(temparray == NULL)
{
log_err("Memory allocation failed in %s()", __FUNCTION__);
return 0;
}
bool blocked = false; // Can be overwritten by query string
int count = 10;
// /api/stats/top_domains?blocked=true
if(api->request->query_string != NULL)
{
// Should blocked domains 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);
}
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);
}
int api_stats_top_clients(struct ftl_conn *api)
{
int count = 10;
// Exit before processing any data if requested via config setting
if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS_CLIENTS)
{
log_debug(DEBUG_API, "Not returning top clients: Privacy level is set to %i",
config.misc.privacylevel.v.privacy_level);
// 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);
}
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);
}
// Lock shared memory
lock_shm();
int clients = counters->clients;
int *temparray = calloc(2*clients, sizeof(int));
if(temparray == NULL)
{
log_err("Memory allocation failed in api_stats_top_clients()");
return 0;
}
unsigned int added_clients = 0;
for(int clientID = 0; clientID < clients; clientID++)
{
// Get client pointer
const clientsData* client = getClient(clientID, true);
// Skip invalid clients and also those managed by alias clients
if(client == NULL || (!client->flags.aliasclient && client->aliasclient_id >= 0))
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;
added_clients++;
}
// Sort temporary array
qsort(temparray, added_clients, sizeof(int[2]), cmpdesc);
// Get clients which the user doesn't want to see
regex_t *regex_clients = NULL;
unsigned int N_regex_clients = 0;
compile_filter_regex(api, "webserver.api.excludeClients",
config.webserver.api.excludeClients.v.json,
&regex_clients, &N_regex_clients);
int n = 0;
cJSON *top_clients = JSON_NEW_ARRAY();
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;
// Skip this client if there is a filter on it
bool skip_client = false;
if(N_regex_clients > 0)
{
// Iterate over all regex filters
for(unsigned int j = 0; j < N_regex_clients; j++)
{
// Check if the domain matches the regex
if(regexec(&regex_clients[j], client_ip, 0, NULL, 0) == 0)
{
// Client IP matches
skip_client = true;
break;
}
else if(client_name != NULL && regexec(&regex_clients[j], client_name, 0, NULL, 0) == 0)
{
// Client name matches
skip_client = true;
break;
}
}
}
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)
{
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++;
}
if(n == count)
break;
}
// Free temporary array
free(temparray);
// Free regexes
if(N_regex_clients > 0)
{
// Free individual regexes
for(unsigned int i = 0; i < N_regex_clients; i++)
regfree(&regex_clients[i]);
// Free array of regex pointers
free(regex_clients);
}
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "clients", top_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);
}
int api_stats_upstreams(struct ftl_conn *api)
{
unsigned int totalcount = 0;
const int upstreams = counters->upstreams;
int *temparray = calloc(2*upstreams, sizeof(int));
if(temparray == NULL)
{
log_err("Memory allocation failed in api_stats_upstreams()");
return 0;
}
// Lock shared memory
lock_shm();
unsigned int added_upstreams = 0;
for(int upstreamID = 0; upstreamID < upstreams; upstreamID++)
{
// Get upstream pointer
const upstreamsData* upstream = getUpstream(upstreamID, true);
if(upstream == NULL)
continue;
temparray[2*added_upstreams + 0] = upstreamID;
temparray[2*added_upstreams + 1] = upstream->count;
totalcount += upstream->count;
added_upstreams++;
}
// Sort temporary array in descending order
qsort(temparray, upstreams, sizeof(int[2]), cmpdesc);
// Loop over available forward destinations
cJSON *top_upstreams = JSON_NEW_ARRAY();
for(int i = -2; i < (int)added_upstreams; i++)
{
int count = 0;
const char* ip, *name;
int port = -1;
double responsetime = 0.0, uncertainty = 0.0;
if(i == -2)
{
// Blocked queries (local lists)
ip = "blocklist";
name = ip;
count = get_blocked_count();
}
else if(i == -1)
{
// Local cache
ip = "cache";
name = ip;
count = get_cached_count();
}
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;
// Compute average response time and uncertainty (unit: seconds)
if(upstream->responses > 0)
{
// Simple average of the response times
responsetime = upstream->rtime / upstream->responses;
}
if(upstream->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));
}
}
// 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)
{
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 *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);
}
}
// Free temporary array
free(temparray);
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);
}
int api_stats_query_types(struct ftl_conn *api)
{
lock_shm();
cJSON *types = JSON_NEW_OBJECT();
int ret = get_query_types_obj(api, types);
if(ret != 0)
{
unlock_shm();
return ret;
}
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "types", types);
// Send response
JSON_SEND_OBJECT_UNLOCK(json);
}
int api_stats_recentblocked(struct ftl_conn *api)
{
// Exit before processing any data if requested via config setting
if(config.misc.privacylevel.v.privacy_level >= PRIVACY_HIDE_DOMAINS)
{
// Minimum structure is
// {"blocked":[]}
cJSON *json = JSON_NEW_OBJECT();
cJSON *blocked = JSON_NEW_ARRAY();
JSON_ADD_ITEM_TO_OBJECT(json, "blocked", blocked);
JSON_SEND_OBJECT(json);
}
unsigned int count = 1;
if(api->request->query_string != NULL)
{
// Does the user request a non-default number of replies?
// Note: We do not accept zero query requests here
get_uint_var(api->request->query_string, "count", &count);
}
// Lock shared memory
lock_shm();
// Find most recently blocked query
unsigned int found = 0;
cJSON *blocked = JSON_NEW_ARRAY();
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_REF_STR_IN_ARRAY(blocked, domain);
// Only count when added successfully
found++;
}
if(found >= count)
break;
}
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "blocked", blocked);
JSON_SEND_OBJECT_UNLOCK(json);
}

841
src/api/stats_database.c Normal file
View File

@ -0,0 +1,841 @@
/* 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 database statistics implementation
*
* 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"
// querytypes[]
#include "../datastructure.h"
// logging routines
#include "log.h"
// db
#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)"
#define FILTER_STATUS_BLOCKED "status NOT IN (0,2,3,12,13,14,17)"
int api_history_database(struct ftl_conn *api)
{
double from = 0, until = 0;
const int interval = 600;
if(api->request->query_string != NULL)
{
get_double_var(api->request->query_string, "from", &from);
get_double_var(api->request->query_string, "until", &until);
}
// Check if we received the required information
if(from < 1.0 || until < 1.0)
{
return send_json_error(api, 400,
"bad_request",
"You need to specify both \"from\" and \"until\" in the request.",
NULL);
}
// Open the database
sqlite3 *db = dbopen(false, false);
if(db == NULL)
return send_json_error(api, 500,
"internal_error",
"Failed to open long-term database",
NULL);
// Build SQL string
const char *querystr = "SELECT (timestamp/:interval)*:interval interval,status,COUNT(*) FROM query_storage "
"WHERE (status != 0) AND timestamp >= :from AND timestamp <= :until "
"GROUP by interval,status ORDER by interval";
// Prepare SQLite statement
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK ){
log_err("api_stats_database_history() - SQL error prepare (%i): %s",
rc, sqlite3_errstr(rc));
return false;
}
// Bind interval to prepared statement
if((rc = sqlite3_bind_int(stmt, 1, interval)) != SQLITE_OK)
{
log_err("api_stats_database_history(): Failed to bind interval (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind interval",
NULL);
}
// Bind from to prepared statement
if((rc = sqlite3_bind_double(stmt, 2, from)) != SQLITE_OK)
{
log_err("api_stats_database_history(): Failed to bind from (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind from",
NULL);
}
// Bind until to prepared statement
if((rc = sqlite3_bind_double(stmt, 3, until)) != SQLITE_OK)
{
log_err("api_stats_database_history(): Failed to bind until (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind until",
NULL);
}
// Loop over returned data and accumulate results
cJSON *history = JSON_NEW_ARRAY();
cJSON *item = NULL;
unsigned int previous_timeslot = 0u, blocked = 0u, total = 0u, cached = 0u;
while((rc = sqlite3_step(stmt)) == SQLITE_ROW)
{
// Get timestamp and derive timeslot from it
const unsigned int timestamp = sqlite3_column_int(stmt, 0);
const unsigned int timeslot = timestamp - timestamp % interval;
// Begin new array item for each new timeslot
if(timeslot != previous_timeslot)
{
previous_timeslot = timeslot;
if(item != NULL)
{
// Add and reset total counter
JSON_ADD_NUMBER_TO_OBJECT(item, "total", total);
total = 0;
// Add and reset totacachedl counter
JSON_ADD_NUMBER_TO_OBJECT(item, "cached", cached);
cached = 0;
// Add and reset blocked counter
JSON_ADD_NUMBER_TO_OBJECT(item, "blocked", blocked);
blocked = 0;
JSON_ADD_ITEM_TO_ARRAY(history, item);
}
item = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(item, "timestamp", previous_timeslot);
}
const int status = sqlite3_column_int(stmt, 1);
const int count = sqlite3_column_int(stmt, 2);
// Always add to total count
total += count;
// Add to blocked / cached count if applicable
if(is_blocked(status))
blocked += count;
else if(is_cached(status))
cached += count;
}
// Append final timeslot at the end if applicable
if(total > 0 && item != NULL)
{
// Add total counter
JSON_ADD_NUMBER_TO_OBJECT(item, "total", total);
// Add cached counter
JSON_ADD_NUMBER_TO_OBJECT(item, "cached", cached);
// Add blocked counter
JSON_ADD_NUMBER_TO_OBJECT(item, "blocked", blocked);
JSON_ADD_ITEM_TO_ARRAY(history, item);
}
// Finalize statement and close (= unlock) database connection
sqlite3_finalize(stmt);
dbclose(&db);
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "history", history);
JSON_SEND_OBJECT(json);
}
int api_stats_database_top_items(struct ftl_conn *api)
{
unsigned int count = 10;
double from = 0.0, until = 0.0;
// Get options from API struct
bool blocked = false; // Can be overwritten by query string
const bool domains = api->opts.flags & API_DOMAINS;
// Get parameters from query string
if(api->request->query_string != NULL)
{
// Get time interval from query string
get_double_var(api->request->query_string, "from", &from);
get_double_var(api->request->query_string, "until", &until);
// Get blocked queries not only for .../top_blocked
// but also for .../top_domains?blocked=true
// Note: this may overwrite the blocked property from the URL
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_uint_var(api->request->query_string, "count", &count);
}
// Check if we received the required information
if(from < 1.0 || until < 1.0)
{
return send_json_error(api, 400,
"bad_request",
"You need to specify both \"from\" and \"until\" in the request.",
NULL);
}
// Open the database
sqlite3 *db = dbopen(false, false);
if(db == NULL)
return send_json_error(api, 500,
"internal_error",
"Failed to open long-term database",
NULL);
// Build SQL string
const char *querystr, *count_total_str, *count_blocked_str;
if(domains)
{
if(blocked)
{
// Get domains and count of queries (blocked)
querystr = "SELECT COUNT(*),d.domain AS cnt FROM query_storage q "
"JOIN domain_by_id d ON d.id = q.domain "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND " FILTER_STATUS_BLOCKED " "
"GROUP by q.domain";
}
else
{
// Get domains and count of queries (not blocked)
querystr = "SELECT COUNT(*),d.domain AS cnt FROM query_storage q "
"JOIN domain_by_id d ON d.id = q.domain "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND " FILTER_STATUS_NOT_BLOCKED " "
"GROUP by q.domain";
}
// Count total number of queries for domains
count_total_str = "SELECT COUNT(DISTINCT domain) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until";
// Count total number of blocked queries for domains
count_blocked_str = "SELECT COUNT(DISTINCT domain) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND " FILTER_STATUS_BLOCKED;
}
else
{
if(blocked)
{
// Get clients and count of queries (blocked)
querystr = "SELECT COUNT(*),c.ip,c.name AS cnt FROM query_storage q "
"JOIN client_by_id c ON c.id = q.client"
"WHERE timestamp >= :from AND timestamp <= :until "
"AND " FILTER_STATUS_BLOCKED " "
"GROUP by q.client";
}
else
{
// Get clients and count of queries (not blocked)
querystr = "SELECT COUNT(*),c.ip,c.name AS cnt FROM query_storage q "
"JOIN client_by_id c ON c.id = q.client "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND " FILTER_STATUS_NOT_BLOCKED " "
"GROUP by q.client";
}
// Count total number of queries for clients
count_total_str = "SELECT COUNT(DISTINCT client) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until";
// Count number of blocked queries for clients
count_blocked_str = "SELECT COUNT(DISTINCT client) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND " FILTER_STATUS_BLOCKED;
}
// Prepare SQLite statement
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK ){
log_err("api_stats_database_history() - SQL error prepare (%i): %s",
rc, sqlite3_errstr(rc));
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to prepare query string",
querystr);
}
// Bind from to prepared statement
if((rc = sqlite3_bind_double(stmt, 1, from)) != SQLITE_OK)
{
log_err("api_stats_database_history(): Failed to bind from (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind from",
NULL);
}
// Bind until to prepared statement
if((rc = sqlite3_bind_double(stmt, 2, until)) != SQLITE_OK)
{
log_err("api_stats_database_history(): Failed to bind until (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind until",
NULL);
}
// Loop over and accumulate results
cJSON *top_items = JSON_NEW_ARRAY();
unsigned int total = 0;
while((rc = sqlite3_step(stmt)) == SQLITE_ROW &&
++total < count)
{
// Get count
const int cnt = sqlite3_column_int(stmt, 0);
cJSON *item = JSON_NEW_OBJECT();
if(domains)
{
// Add domain to item
JSON_COPY_STR_TO_OBJECT(item, "domain", sqlite3_column_text(stmt, 1));
}
else
{
// Add client to item
JSON_COPY_STR_TO_OBJECT(item, "ip", sqlite3_column_text(stmt, 1));
JSON_COPY_STR_TO_OBJECT(item, "name", sqlite3_column_text(stmt, 2));
}
JSON_ADD_NUMBER_TO_OBJECT(item, "count", cnt);
JSON_ADD_ITEM_TO_ARRAY(top_items, item);
}
// Finalize statement and close (= unlock) database connection
sqlite3_finalize(stmt);
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, (domains ? "domains" : "clients"), top_items);
const int total_num = db_query_int_from_until(db, count_total_str, from, until);
const int blocked_num = db_query_int_from_until(db, count_blocked_str, from, until);
JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", total_num);
JSON_ADD_NUMBER_TO_OBJECT(json, "blocked_queries", blocked_num);
dbclose(&db);
JSON_SEND_OBJECT(json);
}
int api_stats_database_summary(struct ftl_conn *api)
{
double from = 0, until = 0;
if(api->request->query_string != NULL)
{
get_double_var(api->request->query_string, "from", &from);
get_double_var(api->request->query_string, "until", &until);
}
// Check if we received the required information
if(from < 1.0 || until < 1.0)
{
return send_json_error(api, 400,
"bad_request",
"You need to specify both \"from\" and \"until\" in the request.",
NULL);
}
// Open the database
sqlite3 *db = dbopen(false, false);
if(db == NULL)
return send_json_error(api, 500,
"internal_error",
"Failed to open long-term database",
NULL);
// Perform SQL queries
const char *querystr;
querystr = "SELECT COUNT(*) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until";
const int sum_queries = db_query_int_from_until(db, querystr, from, until);
querystr = "SELECT COUNT(*) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND " FILTER_STATUS_BLOCKED;
const int sum_blocked = db_query_int_from_until(db, querystr, from, until);
querystr = "SELECT COUNT(DISTINCT client) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until";
const int total_clients = db_query_int_from_until(db, querystr, from, until);
// Calculate percentage of blocked queries, substituting 0.0 if there
// are no blocked queries
float percent_blocked = 0.0;
if(sum_queries > 0.0)
percent_blocked = 1e2f*sum_blocked/sum_queries;
if(sum_queries < 0 || sum_blocked < 0 || total_clients < 0)
{
// Close (= unlock) database connection
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Internal server error",
NULL);
}
// Loop over and accumulate results
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(json, "sum_queries", sum_queries);
JSON_ADD_NUMBER_TO_OBJECT(json, "sum_blocked", sum_blocked);
JSON_ADD_NUMBER_TO_OBJECT(json, "percent_blocked", percent_blocked);
JSON_ADD_NUMBER_TO_OBJECT(json, "total_clients", total_clients);
// Close (= unlock) database connection
dbclose(&db);
// Send JSON object
JSON_SEND_OBJECT(json);
}
int api_history_database_clients(struct ftl_conn *api)
{
double from = 0, until = 0;
const int interval = 600;
if(api->request->query_string != NULL)
{
get_double_var(api->request->query_string, "from", &from);
get_double_var(api->request->query_string, "until", &until);
}
// Check if we received the required information
if(from < 1.0 || until < 1.0)
{
return send_json_error(api, 400,
"bad_request",
"You need to specify both \"from\" and \"until\" in the request.",
NULL);
}
// Open the database
sqlite3 *db = dbopen(false, false);
if(db == NULL)
return send_json_error(api, 500,
"internal_error",
"Failed to open long-term database",
NULL);
const char *querystr = "SELECT DISTINCT(client),ip,name FROM query_storage "
"JOIN client_by_id ON client_by_id.id = client "
"WHERE timestamp >= :from AND timestamp <= :until "
"ORDER BY client DESC";
// Prepare SQLite statement
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK ){
log_err("api_stats_database_clients() - SQL error prepare outer (%i): %s",
rc, sqlite3_errstr(rc));
return send_json_error(api, 500,
"internal_error",
"Failed to prepare outer statement",
NULL);
}
// Bind from to prepared statement
if((rc = sqlite3_bind_double(stmt, 1, from)) != SQLITE_OK)
{
log_err("api_stats_database_clients(): Failed to bind from (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind from",
NULL);
}
// Bind until to prepared statement
if((rc = sqlite3_bind_double(stmt, 2, until)) != SQLITE_OK)
{
log_err("api_stats_database_clients(): Failed to bind until (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind until",
NULL);
}
// 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);
// Build SQL string
querystr = "SELECT (timestamp/:interval)*:interval interval,client,COUNT(*) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until "
"GROUP BY interval,client ORDER BY interval DESC, client DESC";
// Prepare SQLite statement
rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK ){
log_err("api_stats_database_clients() - SQL error prepare (%i): %s",
rc, sqlite3_errstr(rc));
return send_json_error(api, 500,
"internal_error",
"Failed to prepare inner statement",
NULL);
}
// Bind interval to prepared statement
if((rc = sqlite3_bind_int(stmt, 1, interval)) != SQLITE_OK)
{
log_err("api_stats_database_clients(): Failed to bind interval (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind interval",
NULL);
}
// Bind from to prepared statement
if((rc = sqlite3_bind_int(stmt, 2, from)) != SQLITE_OK)
{
log_err("api_stats_database_clients(): Failed to bind from (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind from",
NULL);
}
// Bind until to prepared statement
if((rc = sqlite3_bind_int(stmt, 3, until)) != SQLITE_OK)
{
log_err("api_stats_database_clients(): Failed to bind until (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind until",
NULL);
}
cJSON *item = NULL;
cJSON *data = NULL;
unsigned int previous_timeslot = 0u;
cJSON *over_time = JSON_NEW_ARRAY();
while((rc = sqlite3_step(stmt)) == SQLITE_ROW)
{
// Get timestamp and derive timeslot from it
const unsigned int timestamp = sqlite3_column_int(stmt, 0);
const unsigned int timeslot = timestamp - timestamp % interval;
// Begin new array item for each new timeslot
if(timeslot != previous_timeslot)
{
previous_timeslot = timeslot;
if(item != NULL && data != NULL)
{
JSON_ADD_ITEM_TO_OBJECT(item, "data", data);
JSON_ADD_ITEM_TO_ARRAY(over_time, item);
}
item = JSON_NEW_OBJECT();
data = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(item, "timestamp", previous_timeslot);
}
const char *client = (char*)sqlite3_column_text(stmt, 1);
const int count = sqlite3_column_int(stmt, 2);
JSON_ADD_NUMBER_TO_OBJECT(data, client, count);
}
// Append final timeslot at the end if applicable
if(item != NULL && data != NULL)
{
JSON_ADD_ITEM_TO_OBJECT(item, "data", data);
JSON_ADD_ITEM_TO_ARRAY(over_time, item);
}
// Finalize statement and close (= unlock) database connection
sqlite3_finalize(stmt);
dbclose(&db);
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "history", over_time);
JSON_ADD_ITEM_TO_OBJECT(json, "clients", clients);
JSON_SEND_OBJECT(json);
}
int api_stats_database_query_types(struct ftl_conn *api)
{
double from = 0, until = 0;
if(api->request->query_string != NULL)
{
get_double_var(api->request->query_string, "from", &from);
get_double_var(api->request->query_string, "until", &until);
}
// Check if we received the required information
if(from < 1.0 || until < 1.0)
{
return send_json_error(api, 400,
"bad_request",
"You need to specify both \"from\" and \"until\" in the request.",
NULL);
}
// Open the database
sqlite3 *db = dbopen(false, false);
if(db == NULL)
return send_json_error(api, 500,
"internal_error",
"Failed to open long-term database",
NULL);
// Perform SQL queries
cJSON *types = JSON_NEW_OBJECT();
for(int i = TYPE_A; i < TYPE_MAX; i++)
{
const char *querystr = "SELECT COUNT(*) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND type = :type";
// Add 1 as type is stored one-based in the database for historical reasons
int count = db_query_int_from_until_type(db, querystr, from, until, i+1);
JSON_ADD_NUMBER_TO_OBJECT(types, get_query_type_str(i, NULL, NULL), count);
}
// Close (= unlock) database connection
dbclose(&db);
// Send JSON object
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "types", types);
JSON_SEND_OBJECT(json);
}
int api_stats_database_upstreams(struct ftl_conn *api)
{
double from = 0, until = 0;
if(api->request->query_string != NULL)
{
get_double_var(api->request->query_string, "from", &from);
get_double_var(api->request->query_string, "until", &until);
}
// Check if we received the required information
if(from < 1.0 || until < 1.0)
{
return send_json_error(api, 400,
"bad_request",
"You need to specify both \"from\" and \"until\" in the request.",
NULL);
}
// Open the database
sqlite3 *db = dbopen(false, false);
if(db == NULL)
return send_json_error(api, 500,
"internal_error",
"Failed to open long-term database",
NULL);
// Perform simple SQL queries
unsigned int sum_queries = 0;
const char *querystr;
querystr = "SELECT COUNT(*) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND status = 3";
int cached_queries = db_query_int_from_until(db, querystr, from, until);
sum_queries += cached_queries;
querystr = "SELECT COUNT(*) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND status != 0 AND status != 2 AND status != 3";
int blocked_queries = db_query_int_from_until(db, querystr, from, until);
sum_queries += blocked_queries;
querystr = "SELECT forward,COUNT(*) FROM query_storage "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND forward IS NOT NULL "
"GROUP BY forward ORDER BY forward";
// Prepare SQLite statement
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(db, querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK ){
log_err("api_stats_database_clients() - SQL error prepare (%i): %s",
rc, sqlite3_errstr(rc));
return send_json_error(api, 500,
"internal_error",
"Failed to prepare statement",
NULL);
}
// Bind from to prepared statement
if((rc = sqlite3_bind_double(stmt, 1, from)) != SQLITE_OK)
{
log_err("api_stats_database_clients(): Failed to bind from (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind from",
NULL);
}
// Bind until to prepared statement
if((rc = sqlite3_bind_double(stmt, 2, until)) != SQLITE_OK)
{
log_err("api_stats_database_clients(): Failed to bind until (error %d) - %s",
rc, sqlite3_errstr(rc));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose(&db);
return send_json_error(api, 500,
"internal_error",
"Failed to bind until",
NULL);
}
// Loop over clients and accumulate results
cJSON *upstreams = JSON_NEW_ARRAY();
int forwarded_queries = 0;
while((rc = sqlite3_step(stmt)) == SQLITE_ROW)
{
const char *upstream = (char*)sqlite3_column_text(stmt, 0);
const int count = sqlite3_column_int(stmt, 1);
cJSON *item = JSON_NEW_OBJECT();
unsigned int port = -1;
char buffer[512] = { 0 };
if(sscanf(upstream, "%511[^#]#%u", buffer, &port) == 2)
{
buffer[sizeof(buffer)-1] = '\0';
JSON_COPY_STR_TO_OBJECT(item, "ip", buffer);
}
else
JSON_COPY_STR_TO_OBJECT(item, "ip", upstream);
JSON_REF_STR_IN_OBJECT(item, "name", "");
JSON_ADD_NUMBER_TO_OBJECT(item, "port", port);
JSON_ADD_NUMBER_TO_OBJECT(item, "count", count);
cJSON *statistics = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(statistics, "response", 0);
JSON_ADD_NUMBER_TO_OBJECT(statistics, "variance", 0);
JSON_ADD_ITEM_TO_OBJECT(item, "statistics", statistics);
JSON_ADD_ITEM_TO_ARRAY(upstreams, item);
forwarded_queries += count;
}
sqlite3_finalize(stmt);
// Add number of forwarded queries to total query count
sum_queries += forwarded_queries;
// Add cache and blocklist as upstreams
cJSON *cached = JSON_NEW_OBJECT();
JSON_REF_STR_IN_OBJECT(cached, "ip", "cache");
JSON_REF_STR_IN_OBJECT(cached, "name", "cache");
JSON_ADD_NUMBER_TO_OBJECT(cached, "port", -1);
JSON_ADD_NUMBER_TO_OBJECT(cached, "count", cached_queries);
cJSON *statistics = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(statistics, "response", 0);
JSON_ADD_NUMBER_TO_OBJECT(statistics, "variance", 0);
JSON_ADD_ITEM_TO_OBJECT(cached, "statistics", statistics);
JSON_ADD_ITEM_TO_ARRAY(upstreams, cached);
cJSON *blocked = JSON_NEW_OBJECT();
JSON_REF_STR_IN_OBJECT(blocked, "ip", "blocklist");
JSON_REF_STR_IN_OBJECT(blocked, "name", "blocklist");
JSON_ADD_NUMBER_TO_OBJECT(blocked, "port", -1);
JSON_ADD_NUMBER_TO_OBJECT(blocked, "count", blocked_queries);
statistics = JSON_NEW_OBJECT();
JSON_ADD_NUMBER_TO_OBJECT(statistics, "response", 0);
JSON_ADD_NUMBER_TO_OBJECT(statistics, "variance", 0);
JSON_ADD_ITEM_TO_OBJECT(cached, "statistics", statistics);
JSON_ADD_ITEM_TO_ARRAY(upstreams, blocked);
// Close (= unlock) database connection
dbclose(&db);
// Send JSON object
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "upstreams", upstreams);
JSON_ADD_NUMBER_TO_OBJECT(json, "forwarded_queries", forwarded_queries);
JSON_ADD_NUMBER_TO_OBJECT(json, "total_queries", sum_queries);
JSON_SEND_OBJECT(json);
}

830
src/api/teleporter.c Normal file
View File

@ -0,0 +1,830 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API Implementation /api/teleporter
*
* 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 "zip/teleporter.h"
#include "api/api.h"
// ERRBUF_SIZE
#include "config/dnsmasq_config.h"
// inflate_buffer()
#include "zip/gzip.h"
// find_file_in_tar()
#include "zip/tar.h"
// sqlite3_open_v2()
#include "database/sqlite3.h"
// dbquery()
#include "database/common.h"
// MAX_ROTATIONS
#include "files.h"
#define MAXFILESIZE (50u*1024*1024)
static int api_teleporter_GET(struct ftl_conn *api)
{
mz_zip_archive zip = { 0 };
void *ptr = NULL;
size_t size = 0u;
char filename[128] = "";
const char *error = generate_teleporter_zip(&zip, filename, &ptr, &size);
if(error != NULL)
return send_json_error(api, 500,
"compression_error",
error,
NULL);
// Add header indicating that this is a file to be downloaded and stored as
// teleporter.zip (rather than showing the binary data in the browser
// window). This client is free to ignore and do whatever it wants with this
// data stream.
snprintf(pi_hole_extra_headers, sizeof(pi_hole_extra_headers),
"Content-Disposition: attachment; filename=\"%s\"",
filename);
// Send 200 OK with appropriate headers
mg_send_http_ok(api->conn, "application/zip", size);
// Clear extra headers
pi_hole_extra_headers[0] = '\0';
// Send raw (binary) ZIP content
mg_write(api->conn, ptr, size);
// Free allocated ZIP memory
free_teleporter_zip(&zip);
return 200;
}
// Struct to store the data we want to process
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 int field_found(const char *key,
const char *filename,
char *path,
size_t pathlen,
void *user_data)
{
struct upload_data *data = (struct upload_data *)user_data;
log_debug(DEBUG_API, "Found field: \"%s\", filename: \"%s\"", key, filename);
// Set all fields to false
memset(&data->field, false, sizeof(data->field));
if(strcasecmp(key, "file") == 0 && filename && *filename)
{
data->filename = strdup(filename);
data->field.file = true;
return MG_FORM_FIELD_STORAGE_GET;
}
else if(strcasecmp(key, "sid") == 0)
{
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;
}
// Ignore any other fields
return MG_FORM_FIELD_STORAGE_SKIP;
}
// Callback function for CivetWeb to receive the data of the fields we want to process.
// This function might be called several times for the same field (large (> 8KB)
// or chunked data), so we may need to append new data to existing data.
static int field_get(const char *key, const char *value, size_t valuelen, void *user_data)
{
struct upload_data *data = (struct upload_data *)user_data;
log_debug(DEBUG_API, "Received field: \"%s\" (length %zu bytes)", key, valuelen);
if(data->field.file)
{
if(data->filesize + valuelen > MAXFILESIZE)
{
log_warn("Uploaded Teleporter file is too large (limit is %u bytes)",
MAXFILESIZE);
data->too_large = true;
return MG_FORM_FIELD_HANDLE_ABORT;
}
// Allocate memory for the raw file data
data->data = realloc(data->data, data->filesize + valuelen);
// Copy the raw file data
memcpy(data->data + data->filesize, value, valuelen);
// Store the size of the file raw data
data->filesize += valuelen;
log_debug(DEBUG_API, "Received file (%zu bytes, buffer is now %zu bytes)",
valuelen, data->filesize);
}
else if(data->field.sid)
{
// Allocate memory for the SID
data->sid = calloc(valuelen + 1, sizeof(char));
// Copy the SID string
memcpy(data->sid, value, valuelen);
// 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.
return MG_FORM_FIELD_HANDLE_GET;
}
// We don't use this function, but it is required by the CivetWeb API
static int field_stored(const char *path, long long file_size, void *user_data)
{
return 0;
}
static int free_upload_data(struct upload_data *data)
{
// Free allocated memory
if(data->filename)
{
free(data->filename);
data->filename = NULL;
}
if(data->sid)
{
free(data->sid);
data->sid = NULL;
}
if(data->data)
{
free(data->data);
data->data = NULL;
}
if(data->import)
{
cJSON_Delete(data->import);
data->import = NULL;
}
return 0;
}
// Private function prototypes
static int process_received_zip(struct ftl_conn *api, struct upload_data *data);
static int process_received_tar_gz(struct ftl_conn *api, struct upload_data *data);
static int api_teleporter_POST(struct ftl_conn *api)
{
struct upload_data data;
memset(&data, 0, sizeof(struct upload_data));
const struct mg_request_info *req_info = mg_get_request_info(api->conn);
struct mg_form_data_handler fdh = {field_found, field_get, field_stored, &data};
// Disallow large ZIP archives (> 50 MB) to prevent DoS attacks.
// Typically, the ZIP archive size should be around 30-100 kB.
if(req_info->content_length > MAXFILESIZE)
{
free_upload_data(&data);
return send_json_error(api, 400,
"bad_request",
"ZIP archive too large",
NULL);
}
// Call the form handler to process the POST request content
const int ret = mg_handle_form_request(api->conn, &fdh);
if(ret < 0)
{
free_upload_data(&data);
return send_json_error(api, 400,
"bad_request",
"Invalid form request",
NULL);
}
// Check if we received something we consider being a file
if(data.data == NULL || data.filesize == 0)
{
free_upload_data(&data);
return send_json_error(api, 400,
"bad_request",
"No ZIP archive received",
NULL);
}
// Check if the file we received is too large
if(data.too_large)
{
free_upload_data(&data);
return send_json_error(api, 400,
"bad_request",
"ZIP archive too large",
NULL);
}
// Check if we received something that claims to be a ZIP archive
// - filename should end in ".zip"
// - the data itself
// - should be at least 40 bytes long
// - start with 0x04034b50 (local file header signature, see https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.3.9.TXT)
if(strlen(data.filename) > 4 &&
strcmp(data.filename + strlen(data.filename) - 4, ".zip") == 0 &&
data.filesize >= 40 &&
memcmp(data.data, "\x50\x4b\x03\x04", 4) == 0)
{
return process_received_zip(api, &data);
}
// Check if we received something that claims to be a TAR.GZ archive
// - filename should end in ".tar.gz"
// - the data itself
// - should be at least 40 bytes long
// - start with 0x8b1f (local file header signature, see https://www.ietf.org/rfc/rfc1952.txt)
else if(strlen(data.filename) > 7 &&
strcmp(data.filename + strlen(data.filename) - 7, ".tar.gz") == 0 &&
data.filesize >= 40 &&
memcmp(data.data, "\x1f\x8b", 2) == 0)
{
return process_received_tar_gz(api, &data);
}
// else: invalid file
free_upload_data(&data);
return send_json_error(api, 400,
"bad_request",
"Invalid file",
"The uploaded file does not appear to be a valid Pi-hole Teleporter archive");
}
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, data->import, json_files);
if(error != NULL)
{
const size_t msglen = strlen(error) + strlen(hint) + 4;
char *msg = calloc(msglen, sizeof(char));
strncpy(msg, error, msglen);
if(strlen(hint) > 0)
{
// Concatenate error message and hint into a single string
strcat(msg, ": ");
strcat(msg, hint);
}
free_upload_data(data);
return send_json_error_free(api, 400,
"bad_request",
"Invalid request",
msg, true);
}
// Free allocated memory
free_upload_data(data);
// Send response
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "files", json_files);
JSON_SEND_OBJECT(json);
}
static struct teleporter_files {
const char *filename; // Filename of the file in the archive
const char *table_name; // Name of the table in the database
const int listtype; // Type of list (only used for domainlist table)
const size_t num_columns; // Number of columns in the table
const char *columns[10]; // List of columns in the table
} teleporter_v5_files[] = {
{
.filename = "adlist.json",
.table_name = "adlist",
.listtype = -1,
.num_columns = 10,
.columns = { "id", "address", "enabled", "date_added", "date_modified", "comment", "date_updated", "number", "invalid_domains", "status" } // abp_entries and type are not defined in Pi-hole v5.x
},{
.filename = "adlist_by_group.json",
.table_name = "adlist_by_group",
.listtype = -1,
.num_columns = 2,
.columns = { "group_id", "adlist_id" }
},{
.filename = "blacklist.exact.json",
.table_name = "domainlist",
.listtype = 1, // GRAVITY_DOMAINLIST_DENY_EXACT
.num_columns = 7,
.columns = { "id", "domain", "enabled", "date_added", "date_modified", "comment", "type" }
},{
.filename = "blacklist.regex.json",
.table_name = "domainlist",
.listtype = 3, // GRAVITY_DOMAINLIST_DENY_REGEX
.num_columns = 7,
.columns = { "id", "domain", "enabled", "date_added", "date_modified", "comment", "type" }
},{
.filename = "client.json",
.table_name = "client",
.listtype = -1,
.num_columns = 5,
.columns = { "id", "ip", "date_added", "date_modified", "comment" }
},{
.filename = "client_by_group.json",
.table_name = "client_by_group",
.listtype = -1,
.num_columns = 2,
.columns = { "group_id", "client_id" }
},{
.filename = "domainlist_by_group.json",
.table_name = "domainlist_by_group",
.listtype = -1,
.num_columns = 2,
.columns = { "group_id", "domainlist_id" }
},{
.filename = "group.json",
.table_name = "group",
.listtype = -1,
.num_columns = 6,
.columns = { "id", "enabled", "name", "date_added", "date_modified", "description" }
},{
.filename = "whitelist.exact.json",
.table_name = "domainlist",
.listtype = 0, // GRAVITY_DOMAINLIST_ALLOW_EXACT
.num_columns = 7,
.columns = { "id", "domain", "enabled", "date_added", "date_modified", "comment", "type" }
},{
.filename = "whitelist.regex.json",
.table_name = "domainlist",
.listtype = 2, // GRAVITY_DOMAINLIST_ALLOW_REGEX
.num_columns = 7,
.columns = { "id", "domain", "enabled", "date_added", "date_modified", "comment", "type" }
}
};
static bool import_json_table(cJSON *json, struct teleporter_files *file)
{
// Check if the JSON object is an array
if(!cJSON_IsArray(json))
{
log_err("import_json_table(%s): JSON object is not an array", file->filename);
return false;
}
// Check if the JSON array is empty, if so, we can return early
const int num_entries = cJSON_GetArraySize(json);
// Check if all the JSON entries contain all the expected columns
cJSON *json_object = NULL;
cJSON_ArrayForEach(json_object, json)
{
if(!cJSON_IsObject(json_object))
{
log_err("import_json_table(%s): JSON array does not contain objects", file->filename);
return false;
}
// If this is a record for the domainlist table, add type/kind
if(strcmp(file->table_name, "domainlist") == 0)
{
// Add type/kind to the JSON object
cJSON_AddNumberToObject(json_object, "type", file->listtype);
}
// Check if the JSON object contains the expected columns
for(size_t i = 0; i < file->num_columns; i++)
{
if(cJSON_GetObjectItemCaseSensitive(json_object, file->columns[i]) == NULL)
{
log_err("import_json_table(%s): JSON object does not contain column \"%s\"", file->filename, file->columns[i]);
return false;
}
}
}
log_info("import_json_table(%s): JSON array contains %d entr%s", file->filename, num_entries, num_entries == 1 ? "y" : "ies");
// Open database connection
sqlite3 *db = NULL;
if(sqlite3_open_v2(config.files.gravity.v.s, &db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK)
{
log_err("import_json_table(%s): Unable to open database file \"%s\": %s",
file->filename, config.files.database.v.s, sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
// Disable foreign key constraints
if(sqlite3_exec(db, "PRAGMA foreign_keys = OFF;", NULL, NULL, NULL) != SQLITE_OK)
{
log_err("import_json_table(%s): Unable to disable foreign key constraints: %s", file->filename, sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
// Start transaction
if(sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK)
{
log_err("import_json_table(%s): Unable to start transaction: %s", file->filename, sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
// Clear existing table entries
if(file->listtype < 0)
{
// Delete all entries in the table
log_debug(DEBUG_API, "import_json_table(%s): Deleting all entries from table \"%s\"", file->filename, file->table_name);
if(dbquery(db, "DELETE FROM \"%s\";", file->table_name) != SQLITE_OK)
{
log_err("import_json_table(%s): Unable to delete entries from table \"%s\": %s",
file->filename, file->table_name, sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
}
else
{
// Delete all entries in the table of the same type
log_debug(DEBUG_API, "import_json_table(%s): Deleting all entries from table \"%s\" of type %d", file->filename, file->table_name, file->listtype);
if(dbquery(db, "DELETE FROM \"%s\" WHERE type = %d;", file->table_name, file->listtype) != SQLITE_OK)
{
log_err("import_json_table(%s): Unable to delete entries from table \"%s\": %s",
file->filename, file->table_name, sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
}
// Build dynamic SQL insertion statement
// "INSERT OR IGNORE INTO table (column1, column2, ...) VALUES (?, ?, ...);"
char *sql = sqlite3_mprintf("INSERT OR IGNORE INTO \"%s\" (", file->table_name);
for(size_t i = 0; i < file->num_columns; i++)
{
char *sql2 = sqlite3_mprintf("%s%s", sql, file->columns[i]);
sqlite3_free(sql);
sql = NULL;
if(i < file->num_columns - 1)
{
sql = sqlite3_mprintf("%s, ", sql2);
sqlite3_free(sql2);
sql2 = NULL;
}
else
{
sql = sqlite3_mprintf("%s) VALUES (", sql2);
sqlite3_free(sql2);
sql2 = NULL;
}
}
for(size_t i = 0; i < file->num_columns; i++)
{
char *sql2 = sqlite3_mprintf("%s?", sql);
sqlite3_free(sql);
sql = NULL;
if(i < file->num_columns - 1)
{
sql = sqlite3_mprintf("%s, ", sql2);
sqlite3_free(sql2);
sql2 = NULL;
}
else
{
sql = sqlite3_mprintf("%s);", sql2);
sqlite3_free(sql2);
sql2 = NULL;
}
}
// Prepare SQL statement
sqlite3_stmt *stmt = NULL;
if(sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
{
log_err("Unable to prepare SQL statement: %s", sqlite3_errmsg(db));
sqlite3_free(sql);
sqlite3_close(db);
return false;
}
// Free allocated memory
sqlite3_free(sql);
sql = NULL;
// Iterate over all JSON objects
cJSON_ArrayForEach(json_object, json)
{
// Bind values to SQL statement
for(size_t i = 0; i < file->num_columns; i++)
{
cJSON *json_value = cJSON_GetObjectItemCaseSensitive(json_object, file->columns[i]);
if(cJSON_IsString(json_value))
{
// Bind string value
if(sqlite3_bind_text(stmt, i + 1, json_value->valuestring, -1, SQLITE_STATIC) != SQLITE_OK)
{
log_err("Unable to bind text value to SQL statement: %s", sqlite3_errmsg(db));
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
}
else if(cJSON_IsNumber(json_value))
{
// Bind integer value
if(sqlite3_bind_int(stmt, i + 1, json_value->valueint) != SQLITE_OK)
{
log_err("Unable to bind integer value to SQL statement: %s", sqlite3_errmsg(db));
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
}
else if(cJSON_IsNull(json_value))
{
// Bind NULL value
if(sqlite3_bind_null(stmt, i + 1) != SQLITE_OK)
{
log_err("Unable to bind NULL value to SQL statement: %s", sqlite3_errmsg(db));
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
}
else
{
log_err("Unable to bind value to SQL statement: type = %X", (unsigned int)json_value->type & 0xFF);
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
}
// Execute SQL statement
if(sqlite3_step(stmt) != SQLITE_DONE)
{
log_err("Unable to execute SQL statement: %s", sqlite3_errmsg(db));
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
// Reset SQL statement
if(sqlite3_reset(stmt) != SQLITE_OK)
{
log_err("Unable to reset SQL statement: %s", sqlite3_errmsg(db));
sqlite3_finalize(stmt);
sqlite3_close(db);
return false;
}
}
// Finalize SQL statement
if(sqlite3_finalize(stmt) != SQLITE_OK)
{
log_err("Unable to finalize SQL statement: %s", sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
// Commit transaction
if(sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK)
{
log_err("Unable to commit transaction: %s", sqlite3_errmsg(db));
sqlite3_close(db);
return false;
}
// Close database connection
sqlite3_close(db);
return true;
}
static int process_received_tar_gz(struct ftl_conn *api, struct upload_data *data)
{
// Try to decompress the received data
uint8_t *archive = NULL;
mz_ulong archive_size = 0u;
if(!inflate_buffer(data->data, data->filesize, &archive, &archive_size))
{
free_upload_data(data);
return send_json_error(api, 400,
"bad_request",
"Invalid GZIP archive",
"The uploaded file does not appear to be a valid gzip archive - decompression failed");
}
// Print all files in the TAR archive if in debug mode
if(config.debug.api.v.b)
{
cJSON *json_files = list_files_in_tar(archive, archive_size);
cJSON *file = NULL;
cJSON_ArrayForEach(file, json_files)
{
const cJSON *name = cJSON_GetObjectItemCaseSensitive(file, "name");
const cJSON *size = cJSON_GetObjectItemCaseSensitive(file, "size");
if(name == NULL || size == NULL)
continue;
log_debug(DEBUG_API, "Found file in TAR archive: \"%s\" (%d bytes)",
name->valuestring, size->valueint);
}
}
// 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);
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
struct {
const char *archive_name;
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
}
};
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
log_info("Writing file \"%s\" (%zu bytes) to \"%s\"",
extract_files[i].archive_name, fileSize, extract_files[i].destination);
FILE *fp = fopen(extract_files[i].destination, "wb");
if(fp == NULL)
{
log_err("Unable to open file \"%s\" for writing: %s", extract_files[i].destination, strerror(errno));
continue;
}
if(fwrite(file, fileSize, 1, fp) != 1)
{
log_err("Unable to write file \"%s\": %s", extract_files[i].destination, strerror(errno));
fclose(fp);
continue;
}
fclose(fp);
JSON_COPY_STR_TO_ARRAY(imported_files, extract_files[i].destination);
}
}
// Append WEB_PORTS to setupVars.conf
FILE *fp = fopen(config.files.setupVars.v.s, "a");
if(fp == NULL)
log_err("Unable to open file \"%s\" for appending: %s", config.files.setupVars.v.s, strerror(errno));
else
{
fprintf(fp, "WEB_PORTS=%s\n", config.webserver.port.v.s);
fclose(fp);
}
// Remove pihole.toml to prevent it from being imported on restart
if(remove(GLOBALTOMLPATH) != 0)
log_err("Unable to remove file \"%s\": %s", GLOBALTOMLPATH, strerror(errno));
// Remove all rotated pihole.toml files to avoid automatic config
// restore on restart
for(unsigned int i = MAX_ROTATIONS; i > 0; i--)
{
const char *fname = GLOBALTOMLPATH;
const 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));
snprintf(path, buflen, BACKUP_DIR"/%s.%u", filename, i);
// Remove file (if it exists)
if(remove(path) != 0 && errno != ENOENT)
log_err("Unable to remove file \"%s\": %s", path, strerror(errno));
}
// Free allocated memory
free_upload_data(data);
// Signal FTL we want to restart for re-import
api->ftl.restart = true;
// Send response
cJSON *json = JSON_NEW_OBJECT();
JSON_ADD_ITEM_TO_OBJECT(json, "files", imported_files);
JSON_SEND_OBJECT(json);
}
int api_teleporter(struct ftl_conn *api)
{
if(api->method == HTTP_GET)
return api_teleporter_GET(api);
if(api->method == HTTP_POST)
return api_teleporter_POST(api);
return 0;
}

89
src/api/theme.c Normal file
View File

@ -0,0 +1,89 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* Theme-related routines
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
// NULL
#include <stddef.h>
// strcasecmp()
#include <string.h>
#include "theme.h"
struct web_themes webthemes[THEME_MAX] = {
{
/* id */ THEME_DEFAULT_AUTO,
/* name */ "default-auto",
/* description */ "Pi-hole auto",
/* dark */ true,
/* color */ "#367fa9"
},
{
/* id */ THEME_DEFAULT_LIGHT,
/* name */ "default-light",
/* description */ "Pi-hole day",
/* dark */ false,
/* color */ "#367fa9"
},
{
/* id */ THEME_DEFAULT_DARK,
/* name */ "default-dark",
/* description */ "Pi-hole midnight",
/* dark */ true,
/* color */ "#272c30"
},
{
/* id */ THEME_DEFAULT_DARKER,
/* name */ "default-darker",
/* description */ "Pi-hole deep-midnight",
/* dark */ true,
/* color */ "#2e6786"
},
{
/* id */ THEME_HIGH_CONTRAST,
/* name */ "high-contrast",
/* description */ "High-contrast light",
/* dark */ false,
/* color */ "#0078a0"
},
{
/* id */ THEME_HIGH_CONTRAST_DARK,
/* name */ "high-contrast-dark",
/* description */ "High-contrast dark",
/* dark */ true,
/* color */ "#0077c7"
},
{
/* id */ THEME_LCARS,
/* name */ "lcars",
/* description */ "Star Trek LCARS",
/* dark */ true,
/* color */ "#4488FF"
},
};
const char * __attribute__ ((pure)) get_web_theme_str(const enum web_theme web_theme)
{
for(enum web_theme i = 0; i < THEME_MAX; i++)
if(webthemes[i].id == web_theme)
return webthemes[i].name;
return NULL;
}
int __attribute__ ((pure)) get_web_theme_val(const char *web_theme)
{
// Iterate over all possible theme values
for(enum web_theme i = 0; i < THEME_MAX; i++)
{
if(strcasecmp(web_theme, webthemes[i].name) == 0)
return i;
}
// Invalid value
return -1;
}

41
src/api/theme.h Normal file
View File

@ -0,0 +1,41 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* API route prototypes
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef THEME_H
#define THEME_H
#include <stdbool.h>
enum web_theme {
THEME_DEFAULT_AUTO = 0,
THEME_DEFAULT_LIGHT,
THEME_DEFAULT_DARK,
THEME_DEFAULT_DARKER,
THEME_HIGH_CONTRAST,
THEME_HIGH_CONTRAST_DARK,
THEME_LCARS,
THEME_MAX // This needs to be the last element in this enum
} __attribute__ ((packed));
struct web_themes{
const enum web_theme id;
const char *name;
const char *description;
const bool dark;
const char *color;
};
// defined in theme.c
extern struct web_themes webthemes[THEME_MAX];
// Prototypes
const char * __attribute__ ((pure)) get_web_theme_str(const enum web_theme web_theme);
int __attribute__ ((pure)) get_web_theme_val(const char *web_theme);
#endif // THEME_H

View File

@ -19,6 +19,10 @@
# define NETTLE_VERSION_MINOR 0
#endif
#ifdef HAVE_MBEDTLS
#include <mbedtls/version.h>
#endif
#include "FTL.h"
#include "args.h"
#include "version.h"
@ -36,8 +40,33 @@
#include "tools/gravity-parseList.h"
// run_dhcp_discover()
#include "tools/dhcp-discover.h"
// mg_version()
#include "webserver/civetweb/civetweb.h"
// cJSON_Version()
#include "webserver/cJSON/cJSON.h"
#include "config/cli.h"
#include "config/config.h"
// compression functions
#include "zip/gzip.h"
// teleporter functions
#include "zip/teleporter.h"
// printTOTP()
#include "api/api.h"
// generate_certificate()
#include "webserver/x509.h"
// run_dhcp_discover()
#include "tools/dhcp-discover.h"
// run_arp_scan()
#include "tools/arp-scan.h"
// run_performance_test()
#include "config/password.h"
// idn2_to_ascii_lz()
#include <idn2.h>
// sha256sum()
#include "files.h"
// resolveHostname()
#include "resolve.h"
// defined in dnsmasq.c
extern void print_dnsmasq_version(const char *yellow, const char *green, const char *bold, const char *normal);
@ -74,7 +103,7 @@ const char** argv_dnsmasq = NULL;
#define COL_PURPLE "\x1b[95m" // bright foreground color
#define COL_CYAN "\x1b[96m" // bright foreground color
static inline bool __attribute__ ((pure)) is_term(void)
static bool __attribute__ ((pure)) is_term(void)
{
// test whether STDOUT refers to a terminal
return isatty(fileno(stdout)) == 1;
@ -175,15 +204,203 @@ void parse_args(int argc, char* argv[])
(argc > 1 && strEndsWith(argv[1], ".db")))
exit(sqlite3_shell_main(argc, argv));
// Compression feature
if((argc == 3 || argc == 4) &&
(strcmp(argv[1], "gzip") == 0 || strcmp(argv[1], "--gzip") == 0))
{
// Enable stdout printing
cli_mode = true;
log_ctrl(false, true);
// Get input and output file names
const char *infile = argv[2];
bool is_gz = strEndsWith(infile, ".gz");
char *outfile = NULL;
if(argc == 4)
{
// If an output file is given, we use it
outfile = strdup(argv[3]);
}
else if(is_gz)
{
// If no output file is given, and this is a gzipped
// file, we use the input file name without ".gz"
// appended
outfile = calloc(strlen(infile)-2, sizeof(char));
memcpy(outfile, infile, strlen(infile)-3);
}
else
{
// If no output file is given, and this is not a gzipped
// file, we use the input file name with ".gz" appended
outfile = calloc(strlen(infile)+4, sizeof(char));
strcpy(outfile, infile);
strcat(outfile, ".gz");
}
bool success = false;
if(is_gz)
{
// If the input file is already gzipped, we decompress it
success = inflate_file(infile, outfile, true);
}
else
{
// If the input file is not gzipped, we compress it
success = deflate_file(infile, outfile, true);
}
// Free allocated memory
free(outfile);
// Return exit code
exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
}
// Set config option through CLI
if(argc > 1 && strcmp(argv[1], "--config") == 0)
{
// Enable stdout printing
cli_mode = true;
log_ctrl(false, false);
readFTLconf(&config, false);
log_ctrl(false, true);
clear_debug_flags(); // No debug printing wanted
if(argc == 2)
exit(get_config_from_CLI(NULL, false));
else if(argc == 3)
exit(get_config_from_CLI(argv[2], false));
else if(argc == 4 && strcmp(argv[2], "-q") == 0)
exit(get_config_from_CLI(argv[3], true));
else if(argc == 4)
exit(set_config_from_CLI(argv[2], argv[3]));
else
{
printf("Usage: %s --config [<config item key>] [<value>]\n", argv[0]);
printf("Example: %s --config dns.blockESNI true\n", argv[0]);
exit(EXIT_FAILURE);
}
}
// Set config option through CLI
if(argc == 2 && strcmp(argv[1], "--totp") == 0)
{
cli_mode = true;
log_ctrl(false, false);
readFTLconf(&config, false);
log_ctrl(false, true);
clear_debug_flags(); // No debug printing wanted
exit(printTOTP());
}
// Create teleporter archive through CLI
if(argc == 2 && strcmp(argv[1], "--teleporter") == 0)
{
// Enable stdout printing
cli_mode = true;
log_ctrl(false, true);
readFTLconf(&config, false);
exit(write_teleporter_zip_to_disk() ? EXIT_SUCCESS : EXIT_FAILURE);
}
// Import teleporter archive through CLI
if(argc == 3 && strcmp(argv[1], "--teleporter") == 0)
{
// Enable stdout printing
cli_mode = true;
log_ctrl(false, true);
readFTLconf(&config, false);
exit(read_teleporter_zip_from_disk(argv[2]) ? EXIT_SUCCESS : EXIT_FAILURE);
}
// Generate X.509 certificate
if(argc > 1 && strcmp(argv[1], "--gen-x509") == 0)
{
if(argc < 3 || argc > 5)
{
printf("Usage: %s --gen-x509 <output file> [<domain>] [rsa]\n", argv[0]);
printf("Example: %s --gen-x509 /etc/pihole/tls.pem\n", argv[0]);
printf(" with domain: %s --gen-x509 /etc/pihole/tls.pem pi.hole\n", argv[0]);
printf(" RSA with domain: %s --gen-x509 /etc/pihole/tls.pem nanopi.lan rsa\n", argv[0]);
exit(EXIT_FAILURE);
}
// Enable stdout printing
cli_mode = true;
log_ctrl(false, true);
const char *domain = argc > 3 ? argv[3] : "pi.hole";
const bool rsa = argc > 4 && strcasecmp(argv[4], "rsa") == 0;
exit(generate_certificate(argv[2], rsa, domain) ? EXIT_SUCCESS : EXIT_FAILURE);
}
// Parse X.509 certificate
if(argc > 1 &&
(strcmp(argv[1], "--read-x509") == 0 ||
strcmp(argv[1], "--read-x509-key") == 0))
{
if(argc > 4)
{
printf("Usage: %s %s [<input file>] [<domain>]\n", argv[0], argv[1]);
printf("Example: %s %s /etc/pihole/tls.pem\n", argv[0], argv[1]);
printf(" with domain: %s %s /etc/pihole/tls.pem pi.hole\n", argv[0], argv[1]);
exit(EXIT_FAILURE);
}
// Option parsing
// Should we report on the private key?
const bool private_key = strcmp(argv[1], "--read-x509-key") == 0;
// If no certificate file is given, we use the one from the config
const char *certfile = NULL;
if(argc == 2)
{
readFTLconf(&config, false);
certfile = config.webserver.tls.cert.v.s;
}
else
certfile = argv[2];
// If no domain is given, we only check the certificate
const char *domain = argc > 3 ? argv[3] : NULL;
// Enable stdout printing
cli_mode = true;
log_ctrl(false, true);
enum cert_check result = read_certificate(certfile, domain, private_key);
if(argc < 4)
exit(result == CERT_OKAY ? EXIT_SUCCESS : EXIT_FAILURE);
else if(result == CERT_DOMAIN_MATCH)
{
printf("Certificate matches domain %s\n", argv[3]);
exit(EXIT_SUCCESS);
}
else
{
printf("Certificate does not match domain %s\n", argv[3]);
exit(EXIT_FAILURE);
}
}
// If the first argument is "gravity" (e.g., /usr/bin/pihole-FTL gravity),
// we offer some specialized gravity tools
if(argc > 1 && strcmp(argv[1], "gravity") == 0)
if(argc > 1 && (strcmp(argv[1], "gravity") == 0 || strcmp(argv[1], "antigravity") == 0))
{
const bool antigravity = strcmp(argv[1], "antigravity") == 0;
// pihole-FTL gravity parseList <infile> <outfile> <adlistID>
if(argc == 6 && strcmp(argv[2], "parseList") == 0)
if(argc == 6 && strcasecmp(argv[2], "parseList") == 0)
{
// Parse the given list and write the result to the given file
exit(gravity_parseList(argv[3], argv[4], argv[5]));
exit(gravity_parseList(argv[3], argv[4], argv[5], false, antigravity));
}
// pihole-FTL gravity checkList <infile>
if(argc == 4 && strcasecmp(argv[2], "checkList") == 0)
{
// Parse the given list and write the result to the given file
exit(gravity_parseList(argv[3], "", "-1", true, antigravity));
}
printf("Incorrect usage of pihole-FTL gravity subcommand\n");
@ -198,6 +415,14 @@ void parse_args(int argc, char* argv[])
exit(run_dhcp_discover());
}
// Password hashing performance test
if(argc > 1 && (strcmp(argv[1], "--perf") == 0 || strcmp(argv[1], "performance") == 0))
{
// Enable stdout printing
cli_mode = true;
exit(run_performance_test());
}
// ARP scanning mode
if(argc > 1 && strcmp(argv[1], "arp-scan") == 0)
{
@ -208,6 +433,90 @@ void parse_args(int argc, char* argv[])
exit(run_arp_scan(scan_all, extreme_mode));
}
// IDN2 conversion mode
if(argc > 1 && strcmp(argv[1], "idn2") == 0)
{
// Enable stdout printing
cli_mode = true;
if(argc == 3)
{
// Convert unicode domain to punycode
char *punycode = NULL;
const int rc = idn2_to_ascii_lz(argv[2], &punycode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
if (rc != IDN2_OK)
{
// Invalid domain name
printf("Invalid domain name: %s\n", argv[2]);
exit(EXIT_FAILURE);
}
// Convert punycode domain to lowercase
for(unsigned int i = 0u; i < strlen(punycode); i++)
punycode[i] = tolower(punycode[i]);
printf("%s\n", punycode);
exit(EXIT_SUCCESS);
}
else if(argc == 4 && (strcmp(argv[2], "-d") == 0 || strcmp(argv[2], "--decode") == 0))
{
// Convert punycode domain to unicode
char *unicode = NULL;
const int rc = idn2_to_unicode_lzlz(argv[3], &unicode, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
if (rc != IDN2_OK)
{
// Invalid domain name
printf("Invalid domain name: %s\n", argv[3]);
exit(EXIT_FAILURE);
}
printf("%s\n", unicode);
exit(EXIT_SUCCESS);
}
else
{
printf("Usage: %s idn2 [--decode] <domain>\n", argv[0]);
exit(EXIT_FAILURE);
}
}
// sha256sum mode
if(argc == 3 && strcmp(argv[1], "sha256sum") == 0)
{
// Enable stdout printing
cli_mode = true;
uint8_t checksum[SHA256_DIGEST_SIZE];
if(!sha256sum(argv[2], checksum))
exit(EXIT_FAILURE);
// Convert checksum to hex string
char hex[SHA256_DIGEST_SIZE*2+1];
sha256_raw_to_hex(checksum, hex);
// Print result
printf("%s %s\n", hex, argv[2]);
exit(EXIT_SUCCESS);
}
// Local reverse name resolver
if(argc == 3 && strcasecmp(argv[1], "ptr") == 0)
{
// Enable stdout printing
cli_mode = true;
// Need to get dns.port and the resolver settings
readFTLconf(&config, false);
char *name = resolveHostname(argv[2], true);
if(name == NULL)
exit(EXIT_FAILURE);
// Print result
printf("%s\n", name);
free(name);
exit(EXIT_SUCCESS);
}
// start from 1, as argv[0] is the executable name
for(int i = 1; i < argc; i++)
{
@ -276,7 +585,23 @@ void parse_args(int argc, char* argv[])
const char *arg[2];
arg[0] = "";
arg[1] = "--test";
exit(main_dnsmasq(2, arg));
log_ctrl(false, true);
exit(main_dnsmasq(2, (char**)arg));
}
// Implement dnsmasq's test function, no need to prepare the entire FTL
// environment (initialize shared memory, lead queries from long-term
// database, ...) when the task is a simple (dnsmasq) syntax check
if(argc == 3 && strcmp(argv[1], "dnsmasq-test-file") == 0)
{
const char *arg[3];
char *filename = calloc(strlen(argv[2])+strlen("--conf-file=")+1, sizeof(char));
arg[0] = "";
sprintf(filename, "--conf-file=%s", argv[2]);
arg[1] = filename;
arg[2] = "--test";
log_ctrl(false, true);
exit(main_dnsmasq(3, (char**)arg));
}
// If we find "--" we collect everything behind that for dnsmasq
@ -379,6 +704,7 @@ void parse_args(int argc, char* argv[])
const char *bold = cli_bold();
const char *normal = cli_normal();
const char *green = cli_color(COL_GREEN);
const char *red = cli_color(COL_RED);
const char *yellow = cli_color(COL_YELLOW);
// Print FTL version
@ -421,6 +747,69 @@ void parse_args(int argc, char* argv[])
printf("Version: %s%s" xstr(NETTLE_VERSION_MAJOR) "." xstr(NETTLE_VERSION_MINOR) "%s\n",
green, bold, normal);
printf("GMP: %s\n", NETTLE_USE_MINI_GMP ? "Mini" : "Full");
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",
green, bold, mg_version(), normal, yellow, green, bold, normal);
#else
printf("Version: %s%s%s%s\n", green, bold, mg_version(), normal);
#endif
printf("Features: ");
if(mg_check_feature(MG_FEATURES_FILES))
printf("Files: %sYes%s, ", green, normal);
else
printf("Files: %sNo%s, ", red, normal);
if(mg_check_feature(MG_FEATURES_TLS))
printf("TLS: %sYes%s, ", green, normal);
else
printf("TLS: %sNo%s, ", red, normal);
if(mg_check_feature(MG_FEATURES_CGI))
printf("CGI: %sYes%s, ", green, normal);
else
printf("CGI: %sNo%s, ", red, normal);
if(mg_check_feature(MG_FEATURES_IPV6))
printf("IPv6: %sYes%s, \n", green, normal);
else
printf("IPv6: %sNo%s, \n", red, normal);
if(mg_check_feature(MG_FEATURES_WEBSOCKET))
printf(" WebSockets: %sYes%s, ", green, normal);
else
printf(" WebSockets: %sNo%s, ", red, normal);
if(mg_check_feature(MG_FEATURES_SSJS))
printf("Server-side JavaScript: %sYes%s\n", green, normal);
else
printf("Server-side JavaScript: %sNo%s\n", red, normal);
if(mg_check_feature(MG_FEATURES_LUA))
printf(" Lua: %sYes%s, ", green, normal);
else
printf(" Lua: %sNo%s, ", red, normal);
if(mg_check_feature(MG_FEATURES_CACHE))
printf("Cache: %sYes%s, ", green, normal);
else
printf("Cache: %sNo%s, ", red, normal);
if(mg_check_feature(MG_FEATURES_STATS))
printf("Stats: %sYes%s, ", green, normal);
else
printf("Stats: %sNo%s, ", red, normal);
if(mg_check_feature(MG_FEATURES_COMPRESSION))
printf("Compression: %sYes%s\n", green, normal);
else
printf("Compression: %sNo%s\n", red, normal);
if(mg_check_feature(MG_FEATURES_HTTP2))
printf(" HTTP2: %sYes%s, ", green, normal);
else
printf(" HTTP2: %sNo%s, ", red, normal);
if(mg_check_feature(MG_FEATURES_X_DOMAIN_SOCKET))
printf("Unix domain sockets: %sYes%s\n", green, normal);
else
printf("Unix domain sockets: %sNo%s\n", red, normal);
printf("\n");
printf("****************************** %s%scJSON%s ********************************\n",
yellow, bold, normal);
printf("Version: %s%s%s%s\n", green, bold, cJSON_Version(), normal);
printf("\n");
exit(EXIT_SUCCESS);
}
@ -557,12 +946,63 @@ void parse_args(int argc, char* argv[])
printf("\t%s--list-dhcp6%s List known DHCPv6 config options\n\n", green, normal);
printf("%sDebugging and special use:%s\n", yellow, normal);
printf("\t%sd%s, %sdebug%s Enter debugging mode\n", green, normal, green, normal);
printf("\t%sd%s, %sdebug%s Enter debugging mode: Don't go into \n", green, normal, green, normal);
printf("\t daemon mode and verbose logging\n");
printf("\t%stest%s Don't start pihole-FTL but instead\n", green, normal);
printf("\t quit immediately\n");
printf("\t process everything and quit immediately\n");
printf("\t%s-f%s, %sno-daemon%s Don't go into daemon mode\n\n", green, normal, green, normal);
printf("%sConfig options:%s\n", yellow, normal);
printf("\t%s--config %skey%s Get current value of config item %skey%s\n", green, blue, normal, blue, normal);
printf("\t%s--config %skey %svalue%s Set new %svalue%s of config item %skey%s\n\n", green, blue, cyan, normal, cyan, normal, blue, normal);
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("%sTeleporter:%s\n", yellow, normal);
printf("\t%s--teleporter%s Create a Teleporter archive in the\n", green, normal);
printf("\t current directory and print its name\n");
printf("\t%s--teleporter%s file%s Import the Teleporter archive %sfile%s\n\n", green, cyan, normal, cyan, normal);
printf("%sTLS X.509 certificate generator:%s\n", yellow, normal);
printf(" Generate a self-signed certificate suitable for SSL/TLS\n");
printf(" and store it in %soutfile%s.\n\n", cyan, normal);
printf(" By default, this new certificate is based on the elliptic\n");
printf(" curve secp521r1. If the optional flag %s[rsa]%s is specified,\n", purple, normal);
printf(" an RSA (4096 bit) key will be generated instead.\n\n");
printf(" Usage: %spihole-FTL --gen-x509 %soutfile %s[rsa]%s\n\n", green, cyan, purple, normal);
printf("%sTLS X.509 certificate parser:%s\n", yellow, normal);
printf(" Parse the given X.509 certificate and optionally check if\n");
printf(" it matches a given domain. If no domain is given, only a\n");
printf(" human-readable output string is printed.\n\n");
printf(" If no certificate file is given, the one from the config\n");
printf(" is used (if applicable). If --read-x509-key is used, details\n");
printf(" about the private key are printed as well.\n\n");
printf(" Usage: %spihole-FTL --read-x509 %s[certfile] %s[domain]%s\n", green, cyan, purple, normal);
printf(" Usage: %spihole-FTL --read-x509-key %s[certfile] %s[domain]%s\n\n", green, cyan, purple, normal);
printf("%sGravity tools:%s\n", yellow, normal);
printf(" Check domains in a given file for validity using Pi-hole's\n");
printf(" gravity filters. The expected input format is one domain\n");
printf(" per line (no HOSTS lists, etc.)\n\n");
printf(" Usage: %spihole-FTL gravity checkList %sinfile%s\n\n", green, cyan, normal);
printf("%sIDN2 conversion:%s\n", yellow, normal);
printf(" Convert a given internationalized domain name (IDN) to\n");
printf(" punycode or vice versa.\n\n");
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("%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%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);
@ -571,6 +1011,11 @@ void parse_args(int argc, char* argv[])
printf("\t interfaces\n");
printf("\t Append %s-x%s to force scan on all\n", cyan, normal);
printf("\t interfaces and scan 10x more often\n");
printf("\t%s--totp%s Generate valid TOTP token for 2FA\n", green, normal);
printf("\t authentication (if enabled)\n");
printf("\t%s--perf%s Run performance-tests based on the\n", green, normal);
printf("\t BALLOON password-hashing algorithm\n");
printf("\t%s--%s [OPTIONS]%s Pass OPTIONS to internal dnsmasq resolver\n", green, cyan, normal);
printf("\t%s-h%s, %shelp%s Display this help and exit\n\n", green, normal, green, normal);
exit(EXIT_SUCCESS);
}
@ -582,12 +1027,6 @@ void parse_args(int argc, char* argv[])
exit(EXIT_SUCCESS);
}
// Return number of errors on this undocumented flag
if(strcmp(argv[i], "--check-structs") == 0)
{
exit(check_struct_sizes());
}
// Complain if invalid options have been found
if(!ok)
{
@ -604,3 +1043,21 @@ void parse_args(int argc, char* argv[])
}
}
}
// defined in src/dnsmasq/option.c
extern void reset_usage_indicator(void);
// defined in src/log.h
bool only_testing = false;
void test_dnsmasq_options(int argc, const char *argv[])
{
// Reset getopt before calling read_opts
optind = 0;
// Signal we don't want to jump back to FTL's main()
// but die after configuration parsing
only_testing = true;
// Call dnsmasq's option parser
reset_usage_indicator();
read_opts(argc, (char**)argv, NULL);
}

View File

@ -25,7 +25,6 @@ const char *cli_bold(void) __attribute__ ((pure));
const char *cli_normal(void) __attribute__ ((pure));
const char *cli_over(void) __attribute__ ((pure));
// defined in dnsmasq_interface.c
int check_struct_sizes(void);
void test_dnsmasq_options(int argc, const char *argv[]);
#endif //ARGS_H

View File

@ -14,12 +14,11 @@
#undef __USE_XOPEN
#include "FTL.h"
#include "capabilities.h"
#include "config.h"
#include "config/config.h"
#include "log.h"
static const unsigned int capabilityIDs[] = { CAP_CHOWN , CAP_DAC_OVERRIDE , CAP_DAC_READ_SEARCH , CAP_FOWNER , CAP_FSETID , CAP_KILL , CAP_SETGID , CAP_SETUID , CAP_SETPCAP , CAP_LINUX_IMMUTABLE , CAP_NET_BIND_SERVICE , CAP_NET_BROADCAST , CAP_NET_ADMIN , CAP_NET_RAW , CAP_IPC_LOCK , CAP_IPC_OWNER , CAP_SYS_MODULE , CAP_SYS_RAWIO , CAP_SYS_CHROOT , CAP_SYS_PTRACE , CAP_SYS_PACCT , CAP_SYS_ADMIN , CAP_SYS_BOOT , CAP_SYS_NICE , CAP_SYS_RESOURCE , CAP_SYS_TIME , CAP_SYS_TTY_CONFIG , CAP_MKNOD , CAP_LEASE , CAP_AUDIT_WRITE , CAP_AUDIT_CONTROL , CAP_SETFCAP };
static const char* capabilityNames[] = {"CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_DAC_READ_SEARCH", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_SETGID", "CAP_SETUID", "CAP_SETPCAP", "CAP_LINUX_IMMUTABLE", "CAP_NET_BIND_SERVICE", "CAP_NET_BROADCAST", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_IPC_LOCK", "CAP_IPC_OWNER", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_CHROOT", "CAP_SYS_PTRACE", "CAP_SYS_PACCT", "CAP_SYS_ADMIN", "CAP_SYS_BOOT", "CAP_SYS_NICE", "CAP_SYS_RESOURCE", "CAP_SYS_TIME", "CAP_SYS_TTY_CONFIG", "CAP_MKNOD", "CAP_LEASE", "CAP_AUDIT_WRITE", "CAP_AUDIT_CONTROL", "CAP_SETFCAP"};
static const unsigned int numCaps = sizeof(capabilityIDs) / sizeof(*capabilityIDs);
bool check_capability(const unsigned int cap)
{
@ -93,53 +92,53 @@ bool check_capabilities(void)
data = calloc(sizeof(*data), capsize);
capget(hdr, data);
logg("***************************************");
logg("* Linux capability debugging enabled *");
for(unsigned int i = 0u; i < numCaps; i++)
log_debug(DEBUG_CAPS, "***************************************");
log_debug(DEBUG_CAPS, "* Linux capability debugging enabled *");
for(unsigned int i = 0u; i < ArraySize(capabilityIDs); i++)
{
const unsigned int capid = capabilityIDs[i];
logg("* %-24s (%02u) = %s%s%s *",
log_debug(DEBUG_CAPS, "* %-24s (%02u) = %s%s%s *",
capabilityNames[capid], capid,
((data->permitted & (1 << capid)) ? "P":"-"),
((data->inheritable & (1 << capid)) ? "I":"-"),
((data->effective & (1 << capid)) ? "E":"-"));
}
logg("***************************************");
log_debug(DEBUG_CAPS, "***************************************");
bool capabilities_okay = true;
if (!(data->permitted & (1 << CAP_NET_ADMIN)) ||
!(data->effective & (1 << CAP_NET_ADMIN)))
{
// Needed for ARP-injection (used when we're the DHCP server)
logg("WARNING: Required Linux capability CAP_NET_ADMIN not available");
log_warn("Required Linux capability CAP_NET_ADMIN not available");
capabilities_okay = false;
}
if (!(data->permitted & (1 << CAP_NET_RAW)) ||
!(data->effective & (1 << CAP_NET_RAW)))
{
// Needed for raw socket access (necessary for ICMP)
logg("WARNING: Required Linux capability CAP_NET_RAW not available");
log_warn("Required Linux capability CAP_NET_RAW not available");
capabilities_okay = false;
}
if (!(data->permitted & (1 << CAP_NET_BIND_SERVICE)) ||
!(data->effective & (1 << CAP_NET_BIND_SERVICE)))
{
// Necessary for dynamic port binding
logg("WARNING: Required Linux capability CAP_NET_BIND_SERVICE not available");
log_warn("Required Linux capability CAP_NET_BIND_SERVICE not available");
capabilities_okay = false;
}
if (!(data->permitted & (1 << CAP_SYS_NICE)) ||
!(data->effective & (1 << CAP_SYS_NICE)))
{
// Necessary for setting higher process priority through nice
logg("WARNING: Required Linux capability CAP_SYS_NICE not available");
log_warn("Required Linux capability CAP_SYS_NICE not available");
capabilities_okay = false;
}
if (!(data->permitted & (1 << CAP_CHOWN)) ||
!(data->effective & (1 << CAP_CHOWN)))
{
// Necessary to chown required files that are owned by another user
logg("WARNING: Required Linux capability CAP_CHOWN not available");
log_warn("Required Linux capability CAP_CHOWN not available");
capabilities_okay = false;
}

View File

@ -10,6 +10,8 @@
#ifndef CAPABILITIES_H
#define CAPABILITIES_H
#include <linux/capability.h>
bool check_capability(const unsigned int cap);
bool check_capabilities(void);

File diff suppressed because it is too large Load Diff

View File

@ -1,114 +0,0 @@
/* 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
* FTL config file prototypes
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef CONFIG_H
#define CONFIG_H
// enum privacy_level
#include "enums.h"
// typedef int16_t
#include <sys/types.h>
// typedef uni32_t
#include <stdint.h>
// struct in_addr, in6_addr
#include <netinet/in.h>
// type bool
#include <stdbool.h>
// type FILE
#include <stdio.h>
void init_config_mutex(void);
void getLogFilePath(void);
void read_FTLconf(void);
void get_privacy_level(FILE *fp);
void get_blocking_mode(FILE *fp);
void read_debuging_settings(FILE *fp);
// We do not use bitfields in here as this struct exists only once in memory.
// Accessing bitfields may produce slightly more inefficient code on some
// architectures (such as ARM) and savng a few bit of RAM but bloating up the
// rest of the application each time these fields are accessed is bad.
typedef struct {
bool socket_listenlocal :1;
bool analyze_AAAA :1;
bool resolveIPv6 :1;
bool resolveIPv4 :1;
bool ignore_localhost :1;
bool analyze_only_A_AAAA :1;
bool DBimport :1;
bool DBexport :1;
bool parse_arp_cache :1;
bool cname_inspection :1;
bool block_esni :1;
bool names_from_netdb :1;
bool edns0_ecs :1;
bool show_dnssec :1;
bool addr2line :1;
struct {
bool mozilla_canary :1;
bool icloud_private_relay :1;
} special_domains;
struct {
bool load :1;
unsigned char shmem;
unsigned char disk;
} check;
enum privacy_level privacylevel;
enum blocking_mode blockingmode;
enum refresh_hostnames refresh_hostnames;
enum busy_reply reply_when_busy;
enum ptr_type pihole_ptr;
int maxDBdays;
int port;
int maxlogage;
int dns_port;
unsigned int delay_startup;
unsigned int network_expire;
unsigned int block_ttl;
struct {
unsigned int count;
unsigned int interval;
} rate_limit;
enum debug_flags debug;
time_t DBinterval;
struct {
struct {
bool overwrite_v4 :1;
bool overwrite_v6 :1;
struct in_addr v4;
struct in6_addr v6;
} own_host;
struct {
bool overwrite_v4 :1;
bool overwrite_v6 :1;
struct in_addr v4;
struct in6_addr v6;
} ip_blocking;
} reply_addr;
} ConfigStruct;
typedef struct {
const char* conf;
const char* snapConf;
char* log;
char* pid;
char* port;
char* socketfile;
char* FTL_db;
char* gravity_db;
char* macvendor_db;
char* setupVars;
char* auditlist;
} FTLFileNamesStruct;
extern ConfigStruct config;
extern FTLFileNamesStruct FTLfiles;
#endif //CONFIG_H

44
src/config/CMakeLists.txt Normal file
View File

@ -0,0 +1,44 @@
# Pi-hole: A black hole for Internet advertisements
# (c) 2021 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# FTL Engine
# /src/config/CMakeList.txt
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
set(sources
cli.c
cli.h
config.c
config.h
dnsmasq_config.c
dnsmasq_config.h
env.c
env.h
inotify.c
inotify.h
legacy_reader.c
legacy_reader.h
password.c
password.h
suggest.c
suggest.h
setupVars.c
setupVars.h
toml_writer.c
toml_writer.h
toml_reader.c
toml_reader.h
toml_helper.c
toml_helper.h
validator.c
validator.h
)
add_library(config OBJECT ${sources})
target_compile_options(config PRIVATE ${EXTRAWARN})
target_include_directories(config PRIVATE ${PROJECT_SOURCE_DIR}/src)
add_subdirectory(tomlc99)

583
src/config/cli.c Normal file
View File

@ -0,0 +1,583 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* CLI config routines
*
* 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 "config/cli.h"
#include "config/config.h"
#include "config/toml_helper.h"
#include "config/toml_writer.h"
#include "config/dnsmasq_config.h"
#include "log.h"
#include "datastructure.h"
// toml_table_t
#include "tomlc99/toml.h"
// hash_password()
#include "config/password.h"
// check_capability()
#include "capabilities.h"
// suggest_closest_conf_key()
#include "config/suggest.h"
enum exit_codes {
OKAY = 0,
FAIL = 1,
VALUE_INVALID = 2,
DNSMASQ_TEST_FAILED = 3,
KEY_UNKNOWN = 4,
ENV_VAR_FORCED = 5,
} __attribute__((packed));
// Read a TOML value from a table depending on its type
static bool readStringValue(struct conf_item *conf_item, const char *value, struct config *newconf)
{
if(conf_item == NULL || value == NULL)
{
log_debug(DEBUG_CONFIG, "readStringValue(%p, %p) called with invalid arguments, skipping",
conf_item, value);
return false;
}
switch(conf_item->t)
{
case CONF_BOOL:
{
if(strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0)
conf_item->v.b = true;
else if(strcasecmp(value, "false") == 0 || strcasecmp(value, "no") == 0)
conf_item->v.b = false;
else
{
log_err("Config setting %s is invalid, allowed options are: [ true, false, yes, no ]", conf_item->k);
return false;
}
break;
}
case CONF_ALL_DEBUG_BOOL:
{
if(strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0)
{
set_all_debug(newconf, true);
conf_item->v.b = true;
set_debug_flags(newconf);
}
else if(strcasecmp(value, "false") == 0 || strcasecmp(value, "no") == 0)
{
set_all_debug(newconf, false);
conf_item->v.b = false;
set_debug_flags(newconf);
}
else
{
log_err("Config setting %s is invalid, allowed options are: [ true, false, yes, no ]", conf_item->k);
return false;
}
break;
}
case CONF_INT:
{
int val;
if(sscanf(value, "%i", &val) == 1)
conf_item->v.i = val;
else
{
log_err("Config setting %s is invalid, allowed options are: integer", conf_item->k);
return false;
}
break;
}
case CONF_UINT:
{
unsigned int val;
if(sscanf(value, "%u", &val) == 1)
conf_item->v.ui = val;
else
{
log_err("Config setting %s is invalid, allowed options are: unsigned integer", conf_item->k);
return false;
}
break;
}
case CONF_UINT16:
{
uint16_t val;
if(sscanf(value, "%hu", &val) == 1)
conf_item->v.ui = val;
else
{
log_err("Config setting %s is invalid, allowed options are: unsigned integer (16 bit)", conf_item->k);
return false;
}
break;
}
case CONF_LONG:
{
long val;
if(sscanf(value, "%li", &val) == 1)
conf_item->v.l = val;
else
{
log_err("Config setting %s is invalid, allowed options are: long integer", conf_item->k);
return false;
}
break;
}
case CONF_ULONG:
{
unsigned long val;
if(sscanf(value, "%lu", &val) == 1)
conf_item->v.ul = val;
else
{
log_err("Config setting %s is invalid, allowed options are: unsigned long integer", conf_item->k);
return false;
}
break;
}
case CONF_DOUBLE:
{
double val;
if(sscanf(value, "%lf", &val) == 1)
conf_item->v.d = val;
else
{
log_err("Config setting %s is invalid, allowed options are: double", conf_item->k);
return false;
}
break;
}
case CONF_STRING:
case CONF_STRING_ALLOCATED:
{
if(conf_item->t == CONF_STRING_ALLOCATED)
free(conf_item->v.s);
conf_item->v.s = strdup(value);
conf_item->t = CONF_STRING_ALLOCATED;
break;
}
case CONF_PASSWORD:
{
// Get pointer to pwhash instead of the password by
// decrementing the pointer by one. This is safe as we
// know that the pwhash is the immediately preceding
// item in the struct
conf_item--;
// Get password hash as allocated string (an empty string is hashed to an empty string)
char *pwhash = strlen(value) > 0 ? create_password(value) : strdup("");
// Verify that the password hash is either valid or empty
const enum password_result status = verify_password(value, pwhash, false);
if(status != PASSWORD_CORRECT && status != NO_PASSWORD_SET)
{
log_err("Failed to create password hash (verification failed), password remains unchanged");
free(pwhash);
return false;
}
// Free old password hash if it was allocated
if(conf_item->t == CONF_STRING_ALLOCATED)
free(conf_item->v.s);
// Store new password hash
conf_item->v.s = pwhash;
conf_item->t = CONF_STRING_ALLOCATED;
break;
}
case CONF_ENUM_PTR_TYPE:
{
const int ptr_type = get_ptr_type_val(value);
if(ptr_type != -1)
conf_item->v.ptr_type = ptr_type;
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_ENUM_BUSY_TYPE:
{
const int busy_reply = get_busy_reply_val(value);
if(busy_reply != -1)
conf_item->v.busy_reply = busy_reply;
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_ENUM_BLOCKING_MODE:
{
const int blocking_mode = get_blocking_mode_val(value);
if(blocking_mode != -1)
conf_item->v.blocking_mode = blocking_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_ENUM_REFRESH_HOSTNAMES:
{
const int refresh_hostnames = get_refresh_hostnames_val(value);
if(refresh_hostnames != -1)
conf_item->v.refresh_hostnames = refresh_hostnames;
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_ENUM_LISTENING_MODE:
{
const int listeningMode = get_listeningMode_val(value);
if(listeningMode != -1)
conf_item->v.listeningMode = listeningMode;
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_ENUM_PRIVACY_LEVEL:
{
int val;
if(sscanf(value, "%i", &val) == 1 && val >= PRIVACY_SHOW_ALL && val <= PRIVACY_MAXIMUM)
conf_item->v.i = val;
else
{
log_err("Config setting %s is invalid, allowed options are: integer between %d and %d", conf_item->k, PRIVACY_SHOW_ALL, PRIVACY_MAXIMUM);
return false;
}
break;
}
case CONF_ENUM_WEB_THEME:
{
const int web_theme = get_web_theme_val(value);
if(web_theme != -1)
conf_item->v.web_theme = web_theme;
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_ENUM_TEMP_UNIT:
{
const int temp_unit = get_temp_unit_val(value);
if(temp_unit != -1)
conf_item->v.temp_unit = temp_unit;
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 };
if(strlen(value) == 0)
{
// Special case: empty string -> 0.0.0.0
conf_item->v.in_addr.s_addr = INADDR_ANY;
}
else if(inet_pton(AF_INET, value, &addr4))
memcpy(&conf_item->v.in_addr, &addr4, sizeof(addr4));
else
{
log_err("Config setting %s is invalid (%s), allowed options are: IPv4 address", conf_item->k, strerror(errno));
return false;
}
break;
}
case CONF_STRUCT_IN6_ADDR:
{
struct in6_addr addr6 = { 0 };
if(strlen(value) == 0)
{
// Special case: empty string -> ::
memcpy(&conf_item->v.in6_addr, &in6addr_any, sizeof(in6addr_any));
}
else if(inet_pton(AF_INET6, value, &addr6))
memcpy(&conf_item->v.in6_addr, &addr6, sizeof(addr6));
else
{
log_err("Config setting %s is invalid (%s), allowed options are: IPv6 address", conf_item->k, strerror(errno));
return false;
}
break;
}
case CONF_JSON_STRING_ARRAY:
{
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 at: %.20s", conf_item->k, json_error);
return false;
}
if(!cJSON_IsArray(elem))
{
log_err("Config setting %s is invalid: not a valid string array (example: [ \"a\", \"b\", \"c\" ])", conf_item->k);
return false;
}
const unsigned int elems = cJSON_GetArraySize(elem);
for(unsigned int i = 0; i < elems; i++)
{
const cJSON *item = cJSON_GetArrayItem(elem, i);
if(!cJSON_IsString(item))
{
log_err("Config setting %s is invalid: element with index %u is not a string", conf_item->k, i);
cJSON_Delete(elem);
return false;
}
}
// If we reach this point, all elements are valid
// Free previously allocated JSON array and replace with new
cJSON_Delete(conf_item->v.json);
conf_item->v.json = elem;
break;
}
}
return true;
}
int set_config_from_CLI(const char *key, const char *value)
{
// Check if we are either
// - root, or
// - pihole with CAP_CHOWN capability on the pihole-FTL binary
const uid_t euid = geteuid();
const struct passwd *current_user = getpwuid(euid);
const bool is_root = euid == 0;
const bool is_pihole = current_user != NULL && strcmp(current_user->pw_name, "pihole") == 0;
const bool have_chown_cap = check_capability(CAP_CHOWN);
if(!is_root && !(is_pihole && have_chown_cap))
{
if(is_pihole)
printf("Permission error: CAP_CHOWN is missing on the binary\n");
else
printf("Permission error: User %s is not allowed to edit Pi-hole's config\n", current_user->pw_name);
printf("Please run this command using sudo\n\n");
return EXIT_FAILURE;
}
// Identify config option
struct config newconf;
duplicate_config(&newconf, &config);
struct conf_item *conf_item = NULL;
struct conf_item *new_item = NULL;
for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++)
{
// Get pointer to (copied) memory location of this conf_item
struct conf_item *item = get_conf_item(&newconf, i);
if(strcmp(item->k, key) != 0)
continue;
if(item->f & FLAG_ENV_VAR)
{
log_err("Config option %s is read-only (set via environmental variable)", key);
free_config(&newconf);
return ENV_VAR_FORCED;
}
// This is the config option we are looking for
new_item = item;
// Also get pointer to memory location of this conf_item
conf_item = get_conf_item(&config, i);
// Break early
break;
}
// Check if we found the config option
if(new_item == NULL)
{
unsigned int N = 0;
char **matches = suggest_closest_conf_key(false, key, &N);
log_err("Unknown config option %s, did you mean:", key);
for(unsigned int i = 0; i < N; i++)
log_err(" - %s", matches[i]);
free(matches);
free_config(&newconf);
return KEY_UNKNOWN;
}
// Parse value
if(!readStringValue(new_item, value, &newconf))
{
free_config(&newconf);
return VALUE_INVALID;
}
// Check if value changed compared to current value
// Also check if this is the password config item change as this
// actually changed pwhash behind the scenes
if(!compare_config_item(conf_item->t, &new_item->v, &conf_item->v) ||
conf_item->t == CONF_PASSWORD)
{
// Config item changed
// Validate new value(if validation function is defined)
if(new_item->c != NULL)
{
char errbuf[VALIDATOR_ERRBUF_LEN] = { 0 };
if(!new_item->c(&new_item->v, new_item->k, errbuf))
{
free_config(&newconf);
log_err("Invalid value: %s", errbuf);
return 3;
}
}
// Is this a dnsmasq option we need to check?
if(conf_item->f & FLAG_RESTART_FTL)
{
char errbuf[ERRBUF_SIZE] = { 0 };
if(!write_dnsmasq_config(&newconf, true, errbuf))
{
// Test failed
log_debug(DEBUG_CONFIG, "Config item %s: dnsmasq config test failed", conf_item->k);
free_config(&newconf);
return DNSMASQ_TEST_FAILED;
}
}
else if(conf_item == &config.dns.hosts)
{
// We need to rewrite the custom.list file but do not
// need to restart dnsmasq
write_custom_list();
}
// Install new configuration
replace_config(&newconf);
// Print value
writeTOMLvalue(stdout, -1, new_item->t, &new_item->v);
}
else
{
// No change
log_debug(DEBUG_CONFIG, "Config item %s: Unchanged", conf_item->k);
free_config(&newconf);
// Print value
writeTOMLvalue(stdout, -1, conf_item->t, &conf_item->v);
}
putchar('\n');
writeFTLtoml(false);
return OKAY;
}
int get_config_from_CLI(const char *key, const bool quiet)
{
// Identify config option
struct conf_item *conf_item = NULL;
// We first loop over all config options to check if the one we are
// looking for is an exact match, use partial match otherwise
bool exactMatch = false;
for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++)
{
// Get pointer to memory location of this conf_item
struct conf_item *item = get_conf_item(&config, i);
// Check if item.k is identical with key
if(strcmp(item->k, key) == 0)
{
exactMatch = true;
break;
}
}
// Loop over all config options again to find the one we are looking for
// (possibly partial match)
for(unsigned int i = 0; i < CONFIG_ELEMENTS; i++)
{
// Get pointer to memory location of this conf_item
struct conf_item *item = get_conf_item(&config, i);
// Check if item.k starts with key
if(key != NULL &&
((exactMatch && strcmp(item->k, key) != 0) ||
(!exactMatch && strncmp(item->k, key, strlen(key)))))
continue;
// Skip write-only options
if(item->f & FLAG_WRITE_ONLY)
continue;
// This is the config option we are looking for
conf_item = item;
// Print key if this is not an exact match
if(key == NULL || strcmp(item->k, key) != 0)
printf("%s = ", item->k);
// Print value
if(conf_item-> f & FLAG_WRITE_ONLY)
puts("<write-only property>");
else
writeTOMLvalue(stdout, -1, conf_item->t, &conf_item->v);
putchar('\n');
}
// Check if we found the config option
if(conf_item == NULL)
{
unsigned int N = 0;
char **matches = suggest_closest_conf_key(false, key, &N);
log_err("Unknown config option %s, did you mean:", key);
for(unsigned int i = 0; i < N; i++)
log_err(" - %s", matches[i]);
free(matches);
return KEY_UNKNOWN;
}
// Use return status if this is a boolean value
// and we are in quiet mode
if(quiet && conf_item != NULL && conf_item->t == CONF_BOOL)
return conf_item->v.b ? OKAY : FAIL;
return OKAY;
}

16
src/config/cli.h Normal file
View File

@ -0,0 +1,16 @@
/* Pi-hole: A black hole for Internet advertisements
* (c) 2023 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* FTL CLI config file prototypes
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#ifndef CONFIG_CLI_H
#define CONFIG_CLI_H
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

1852
src/config/config.c Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More