Soft merge branch 'dev' into branch '1.6'

This commit is contained in:
Théophile Diot 2024-06-18 09:29:12 +01:00
commit d1ca342332
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
209 changed files with 2571 additions and 1825 deletions

View file

@ -133,7 +133,7 @@ jobs:
versionrpm: ${{ steps.getversionrpm.outputs.versionrpm }}
steps:
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Get VERSION
id: getversion
run: echo "version=$(cat src/VERSION | tr -d '\n')" >> "$GITHUB_OUTPUT"

View file

@ -19,7 +19,7 @@ jobs:
language: ["python", "javascript"]
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up Python 3.9
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
if: matrix.language == 'python'
@ -35,12 +35,12 @@ jobs:
python -m pip install --no-cache-dir --require-hashes -r src/common/db/requirements.txt
echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV
- name: Initialize CodeQL
uses: github/codeql-action/init@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8
uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql.yml
setup-python-dependencies: false
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8
uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
with:
category: "/language:${{matrix.language}}"

View file

@ -45,7 +45,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Replace VERSION
if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev'
run: ./misc/update-version.sh ${{ inputs.RELEASE }}
@ -92,7 +92,7 @@ jobs:
# Build cached image
- name: Build image
if: inputs.CACHE == true
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
file: ${{ inputs.DOCKERFILE }}
@ -105,7 +105,7 @@ jobs:
# Build non-cached image
- name: Build image
if: inputs.CACHE != true
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
file: ${{ inputs.DOCKERFILE }}

View file

@ -33,7 +33,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Get ARM availabilities
id: availabilities
uses: scaleway/action-scw@be2696f261325a78354eda14988c80405f33e082

View file

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
token: ${{ secrets.BUNKERBOT_TOKEN }}

View file

@ -80,7 +80,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- id: set-matrix
run: |
tests=$(find ./tests/ui/ -name "*_page.py" -type f -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')
@ -113,7 +113,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- id: set-matrix
run: |
tests=$(find ./tests/core/ -maxdepth 1 -mindepth 1 -type d -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')

View file

@ -13,7 +13,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install Python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:

View file

@ -37,7 +37,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Replace VERSION
if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev' || inputs.RELEASE == 'ui'
run: ./misc/update-version.sh ${{ inputs.RELEASE }}
@ -94,7 +94,7 @@ jobs:
# Build testing package image
- name: Build package image
if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev' || inputs.RELEASE == 'ui'
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
load: true
@ -106,7 +106,7 @@ jobs:
# Build non-testing package image
- name: Build package image
if: inputs.RELEASE != 'testing' && inputs.RELEASE != 'dev'
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
load: true
@ -142,7 +142,7 @@ jobs:
images: ghcr.io/bunkerity/${{ inputs.LINUX }}-tests:${{ inputs.RELEASE }}
- name: Build test image
if: inputs.TEST == true
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
file: tests/linux/Dockerfile-${{ inputs.LINUX }}

View file

@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 0
token: ${{ secrets.BUNKERBOT_TOKEN }}

View file

@ -33,7 +33,7 @@ jobs:
steps:
# Prepare
- name: Check out repository code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to Docker Hub
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:
@ -70,7 +70,7 @@ jobs:
images: bunkerity/${{ inputs.IMAGE }}
# Build and push
- name: Build and push
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
file: ${{ inputs.DOCKERFILE }}

View file

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# Checkout
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
# Get PDF doc
- name: Get documentation
if: inputs.VERSION != 'testing'

View file

@ -40,9 +40,9 @@ jobs:
steps:
# Prepare
- name: Check out repository code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install ruby
uses: ruby/setup-ruby@d5fb7a202fc07872cb44f00ba8e6197b70cb0c55 # v1.179.0
uses: ruby/setup-ruby@ff740bc00a01b3a50fffc55a1071b1060eeae9dc # v1.180.0
with:
ruby-version: "3.0"
- name: Install packagecloud

View file

@ -143,7 +143,7 @@ jobs:
versionrpm: ${{ steps.getversionrpm.outputs.versionrpm }}
steps:
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Get VERSION
id: getversion
run: echo "version=$(cat src/VERSION | tr -d '\n')" >> "$GITHUB_OUTPUT"

View file

@ -21,7 +21,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Delete ARM VM
uses: scaleway/action-scw@be2696f261325a78354eda14988c80405f33e082
with:

View file

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Checkout code"
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
persist-credentials: false
- name: "Run analysis"
@ -25,6 +25,6 @@ jobs:
results_format: sarif
publish_results: true
- name: "Upload SARIF results to code scanning"
uses: github/codeql-action/upload-sarif@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8
uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
with:
sarif_file: results.sarif

View file

@ -21,7 +21,7 @@ jobs:
run: ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N "" && ssh-keygen -f ~/.ssh/id_rsa -y > ~/.ssh/id_rsa.pub && echo -e "Host *\n StrictHostKeyChecking no" > ~/.ssh/ssh_config
if: inputs.TYPE != 'k8s'
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install terraform
uses: hashicorp/setup-terraform@651471c36a6092792c552e8b1bef71e592b462d8 # v3.1.1
- name: Install kubectl

View file

@ -20,7 +20,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install terraform
uses: hashicorp/setup-terraform@651471c36a6092792c552e8b1bef71e592b462d8 # v3.1.1
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7

View file

@ -25,7 +25,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to ghcr
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:

View file

@ -91,7 +91,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- id: set-matrix
run: |
tests=$(find ./tests/core/ -maxdepth 1 -mindepth 1 -type d -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')
@ -102,7 +102,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- id: set-matrix
run: |
tests=$(find ./tests/ui/ -name "*_page.py" -type f -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')

View file

@ -16,7 +16,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up Python 3.12
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:

View file

@ -16,7 +16,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to ghcr
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:

View file

@ -16,7 +16,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up Python 3.12
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:

View file

@ -15,7 +15,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to ghcr
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:

View file

@ -67,7 +67,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- id: set-matrix
run: |
tests=$(find ./tests/ui/ -name "*_page.py" -type f -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')

View file

@ -10,6 +10,7 @@ src/ui/templates/settings_plugins.html:hashicorp-tf-password:297
src/ui/templates/settings_plugins.html:hashicorp-tf-password:106
src/ui/templates/account.html:hashicorp-tf-password:154
src/ui/templates/account.html:hashicorp-tf-password:162
src/ui/templates/setup_loading.html:aws-access-token:14
src/common/core/errors/files/error.html:aws-access-token:20
src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.c:generic-api-key:164
src/deps/src/nginx/src/event/quic/ngx_event_quic_protection.c:generic-api-key:165

View file

@ -15,8 +15,10 @@
- [FEATURE] Add experimental support of HTTP/3 (QUIC)
- [FEATURE] Optimize the way the scheduler handles jobs and the way the jobs are executed
- [FEATURE] Optimize the way the cache files are being refreshed from the database
- [FEATURE] Add failover logic in case the NGINX configuration is not valid to fallback to the previous configuration and log the error to prevent the service from being stopped
- [UI] Force HTTPS on setup wizard
- [UI] Fallback to self-signed certificate when UI is installed with setup wizard and let's encrypt is not used
- [UI] Force HTTPS even if UI is installed in advanced mode
- [UI] Add OVERRIDE_ADMIN_CREDS environment variable to allow overriding the default admin credentials even if an admin user already exists
- [UI] Optimize the way the UI handles the requests and the responses
- [AUTOCONF] Refactor Autoconf config parsing and saving logic so that it doesn't override the scheduler or UI config every time

View file

@ -86,6 +86,19 @@ Learn more about the core security features in the [security tuning](https://doc
A demo website protected with BunkerWeb is available at [demo.bunkerweb.io](https://demo.bunkerweb.io/?utm_campaign=self&utm_source=github). Feel free to visit it and perform some security tests.
## BunkerWeb Cloud
Don't want to self-host and manage your own BunkerWeb instance(s) ? You might be interested into BunkerWeb Cloud, our fully managed SaaS offer for BunkerWeb.
Try our [BunkerWeb Cloud beta offer for free](https://panel.bunkerweb.io/order/bunkerweb-cloud/14?utm_source=github&utm_campaign=self) and get access to :
- Fully managed BunkerWeb instance hosted in our cloud
- All BunkerWeb features including PRO ones
- Monitoring platform including dashboards and alerts
- Technical support to assist you in the configuration
You will find more information about BunkerWeb Cloud in the [FAQ page](https://panel.bunkerweb.io/knowledgebase/55/BunkerWeb-Cloud?utm_source=github&utm_campaign=self) of the BunkerWeb panel.
## PRO version
When using BunkerWeb you have the choice of the version you want to use : open-source or PRO.

View file

@ -59,6 +59,19 @@ To delve deeper into the core security features, we invite you to explore the [s
A demo website protected with BunkerWeb is available at [demo.bunkerweb.io](https://demo.bunkerweb.io/?utm_campaign=self&utm_source=doc). Feel free to visit it and perform some security tests.
## BunkerWeb Cloud
Don't want to self-host and manage your own BunkerWeb instance(s) ? You might be interested into BunkerWeb Cloud, our fully managed SaaS offer for BunkerWeb.
Try our [BunkerWeb Cloud beta offer for free](https://panel.bunkerweb.io/order/bunkerweb-cloud/14?utm_source=doc&utm_campaign=self) and get access to :
- Fully managed BunkerWeb instance hosted in our cloud
- All BunkerWeb features including PRO ones
- Monitoring platform including dashboards and alerts
- Technical support to assist you in the configuration
You will find more information about BunkerWeb Cloud in the [FAQ page](https://panel.bunkerweb.io/knowledgebase/55/BunkerWeb-Cloud?utm_source=doc&utm_campaign=self) of the BunkerWeb panel.
## PRO version
When using BunkerWeb you have the choice of the version you want to use : open-source or PRO.

View file

@ -1,4 +1,4 @@
mike==2.1.1
mkdocs-material[imaging]==9.5.26
mkdocs-material[imaging]==9.5.27
mkdocs-print-site-plugin==2.5.0
pytablewriter==1.2.0

View file

@ -317,9 +317,9 @@ mkdocs-get-deps==0.2.0 \
--hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \
--hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134
# via mkdocs
mkdocs-material==9.5.26 \
--hash=sha256:56aeb91d94cffa43b6296fa4fbf0eb7c840136e563eecfd12c2d9e92e50ba326 \
--hash=sha256:5d01fb0aa1c7946a1e3ae8689aa2b11a030621ecb54894e35aabb74c21016312
mkdocs-material==9.5.27 \
--hash=sha256:a7d4a35f6d4a62b0c43a0cfe7e987da0980c13587b5bc3c26e690ad494427ec0 \
--hash=sha256:af8cc263fafa98bb79e9e15a8c966204abf15164987569bd1175fd66a7705182
# via
# -r requirements.in
# mkdocs-print-site-plugin
@ -331,9 +331,9 @@ mkdocs-print-site-plugin==2.5.0 \
--hash=sha256:48b3d41ae80384de72062b2712fce677f2e46d8364d9fe603ba837b0cf7156a4 \
--hash=sha256:95dccc8d5cc8a59da67815a2d3304ef0101b065e363f2b9ac919c23d6196dd24
# via -r requirements.in
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
# via
# mkdocs
# typepy
@ -637,9 +637,9 @@ typepy==1.3.2 \
# dataproperty
# pytablewriter
# tabledata
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
--hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19
urllib3==2.2.2 \
--hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \
--hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168
# via requests
verspec==0.1.0 \
--hash=sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31 \

View file

@ -137,7 +137,7 @@ Besides the HTTPS / SSL/TLS configuration, the following settings related to HTT
| `AUTO_REDIRECT_HTTP_TO_HTTPS` | `yes` | When set to `yes`, will redirect every HTTP request to HTTPS only if BunkerWeb is configured with HTTPS. |
| `SSL_PROTOCOLS` | `TLSv1.2 TLSv1.3` | List of supported SSL/TLS protocols when SSL is enabled. |
| `HTTP2` | `yes` | When set to `yes`, will enable HTTP2 protocol support when using HTTPS. |
| `HTTP3` | `no` | When set to `yes`, will enable HTTP3 protocol support when using HTTPS. |
| `HTTP3` | `no` | When set to `yes`, will enable HTTP3 protocol support when using HTTPS. |
| `HTTP3_ALT_SVC_PORT` | `443` | HTTP3 alternate service port. This value will be used as part of the Alt-Svc header. |
| `LISTEN_HTTP` | `yes` | When set to `no`, BunkerWeb will not listen for HTTP requests. Useful if you want HTTPS only for example. |
@ -330,7 +330,7 @@ Here is the list of related settings :
| --------------------------- | ------------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `USE_ANTIBOT` | `no` | multisite | no | Activate antibot feature. |
| `ANTIBOT_URI` | `/challenge` | multisite | no | Unused URI that clients will be redirected to to solve the challenge. |
| `ANTIBOT_RECAPTCHA_SCORE` | `0.7` | multisite | no | Minimum score required for reCAPTCHA challenge. |
| `ANTIBOT_RECAPTCHA_SCORE` | `0.7` | multisite | no | Minimum score required for reCAPTCHA challenge (Only compatible with reCAPTCHA v3). |
| `ANTIBOT_RECAPTCHA_SITEKEY` | | multisite | no | Sitekey for reCAPTCHA challenge. |
| `ANTIBOT_RECAPTCHA_SECRET` | | multisite | no | Secret for reCAPTCHA challenge. |
| `ANTIBOT_HCAPTCHA_SITEKEY` | | multisite | no | Sitekey for hCaptcha challenge. |
@ -354,30 +354,26 @@ STREAM support :warning:
You can use the following settings to set up blacklisting :
| Setting | Default | Context | Multiple | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------- | -------- | ------------------------------------------------------------------------------------------------ |
| `USE_BLACKLIST` | `yes` | multisite | no | Activate blacklist feature. |
| `BLACKLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to block. |
| `BLACKLIST_IP_URLS` | `https://www.dan.me.uk/torlist/?exit` | global | no | List of URLs, separated with spaces, containing bad IP/network to block. |
| `BLACKLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS blacklist checks on global IP addresses. |
| `BLACKLIST_RDNS` | `.shodan.io .censys.io` | multisite | no | List of reverse DNS suffixes, separated with spaces, to block. |
| `BLACKLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to block. |
| `BLACKLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to block. |
| `BLACKLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to block. |
| `BLACKLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_USER_AGENT_URLS` | `https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list` | global | no | List of URLs, separated with spaces, containing bad User-Agent to block. |
| `BLACKLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to block. |
| `BLACKLIST_IGNORE_IP` | | multisite | no | List of IP/network, separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_IP_URLS` | | global | no | List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. |
| `BLACKLIST_IGNORE_RDNS` | | multisite | no | List of reverse DNS suffixes, separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist. |
| `BLACKLIST_IGNORE_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to ignore in the blacklist. |
| `BLACKLIST_IGNORE_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. |
| `BLACKLIST_IGNORE_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_URI_URLS` | | global | no | List of URLs, separated with spaces, containing URI to ignore in the blacklist. |
| Setting | Default | Context | Multiple | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `USE_BLACKLIST` | `yes` | multisite | no | Activate blacklist feature. |
| `BLACKLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to block. |
| `BLACKLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS blacklist checks on global IP addresses. |
| `BLACKLIST_RDNS` | `.shodan.io .censys.io` | multisite | no | List of reverse DNS suffixes, separated with spaces, to block. |
| `BLACKLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to block. |
| `BLACKLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to block. |
| `BLACKLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_IP_URLS` | `https://www.dan.me.uk/torlist/?exit` | global | no | List of URLs, separated with spaces, containing bad IP/network to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_USER_AGENT_URLS` | `https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list` | global | no | List of URLs, separated with spaces, containing bad User-Agent to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_IP_URLS` | | global | no | List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_URI_URLS` | | global | no | List of URLs, separated with spaces, containing URI to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
When using stream mode, only IP, RDNS and ASN checks will be done.
@ -387,20 +383,20 @@ STREAM support :warning:
You can use the following settings to set up greylisting :
| Setting | Default | Context | Multiple | Description |
| -------------------------- | ------- | --------- | -------- | ---------------------------------------------------------------------------------------------- |
| `USE_GREYLIST` | `no` | multisite | no | Activate greylist feature. |
| `GREYLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to put into the greylist. |
| `GREYLIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to put into the greylist. |
| `GREYLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS greylist checks on global IP addresses. |
| `GREYLIST_RDNS` | | multisite | no | List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
| `GREYLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist. |
| `GREYLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to put into the greylist. |
| `GREYLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to put into the greylist. |
| `GREYLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to put into the greylist. |
| `GREYLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to put into the greylist. |
| Setting | Default | Context | Multiple | Description |
| -------------------------- | ------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `USE_GREYLIST` | `no` | multisite | no | Activate greylist feature. |
| `GREYLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to put into the greylist. |
| `GREYLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS greylist checks on global IP addresses. |
| `GREYLIST_RDNS` | | multisite | no | List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
| `GREYLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to put into the greylist. |
| `GREYLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `GREYLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `GREYLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `GREYLIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `GREYLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
When using stream mode, only IP, RDNS and ASN checks will be done.
@ -410,20 +406,20 @@ STREAM support :warning:
You can use the following settings to set up whitelisting :
| Setting | Default | Context | Multiple | Description |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ---------------------------------------------------------------------------------- |
| `USE_WHITELIST` | `yes` | multisite | no | Activate whitelist feature. |
| `WHITELIST_IP` | `20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247 54.208.102.37 107.21.1.8` | multisite | no | List of IP/network, separated with spaces, to put into the whitelist. |
| `WHITELIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to whitelist. |
| `WHITELIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS whitelist checks on global IP addresses. |
| `WHITELIST_RDNS` | `.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com` | multisite | no | List of reverse DNS suffixes, separated with spaces, to whitelist. |
| `WHITELIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist. |
| `WHITELIST_ASN` | `32934` | multisite | no | List of ASN numbers, separated with spaces, to whitelist. |
| `WHITELIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to whitelist. |
| `WHITELIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to whitelist. |
| `WHITELIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to whitelist. |
| Setting | Default | Context | Multiple | Description |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `USE_WHITELIST` | `yes` | multisite | no | Activate whitelist feature. |
| `WHITELIST_IP` | `20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247 54.208.102.37 107.21.1.8` | multisite | no | List of IP/network, separated with spaces, to put into the whitelist. |
| `WHITELIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS whitelist checks on global IP addresses. |
| `WHITELIST_RDNS` | `.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com` | multisite | no | List of reverse DNS suffixes, separated with spaces, to whitelist. |
| `WHITELIST_ASN` | `32934` | multisite | no | List of ASN numbers, separated with spaces, to whitelist. |
| `WHITELIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `WHITELIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `WHITELIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `WHITELIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `WHITELIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
When using stream mode, only IP, RDNS and ASN checks will be done.

View file

@ -65,7 +65,7 @@ Bot detection by using a challenge.
|`ANTIBOT_URI` |`/challenge`|multisite|no |Unused URI that clients will be redirected to to solve the challenge. |
|`ANTIBOT_TIME_RESOLVE` |`60` |multisite|no |Maximum time (in seconds) clients have to resolve the challenge. Once this time has passed, a new challenge will be generated.|
|`ANTIBOT_TIME_VALID` |`86400` |multisite|no |Maximum validity time of solved challenges. Once this time has passed, clients will need to resolve a new one. |
|`ANTIBOT_RECAPTCHA_SCORE` |`0.7` |multisite|no |Minimum score required for reCAPTCHA challenge. |
|`ANTIBOT_RECAPTCHA_SCORE` |`0.7` |multisite|no |Minimum score required for reCAPTCHA challenge (Only compatible with reCAPTCHA v3). |
|`ANTIBOT_RECAPTCHA_SITEKEY`| |multisite|no |Sitekey for reCAPTCHA challenge. |
|`ANTIBOT_RECAPTCHA_SECRET` | |multisite|no |Secret for reCAPTCHA challenge. |
|`ANTIBOT_HCAPTCHA_SITEKEY` | |multisite|no |Sitekey for hCaptcha challenge. |
@ -140,30 +140,30 @@ STREAM support :warning:
Deny access based on internal and external IP/network/rDNS/ASN blacklists.
| Setting | Default | Context |Multiple| Description |
|----------------------------------|------------------------------------------------------------------------------------------------------------------------------|---------|--------|------------------------------------------------------------------------------------------------|
|`USE_BLACKLIST` |`yes` |multisite|no |Activate blacklist feature. |
|`BLACKLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to block. |
|`BLACKLIST_RDNS` |`.shodan.io .censys.io` |multisite|no |List of reverse DNS suffixes, separated with spaces, to block. |
|`BLACKLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS blacklist checks on global IP addresses. |
|`BLACKLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to block. |
|`BLACKLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_IGNORE_IP` | |multisite|no |List of IP/network, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IP_URLS` |`https://www.dan.me.uk/torlist/?exit` |global |no |List of URLs, separated with spaces, containing bad IP/network to block. |
|`BLACKLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to block. |
|`BLACKLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to block. |
|`BLACKLIST_USER_AGENT_URLS` |`https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list`|global |no |List of URLs, separated with spaces, containing bad User-Agent to block. |
|`BLACKLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to block. |
|`BLACKLIST_IGNORE_IP_URLS` | |global |no |List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. |
|`BLACKLIST_IGNORE_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist.|
|`BLACKLIST_IGNORE_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to ignore in the blacklist. |
|`BLACKLIST_IGNORE_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. |
|`BLACKLIST_IGNORE_URI_URLS` | |global |no |List of URLs, separated with spaces, containing URI to ignore in the blacklist. |
| Setting | Default | Context |Multiple| Description |
|----------------------------------|------------------------------------------------------------------------------------------------------------------------------|---------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`USE_BLACKLIST` |`yes` |multisite|no |Activate blacklist feature. |
|`BLACKLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to block. |
|`BLACKLIST_RDNS` |`.shodan.io .censys.io` |multisite|no |List of reverse DNS suffixes, separated with spaces, to block. |
|`BLACKLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS blacklist checks on global IP addresses. |
|`BLACKLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to block. |
|`BLACKLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_IGNORE_IP` | |multisite|no |List of IP/network, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IP_URLS` |`https://www.dan.me.uk/torlist/?exit` |global |no |List of URLs, separated with spaces, containing bad IP/network to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_USER_AGENT_URLS` |`https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list`|global |no |List of URLs, separated with spaces, containing bad User-Agent to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_IGNORE_IP_URLS` | |global |no |List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_IGNORE_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.|
|`BLACKLIST_IGNORE_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_IGNORE_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_IGNORE_URI_URLS` | |global |no |List of URLs, separated with spaces, containing URI to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
## Brotli
@ -287,20 +287,20 @@ STREAM support :warning:
Allow access while keeping security features based on internal and external IP/network/rDNS/ASN greylists.
| Setting |Default| Context |Multiple| Description |
|--------------------------|-------|---------|--------|----------------------------------------------------------------------------------------------|
|`USE_GREYLIST` |`no` |multisite|no |Activate greylist feature. |
|`GREYLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to put into the greylist. |
|`GREYLIST_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
|`GREYLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS greylist checks on global IP addresses. |
|`GREYLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to put into the greylist. |
|`GREYLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to put into the greylist. |
|`GREYLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist.|
|`GREYLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to put into the greylist. |
|`GREYLIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to put into the greylist. |
|`GREYLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to put into the greylist. |
| Setting |Default| Context |Multiple| Description |
|--------------------------|-------|---------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`USE_GREYLIST` |`no` |multisite|no |Activate greylist feature. |
|`GREYLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to put into the greylist. |
|`GREYLIST_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
|`GREYLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS greylist checks on global IP addresses. |
|`GREYLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to put into the greylist. |
|`GREYLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`GREYLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.|
|`GREYLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`GREYLIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`GREYLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
## Gzip
@ -516,14 +516,14 @@ STREAM support :warning:
Get real IP of clients when BunkerWeb is behind a reverse proxy / load balancer.
| Setting | Default | Context |Multiple| Description |
|--------------------|-----------------------------------------|---------|--------|--------------------------------------------------------------------------------------------------------|
|`USE_REAL_IP` |`no` |multisite|no |Retrieve the real IP of client. |
|`USE_PROXY_PROTOCOL`|`no` |multisite|no |Enable PROXY protocol communication. |
|`REAL_IP_FROM` |`192.168.0.0/16 172.16.0.0/12 10.0.0.0/8`|multisite|no |List of trusted IPs / networks, separated with spaces, where proxied requests come from. |
|`REAL_IP_HEADER` |`X-Forwarded-For` |multisite|no |HTTP header containing the real IP or special value proxy_protocol for PROXY protocol. |
|`REAL_IP_RECURSIVE` |`yes` |multisite|no |Perform a recursive search in the header container IP address. |
|`REAL_IP_FROM_URLS` | |global |no |List of URLs containing trusted IPs / networks, separated with spaces, where proxied requests come from.|
| Setting | Default | Context |Multiple| Description |
|--------------------|-----------------------------------------|---------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`USE_REAL_IP` |`no` |multisite|no |Retrieve the real IP of client. |
|`USE_PROXY_PROTOCOL`|`no` |multisite|no |Enable PROXY protocol communication. |
|`REAL_IP_FROM` |`192.168.0.0/16 172.16.0.0/12 10.0.0.0/8`|multisite|no |List of trusted IPs / networks, separated with spaces, where proxied requests come from. |
|`REAL_IP_HEADER` |`X-Forwarded-For` |multisite|no |HTTP header containing the real IP or special value proxy_protocol for PROXY protocol. |
|`REAL_IP_RECURSIVE` |`yes` |multisite|no |Perform a recursive search in the header container IP address. |
|`REAL_IP_FROM_URLS` | |global |no |List of URLs containing trusted IPs / networks, separated with spaces, where proxied requests come from. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.|
## Redirect
@ -676,17 +676,17 @@ STREAM support :warning:
Allow access based on internal and external IP/network/rDNS/ASN whitelists.
| Setting | Default | Context |Multiple| Description |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|----------------------------------------------------------------------------------|
|`USE_WHITELIST` |`yes` |multisite|no |Activate whitelist feature. |
|`WHITELIST_IP` |`20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247` |multisite|no |List of IP/network, separated with spaces, to put into the whitelist. |
|`WHITELIST_RDNS` |`.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com`|multisite|no |List of reverse DNS suffixes, separated with spaces, to whitelist. |
|`WHITELIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS whitelist checks on global IP addresses. |
|`WHITELIST_ASN` |`32934` |multisite|no |List of ASN numbers, separated with spaces, to whitelist. |
|`WHITELIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to whitelist. |
|`WHITELIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist.|
|`WHITELIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to whitelist. |
|`WHITELIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to whitelist. |
|`WHITELIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to whitelist. |
| Setting | Default | Context |Multiple| Description |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`USE_WHITELIST` |`yes` |multisite|no |Activate whitelist feature. |
|`WHITELIST_IP` |`20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247` |multisite|no |List of IP/network, separated with spaces, to put into the whitelist. |
|`WHITELIST_RDNS` |`.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com`|multisite|no |List of reverse DNS suffixes, separated with spaces, to whitelist. |
|`WHITELIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS whitelist checks on global IP addresses. |
|`WHITELIST_ASN` |`32934` |multisite|no |List of ASN numbers, separated with spaces, to whitelist. |
|`WHITELIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`WHITELIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.|
|`WHITELIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`WHITELIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`WHITELIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |

View file

@ -31,6 +31,8 @@ Because the web UI is a web application, the recommended installation procedure
* Do not open the web UI on the Internet without any further restrictions
* Apply settings listed in the [security tuning section](security-tuning.md) of the documentation
**Please note that using HTTPS in front the web UI is mandatory since version 1.5.8 of BunkerWeb.**
!!! info "Multisite mode"
The usage of the web UI implies enabling the [multisite mode](concepts.md#multisite-mode).
@ -39,7 +41,7 @@ Because the web UI is a web application, the recommended installation procedure
!!! info "Wizard"
The setup wizard is a feature that helps you to **configure** and **install the web UI** using a **user-friendly interface**. You will need to set the `UI_HOST` setting (`https://hostname-of-web-ui:7000`) and browse the `/setup` URI of your server to access the setup wizard.
The setup wizard is a feature that helps you to **configure** and **install the web UI** using a **user-friendly interface**. You will need to set the `UI_HOST` setting (`http://hostname-of-web-ui:7000`) and browse the `/setup` URI of your server to access the setup wizard.
<figure markdown>
![Overview](assets/img/ui-wizard-account.webp){ align=center, width="350" }
@ -70,7 +72,7 @@ Review your final BunkerWeb UI URL and then click on the `Setup` button. Once th
!!! tip "Accessing the setup wizard"
You can access the setup wizard by browsing the `https://your-ip-address/setup` URI of your server.
You can access the setup wizard by browsing the `https://your-ip-address-or-fqdn/setup` URI of your server.
Here is the docker-compose boilerplate that you can use (don't forget to edit the `changeme` data) :
@ -162,7 +164,7 @@ Review your final BunkerWeb UI URL and then click on the `Setup` button. Once th
!!! tip "Accessing the setup wizard"
You can access the setup wizard by browsing the `https://your-ip-address/setup` URI of your server.
You can access the setup wizard by browsing the `https://your-ip-address-or-fqdn/setup` URI of your server.
Here is the docker-compose boilerplate that you can use (don't forget to edit the `changeme` data) :
@ -269,7 +271,7 @@ Review your final BunkerWeb UI URL and then click on the `Setup` button. Once th
!!! tip "Accessing the setup wizard"
You can access the setup wizard by browsing the `https://your-ip-address/setup` URI of your server.
You can access the setup wizard by browsing the `https://your-ip-address-or-fqdn/setup` URI of your server.
Here is the stack boilerplate that you can use (don't forget to edit the `changeme` data) :
@ -399,7 +401,7 @@ Review your final BunkerWeb UI URL and then click on the `Setup` button. Once th
!!! tip "Accessing the setup wizard"
You can access the setup wizard by browsing the `https://your-ip-address/setup` URI of your server.
You can access the setup wizard by browsing the `https://your-ip-address-or-fqdn/setup` URI of your server.
Here is the yaml boilerplate that you can use (don't forget to edit the `changeme` data) :
@ -832,6 +834,7 @@ After a successful login/password combination, you will be prompted to enter you
- `ADMIN_USERNAME` : username to access the web UI
- `ADMIN_PASSWORD` : password to access the web UI
- `OVERRIDE_ADMIN_CREDS` : force override the admin credentials even if we already have a user in the database (default = `no`)
Accessing the web UI through BunkerWeb is a classical [reverse proxy setup](quickstart-guide.md#protect-http-applications). We recommend you to connect BunkerWeb and web UI using a dedicated network (like `bw-universe` also used by the scheduler) so it won't be on the same network of your web services for obvious security reasons. Please note that the web UI container is listening on the `7000` port.
@ -953,6 +956,7 @@ After a successful login/password combination, you will be prompted to enter you
- `ADMIN_USERNAME` : username to access the web UI
- `ADMIN_PASSWORD` : password to access the web UI
- `OVERRIDE_ADMIN_CREDS` : force override the admin credentials even if we already have a user in the database (default = `no`)
Accessing the web UI through BunkerWeb is a classical [reverse proxy setup](quickstart-guide.md#protect-http-applications). We recommend you to connect BunkerWeb and web UI using a dedicated network (like `bw-universe` also used by the scheduler and autoconf) so it won't be on the same network of your web services for obvious security reasons. Please note that the web UI container is listening on the `7000` port.
@ -1088,6 +1092,7 @@ After a successful login/password combination, you will be prompted to enter you
- `ADMIN_USERNAME` : username to access the web UI
- `ADMIN_PASSWORD` : password to access the web UI
- `OVERRIDE_ADMIN_CREDS` : force override the admin credentials even if we already have a user in the database (default = `no`)
Accessing the web UI through BunkerWeb is a classical [reverse proxy setup](quickstart-guide.md#protect-http-applications). We recommend you to connect BunkerWeb and web UI using a dedicated network (like `bw-universe` also used by the scheduler and autoconf) so it won't be on the same network of your web services for obvious security reasons. Please note that the web UI container is listening on the `7000` port.
@ -1236,6 +1241,7 @@ After a successful login/password combination, you will be prompted to enter you
- `ADMIN_USERNAME` : username to access the web UI
- `ADMIN_PASSWORD` : password to access the web UI
- `OVERRIDE_ADMIN_CREDS` : force override the admin credentials even if we already have a user in the database (default = `no`)
Accessing the web UI through BunkerWeb is a classical [reverse proxy setup](quickstart-guide.md#protect-http-applications). Network segmentation between web UI and web services is not covered in this documentation. Please note that the web UI container is listening on the `7000` port.
@ -1590,6 +1596,7 @@ After a successful login/password combination, you will be prompted to enter you
```conf
ADMIN_USERNAME=changeme
ADMIN_PASSWORD=changeme
OVERRIDE_ADMIN_CREDS=no
```
Each time you edit the `/etc/bunkerweb/ui.env` file, you will need to restart the service :

View file

@ -113,6 +113,8 @@ services:
- "bunkerweb.REVERSE_PROXY_URL=/admin"
- "bunkerweb.REVERSE_PROXY_HOST=http://bw-ui:7000"
- "bunkerweb.INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504"
- "bunkerweb.GENERATE_SELF_SIGNED_SSL=yes"
- bunkerweb.CUSTOM_CONF_MODSEC_CRS_ip-host=SecRuleRemoveById 920350
bw-db:
image: mariadb:11

View file

@ -110,6 +110,7 @@ services:
- "bunkerweb.REVERSE_PROXY_URL=/admin"
- "bunkerweb.REVERSE_PROXY_HOST=http://bw-ui:7000"
- "bunkerweb.INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504"
- "bunkerweb.GENERATE_SELF_SIGNED_SSL=yes"
bw-db:
image: mariadb:11

View file

@ -56,6 +56,7 @@ services:
www.example.com_REVERSE_PROXY_URL: "/admin"
www.example.com_REVERSE_PROXY_HOST: "http://bw-ui:7000"
www.example.com_INTERCEPTED_ERROR_CODES: "400 404 405 413 429 500 501 502 503 504"
www.example.com_GENERATE_SELF_SIGNED_SSL: "yes"
app1.example.com_USE_REVERSE_PROXY: "yes"
app1.example.com_REVERSE_PROXY_URL: "/"
app1.example.com_REVERSE_PROXY_HOST: "http://app1:8080"

View file

@ -53,6 +53,7 @@ services:
www.example.com_REVERSE_PROXY_URL: "/admin"
www.example.com_REVERSE_PROXY_HOST: "http://bw-ui:7000"
www.example.com_INTERCEPTED_ERROR_CODES: "400 404 405 413 429 500 501 502 503 504"
www.example.com_GENERATE_SELF_SIGNED_SSL: "yes"
app1.example.com_USE_REVERSE_PROXY: "yes"
app1.example.com_REVERSE_PROXY_URL: "/"
app1.example.com_REVERSE_PROXY_HOST: "http://app1:8080"

View file

@ -17,3 +17,4 @@ www.example.com_USE_REVERSE_PROXY=yes
www.example.com_REVERSE_PROXY_URL=/admin
www.example.com_REVERSE_PROXY_HOST=http://127.0.0.1:7000
www.example.com_INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504
www.example.com_GENERATE_SELF_SIGNED_SSL=yes

View file

@ -18,4 +18,5 @@ www.example.com_USE_REVERSE_PROXY=yes
www.example.com_REVERSE_PROXY_URL=/admin
www.example.com_REVERSE_PROXY_HOST=http://127.0.0.1:7000
www.example.com_INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504
www.example.com_GENERATE_SELF_SIGNED_SSL=yes
EXTERNAL_PLUGIN_URLS=https://github.com/bunkerity/bunkerweb-plugins/archive/refs/heads/dev.zip

View file

@ -46,8 +46,12 @@ class Config:
if not server_name:
continue
for variable, value in service.items():
if self._db.is_setting(variable, multisite=True):
config[f"{server_name}_{variable}"] = value
if variable.startswith("CUSTOM_CONF") or not variable.isupper():
continue
if not self._db.is_setting(variable, multisite=True):
self.__logger.warning(f"Variable {variable}: {value} is not a valid multisite setting, ignoring it")
continue
config[f"{server_name}_{variable}"] = value
config["SERVER_NAME"] += f" {server_name}"
config["SERVER_NAME"] = config["SERVER_NAME"].strip()
return config
@ -143,6 +147,7 @@ class Config:
# update instances in database
if "instances" in changes:
self.__logger.debug(f"Updating instances in database: {self.__instances}")
err = self._db.update_instances(self.__instances, "autoconf", changed=False)
if err:
self.__logger.error(f"Failed to update instances: {err}")
@ -150,6 +155,7 @@ class Config:
# save config to database
changed_plugins = []
if "config" in changes:
self.__logger.debug(f"Saving config in database: {self.__config}")
err = self._db.save_config(self.__config, "autoconf", changed=False)
if isinstance(err, str):
success = False
@ -158,6 +164,7 @@ class Config:
# save custom configs to database
if "custom_configs" in changes:
self.__logger.debug(f"Saving custom configs in database: {custom_configs}")
err = self._db.save_custom_configs(custom_configs, "autoconf", changed=False)
if err:
success = False

View file

@ -1,4 +1,4 @@
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb as builder
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c as builder
# Export var for specific actions on linux/arm/v7
ARG TARGETPLATFORM
@ -31,7 +31,7 @@ COPY src/common/helpers helpers
COPY src/common/settings.json settings.json
COPY src/common/utils utils
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c
# Set default umask to prevent huge recursive chmod increasing the final image size
RUN umask 027

View file

@ -216,9 +216,9 @@ six==1.16.0 \
# via
# kubernetes
# python-dateutil
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
--hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19
urllib3==2.2.2 \
--hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \
--hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168
# via
# docker
# kubernetes

View file

@ -256,6 +256,15 @@
</div>
<!-- text -->
{-raw-}
<script nonce="{{ nonce_script }}">
// Automatically refresh the page after 2 seconds
setTimeout(() => {
location.reload();
}, 2000);
</script>
{-raw-}
<footer class="fixed bottom-1.5 lg:bottom-2">
<div class="flex justify-center pb-2">
<img

View file

@ -67,18 +67,22 @@ server {
end
local nonce_style = rand(16)
local nonce_script = rand(16)
-- Override CSP header
ngx.header["Content-Security-Policy"] = "default-src 'none'; form-action 'self'; img-src 'self' data:; style-src 'self' 'nonce-"
ngx.header["Content-Security-Policy"] = "default-src 'none'; script-src http: https: 'unsafe-inline' 'strict-dynamic' 'nonce-"
.. nonce_script
.. "'; style-src 'nonce-"
.. nonce_style
.. "'; font-src 'self' data:; base-uri 'self'; require-trusted-types-for 'script';"
.. "'; base-uri 'none'; img-src 'self' data:; font-src 'self' data:; require-trusted-types-for 'script';"
-- Remove server header
ngx.header["Server"] = nil
-- Render template
render("index.html", {
nonce_style = nonce_style
nonce_style = nonce_style,
nonce_script = nonce_script
})
}
}

View file

@ -60,42 +60,36 @@ function antibot:header()
return self:ret(true, "client already resolved the challenge", nil, self.session_data.original_uri)
end
-- Override headers
local header = "Content-Security-Policy"
if self.variables["CONTENT_SECURITY_POLICY_REPORT_ONLY"] == "yes" then
header = header .. "-Report-Only"
end
-- Override CSP header
local csp_directives = {
["default-src"] = "'none'",
["base-uri"] = "'none'",
["img-src"] = "'self' data:",
["font-src"] = "'self' data:",
["script-src"] = "http: https: 'unsafe-inline' 'strict-dynamic' 'nonce-"
.. self.ctx.bw.antibot_nonce_script
.. "'",
["style-src"] = "'self' 'nonce-" .. self.ctx.bw.antibot_nonce_style .. "'",
["require-trusted-types-for"] = "'script'",
}
if self.session_data.type == "recaptcha" then
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
.. self.session_data.nonce_script
.. "' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-inline' http: https:;"
.. " img-src https://www.gstatic.com/recaptcha/ 'self' data:; "
.. " frame-src https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/;"
.. " style-src 'self' 'nonce-"
.. self.session_data.nonce_style
.. "'; font-src 'self' https://fonts.gstatic.com data:; base-uri 'self';"
csp_directives["script-src"] = csp_directives["script-src"]
.. " https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/"
csp_directives["frame-src"] = "https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/"
elseif self.session_data.type == "hcaptcha" then
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
.. self.session_data.nonce_script
.. "' https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' http: https:; img-src 'self' data:;"
.. " frame-src https://hcaptcha.com https://*.hcaptcha.com; style-src 'self' 'nonce-"
.. self.session_data.nonce_style
.. "' https://hcaptcha.com https://*.hcaptcha.com; connect-src https://hcaptcha.com https://*.hcaptcha.com; "
.. " font-src 'self' data:; base-uri 'self';"
csp_directives["script-src"] = csp_directives["script-src"] .. " https://hcaptcha.com https://*.hcaptcha.com"
csp_directives["frame-src"] = "https://hcaptcha.com https://*.hcaptcha.com"
csp_directives["style-src"] = csp_directives["style-src"] .. " https://hcaptcha.com https://*.hcaptcha.com"
csp_directives["connect-src"] = "https://hcaptcha.com https://*.hcaptcha.com"
elseif self.session_data.type == "turnstile" then
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
.. self.session_data.nonce_script
.. "' https://challenges.cloudflare.com 'unsafe-inline' http: https:; img-src 'self' data:;"
.. " frame-src https://challenges.cloudflare.com; style-src 'self' 'nonce-"
.. self.session_data.nonce_style
.. "'; font-src 'self' data:; base-uri 'self';"
else
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
.. self.session_data.nonce_script
.. "' 'unsafe-inline' http: https:; img-src 'self' data:; style-src 'self' 'nonce-"
.. self.session_data.nonce_style
.. "'; font-src 'self' data:; base-uri 'self';"
csp_directives["script-src"] = csp_directives["script-src"] .. " https://challenges.cloudflare.com"
csp_directives["frame-src"] = "https://challenges.cloudflare.com"
end
local csp_content = ""
for directive, value in pairs(csp_directives) do
csp_content = csp_content .. directive .. " " .. value .. "; "
end
ngx.header["Content-Security-Policy"] = csp_content
return self:ret(true, "successfully overridden CSP header")
end
@ -192,6 +186,9 @@ function antibot:content()
return self:ret(true, "no session", nil, "/")
end
self.ctx.bw.antibot_nonce_script = rand(32)
self.ctx.bw.antibot_nonce_style = rand(32)
-- Display content
local ok, err = self:display_challenge()
if not ok then
@ -242,8 +239,6 @@ function antibot:prepare_challenge()
self.session_data.type = self.variables["USE_ANTIBOT"]
self.session_data.resolved = false
self.session_data.original_uri = self.ctx.bw.request_uri
self.session_data.nonce_script = rand(16)
self.session_data.nonce_style = rand(16)
if self.ctx.bw.uri == self.variables["ANTIBOT_URI"] then
self.session_data.original_uri = "/"
end
@ -268,8 +263,8 @@ function antibot:display_challenge()
-- Common variables for templates
local template_vars = {
antibot_uri = self.variables["ANTIBOT_URI"],
nonce_script = self.session_data.nonce_script,
nonce_style = self.session_data.nonce_style,
nonce_script = self.ctx.bw.antibot_nonce_script,
nonce_style = self.ctx.bw.antibot_nonce_style,
}
-- Javascript case
@ -387,7 +382,10 @@ function antibot:check_challenge()
if not ok then
return nil, "error while decoding JSON from reCAPTCHA API : " .. rdata, nil
end
if not rdata.success or rdata.score < tonumber(self.variables["ANTIBOT_RECAPTCHA_SCORE"]) then
if not rdata.success then
return false, "client failed challenge", nil
end
if rdata.score and rdata.score < tonumber(self.variables["ANTIBOT_RECAPTCHA_SCORE"]) then
return false, "client failed challenge with score " .. tostring(rdata.score), nil
end
self.session_data.resolved = true

View file

@ -22,6 +22,65 @@
margin-top: 1rem
}
</style>
{-raw-}
<script
src="https://www.google.com/recaptcha/api.js?onload=onv3Callback&render={*recaptcha_sitekey*}&trustedtypes=true"
nonce="{*nonce_script*}"
></script>
<script
defer
src="https://www.google.com/recaptcha/api.js?onload=onv2Callback&trustedtypes=true"
nonce="{*nonce_script*}"
></script>
<script type="text/javascript" nonce="{*nonce_script*}">
var onSubmit = function (token) {
document.getElementById("token").value = token;
document.getElementById("form").submit();
};
var usedVersion = "v2";
var onv2Callback = function () {
if (usedVersion === "invisible") {
return;
}
// Remove the v3 button
document.getElementById("recaptcha-verify").remove();
// Insert the v2 div
var recaptchaDiv = document.createElement("div");
recaptchaDiv.id = "recaptcha-v2-verify";
recaptchaDiv.className = "g-recaptcha";
recaptchaDiv.setAttribute("data-sitekey", "{*recaptcha_sitekey*}");
recaptchaDiv.setAttribute("data-callback", "onSubmit");
recaptchaDiv.setAttribute("data-action", "submit");
document
.getElementById("recaptcha-container")
.appendChild(recaptchaDiv);
grecaptcha.ready(function () {
grecaptcha.render("recaptcha-v2-verify", {
sitekey: "{*recaptcha_sitekey*}",
callback: onSubmit,
action: "submit",
});
document.querySelector(".grecaptcha-badge").remove();
});
};
var onv3Callback = function () {
usedVersion = "invisible";
grecaptcha.ready(function () {
document
.getElementById("recaptcha-verify")
.addEventListener("click", function () {
grecaptcha.execute();
});
});
};
</script>
{-raw-}
</head>
<body
class="bg-gradient-to-r from-[#075577] to-[#116D70] w-screen h-screen overflow-hidden"
@ -255,14 +314,19 @@
<form class="hidden" method="POST" action="{*antibot_uri*}" id="form">
<input type="hidden" name="token" id="token" />
</form>
{-raw-}
<div class="mt-8 flex flex-col justify-center items-center">
<button
id="recaptcha-verify"
class="text-sm xs:text-base mb-2.5 hover:brightness-90 mt-2 rounded-lg bg-secondary px-6 py-2 text-white font-bold"
>
I'm not a robot
</button>
<div id="recaptcha-container">
<button
id="recaptcha-verify"
class="g-recaptcha text-sm xs:text-base mb-2.5 hover:brightness-90 mt-2 rounded-lg bg-secondary px-6 py-2 text-white font-bold"
data-sitekey="{*recaptcha_sitekey*}"
data-callback="onSubmit"
data-action="submit"
>
I'm not a robot
</button>
</div>
{-raw-}
<p
id="recaptcha-terms"
class="text-gray-100 text-center text-xs sm:text-sm"
@ -284,41 +348,6 @@
</div>
</div>
{-raw-}
<script nonce="{*nonce_script*}">
// recaptcha
const check_robot = function () {
grecaptcha.ready(function () {
grecaptcha
.execute("{*recaptcha_sitekey*}", { action: "recaptcha" })
.then(function (token) {
document.getElementById("token").value = token;
document.getElementById("form").submit();
});
});
};
var cooldown = false;
document
.getElementById("recaptcha-verify")
.addEventListener("click", (e) => {
e.preventDefault();
if (cooldown) return;
cooldown = true;
check_robot();
setTimeout(() => {
cooldown = false;
}, 1500);
});
</script>
<script
async
src="https://www.google.com/recaptcha/api.js?render={*recaptcha_sitekey*}"
nonce="{*nonce_script*}"
></script>
{-raw-}
<!-- text -->
<footer class="fixed bottom-1.5 lg:bottom-2">
<div class="flex justify-center pb-2">

View file

@ -22,7 +22,6 @@
</style>
{-raw-}
<script
async
defer
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
nonce="{*nonce_script*}"

View file

@ -53,9 +53,9 @@
"ANTIBOT_RECAPTCHA_SCORE": {
"context": "multisite",
"default": "0.7",
"help": "Minimum score required for reCAPTCHA challenge.",
"help": "Minimum score required for reCAPTCHA challenge (Only compatible with reCAPTCHA v3).",
"id": "antibot-recaptcha-score",
"label": "reCAPTCHA score",
"label": "reCAPTCHA v3 score",
"regex": "^(0\\.[1-9]|1\\.0)$",
"type": "text"
},

View file

@ -116,7 +116,7 @@
"BLACKLIST_IP_URLS": {
"context": "global",
"default": "https://www.dan.me.uk/torlist/?exit",
"help": "List of URLs, separated with spaces, containing bad IP/network to block.",
"help": "List of URLs, separated with spaces, containing bad IP/network to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ip-urls",
"label": "Blacklist IP/network URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -125,7 +125,7 @@
"BLACKLIST_RDNS_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to block.",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-rdns-urls",
"label": "Blacklist reverse DNS URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -134,7 +134,7 @@
"BLACKLIST_ASN_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing ASN to block.",
"help": "List of URLs, separated with spaces, containing ASN to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-asn-urls",
"label": "Blacklist ASN URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -143,7 +143,7 @@
"BLACKLIST_USER_AGENT_URLS": {
"context": "global",
"default": "https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list",
"help": "List of URLs, separated with spaces, containing bad User-Agent to block.",
"help": "List of URLs, separated with spaces, containing bad User-Agent to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-user-agent-urls",
"label": "Blacklist User-Agent URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -152,7 +152,7 @@
"BLACKLIST_URI_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing bad URI to block.",
"help": "List of URLs, separated with spaces, containing bad URI to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-uri-urls",
"label": "Blacklist URI URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -161,7 +161,7 @@
"BLACKLIST_IGNORE_IP_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing IP/network to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-ip-urls",
"label": "Blacklist ignore IP/network URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -170,7 +170,7 @@
"BLACKLIST_IGNORE_RDNS_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-rdns-urls",
"label": "Blacklist ignore reverse DNS URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -179,7 +179,7 @@
"BLACKLIST_IGNORE_ASN_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing ASN to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing ASN to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-asn-urls",
"label": "Blacklist ignore ASN URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -188,7 +188,7 @@
"BLACKLIST_IGNORE_USER_AGENT_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-user-agent-urls",
"label": "Blacklist ignore User-Agent URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -197,7 +197,7 @@
"BLACKLIST_IGNORE_URI_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing URI to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing URI to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-uri-urls",
"label": "Blacklist ignore URI URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python3
from contextlib import suppress
from os import getenv, sep
from os.path import join
from pathlib import Path
@ -21,8 +20,8 @@ LOGGER = setup_logger("CUSTOM-CERT", getenv("LOG_LEVEL", "INFO"))
JOB = Job(LOGGER)
def check_cert(cert_file: Union[Path, bytes], key_file: Union[Path, bytes], first_server: str) -> Tuple[bool, str]:
with suppress(BaseException):
def check_cert(cert_file: Union[Path, bytes], key_file: Union[Path, bytes], first_server: str) -> Tuple[bool, Union[str, BaseException]]:
try:
ret = False
if not cert_file or not key_file:
return False, "Both variables CUSTOM_SSL_CERT and CUSTOM_SSL_KEY have to be set to use custom certificates"
@ -54,7 +53,8 @@ def check_cert(cert_file: Union[Path, bytes], key_file: Union[Path, bytes], firs
LOGGER.error(f"Error while caching custom-key key.pem file : {err}")
return ret, ""
return False, "exception"
except BaseException as e:
return False, e
status = 0
@ -112,8 +112,8 @@ try:
LOGGER.info(f"Checking certificate for {first_server} ...")
need_reload, err = check_cert(cert_file, key_file, first_server)
if err == "exception":
LOGGER.exception(f"Exception while checking {first_server}'s certificate, skipping ...")
if isinstance(err, BaseException):
LOGGER.error(f"Exception while checking {first_server}'s certificate, skipping ... \n{err}")
skipped_servers.append(first_server)
continue
elif err:

View file

@ -71,7 +71,7 @@
"GREYLIST_IP_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing good IP/network to put into the greylist.",
"help": "List of URLs, separated with spaces, containing good IP/network to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-ip-urls",
"label": "Greylist IP/network URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -80,7 +80,7 @@
"GREYLIST_RDNS_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist.",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-rdns-urls",
"label": "Greylist reverse DNS URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -89,7 +89,7 @@
"GREYLIST_ASN_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing ASN to put into the greylist.",
"help": "List of URLs, separated with spaces, containing ASN to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-asn-urls",
"label": "Greylist ASN URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -98,7 +98,7 @@
"GREYLIST_USER_AGENT_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing good User-Agent to put into the greylist.",
"help": "List of URLs, separated with spaces, containing good User-Agent to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-user-agent-urls",
"label": "Greylist User-Agent URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -107,7 +107,7 @@
"GREYLIST_URI_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing bad URI to put into the greylist.",
"help": "List of URLs, separated with spaces, containing bad URI to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-uri-urls",
"label": "Greylist URI URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",

View file

@ -0,0 +1,25 @@
#!/usr/bin/env python3
from os import getenv, sep
from os.path import join
from sys import exit as sys_exit, path as sys_path
from traceback import format_exc
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("db",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from jobs import Job # type: ignore
from logger import setup_logger # type: ignore
LOGGER = setup_logger("FAILOVER-BACKUP", getenv("LOG_LEVEL", "INFO"))
status = 0
try:
# Restoring the backup failover configuration
JOB = Job(LOGGER)
except:
status = 2
LOGGER.error(f"Exception while running failover-backup.py :\n{format_exc()}")
sys_exit(status)

View file

@ -82,8 +82,8 @@ try:
for chunk in resp.iter_content(chunk_size=4 * 1024):
if chunk:
file_content.write(chunk)
except RequestException:
LOGGER.error(f"Error while downloading mmdb file from {mmdb_url}")
except RequestException as e:
LOGGER.error(f"Error while downloading mmdb file from {mmdb_url}: {e}")
sys_exit(2)
try:

View file

@ -82,8 +82,8 @@ try:
for chunk in resp.iter_content(chunk_size=4 * 1024):
if chunk:
file_content.write(chunk)
except RequestException:
LOGGER.error(f"Error while downloading mmdb file from {mmdb_url}")
except RequestException as e:
LOGGER.error(f"Error while downloading mmdb file from {mmdb_url}: {e}")
sys_exit(2)
try:

View file

@ -23,6 +23,12 @@
"file": "update-check.py",
"every": "day",
"reload": false
},
{
"name": "failover-backup",
"file": "failover-backup.py",
"every": "once",
"reload": false
}
]
}

View file

@ -41,7 +41,7 @@ try:
if key not in ("version", "integration", "database_version", "is_pro"):
data.pop(key, None)
db_config = JOB.db.get_config(methods=True, with_drafts=True)
db_config = JOB.db.get_non_default_settings(methods=True, with_drafts=True)
services = db_config.get("SERVER_NAME", {"value": ""})["value"].split(" ")
multisite = db_config.get("MULTISITE", {"value": "no"})["value"] == "yes"
@ -85,7 +85,7 @@ try:
data["non_default_settings"] = {}
for setting, setting_data in db_config.items():
if isinstance(setting_data, dict) and setting_data["method"] != "default":
if isinstance(setting_data, dict):
for server in services:
if setting.startswith(server + "_"):
setting = setting[len(server) + 1 :] # noqa: E203

View file

@ -15,7 +15,7 @@
"letsencrypt",
"selfsigned"
],
"set": ["sessions", "whitelist", "letsencrypt", "customcert", "selfsigned"],
"set": ["sessions", "whitelist", "letsencrypt", "customcert", "selfsigned", "ui"],
"ssl_certificate": ["customcert", "letsencrypt", "selfsigned"],
"access": [
"whitelist",

View file

@ -95,7 +95,6 @@ def install_plugin(plugin_path: Path, db, preview: bool = True) -> bool:
try:
db = Database(LOGGER, sqlalchemy_string=getenv("DATABASE_URI"))
db_metadata = db.get_metadata()
db_config = db.get_config()
current_date = datetime.now()
pro_license_key = getenv("PRO_LICENSE_KEY", "").strip()
@ -110,6 +109,7 @@ try:
headers = {"User-Agent": f"BunkerWeb/{data['version']}"}
default_metadata = {
"is_pro": False,
"pro_license": pro_license_key,
"pro_expire": None,
"pro_status": "invalid",
"pro_overlapped": False,
@ -157,7 +157,7 @@ try:
# ? If we already checked today, skip the check and if the metadata is the same, skip the check
if (
pro_license_key == db_config["PRO_LICENSE_KEY"]
pro_license_key == db_metadata.get("pro_license", "")
and metadata.get("is_pro", False) == db_metadata["is_pro"]
and db_metadata["last_pro_check"]
and current_date.replace(hour=0, minute=0, second=0, microsecond=0) == db_metadata["last_pro_check"].replace(hour=0, minute=0, second=0, microsecond=0)

View file

@ -53,7 +53,7 @@
"REAL_IP_FROM_URLS": {
"context": "global",
"default": "",
"help": "List of URLs containing trusted IPs / networks, separated with spaces, where proxied requests come from.",
"help": "List of URLs containing trusted IPs / networks, separated with spaces, where proxied requests come from. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "real-ip-from-urls",
"label": "Real IP from URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",

View file

@ -38,7 +38,27 @@ location /setup/check {
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
default_type 'text/plain';
content_by_lua_block {
ngx.say("ok")
local logger = require "bunkerweb.logger":new("UI")
local args, err = ngx.req.get_uri_args(1)
if err == "truncated" or not args["server_name"] or args["server_name"] == "" then
logger:log(ngx.NOTICE, "Received standard server name check")
ngx.print("ok")
else
logger:log(ngx.NOTICE, "Received remote server name check for " .. args["server_name"])
local http = require "resty.http".new()
local res, err = http:request_uri("https://" .. args["server_name"] .. "/setup/check", {ssl_verify = false})
if not res then
ngx.print("ko")
logger:log(ngx.ERR, "Server name check failed : " .. err)
return
end
if res.status == 200 and res.body == "ok" then
ngx.print("ok")
return
end
logger:log(ngx.ERR, "Server name check failed : status = " .. tostring(res.status) .. " and body != ok")
ngx.print("ko")
end
}
}

19
src/common/core/ui/ui.lua Normal file
View file

@ -0,0 +1,19 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local ui = class("ui", plugin)
function ui:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "ui", ctx)
end
function ui:set()
local https_configured = self.variables["USE_UI"]
if https_configured == "yes" then
self.ctx.bw.https_configured = "yes"
end
return self:ret(true, "set https_configured to " .. https_configured)
end
return ui

View file

@ -71,7 +71,7 @@
"WHITELIST_IP_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing good IP/network to whitelist.",
"help": "List of URLs, separated with spaces, containing good IP/network to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-ip-urls",
"label": "Whitelist IP/network URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -80,7 +80,7 @@
"WHITELIST_RDNS_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist.",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-rdns-urls",
"label": "Whitelist reverse DNS URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -89,7 +89,7 @@
"WHITELIST_ASN_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing ASN to whitelist.",
"help": "List of URLs, separated with spaces, containing ASN to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-asn-urls",
"label": "Whitelist ASN URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -98,7 +98,7 @@
"WHITELIST_USER_AGENT_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing good User-Agent to whitelist.",
"help": "List of URLs, separated with spaces, containing good User-Agent to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-user-agent-urls",
"label": "Whitelist User-Agent URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -107,7 +107,7 @@
"WHITELIST_URI_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing bad URI to whitelist.",
"help": "List of URLs, separated with spaces, containing bad URI to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-uri-urls",
"label": "Whitelist URI URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",

View file

@ -6,9 +6,9 @@ from datetime import datetime
from io import BytesIO
from logging import Logger
from os import _exit, getenv, listdir, sep
from os.path import join
from os.path import join as os_join
from pathlib import Path
from re import compile as re_compile
from re import compile as re_compile, escape, search
from sys import argv, path as sys_path
from threading import Lock
from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union
@ -34,14 +34,14 @@ from model import (
Metadata,
)
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",))]:
for deps_path in [os_join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from common_utils import bytes_hash # type: ignore
from pymysql import install_as_MySQLdb
from sqlalchemy import create_engine, event, MetaData as sql_metadata, text, inspect
from sqlalchemy import create_engine, event, MetaData as sql_metadata, join, select as db_select, text, inspect
from sqlalchemy.engine import Engine
from sqlalchemy.exc import (
ArgumentError,
@ -304,12 +304,42 @@ class Database:
"""Check if the setting exists in the database and optionally if it's multisite"""
with self.__db_session() as session:
try:
if multisite:
return session.query(Settings).filter_by(id=setting, context="multisite").first() is not None
return session.query(Settings).filter_by(id=setting).first() is not None
multiple = False
if self.suffix_rx.search(setting):
setting = setting.rsplit("_", 1)[0]
multiple = True
db_setting = session.query(Settings).filter_by(id=setting).first()
if not db_setting:
return False
elif multisite and db_setting.context != "multisite":
return False
elif multiple and db_setting.multiple is None:
return False
return True
except (ProgrammingError, OperationalError):
return False
def set_failover(self, value: bool = True) -> str:
"""Set the failover value"""
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
try:
metadata = session.query(Metadata).get(1)
if not metadata:
return "The metadata are not set yet, try again"
metadata.failover = value
session.commit()
except BaseException as e:
return str(e)
return ""
def initialize_db(self, version: str, integration: str = "Unknown") -> str:
"""Initialize the database"""
with self.__db_session() as session:
@ -338,16 +368,18 @@ class Database:
return ""
def get_metadata(self) -> Dict[str, str]:
def get_metadata(self) -> Dict[str, Any]:
"""Get the metadata from the database"""
data = {
"is_initialized": False,
"is_pro": "no",
"pro_license": "",
"pro_expire": None,
"pro_status": "invalid",
"pro_services": 0,
"pro_overlapped": False,
"last_pro_check": None,
"failover": False,
"first_config_saved": False,
"autoconf_loaded": False,
"scheduler_first_start": True,
@ -1367,12 +1399,124 @@ class Database:
return message
def get_config(self, global_only: bool = False, methods: bool = False, with_drafts: bool = False) -> Dict[str, Any]:
def get_non_default_settings(
self,
global_only: bool = False,
methods: bool = False,
with_drafts: bool = False,
filtered_settings: Optional[Union[List[str], Set[str], Tuple[str]]] = None,
*,
original_config: Optional[Dict[str, Any]] = None,
original_multisite: Optional[Set[str]] = None,
) -> Dict[str, Any]:
"""Get the config from the database"""
filtered_settings = set(filtered_settings or [])
if filtered_settings and not global_only:
filtered_settings.update(("SERVER_NAME", "MULTISITE"))
with self.__db_session() as session:
config = original_config or {}
multisite = original_multisite or set()
# Define the join operation
j = join(Settings, Global_values, Settings.id == Global_values.setting_id)
# Define the select statement
stmt = (
db_select(Settings.id.label("setting_id"), Settings.context, Settings.multiple, Global_values.value, Global_values.suffix, Global_values.method)
.select_from(j)
.order_by(Settings.order)
)
if filtered_settings:
stmt = stmt.where(Settings.id.in_(filtered_settings))
# Execute the query and fetch all results
results = session.execute(stmt).fetchall()
for global_value in results:
setting_id = global_value.setting_id + (f"_{global_value.suffix}" if global_value.multiple and global_value.suffix > 0 else "")
config[setting_id] = global_value.value if not methods else {"value": global_value.value, "global": True, "method": global_value.method}
if global_value.context == "multisite":
multisite.add(setting_id)
is_multisite = config.get("MULTISITE", {"value": "no"})["value"] == "yes" if methods else config.get("MULTISITE", "no") == "yes"
services = session.query(Services).with_entities(Services.id, Services.is_draft)
if not with_drafts:
services = services.filter_by(is_draft=False)
if not global_only and is_multisite:
servers = ""
for service in services:
config[f"{service.id}_IS_DRAFT"] = "yes" if service.is_draft else "no"
if methods:
config[f"{service.id}_IS_DRAFT"] = {"value": config[f"{service.id}_IS_DRAFT"], "global": False, "method": "default"}
for key in multisite:
config[f"{service.id}_{key}"] = config[key]
servers += f"{service.id} "
servers = servers.strip()
# Define the join operation
j = join(Services, Services_settings, Services.id == Services_settings.service_id)
j = j.join(Settings, Settings.id == Services_settings.setting_id)
# Define the select statement
stmt = (
db_select(
Services.id.label("service_id"),
Settings.id.label("setting_id"),
Settings.multiple,
Services_settings.value,
Services_settings.suffix,
Services_settings.method,
)
.select_from(j)
.order_by(Services.id, Settings.order)
)
if not with_drafts:
stmt = stmt.where(Services.is_draft == False) # noqa: E712
if filtered_settings:
stmt = stmt.where(Settings.id.in_(filtered_settings))
# Execute the query and fetch all results
results = session.execute(stmt).fetchall()
for result in results:
value = result.value
if result.setting_id == "SERVER_NAME" and not search(r"^" + escape(result.service_id) + r"( |$)", value):
split = set(value.split(" "))
split.discard(result.service_id)
value = result.service_id + " " + " ".join(split)
config[f"{result.service_id}_{result.setting_id}" + (f"_{result.suffix}" if result.multiple and result.suffix else "")] = (
value if not methods else {"value": value, "global": False, "method": result.method}
)
else:
servers = " ".join(service.id for service in services)
config["SERVER_NAME"] = servers if not methods else {"value": servers, "global": True, "method": "default"}
return config
def get_config(
self,
global_only: bool = False,
methods: bool = False,
with_drafts: bool = False,
filtered_settings: Optional[Union[List[str], Set[str], Tuple[str]]] = None,
) -> Dict[str, Any]:
"""Get the config from the database"""
with self.__db_session() as session:
config = {}
multisite = []
for setting in (
multisite = set()
query = (
session.query(Settings)
.with_entities(
Settings.id,
@ -1381,81 +1525,25 @@ class Database:
Settings.multiple,
)
.order_by(Settings.order)
):
)
if filtered_settings:
query = query.filter(Settings.id.in_(filtered_settings))
for setting in query:
default = setting.default or ""
config[setting.id] = default if not methods else {"value": default, "global": True, "method": "default"}
for global_value in (
session.query(Global_values).with_entities(Global_values.value, Global_values.suffix, Global_values.method).filter_by(setting_id=setting.id)
):
config[setting.id + (f"_{global_value.suffix}" if setting.multiple and global_value.suffix > 0 else "")] = (
global_value.value
if not methods
else {
"value": global_value.value,
"global": True,
"method": global_value.method,
}
)
if setting.context == "multisite":
multisite.append(setting.id)
multisite.add(setting.id)
is_multisite = config.get("MULTISITE", {"value": "no"})["value"] == "yes" if methods else config.get("MULTISITE", "no") == "yes"
services = session.query(Services).with_entities(Services.id, Services.is_draft)
if not with_drafts:
services = services.filter_by(is_draft=False)
if not global_only and is_multisite:
for service in services:
config[f"{service.id}_IS_DRAFT"] = "yes" if service.is_draft else "no"
if methods:
config[f"{service.id}_IS_DRAFT"] = {"value": config[f"{service.id}_IS_DRAFT"], "global": False, "method": "default"}
checked_settings = []
for key, value in config.copy().items():
original_key = key
if self.suffix_rx.search(key):
key = key[: -len(str(key.split("_")[-1])) - 1]
if key not in multisite:
continue
elif f"{service.id}_{original_key}" not in config:
config[f"{service.id}_{original_key}"] = value
if original_key not in checked_settings:
checked_settings.append(original_key)
else:
continue
for service_setting in (
session.query(Services_settings)
.with_entities(
Services_settings.value,
Services_settings.suffix,
Services_settings.method,
)
.filter_by(service_id=service.id, setting_id=key)
):
value = service_setting.value
if key == "SERVER_NAME" and service.id not in value.split(" "):
value = f"{service.id} {value}".strip()
config[f"{service.id}_{key}" + (f"_{service_setting.suffix}" if service_setting.suffix > 0 else "")] = (
value
if not methods
else {
"value": value,
"global": False,
"method": service_setting.method,
}
)
servers = " ".join(service.id for service in services)
config["SERVER_NAME"] = servers if not methods else {"value": servers, "global": True, "method": "default"}
return config
return self.get_non_default_settings(
global_only=global_only,
methods=methods,
with_drafts=with_drafts,
filtered_settings=filtered_settings,
original_config=config,
original_multisite=multisite,
)
def get_custom_configs(self) -> List[Dict[str, Any]]:
"""Get the custom configs from the database"""

View file

@ -235,6 +235,7 @@ class Metadata(Base):
id = Column(Integer, primary_key=True, default=1)
is_initialized = Column(Boolean, nullable=False)
is_pro = Column(Boolean, default=False, nullable=False)
pro_license = Column(String(128), default="", nullable=True)
pro_expire = Column(DateTime, nullable=True)
pro_status = Column(PRO_STATUS_ENUM, default="invalid", nullable=False)
pro_services = Column(Integer, default=0, nullable=False)
@ -251,5 +252,6 @@ class Metadata(Base):
last_pro_plugins_change = Column(DateTime, nullable=True)
instances_changed = Column(Boolean, default=False, nullable=True)
last_instances_change = Column(DateTime, nullable=True)
failover = Column(Boolean, default=None, nullable=True)
integration = Column(INTEGRATIONS_ENUM, default="Unknown", nullable=False)
version = Column(String(32), default="1.5.8", nullable=False)

View file

@ -1,4 +1,4 @@
jinja2==3.1.4
python-dotenv==1.0.1
redis==5.0.5
redis==5.0.6
requests==2.32.3

View file

@ -178,15 +178,15 @@ python-dotenv==1.0.1 \
--hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \
--hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a
# via -r requirements.in
redis==5.0.5 \
--hash=sha256:30b47d4ebb6b7a0b9b40c1275a19b87bb6f46b3bed82a89012cf56dea4024ada \
--hash=sha256:3417688621acf6ee368dec4a04dd95881be24efd34c79f00d31f62bb528800ae
redis==5.0.6 \
--hash=sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197 \
--hash=sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee
# via -r requirements.in
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
--hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19
urllib3==2.2.2 \
--hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \
--hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168
# via requests

View file

@ -71,15 +71,15 @@
},
{
"id": "libmaxminddb",
"name": "libmaxminddb v1.9.1",
"name": "libmaxminddb v1.10.0",
"url": "https://github.com/maxmind/libmaxminddb.git",
"commit": "e26013e1d2b57eff0ed22b7364270358adb72205"
"commit": "7acfe43a72a5043d01cc3dd6429005acdf812cb3"
},
{
"id": "lua-cjson",
"name": "lua-cjson v2.1.0.12",
"name": "lua-cjson v2.1.0.14",
"url": "https://github.com/openresty/lua-cjson.git",
"commit": "881accc8fadca5ec02aa34d364df2a1aa25cd2f9"
"commit": "f95cd9ea1e39221a36818772eb85f05b4164bbb1"
},
{
"id": "lua-ffi-zlib",

View file

@ -125,9 +125,9 @@ importlib-metadata==7.1.0 \
--hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570 \
--hash=sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2
# via build
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
@ -182,9 +182,9 @@ toposort==1.10 \
--hash=sha256:bfbb479c53d0a696ea7402601f4e693c97b0367837c8898bc6471adfca37a6bd \
--hash=sha256:cbdbc0d0bee4d2695ab2ceec97fe0679e9c10eab4b2a87a9372b929e70563a87
# via pip-compile-multi
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
--hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19
urllib3==2.2.2 \
--hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \
--hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168
# via requests
wheel==0.43.0 \
--hash=sha256:465ef92c69fa5c5da2d1cf8ac40559a8c940886afcef87dcf14b9470862f1d85 \

View file

@ -16,9 +16,9 @@ importlib-metadata==7.1.0 \
--hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570 \
--hash=sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2
# via build
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:

View file

@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.9)
project(maxminddb
LANGUAGES C
VERSION 1.9.1
VERSION 1.10.0
)
set(MAXMINDDB_SOVERSION 0.0.7)
set(CMAKE_C_STANDARD 99)
@ -13,6 +13,8 @@ if (WIN32)
endif()
option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF)
option(BUILD_TESTING "Build test programs" ON)
option(MAXMINDDB_BUILD_BINARIES "Build binaries" ON)
option(MAXMINDDB_INSTALL "Generate the install target" ON)
include(GNUInstallDirs)
@ -90,17 +92,20 @@ set(MAXMINDB_HEADERS
)
set_target_properties(maxminddb PROPERTIES PUBLIC_HEADER "${MAXMINDB_HEADERS}")
install(TARGETS maxminddb
EXPORT maxminddb)
if (MAXMINDDB_INSTALL)
install(TARGETS maxminddb
EXPORT maxminddb)
# This is required to work with FetchContent
install(EXPORT maxminddb
FILE maxminddb-config.cmake
NAMESPACE maxminddb::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/maxminddb)
# This is required to work with FetchContent
install(EXPORT maxminddb
FILE maxminddb-config.cmake
NAMESPACE maxminddb::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/maxminddb)
endif()
# We always want to build mmdblookup
add_subdirectory(bin)
if (MAXMINDDB_BUILD_BINARIES)
add_subdirectory(bin)
endif()
if (BUILD_TESTING)
enable_testing()
@ -110,14 +115,16 @@ endif()
# Generate libmaxminddb.pc file for pkg-config
# Set the required variables as same with autotools
set(prefix ${CMAKE_INSTALL_PREFIX})
set(exec_prefix \${prefix})
set(libdir \${exec_prefix}/lib)
set(includedir \${prefix}/include)
set(exec_prefix ${CMAKE_INSTALL_PREFIX})
set(libdir ${CMAKE_INSTALL_LIBDIR})
set(includedir ${CMAKE_INSTALL_INCLUDEDIR})
set(PACKAGE_VERSION ${maxminddb_VERSION})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/libmaxminddb.pc.in
${CMAKE_CURRENT_BINARY_DIR}/src/libmaxminddb.pc
@ONLY)
if (MAXMINDDB_INSTALL)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/libmaxminddb.pc.in
${CMAKE_CURRENT_BINARY_DIR}/src/libmaxminddb.pc
@ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/src/libmaxminddb.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/src/libmaxminddb.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif()

View file

@ -1,3 +1,15 @@
## 1.10.0 - 2024-06-10
* When building with CMake, it is now possible to disable the building
of binaries (e.g., `mmdblookup`) with the `MAXMINDDB_BUILD_BINARIES`
option and the install target generation with the `MAXMINDDB_INSTALL`
option. Pull request by Seena Fallah. GitHub #342.
* CMake now makes greater use of GNUInstallDirs. Pull request by Maximilian
Downey Twiss. GitHub #346.
* The reader can now lookup records on a database with a search tree
that is greater than 4 gigabytes without sometimes returning erroneous
results due to an integer overflow.
## 1.9.1 - 2024-01-09
* `SSIZE_MAX` is now defined conditionally on Windows. The 1.9.0
@ -230,7 +242,7 @@
code to think it had found valid metadata when none existed. In addition,
this could lead to an attempt to read past the end of the database
entirely. Finally, if there are multiple metadata markers in the database,
we treat the final one as the start of the metdata, instead of the first.
we treat the final one as the start of the metadata, instead of the first.
Implemented by Tobias Stoeckmann. GitHub #102.
* Don't attempt to mmap a file that is too large to be mmapped on the
system. Implemented by Tobias Stoeckmann. GitHub #101.

View file

@ -15,8 +15,10 @@ if(NOT MSVC)
target_link_libraries(mmdblookup maxminddb pthread)
install(
TARGETS mmdblookup
DESTINATION bin
)
if (MAXMINDDB_INSTALL)
install(
TARGETS mmdblookup
DESTINATION ${CMAKE_INSTALL_BINDIR}
)
endif()
endif()

View file

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.63])
AC_INIT([libmaxminddb], [1.9.1], [support@maxmind.com])
AC_INIT([libmaxminddb], [1.10.0], [support@maxmind.com])
AC_CONFIG_SRCDIR([include/maxminddb.h])
AC_CONFIG_HEADERS([config.h include/maxminddb_config.h])

View file

@ -9,8 +9,10 @@ mmdblookup --file [FILE PATH] --ip [IP ADDRESS] [DATA PATH]
# DESCRIPTION
`mmdblookup` looks up an IP address in the specified MaxMind DB file. The
record for the IP address is displayed in a JSON-like structure with type
annotations.
record for the IP address is displayed with `{}` to denote maps and `[]` to
denote arrays. The values are followed by type annotations. This output is
_not_ JSON and is not intended to be used as such. If you need JSON, please
see [`mmdbinspect`](https://github.com/maxmind/mmdbinspect).
If an IP's data entry resolves to a map or array, you can provide a lookup
path to only show part of that data.

View file

@ -2,7 +2,7 @@
#define MAXMINDDB_CONFIG_H
#ifndef MMDB_UINT128_USING_MODE
/* Define as 1 if we use unsigned int __atribute__ ((__mode__(TI))) for uint128 values */
/* Define as 1 if we use unsigned int __attribute__ ((__mode__(TI))) for uint128 values */
#cmakedefine MMDB_UINT128_USING_MODE @MMDB_UINT128_USING_MODE@
#endif

View file

@ -2,7 +2,7 @@
#define MAXMINDDB_CONFIG_H
#ifndef MMDB_UINT128_USING_MODE
/* Define as 1 if we use unsigned int __atribute__ ((__mode__(TI))) for uint128 values */
/* Define as 1 if we use unsigned int __attribute__ ((__mode__(TI))) for uint128 values */
#define MMDB_UINT128_USING_MODE 0
#endif

View file

@ -947,7 +947,7 @@ static int find_address_in_search_tree(const MMDB_s *const mmdb,
return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
}
uint32_t value = 0;
uint64_t value = 0;
uint16_t current_bit = 0;
if (mmdb->metadata.ip_version == 6 && address_family == AF_INET) {
value = mmdb->ipv4_start_node.node_value;
@ -961,6 +961,7 @@ static int find_address_in_search_tree(const MMDB_s *const mmdb,
uint8_t bit =
1U & (address[current_bit >> 3] >> (7 - (current_bit % 8)));
// Note that value*record_info.record_length can be larger than 2**32
record_pointer = &search_tree[value * record_info.record_length];
if (record_pointer + record_info.record_length > mmdb->data_section) {
return MMDB_CORRUPT_SEARCH_TREE_ERROR;

@ -1 +1 @@
Subproject commit 31a33b3c09ac53028216e82f3d8a6d33749a8df5
Subproject commit 880f6b4b5eb6c12ea9d5c70dd201dec2cb5639a2

View file

@ -3,8 +3,8 @@
void test_metadata(MMDB_s *mmdb, const char *mode_desc) {
cmp_ok(mmdb->metadata.node_count,
"==",
37,
"node_count is 37 - %s",
163,
"node_count is 163 - %s",
mode_desc);
cmp_ok(mmdb->metadata.record_size,
"==",
@ -197,7 +197,7 @@ void test_metadata_as_data_entry_list(MMDB_s *mmdb, const char *mode_desc) {
if (strcmp(key_name, "node_count") == 0) {
MMDB_entry_data_list_s *value = entry_data_list =
entry_data_list->next;
cmp_ok(value->entry_data.uint32, "==", 37, "node_count == 37");
cmp_ok(value->entry_data.uint32, "==", 163, "node_count == 163");
} else if (strcmp(key_name, "record_size") == 0) {
MMDB_entry_data_list_s *value = entry_data_list =
entry_data_list->next;

View file

@ -74,50 +74,55 @@ void run_24_bit_record_tests(int mode, const char *mode_desc) {
MMDB_s *mmdb = open_ok(path, mode, mode_desc);
free(path);
const uint32_t tests[7][5] = {
{0, 1, MMDB_RECORD_TYPE_SEARCH_NODE, 242, MMDB_RECORD_TYPE_EMPTY},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
197,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
103,
242,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
242,
MMDB_RECORD_TYPE_EMPTY,
315,
MMDB_RECORD_TYPE_DATA,
},
{
132,
329,
MMDB_RECORD_TYPE_DATA,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
96,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
}};
const uint32_t tests[7][5] = {{
0,
1,
MMDB_RECORD_TYPE_SEARCH_NODE,
435,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
323,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
148,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
103,
444,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
444,
MMDB_RECORD_TYPE_EMPTY,
514,
MMDB_RECORD_TYPE_DATA,
},
{
132,
527,
MMDB_RECORD_TYPE_DATA,
444,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
444,
MMDB_RECORD_TYPE_EMPTY,
242,
MMDB_RECORD_TYPE_SEARCH_NODE,
}};
run_read_node_tests(mmdb, tests, 7, 24);
MMDB_close(mmdb);
@ -130,50 +135,55 @@ void run_28_bit_record_tests(int mode, const char *mode_desc) {
MMDB_s *mmdb = open_ok(path, mode, mode_desc);
free(path);
const uint32_t tests[7][5] = {
{0, 1, MMDB_RECORD_TYPE_SEARCH_NODE, 242, MMDB_RECORD_TYPE_EMPTY},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
197,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
103,
242,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
242,
MMDB_RECORD_TYPE_EMPTY,
315,
MMDB_RECORD_TYPE_DATA,
},
{
132,
329,
MMDB_RECORD_TYPE_DATA,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
96,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
}};
const uint32_t tests[7][5] = {{
0,
1,
MMDB_RECORD_TYPE_SEARCH_NODE,
435,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
323,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
148,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
103,
444,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
444,
MMDB_RECORD_TYPE_EMPTY,
514,
MMDB_RECORD_TYPE_DATA,
},
{
132,
527,
MMDB_RECORD_TYPE_DATA,
444,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
444,
MMDB_RECORD_TYPE_EMPTY,
242,
MMDB_RECORD_TYPE_SEARCH_NODE,
}};
run_read_node_tests(mmdb, tests, 7, 28);
MMDB_close(mmdb);
@ -186,50 +196,55 @@ void run_32_bit_record_tests(int mode, const char *mode_desc) {
MMDB_s *mmdb = open_ok(path, mode, mode_desc);
free(path);
const uint32_t tests[7][5] = {
{0, 1, MMDB_RECORD_TYPE_SEARCH_NODE, 242, MMDB_RECORD_TYPE_EMPTY},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
197,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
103,
242,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
242,
MMDB_RECORD_TYPE_EMPTY,
315,
MMDB_RECORD_TYPE_DATA,
},
{
132,
329,
MMDB_RECORD_TYPE_DATA,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
96,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
}};
const uint32_t tests[7][5] = {{
0,
1,
MMDB_RECORD_TYPE_SEARCH_NODE,
435,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
323,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
148,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
103,
444,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
444,
MMDB_RECORD_TYPE_EMPTY,
514,
MMDB_RECORD_TYPE_DATA,
},
{
132,
527,
MMDB_RECORD_TYPE_DATA,
444,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
444,
MMDB_RECORD_TYPE_EMPTY,
242,
MMDB_RECORD_TYPE_SEARCH_NODE,
}};
run_read_node_tests(mmdb, tests, 7, 32);

View file

@ -17,7 +17,7 @@ jobs:
runtestArgs: "LUA_INCLUDE_DIR=.lua/include/luajit-2.1"
runtestEnv: "SKIP_CMAKE=1"
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@master
@ -47,7 +47,7 @@ jobs:
sudo cpanm --notest Test::Base Test::LongString
- name: cppcheck
run: cppcheck -i .lua/ -i .install/ -i dtoa.c --force --error-exitcode=1 --enable=warning .
run: cppcheck -i .lua/ -i .install/ -i dtoa.c --force --error-exitcode=1 --enable=warning --inline-suppr .
- name: prove
run: LUA_BIN=lua prove -Itests tests

View file

@ -1,6 +1,7 @@
*.html
*.o
*.so
*.a
notes
packages
tags

View file

@ -27,7 +27,7 @@ env:
- JOBS=3
- LUAROCKS_VER=2.4.2
matrix:
#- LUA=1 LUA_DIR=/usr LUA_INCLUDE_DIR=$LUA_DIR/include/lua5.1
#- LUA=1 LUA_DIR=/usr LUA_INCLUDE_DIR=$LUA_DIR/include/lua5.1
- LUAJIT=1 LUA_DIR=/usr/local LUA_INCLUDE_DIR=$LUA_DIR/include/luajit-2.1 LUA_SUFFIX=--lua-suffix=jit
install:

View file

@ -68,6 +68,13 @@ else()
set(_lua_module_dir "${_lua_lib_dir}/lua/5.1")
endif()
if(MSVC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-Dinline=__inline)
add_definitions(-Dsnprintf=_snprintf)
add_definitions(-Dstrncasecmp=_strnicmp)
endif()
add_library(cjson MODULE lua_cjson.c strbuf.c ${FPCONV_SOURCES})
set_target_properties(cjson PROPERTIES PREFIX "")
target_link_libraries(cjson ${_MODULE_LINK})

View file

@ -23,6 +23,8 @@ LUA_CMODULE_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION)
LUA_MODULE_DIR ?= $(PREFIX)/share/lua/$(LUA_VERSION)
LUA_BIN_DIR ?= $(PREFIX)/bin
AR= $(CC) -o
##### Platform overrides #####
##
## Tweak one of the platform sections below to suit your situation.
@ -84,12 +86,12 @@ OBJS = lua_cjson.o strbuf.o $(FPCONV_OBJS)
.PHONY: all clean install install-extra doc
.SUFFIXES: .html .txt
.SUFFIXES: .html .adoc
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) $(BUILD_CFLAGS) -o $@ $<
.txt.html:
.adoc.html:
$(ASCIIDOC) -n -a toc $<
all: $(TARGET)
@ -97,7 +99,7 @@ all: $(TARGET)
doc: manual.html performance.html
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) $(CJSON_LDFLAGS) -o $@ $(OBJS)
$(AR) $@ $(LDFLAGS) $(CJSON_LDFLAGS) $(OBJS)
install: $(TARGET)
mkdir -p $(DESTDIR)$(LUA_CMODULE_DIR)

View file

@ -5,7 +5,7 @@
# Build packages. Use current checked out version, or a specific tag/commit.
# Files requiring a version bump
VERSION_FILES="lua-cjson-2.1devel-1.rockspec lua-cjson.spec lua_cjson.c manual.txt runtests.sh tests/test.lua"
VERSION_FILES="lua-cjson-2.1devel-1.rockspec lua-cjson.spec lua_cjson.c manual.adoc runtests.sh tests/test.lua"
[ "$1" ] && BRANCH="$1" || BRANCH="`git describe --match '[1-3].[0-9]*'`"
VERSION="`git describe --match '[1-3].[0-9]*' $BRANCH`"

View file

@ -1,3 +1,7 @@
/* The code comes from https://portal.ampl.com/~dmg/netlib/fp/dtoa.c
* Go to https://portal.ampl.com/~dmg/netlib/fp/changes for the detailed changes.
*/
/****************************************************************
*
* The author of this software is David M. Gay.
@ -1533,12 +1537,18 @@ ThInfo {
set_max_dtoa_threads(unsigned int n)
{
size_t L;
ThInfo *newTI1;
if (n > maxthreads) {
L = n*sizeof(ThInfo);
if (TI1) {
TI1 = (ThInfo*)REALLOC(TI1, L);
memset(TI1 + maxthreads, 0, (n-maxthreads)*sizeof(ThInfo));
newTI1 = (ThInfo*)REALLOC(TI1, L);
if (newTI1) {
TI1 = newTI1;
memset(TI1 + maxthreads, 0, (n-maxthreads)*sizeof(ThInfo));
}
else
return;
}
else {
TI1 = (ThInfo*)MALLOC(L);
@ -1871,7 +1881,7 @@ mult(Bigint *a, Bigint *b MTd)
#else
#ifdef Pack_32
for(; xb < xbe; xb++, xc0++) {
if (y = *xb & 0xffff) {
if ((y = *xb & 0xffff)) {
x = xa;
xc = xc0;
carry = 0;
@ -1885,7 +1895,7 @@ mult(Bigint *a, Bigint *b MTd)
while(x < xae);
*xc = carry;
}
if (y = *xb >> 16) {
if ((y = *xb >> 16)) {
x = xa;
xc = xc0;
carry = 0;
@ -2718,13 +2728,14 @@ enum { /* rounding values: same as FLT_ROUNDS */
};
void
gethex( const char **sp, U *rvp, int rounding, int sign MTd)
gethex(const char **sp, U *rvp, int rounding, int sign MTd)
{
Bigint *b;
char d;
const unsigned char *decpt, *s0, *s, *s1;
Long e, e1;
ULong L, lostbits, *x;
int big, denorm, esign, havedig, k, n, nbits, up, zret;
int big, denorm, esign, havedig, k, n, nb, nbits, nz, up, zret;
#ifdef IBM
int j;
#endif
@ -2742,6 +2753,9 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
#endif
#endif /*}}*/
};
#ifdef IEEE_Arith
int check_denorm = 0;
#endif
#ifdef USE_LOCALE
int i;
#ifdef NO_LOCALE_CACHE
@ -2893,7 +2907,7 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
k++;
b = Balloc(k MTa);
x = b->x;
n = 0;
havedig = n = nz = 0;
L = 0;
#ifdef USE_LOCALE
for(i = 0; decimalpoint[i+1]; ++i);
@ -2908,22 +2922,28 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
if (*--s1 == '.')
continue;
#endif
if ((d = hexdig[*s1]))
havedig = 1;
else if (!havedig) {
e += 4;
continue;
}
if (n == ULbits) {
*x++ = L;
L = 0;
n = 0;
}
L |= (hexdig[*s1] & 0x0f) << n;
L |= (d & 0x0f) << n;
n += 4;
}
*x++ = L;
b->wds = n = x - b->x;
n = ULbits*n - hi0bits(L);
nb = ULbits*n - hi0bits(L);
nbits = Nbits;
lostbits = 0;
x = b->x;
if (n > nbits) {
n -= nbits;
if (nb > nbits) {
n = nb - nbits;
if (any_on(b,n)) {
lostbits = 1;
k = n - 1;
@ -2936,8 +2956,8 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
rshift(b, n);
e += n;
}
else if (n < nbits) {
n = nbits - n;
else if (nb < nbits) {
n = nbits - nb;
b = lshift(b, n MTa);
e -= n;
x = b->x;
@ -2992,12 +3012,49 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
return;
}
k = n - 1;
#ifdef IEEE_Arith
if (!k) {
switch(rounding) {
case Round_near:
if (((b->x[0] & 3) == 3) || (lostbits && (b->x[0] & 1))) {
multadd(b, 1, 1 MTa);
emin_check:
if (b->x[1] == (1 << (Exp_shift + 1))) {
rshift(b,1);
e = emin;
goto normal;
}
}
break;
case Round_up:
if (!sign && (lostbits || (b->x[0] & 1))) {
incr_denorm:
multadd(b, 1, 2 MTa);
check_denorm = 1;
lostbits = 0;
goto emin_check;
}
break;
case Round_down:
if (sign && (lostbits || (b->x[0] & 1)))
goto incr_denorm;
break;
}
}
#endif
if (lostbits)
lostbits = 1;
else if (k > 0)
lostbits = any_on(b,k);
#ifdef IEEE_Arith
else if (check_denorm)
goto no_lostbits;
#endif
if (x[k>>kshift] & 1 << (k & kmask))
lostbits |= 2;
#ifdef IEEE_Arith
no_lostbits:
#endif
nbits -= n;
rshift(b,n);
e = emin;
@ -3022,16 +3079,9 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
k = b->wds;
b = increment(b MTa);
x = b->x;
if (denorm) {
#if 0
if (nbits == Nbits - 1
&& x[nbits >> kshift] & 1 << (nbits & kmask))
denorm = 0; /* not currently used */
#endif
}
else if (b->wds > k
if (!denorm && (b->wds > k
|| ((n = nbits & kmask) !=0
&& hi0bits(x[k-1]) < 32-n)) {
&& hi0bits(x[k-1]) < 32-n))) {
rshift(b,1);
if (++e > Emax)
goto ovfl;
@ -3041,8 +3091,10 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
#ifdef IEEE_Arith
if (denorm)
word0(rvp) = b->wds > 1 ? b->x[1] & ~0x100000 : 0;
else
else {
normal:
word0(rvp) = (b->x[1] & ~0x100000) | ((e + 0x3ff + 52) << 20);
}
word1(rvp) = b->x[0];
#endif
#ifdef IBM
@ -3409,6 +3461,7 @@ retlow1:
if ((j = ((word0(rv) & Exp_mask) >> Exp_shift) - bc->scale) <= 0) {
i = 1 - j;
if (i <= 31) {
/* cppcheck-suppress integerOverflowCond */
if (word1(rv) & (0x1 << i))
goto odd;
}
@ -3619,10 +3672,11 @@ fpconv_strtod(const char *s00, char **se)
c = *++s;
if (c > '0' && c <= '9') {
L = c - '0';
s1 = s;
while((c = *++s) >= '0' && c <= '9')
L = 10*L + c - '0';
if (s - s1 > 8 || L > 19999)
while((c = *++s) >= '0' && c <= '9') {
if (L <= 19999)
L = 10*L + c - '0';
}
if (L > 19999)
/* Avoid confusion from exponents
* so large that e might overflow.
*/
@ -4884,6 +4938,7 @@ nrv_alloc(const char *s, char *s0, size_t s0len, char **rve, int n MTd)
s0 = rv_alloc(n MTa);
else if (s0len <= n) {
rv = 0;
/* cppcheck-suppress nullPointerArithmetic */
t = rv + n;
goto rve_chk;
}
@ -5237,9 +5292,11 @@ dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, char
#ifndef SET_INEXACT
#ifdef Check_FLT_ROUNDS
try_quick = Rounding == 1;
#else
try_quick = 1;
#endif
#endif /*SET_INEXACT*/
#endif
#endif /*USE_BF96*/
if (mode > 5) {
mode -= 4;
@ -5281,6 +5338,7 @@ dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, char
else if (blen <= i) {
buf = 0;
if (rve)
/* cppcheck-suppress nullPointerArithmetic */
*rve = buf + i;
return buf;
}
@ -5469,6 +5527,7 @@ dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, char
res3 = p10->b1 * dbhi + (tv3 & 0xffffffffull);
res = p10->b0 * dbhi + (tv3>>32) + (res3>>32);
be += p10->e - 0x3fe;
/* cppcheck-suppress integerOverflowCond */
eulp = j1 = be - 54 + ulpadj;
if (!(res & 0x8000000000000000ull)) {
--be;

View file

@ -130,7 +130,7 @@ double fpconv_strtod(const char *nptr, char **endptr)
/* Duplicate number into buffer */
if (buflen >= FPCONV_G_FMT_BUFSIZE) {
/* Handle unusually large numbers */
buf = malloc(buflen + 1);
buf = (char *)malloc(buflen + 1);
if (!buf) {
fprintf(stderr, "Out of memory");
abort();

View file

@ -1,9 +1,9 @@
package = "lua-cjson"
version = "2.1.0.11-1"
version = "2.1.0.14-1"
source = {
url = "git+https://github.com/openresty/lua-cjson",
tag = "2.1.0.11",
tag = "2.1.0.14",
}
description = {

View file

@ -50,7 +50,7 @@ rm -rf "$RPM_BUILD_ROOT"
%files
%defattr(-,root,root,-)
%doc LICENSE NEWS performance.html performance.txt manual.html manual.txt rfc4627.txt THANKS
%doc LICENSE NEWS performance.html performance.adoc manual.html manual.adoc rfc4627.txt THANKS
%{lualibdir}/*
%{luadatadir}/*
%{_bindir}/*

View file

@ -40,6 +40,7 @@
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include <limits.h>
#include <lua.h>
#include <lauxlib.h>
@ -65,6 +66,13 @@
#endif
#ifdef _MSC_VER
#define CJSON_EXPORT __declspec(dllexport)
#define strncasecmp(x,y,z) _strnicmp(x,y,z)
#else
#define CJSON_EXPORT extern
#endif
/* Workaround for Solaris platforms missing isinf() */
#if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF))
#define isinf(x) (!isnan(x) && isnan((x) - (x)))
@ -103,8 +111,8 @@
#define json_lightudata_mask(ludata) (ludata)
#endif
#if LUA_VERSION_NUM > 501
#define lua_objlen(L,i) lua_rawlen(L, (i))
#if LUA_VERSION_NUM >= 502
#define lua_objlen(L,i) luaL_len(L, (i))
#endif
static const char * const *json_empty_array;
@ -117,6 +125,7 @@ typedef enum {
T_ARR_END,
T_STRING,
T_NUMBER,
T_INTEGER,
T_BOOLEAN,
T_NULL,
T_COLON,
@ -134,6 +143,7 @@ static const char *json_token_type_name[] = {
"T_ARR_END",
"T_STRING",
"T_NUMBER",
"T_INTEGER",
"T_BOOLEAN",
"T_NULL",
"T_COLON",
@ -179,13 +189,14 @@ typedef struct {
typedef struct {
json_token_type_t type;
int index;
size_t index;
union {
const char *string;
double number;
lua_Integer integer;
int boolean;
} value;
int string_len;
size_t string_len;
} json_token_t;
static const char *char2escape[256] = {
@ -233,7 +244,7 @@ static json_config_t *json_fetch_config(lua_State *l)
{
json_config_t *cfg;
cfg = lua_touserdata(l, lua_upvalueindex(1));
cfg = (json_config_t *)lua_touserdata(l, lua_upvalueindex(1));
if (!cfg)
luaL_error(l, "BUG: Unable to fetch CJSON configuration");
@ -442,7 +453,7 @@ static int json_destroy_config(lua_State *l)
{
json_config_t *cfg;
cfg = lua_touserdata(l, 1);
cfg = (json_config_t *)lua_touserdata(l, 1);
if (cfg)
strbuf_free(&cfg->encode_buf);
cfg = NULL;
@ -455,7 +466,11 @@ static void json_create_config(lua_State *l)
json_config_t *cfg;
int i;
cfg = lua_newuserdata(l, sizeof(*cfg));
cfg = (json_config_t *)lua_newuserdata(l, sizeof(*cfg));
if (!cfg)
abort();
memset(cfg, 0, sizeof(*cfg));
/* Create GC method to clean up strbuf */
lua_newtable(l);
@ -547,9 +562,9 @@ static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *js
static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
{
const char *escstr;
unsigned i;
const char *str;
size_t len;
size_t i;
str = lua_tolstring(l, lindex, &len);
@ -557,6 +572,8 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
* This buffer is reused constantly for small strings
* If there are any excess pages, they won't be hit anyway.
* This gains ~5% speedup. */
if (len > SIZE_MAX / 6 - 3)
abort(); /* Overflow check */
strbuf_ensure_empty_length(json, len * 6 + 2);
strbuf_append_char_unsafe(json, '\"');
@ -646,9 +663,9 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
/* json_append_array args:
* - lua_State
* - JSON strbuf
* - Size of passwd Lua array (top of stack) */
* - Size of passed Lua array (top of stack) */
static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth,
strbuf_t *json, int array_length)
strbuf_t *json, int array_length, int raw)
{
int comma, i, json_pos, err;
@ -660,7 +677,17 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
if (comma++ > 0)
strbuf_append_char(json, ',');
lua_rawgeti(l, -1, i);
if (raw) {
lua_rawgeti(l, -1, i);
} else {
#if LUA_VERSION_NUM >= 503
lua_geti(l, -1, i);
#else
lua_pushinteger(l, i);
lua_gettable(l, -2);
#endif
}
err = json_append_data(l, cfg, current_depth, json);
if (err) {
strbuf_set_length(json, json_pos);
@ -677,8 +704,17 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
static void json_append_number(lua_State *l, json_config_t *cfg,
strbuf_t *json, int lindex)
{
double num = lua_tonumber(l, lindex);
int len;
#if LUA_VERSION_NUM >= 503
if (lua_isinteger(l, lindex)) {
lua_Integer num = lua_tointeger(l, lindex);
strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE); /* max length of int64 is 19 */
len = sprintf(strbuf_empty_ptr(json), LUA_INTEGER_FMT, num);
strbuf_extend_length(json, len);
return;
}
#endif
double num = lua_tonumber(l, lindex);
if (cfg->encode_invalid_numbers == 0) {
/* Prevent encoding invalid numbers */
@ -766,6 +802,7 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
int len;
int as_array = 0;
int has_metatable;
int raw = 1;
switch (lua_type(l, -1)) {
case LUA_TSTRING:
@ -790,17 +827,30 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
lua_pushlightuserdata(l, json_lightudata_mask(&json_array));
lua_rawget(l, LUA_REGISTRYINDEX);
as_array = lua_rawequal(l, -1, -2);
lua_pop(l, 2);
if (as_array) {
raw = 1;
lua_pop(l, 2);
len = lua_objlen(l, -1);
} else {
raw = 0;
lua_pop(l, 2);
if (luaL_getmetafield(l, -1, "__len")) {
lua_pushvalue(l, -2);
lua_call(l, 1, 1);
len = lua_tonumber(l, -1);
lua_pop(l, 1);
as_array = 1;
}
}
}
if (as_array) {
len = lua_objlen(l, -1);
json_append_array(l, cfg, current_depth, json, len);
json_append_array(l, cfg, current_depth, json, len, raw);
} else {
len = lua_array_length(l, cfg, json);
if (len > 0 || (len == 0 && !cfg->encode_empty_table_as_object)) {
json_append_array(l, cfg, current_depth, json, len);
json_append_array(l, cfg, current_depth, json, len, raw);
} else {
if (has_metatable) {
lua_getmetatable(l, -1);
@ -810,7 +860,9 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
as_array = lua_rawequal(l, -1, -2);
lua_pop(l, 2); /* pop pointer + metatable */
if (as_array) {
json_append_array(l, cfg, current_depth, json, 0);
len = lua_objlen(l, -1);
raw = 1;
json_append_array(l, cfg, current_depth, json, len, raw);
break;
}
}
@ -825,7 +877,7 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
if (lua_touserdata(l, -1) == NULL) {
strbuf_append_mem(json, "null", 4);
} else if (lua_touserdata(l, -1) == json_lightudata_mask(&json_array)) {
json_append_array(l, cfg, current_depth, json, 0);
json_append_array(l, cfg, current_depth, json, 0, 1);
}
break;
default:
@ -848,7 +900,7 @@ static int json_encode(lua_State *l)
strbuf_t local_encode_buf;
strbuf_t *encode_buf;
char *json;
int len;
size_t len;
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
@ -1138,13 +1190,19 @@ static int json_is_invalid_number(json_parse_t *json)
static void json_next_number_token(json_parse_t *json, json_token_t *token)
{
char *endptr;
token->type = T_NUMBER;
token->value.number = fpconv_strtod(json->ptr, &endptr);
if (json->ptr == endptr)
json_set_token_error(token, json, "invalid number");
else
json->ptr = endptr; /* Skip the processed number */
token->value.integer = strtoll(json->ptr, &endptr, 10);
if (json->ptr == endptr || *endptr == '.' || *endptr == 'e' ||
*endptr == 'E' || *endptr == 'x') {
token->type = T_NUMBER;
token->value.number = fpconv_strtod(json->ptr, &endptr);
if (json->ptr == endptr) {
json_set_token_error(token, json, "invalid number");
return;
}
} else {
token->type = T_INTEGER;
}
json->ptr = endptr; /* Skip the processed number */
return;
}
@ -1380,6 +1438,9 @@ static void json_process_value(lua_State *l, json_parse_t *json,
case T_NUMBER:
lua_pushnumber(l, token->value.number);
break;;
case T_INTEGER:
lua_pushinteger(l, token->value.integer);
break;;
case T_BOOLEAN:
lua_pushboolean(l, token->value.boolean);
break;;
@ -1595,7 +1656,7 @@ static int lua_cjson_safe_new(lua_State *l)
return 1;
}
int luaopen_cjson(lua_State *l)
CJSON_EXPORT int luaopen_cjson(lua_State *l)
{
lua_cjson_new(l);
@ -1609,7 +1670,7 @@ int luaopen_cjson(lua_State *l)
return 1;
}
int luaopen_cjson_safe(lua_State *l)
CJSON_EXPORT int luaopen_cjson_safe(lua_State *l)
{
lua_cjson_safe_new(l);

View file

@ -1,6 +1,6 @@
= Lua CJSON 2.1devel Manual =
Mark Pulford <mark@kyne.com.au>
:revdate: 1st March 2012
:revdate: August 2016
Overview
--------
@ -20,7 +20,7 @@ The Lua CJSON module provides JSON support for Lua.
Lua CJSON is covered by the MIT license. Review the file +LICENSE+ for
details.
The latest version of this software is available from the
The current stable version of this software is available from the
http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON website].
Feel free to email me if you have any patches, suggestions, or comments.
@ -29,8 +29,8 @@ Feel free to email me if you have any patches, suggestions, or comments.
Installation
------------
Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, or
http://www.luajit.org[LuaJIT] to build.
Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, Lua 5.3,
or http://www.luajit.org[LuaJIT] to build.
The build method can be selected from 4 options:
@ -203,8 +203,8 @@ Import Lua CJSON via the Lua +require+ function. Lua CJSON does not
register a global module table.
The +cjson+ module will throw an error during JSON conversion if any
invalid data is encountered. Refer to <<cjson_encode,+cjson.encode+>>
and <<cjson_decode,+cjson.decode+>> for details.
invalid data is encountered. Refer to <<encode,+cjson.encode+>> and
<<decode,+cjson.decode+>> for details.
The +cjson.safe+ module behaves identically to the +cjson+ module,
except when errors are encountered during JSON conversion. On error, the
@ -238,6 +238,7 @@ workaround if required. Lua CJSON should be reinitialised via
different locale per thread is not supported.
[[decode]]
decode
~~~~~~

View file

@ -26,7 +26,7 @@ http://chiselapp.com/user/dhkolf/repository/dkjson/[DKJSON 2.1]::
https://github.com/brimworks/lua-yajl[Lua YAJL 2.0]::
- C wrapper for the YAJL library
http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CSJON 2.0.0]::
http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON 2.0.0]::
- C implementation with no dependencies on other libraries

View file

@ -26,6 +26,7 @@
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include "strbuf.h"
@ -38,38 +39,38 @@ static void die(const char *fmt, ...)
va_end(arg);
fprintf(stderr, "\n");
exit(-1);
abort();
}
void strbuf_init(strbuf_t *s, int len)
void strbuf_init(strbuf_t *s, size_t len)
{
int size;
size_t size;
if (len <= 0)
if (!len)
size = STRBUF_DEFAULT_SIZE;
else
size = len + 1; /* \0 terminator */
size = len + 1;
if (size < len)
die("Overflow, len: %zu", len);
s->buf = NULL;
s->size = size;
s->length = 0;
s->increment = STRBUF_DEFAULT_INCREMENT;
s->dynamic = 0;
s->reallocs = 0;
s->debug = 0;
s->buf = malloc(size);
s->buf = (char *)malloc(size);
if (!s->buf)
die("Out of memory");
strbuf_ensure_null(s);
}
strbuf_t *strbuf_new(int len)
strbuf_t *strbuf_new(size_t len)
{
strbuf_t *s;
s = malloc(sizeof(strbuf_t));
s = (strbuf_t*)malloc(sizeof(strbuf_t));
if (!s)
die("Out of memory");
@ -81,20 +82,10 @@ strbuf_t *strbuf_new(int len)
return s;
}
void strbuf_set_increment(strbuf_t *s, int increment)
{
/* Increment > 0: Linear buffer growth rate
* Increment < -1: Exponential buffer growth rate */
if (increment == 0 || increment == -1)
die("BUG: Invalid string increment");
s->increment = increment;
}
static inline void debug_stats(strbuf_t *s)
{
if (s->debug) {
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n",
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %zd, size: %zd\n",
(long)s, s->reallocs, s->length, s->size);
}
}
@ -113,7 +104,7 @@ void strbuf_free(strbuf_t *s)
free(s);
}
char *strbuf_free_to_string(strbuf_t *s, int *len)
char *strbuf_free_to_string(strbuf_t *s, size_t *len)
{
char *buf;
@ -131,57 +122,63 @@ char *strbuf_free_to_string(strbuf_t *s, int *len)
return buf;
}
static int calculate_new_size(strbuf_t *s, int len)
static size_t calculate_new_size(strbuf_t *s, size_t len)
{
int reqsize, newsize;
size_t reqsize, newsize;
if (len <= 0)
die("BUG: Invalid strbuf length requested");
/* Ensure there is room for optional NULL termination */
reqsize = len + 1;
if (reqsize < len)
die("Overflow, len: %zu", len);
/* If the user has requested to shrink the buffer, do it exactly */
if (s->size > reqsize)
return reqsize;
newsize = s->size;
if (s->increment < 0) {
if (reqsize >= SIZE_MAX / 2) {
newsize = reqsize;
} else {
/* Exponential sizing */
while (newsize < reqsize)
newsize *= -s->increment;
} else if (s->increment != 0) {
/* Linear sizing */
newsize = ((newsize + s->increment - 1) / s->increment) * s->increment;
newsize *= 2;
}
if (newsize < reqsize)
die("BUG: strbuf length would overflow, len: %zu", len);
return newsize;
}
/* Ensure strbuf can handle a string length bytes long (ignoring NULL
* optional termination). */
void strbuf_resize(strbuf_t *s, int len)
void strbuf_resize(strbuf_t *s, size_t len)
{
int newsize;
size_t newsize;
newsize = calculate_new_size(s, len);
if (s->debug > 1) {
fprintf(stderr, "strbuf(%lx) resize: %d => %d\n",
fprintf(stderr, "strbuf(%lx) resize: %zd => %zd\n",
(long)s, s->size, newsize);
}
s->size = newsize;
s->buf = realloc(s->buf, s->size);
s->buf = (char *)realloc(s->buf, s->size);
if (!s->buf)
die("Out of memory");
die("Out of memory, len: %zu", len);
s->reallocs++;
}
void strbuf_append_string(strbuf_t *s, const char *str)
{
int space, i;
int i;
size_t space;
space = strbuf_empty_length(s);
@ -197,55 +194,6 @@ void strbuf_append_string(strbuf_t *s, const char *str)
}
}
/* strbuf_append_fmt() should only be used when an upper bound
* is known for the output string. */
void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...)
{
va_list arg;
int fmt_len;
strbuf_ensure_empty_length(s, len);
va_start(arg, fmt);
fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg);
va_end(arg);
if (fmt_len < 0)
die("BUG: Unable to convert number"); /* This should never happen.. */
s->length += fmt_len;
}
/* strbuf_append_fmt_retry() can be used when the there is no known
* upper bound for the output string. */
void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...)
{
va_list arg;
int fmt_len, try;
int empty_len;
/* If the first attempt to append fails, resize the buffer appropriately
* and try again */
for (try = 0; ; try++) {
va_start(arg, fmt);
/* Append the new formatted string */
/* fmt_len is the length of the string required, excluding the
* trailing NULL */
empty_len = strbuf_empty_length(s);
/* Add 1 since there is also space to store the terminating NULL. */
fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg);
va_end(arg);
if (fmt_len <= empty_len)
break; /* SUCCESS */
if (try > 0)
die("BUG: length of formatted string changed");
strbuf_resize(s, s->length + fmt_len);
}
s->length += fmt_len;
}
/* vi:ai et sw=4 ts=4:
*/

View file

@ -32,15 +32,13 @@
/* Size: Total bytes allocated to *buf
* Length: String length, excluding optional NULL terminator.
* Increment: Allocation increments when resizing the string buffer.
* Dynamic: True if created via strbuf_new()
*/
typedef struct {
char *buf;
int size;
int length;
int increment;
size_t size;
size_t length;
int dynamic;
int reallocs;
int debug;
@ -49,33 +47,27 @@ typedef struct {
#ifndef STRBUF_DEFAULT_SIZE
#define STRBUF_DEFAULT_SIZE 1023
#endif
#ifndef STRBUF_DEFAULT_INCREMENT
#define STRBUF_DEFAULT_INCREMENT -2
#endif
/* Initialise */
extern strbuf_t *strbuf_new(int len);
extern void strbuf_init(strbuf_t *s, int len);
extern void strbuf_set_increment(strbuf_t *s, int increment);
extern strbuf_t *strbuf_new(size_t len);
extern void strbuf_init(strbuf_t *s, size_t len);
/* Release */
extern void strbuf_free(strbuf_t *s);
extern char *strbuf_free_to_string(strbuf_t *s, int *len);
extern char *strbuf_free_to_string(strbuf_t *s, size_t *len);
/* Management */
extern void strbuf_resize(strbuf_t *s, int len);
static int strbuf_empty_length(strbuf_t *s);
static int strbuf_length(strbuf_t *s);
static char *strbuf_string(strbuf_t *s, int *len);
static void strbuf_ensure_empty_length(strbuf_t *s, int len);
extern void strbuf_resize(strbuf_t *s, size_t len);
static size_t strbuf_empty_length(strbuf_t *s);
static size_t strbuf_length(strbuf_t *s);
static char *strbuf_string(strbuf_t *s, size_t *len);
static void strbuf_ensure_empty_length(strbuf_t *s, size_t len);
static char *strbuf_empty_ptr(strbuf_t *s);
static void strbuf_extend_length(strbuf_t *s, int len);
static void strbuf_extend_length(strbuf_t *s, size_t len);
static void strbuf_set_length(strbuf_t *s, int len);
/* Update */
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...);
static void strbuf_append_mem(strbuf_t *s, const char *c, int len);
static void strbuf_append_mem(strbuf_t *s, const char *c, size_t len);
extern void strbuf_append_string(strbuf_t *s, const char *str);
static void strbuf_append_char(strbuf_t *s, const char c);
static void strbuf_ensure_null(strbuf_t *s);
@ -93,12 +85,12 @@ static inline int strbuf_allocated(strbuf_t *s)
/* Return bytes remaining in the string buffer
* Ensure there is space for a NULL terminator. */
static inline int strbuf_empty_length(strbuf_t *s)
static inline size_t strbuf_empty_length(strbuf_t *s)
{
return s->size - s->length - 1;
}
static inline void strbuf_ensure_empty_length(strbuf_t *s, int len)
static inline void strbuf_ensure_empty_length(strbuf_t *s, size_t len)
{
if (len > strbuf_empty_length(s))
strbuf_resize(s, s->length + len);
@ -114,12 +106,12 @@ static inline void strbuf_set_length(strbuf_t *s, int len)
s->length = len;
}
static inline void strbuf_extend_length(strbuf_t *s, int len)
static inline void strbuf_extend_length(strbuf_t *s, size_t len)
{
s->length += len;
}
static inline int strbuf_length(strbuf_t *s)
static inline size_t strbuf_length(strbuf_t *s)
{
return s->length;
}
@ -135,14 +127,14 @@ static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c)
s->buf[s->length++] = c;
}
static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len)
static inline void strbuf_append_mem(strbuf_t *s, const char *c, size_t len)
{
strbuf_ensure_empty_length(s, len);
memcpy(s->buf + s->length, c, len);
s->length += len;
}
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len)
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, size_t len)
{
memcpy(s->buf + s->length, c, len);
s->length += len;
@ -153,7 +145,7 @@ static inline void strbuf_ensure_null(strbuf_t *s)
s->buf[s->length] = 0;
}
static inline char *strbuf_string(strbuf_t *s, int *len)
static inline char *strbuf_string(strbuf_t *s, size_t *len)
{
if (len)
*len = s->length;

View file

@ -332,3 +332,45 @@ Cannot serialise function: type not supported
{"valid":"valid"}
["one","two","three"]
=== TEST 23: array-like proxy object with __len and __index
--- lua
local cjson = require "cjson"
local real_array = {"foo", "bar", "baz"}
local proxy_array = {}
setmetatable(proxy_array, {
__len = function() return 3 end,
__index = function(t, k)
return real_array[k]
end,
})
print(cjson.encode(proxy_array))
--- out
["foo","bar","baz"]
=== TEST 24: check that integers are handled correctly on Lua 5.3+
--- lua
local lv = tonumber((_VERSION):match("Lua 5%.([0-9]+)"))
if lv >= 3 then
local cjson = require "cjson"
local array = cjson.decode [[ [10, 10.0, 3.5] ]]
for i = 1, 4 do
print(tostring(i) .. ": " .. tostring(math.type(array[i])))
end
else
print("1: integer")
print("2: float")
print("3: float")
print("4: nil")
end
--- out
1: integer
2: float
3: float
4: nil

View file

@ -6,6 +6,7 @@
# cff03b039d850f370a7362f3313e5268
use strict;
no warnings 'nonchar';
# 0xD800 - 0xDFFF are used to encode supplementary codepoints
# 0x10000 - 0x10FFFF are supplementary codepoints

View file

@ -1,4 +1,4 @@
FROM redhat/ubi9:9.4@sha256:d31d3e5e92c0c47277c5011c0326b285ab7ae627eff036133be1dccc4208004d as builder
FROM redhat/ubi9:9.4@sha256:d98fdae16212df566150ac975cab860cd8d2cb1b322ed9966d09a13e219112e9 as builder
ENV OS=rhel
ENV NGINX_VERSION 1.26.1
@ -68,7 +68,7 @@ COPY src/scheduler scheduler
COPY src/ui ui
COPY src/VERSION VERSION
FROM redhat/ubi9:9.4@sha256:d31d3e5e92c0c47277c5011c0326b285ab7ae627eff036133be1dccc4208004d
FROM redhat/ubi9:9.4@sha256:d98fdae16212df566150ac975cab860cd8d2cb1b322ed9966d09a13e219112e9
# Set default umask to prevent huge recursive chmod increasing the final image size
RUN umask 027

View file

@ -1,4 +1,4 @@
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb as builder
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c as builder
# Export var for specific actions on linux/arm/v7
ARG TARGETPLATFORM
@ -35,7 +35,7 @@ COPY src/common/utils utils
COPY src/scheduler scheduler
COPY src/VERSION VERSION
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c
# Set default umask to prevent huge recursive chmod increasing the final image size
RUN umask 027

View file

@ -9,7 +9,7 @@ from json import load as json_load
from os import _exit, environ, getenv, getpid, sep
from os.path import join
from pathlib import Path
from shutil import copy, rmtree
from shutil import copy, rmtree, copytree
from signal import SIGINT, SIGTERM, signal, SIGHUP
from stat import S_IEXEC
from subprocess import run as subprocess_run, DEVNULL, STDOUT, PIPE
@ -31,14 +31,16 @@ from common_utils import bytes_hash, dict_to_frozenset, get_integration # type:
from logger import setup_logger # type: ignore
from Database import Database # type: ignore
from JobScheduler import JobScheduler
from jobs import Job # type: ignore
from API import API # type: ignore
from ApiCaller import ApiCaller # type: ignore
from ApiCaller import ApiCaller # type: ignore # type: ignore
APPLYING_CHANGES = Event()
RUN = True
SCHEDULER: Optional[JobScheduler] = None
CACHE_PATH = join(sep, "var", "cache", "bunkerweb")
Path(CACHE_PATH).mkdir(parents=True, exist_ok=True)
CACHE_PATH = Path(sep, "var", "cache", "bunkerweb")
CACHE_PATH.mkdir(parents=True, exist_ok=True)
CUSTOM_CONFIGS_PATH = Path(sep, "etc", "bunkerweb", "configs")
CUSTOM_CONFIGS_PATH.mkdir(parents=True, exist_ok=True)
@ -56,6 +58,8 @@ CUSTOM_CONFIGS_DIRS = (
for custom_config_dir in CUSTOM_CONFIGS_DIRS:
CUSTOM_CONFIGS_PATH.joinpath(custom_config_dir).mkdir(parents=True, exist_ok=True)
CONFIG_PATH = Path(sep, "etc", "nginx")
EXTERNAL_PLUGINS_PATH = Path(sep, "etc", "bunkerweb", "plugins")
EXTERNAL_PLUGINS_PATH.mkdir(parents=True, exist_ok=True)
@ -65,6 +69,9 @@ PRO_PLUGINS_PATH.mkdir(parents=True, exist_ok=True)
TMP_PATH = Path(sep, "var", "tmp", "bunkerweb")
TMP_PATH.mkdir(parents=True, exist_ok=True)
FAILOVER_PATH = TMP_PATH.joinpath("failover")
FAILOVER_PATH.mkdir(parents=True, exist_ok=True)
HEALTHY_PATH = TMP_PATH.joinpath("scheduler.healthy")
SCHEDULER_TMP_ENV_PATH = TMP_PATH.joinpath("scheduler.env")
@ -87,6 +94,14 @@ MASTER_MODE = getenv("MASTER_MODE", "no") == "yes"
def handle_stop(signum, frame):
current_time = datetime.now()
while APPLYING_CHANGES.is_set() and (datetime.now() - current_time).seconds < 30:
LOGGER.warning("Waiting for the changes to be applied before stopping ...")
sleep(1)
if APPLYING_CHANGES.is_set():
LOGGER.warning("Timeout reached, stopping without waiting for the changes to be applied ...")
if SCHEDULER is not None:
SCHEDULER.clear()
stop(0)
@ -135,6 +150,41 @@ def stop(status):
_exit(status)
def send_nginx_configs(sent_path: Path = CONFIG_PATH):
assert SCHEDULER is not None, "SCHEDULER is not defined"
LOGGER.info(f"Sending {sent_path} folder ...")
ret = SCHEDULER.send_files(sent_path.as_posix(), "/confs")
if not ret:
LOGGER.error("Sending nginx configs failed, configuration will not work as expected...")
def send_nginx_cache(sent_path: Path = CACHE_PATH):
assert SCHEDULER is not None, "SCHEDULER is not defined"
LOGGER.info(f"Sending {sent_path} folder ...")
if not SCHEDULER.send_files(sent_path.as_posix(), "/cache"):
LOGGER.error(f"Error while sending {sent_path} folder")
else:
LOGGER.info(f"Successfully sent {sent_path} folder")
def send_nginx_custom_configs(sent_path: Path = CUSTOM_CONFIGS_PATH):
assert SCHEDULER is not None, "SCHEDULER is not defined"
LOGGER.info(f"Sending {sent_path} folder ...")
if not SCHEDULER.send_files(sent_path.as_posix(), "/custom_configs"):
LOGGER.error(f"Error while sending {sent_path} folder")
else:
LOGGER.info(f"Successfully sent {sent_path} folder")
def send_nginx_external_plugins(sent_path: Path = EXTERNAL_PLUGINS_PATH):
assert SCHEDULER is not None, "SCHEDULER is not defined"
LOGGER.info(f"Sending {sent_path} folder ...")
if not SCHEDULER.send_files(sent_path.as_posix(), "/pro_plugins" if sent_path.as_posix().endswith("/pro/plugins") else "/plugins"):
LOGGER.error(f"Error while sending {sent_path} folder")
else:
LOGGER.info(f"Successfully sent {sent_path} folder")
def generate_custom_configs(configs: Optional[List[Dict[str, Any]]] = None, *, original_path: Union[Path, str] = CUSTOM_CONFIGS_PATH):
if not isinstance(original_path, Path):
original_path = Path(original_path)
@ -179,22 +229,17 @@ def generate_custom_configs(configs: Optional[List[Dict[str, Any]]] = None, *, o
)
if SCHEDULER and SCHEDULER.apis:
LOGGER.info("Sending custom configs to BunkerWeb")
ret = SCHEDULER.send_files(original_path, "/custom_configs")
if not ret:
LOGGER.error("Sending custom configs failed, configuration will not work as expected...")
send_nginx_custom_configs(original_path)
def generate_external_plugins(plugins: Optional[List[Dict[str, Any]]] = None, *, original_path: Union[Path, str] = EXTERNAL_PLUGINS_PATH):
def generate_external_plugins(original_path: Union[Path, str] = EXTERNAL_PLUGINS_PATH):
if not isinstance(original_path, Path):
original_path = Path(original_path)
pro = "pro" in original_path.parts
pro = original_path.as_posix().endswith("/pro/plugins")
if not plugins:
assert SCHEDULER is not None
plugins = SCHEDULER.db.get_plugins(_type="pro" if pro else "external", with_data=True)
assert plugins is not None, "Couldn't get plugins from database"
assert SCHEDULER is not None
plugins = SCHEDULER.db.get_plugins(_type="pro" if pro else "external", with_data=True)
assert plugins is not None, "Couldn't get plugins from database"
# Remove old external/pro plugins files
LOGGER.info(f"Removing old/changed {'pro ' if pro else ''}external plugins files ...")
@ -249,10 +294,7 @@ def generate_external_plugins(plugins: Optional[List[Dict[str, Any]]] = None, *,
if SCHEDULER and SCHEDULER.apis:
LOGGER.info(f"Sending {'pro ' if pro else ''}external plugins to BunkerWeb")
ret = SCHEDULER.send_files(original_path, "/pro_plugins" if original_path.as_posix().endswith("/pro/plugins") else "/plugins")
if not ret:
LOGGER.error(f"Sending {'pro ' if pro else ''}external plugins failed, configuration will not work as expected...")
send_nginx_external_plugins(original_path)
def generate_caches():
@ -334,7 +376,7 @@ def run_in_slave_mode(): # TODO: Refactor this feature
threads = [
Thread(target=generate_custom_configs),
Thread(target=generate_external_plugins),
Thread(target=generate_external_plugins, kwargs={"original_path": PRO_PLUGINS_PATH}),
Thread(target=generate_external_plugins, args=(PRO_PLUGINS_PATH,)),
Thread(target=generate_caches),
]
@ -358,7 +400,7 @@ def run_in_slave_mode(): # TODO: Refactor this feature
"--templates",
join(sep, "usr", "share", "bunkerweb", "confs"),
"--output",
join(sep, "etc", "nginx"),
CONFIG_PATH.as_posix(),
"--variables",
str(SCHEDULER_TMP_ENV_PATH),
],
@ -456,11 +498,13 @@ if __name__ == "__main__":
INTEGRATION = get_integration()
tmp_variables_path = Path(args.variables or join(sep, "var", "tmp", "bunkerweb", "variables.env"))
nginx_variables_path = Path(sep, "etc", "nginx", "variables.env")
nginx_variables_path = CONFIG_PATH.joinpath("variables.env")
dotenv_env = dotenv_values(str(tmp_variables_path))
SCHEDULER = JobScheduler(environ, LOGGER, INTEGRATION, db=Database(LOGGER, sqlalchemy_string=dotenv_env.get("DATABASE_URI", getenv("DATABASE_URI", None)))) # type: ignore
JOB = Job(LOGGER, SCHEDULER.db)
if SLAVE_MODE:
run_in_slave_mode()
stop(1)
@ -469,6 +513,8 @@ if __name__ == "__main__":
db_metadata = SCHEDULER.db.get_metadata()
APPLYING_CHANGES.set()
if (
isinstance(db_metadata, str)
or (db_metadata["is_initialized"] and SCHEDULER.db.get_config() != dotenv_env)
@ -523,6 +569,7 @@ if __name__ == "__main__":
def check_configs_changes():
# Checking if any custom config has been created by the user
assert SCHEDULER is not None, "SCHEDULER is not defined"
LOGGER.info("Checking if there are any changes in custom configs ...")
custom_configs = []
db_configs = SCHEDULER.db.get_custom_configs()
@ -564,6 +611,7 @@ if __name__ == "__main__":
def check_plugin_changes(_type: Literal["external", "pro"] = "external"):
# Check if any external or pro plugin has been added by the user
assert SCHEDULER is not None, "SCHEDULER is not defined"
LOGGER.info(f"Checking if there are any changes in {_type} plugins ...")
plugin_path = EXTERNAL_PLUGINS_PATH if _type == "external" else PRO_PLUGINS_PATH
db_plugins = SCHEDULER.db.get_plugins(_type=_type)
@ -603,6 +651,7 @@ if __name__ == "__main__":
| ({"jobs": jobs} if jobs else {})
)
changes = False
if tmp_external_plugins:
changes = {hash(dict_to_frozenset(d)) for d in tmp_external_plugins} != {hash(dict_to_frozenset(d)) for d in db_plugins}
@ -613,8 +662,10 @@ if __name__ == "__main__":
LOGGER.error(f"Couldn't save some manually added {_type} plugins to database: {err}")
except BaseException as e:
LOGGER.error(f"Error while saving {_type} plugins to database: {e}")
else:
return send_nginx_external_plugins(plugin_path)
generate_external_plugins(SCHEDULER.db.get_plugins(_type=_type, with_data=True), original_path=plugin_path)
generate_external_plugins(plugin_path)
threads.extend([Thread(target=check_plugin_changes, args=("external",)), Thread(target=check_plugin_changes, args=("pro",))])
@ -638,7 +689,7 @@ if __name__ == "__main__":
threads.clear()
if db_metadata["pro_plugins_changed"]:
threads.append(Thread(target=generate_external_plugins, kwargs={"original_path": PRO_PLUGINS_PATH}))
threads.append(Thread(target=generate_external_plugins, args=(PRO_PLUGINS_PATH,)))
if db_metadata["external_plugins_changed"]:
threads.append(Thread(target=generate_external_plugins))
@ -679,19 +730,6 @@ if __name__ == "__main__":
RUN_JOBS_ONCE = True
CHANGES = []
def send_nginx_configs():
LOGGER.info(f"Sending {join(sep, 'etc', 'nginx')} folder ...")
ret = SCHEDULER.send_files(join(sep, "etc", "nginx"), "/confs")
if not ret:
LOGGER.error("Sending nginx configs failed, configuration will not work as expected...")
def send_nginx_cache():
LOGGER.info(f"Sending {CACHE_PATH} folder ...")
if not SCHEDULER.send_files(CACHE_PATH, "/cache"):
LOGGER.error(f"Error while sending {CACHE_PATH} folder")
else:
LOGGER.info(f"Successfully sent {CACHE_PATH} folder")
changed_plugins = []
old_changes = {}
@ -722,7 +760,7 @@ if __name__ == "__main__":
"--templates",
join(sep, "usr", "share", "bunkerweb", "confs"),
"--output",
join(sep, "etc", "nginx"),
CONFIG_PATH.as_posix(),
"--variables",
str(SCHEDULER_TMP_ENV_PATH),
]
@ -746,6 +784,7 @@ if __name__ == "__main__":
LOGGER.warning("No BunkerWeb instance found, skipping nginx configs sending ...")
try:
failed = False
if SCHEDULER.apis:
# send cache
thread = Thread(target=send_nginx_cache)
@ -755,10 +794,7 @@ if __name__ == "__main__":
for thread in threads:
thread.join()
if SCHEDULER.send_to_apis("POST", "/reload"):
LOGGER.info("Successfully reloaded nginx")
else:
LOGGER.error("Error while reloading nginx")
failed = not SCHEDULER.send_to_apis("POST", "/reload")
elif INTEGRATION == "Linux":
# Reload nginx
LOGGER.info("Reloading nginx ...")
@ -770,14 +806,47 @@ if __name__ == "__main__":
check=False,
stdout=PIPE,
)
if proc.returncode == 0:
LOGGER.info("Successfully sent reload signal to nginx")
else:
LOGGER.error(
f"Error while reloading nginx - returncode: {proc.returncode} - error: {proc.stdout.decode('utf-8') if proc.stdout else 'no output'}"
)
failed = proc.returncode != 0
else:
LOGGER.warning("No BunkerWeb instance found, skipping nginx reload ...")
LOGGER.warning("No BunkerWeb instance found, skipping bunkerweb reload ...")
try:
SCHEDULER.db.set_failover(failed)
except BaseException as e:
LOGGER.error(f"Error while setting failover to true in the database: {e}")
if failed:
LOGGER.error("Error while reloading bunkerweb, failing over to last working configuration ...")
if (
not FAILOVER_PATH.joinpath("config").is_dir()
or not FAILOVER_PATH.joinpath("custom_configs").is_dir()
or not FAILOVER_PATH.joinpath("cache").is_dir()
):
LOGGER.error("No failover configuration found, ignoring failover ...")
else:
# Failover to last working configuration
if SCHEDULER.apis:
tmp_threads = [
Thread(target=send_nginx_configs, args=(FAILOVER_PATH.joinpath("config"),)),
Thread(target=send_nginx_cache, args=(FAILOVER_PATH.joinpath("cache"),)),
Thread(target=send_nginx_custom_configs, args=(FAILOVER_PATH.joinpath("custom_configs"),)),
]
for thread in tmp_threads:
thread.start()
for thread in tmp_threads:
thread.join()
SCHEDULER.send_to_apis("POST", "/reload")
else:
LOGGER.info("Successfully reloaded bunkerweb")
# Update the failover path with the working configuration
rmtree(FAILOVER_PATH, ignore_errors=True)
FAILOVER_PATH.mkdir(parents=True, exist_ok=True)
copytree(CONFIG_PATH, FAILOVER_PATH.joinpath("config"))
copytree(CUSTOM_CONFIGS_PATH, FAILOVER_PATH.joinpath("custom_configs"))
copytree(CACHE_PATH, FAILOVER_PATH.joinpath("cache"))
Thread(target=JOB.cache_dir, args=(FAILOVER_PATH,), kwargs={"job_name": "failover-backup"}).start()
except:
LOGGER.error(f"Exception while reloading after running jobs once scheduling : {format_exc()}")
@ -813,6 +882,8 @@ if __name__ == "__main__":
if not HEALTHY_PATH.is_file():
HEALTHY_PATH.write_text(datetime.now().isoformat(), encoding="utf-8")
APPLYING_CHANGES.clear()
# infinite schedule for the jobs
LOGGER.info("Executing job scheduler ...")
errors = 0
@ -922,6 +993,7 @@ if __name__ == "__main__":
sleep(5)
if NEED_RELOAD:
APPLYING_CHANGES.set()
LOGGER.debug(f"Changes: {changes}")
SCHEDULER.try_database_readonly(force=True)
CHANGES.clear()
@ -938,12 +1010,12 @@ if __name__ == "__main__":
if PLUGINS_NEED_GENERATION:
CHANGES.append("external_plugins")
generate_external_plugins(SCHEDULER.db.get_plugins(_type="external", with_data=True))
generate_external_plugins()
SCHEDULER.update_jobs()
if PRO_PLUGINS_NEED_GENERATION:
CHANGES.append("pro_plugins")
generate_external_plugins(SCHEDULER.db.get_plugins(_type="pro", with_data=True), original_path=PRO_PLUGINS_PATH)
generate_external_plugins(PRO_PLUGINS_PATH)
SCHEDULER.update_jobs()
if CONFIG_NEED_GENERATION:

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