mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-06-17 03:50:30 +00:00
Compare commits
309 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64fe2e6d0d | |||
| f01ada9377 | |||
| ae6420dc80 | |||
| a1b0004be9 | |||
| 88376566f9 | |||
| 89b4676641 | |||
| a710b0e580 | |||
| b3e6891802 | |||
| 19225b223c | |||
| 3e5a58be8f | |||
| 1fe4cd03e9 | |||
| 12e02e67ff | |||
| b6f57dfb78 | |||
| 3ebf2c2d2d | |||
| 1bac6f1ee7 | |||
| 67e7acd6bd | |||
| 910ce573d6 | |||
| 1ab6af21e3 | |||
| 5d95c48e0d | |||
| dbb9e474b0 | |||
| f8eed8c786 | |||
| ef010aa39c | |||
| 79171ea6f5 | |||
| 4e3294b273 | |||
| 32a6ecddb6 | |||
| f3d9833ecf | |||
| 930ca76ea7 | |||
| 9a2887cf46 | |||
| 9950914086 | |||
| 470cfb0026 | |||
| 6c106b4e4d | |||
| 3d6253a2b2 | |||
| b873812588 | |||
| 514fefd2ed | |||
| 6f9ee2d151 | |||
| 9832006141 | |||
| 0413d26855 | |||
| 7b29c1f304 | |||
| ae3ef391ee | |||
| 7313f996d3 | |||
| 62d16c9e56 | |||
| 674b41ce08 | |||
| 1b833be760 | |||
| 88adb1adf5 | |||
| ec472f13cf | |||
| 2e1d98cc7c | |||
| 07d7e3dc30 | |||
| b0f5aee628 | |||
| d3065612fd | |||
| 9912e41f78 | |||
| 04200c99a4 | |||
| 45666d2c4e | |||
| 9a806e64ce | |||
| 22a09b9795 | |||
| 04d5c43550 | |||
| fbcb8cbeb9 | |||
| 0338a36ecf | |||
| 23fb5e2fca | |||
| 3507ff2773 | |||
| a4970397f1 | |||
| 4132f6bd48 | |||
| 586b3a2ed1 | |||
| 6af2addf3c | |||
| f6eed6c441 | |||
| b85837c803 | |||
| 653fc40d4c | |||
| c17d80a6fd | |||
| 980bfa3aa0 | |||
| 664a954393 | |||
| d5a27c4ccb | |||
| 6a8a2e2136 | |||
| b859a52b8e | |||
| 10e0c42eff | |||
| f47df263d7 | |||
| 2642d9109e | |||
| 6708b94ebb | |||
| 79cf0abc6e | |||
| 7de70322d6 | |||
| 417835dea8 | |||
| 3dcacc4187 | |||
| 69f0552d4f | |||
| c443a9400a | |||
| 5c9f387d94 | |||
| e9414d17e4 | |||
| 6bfa58611e | |||
| df4d3bb6e0 | |||
| e31b6d9a07 | |||
| 455ef084b4 | |||
| c2948735f2 | |||
| 24c62b2f09 | |||
| 1ef0149076 | |||
| 922d173540 | |||
| fd088cb504 | |||
| 721ee2394e | |||
| c217be06c6 | |||
| 871c422ec1 | |||
| 3cc28af607 | |||
| 796e131c3a | |||
| dd160cd508 | |||
| 732b321962 | |||
| c51a769aec | |||
| 45a61755a5 | |||
| 769c57c355 | |||
| 2e7eb7c0fd | |||
| 4c83147d01 | |||
| ca0bec4fc2 | |||
| 6f50dd17da | |||
| 4a331929d0 | |||
| 748bc893b6 | |||
| e462602ddc | |||
| 4e0f435d12 | |||
| 46f0581936 | |||
| 20f04ecf6b | |||
| ff43799763 | |||
| 85ca197615 | |||
| d06d23bbaf | |||
| 702ed85dfd | |||
| 8abe74a562 | |||
| 2f8a181281 | |||
| 5c5287ca21 | |||
| 83ba8d5840 | |||
| ce219668cf | |||
| 5b1b49a418 | |||
| 8978a9ad79 | |||
| 5f4a4fd759 | |||
| 171c591da4 | |||
| 9133b9899c | |||
| 701c9fb1b4 | |||
| eabd22188b | |||
| 7028619742 | |||
| c915bf2ee2 | |||
| 011edd5ac9 | |||
| 7ba3de4ced | |||
| 8ead77083f | |||
| b2774fb50b | |||
| 4440bd46ad | |||
| 28985973eb | |||
| f2c4697ca3 | |||
| 383b5affb5 | |||
| ed4dcff63b | |||
| caca32bbba | |||
| d31e74c778 | |||
| 6c00e29276 | |||
| 9940c503a2 | |||
| 4b2862cb3c | |||
| a36485f0f1 | |||
| 78168ee80a | |||
| 610609378f | |||
| 260906e350 | |||
| 2891bbf82a | |||
| eb26bcbc94 | |||
| ef0f366d1c | |||
| 84e230de8f | |||
| f67a12d157 | |||
| 34b48eedfc | |||
| 0d900d4fc8 | |||
| 642ac6d02c | |||
| 4db1569c93 | |||
| 94c1a6c4e1 | |||
| 7ce3b0faed | |||
| 262fe04286 | |||
| b1c088a57f | |||
| 1c438330c6 | |||
| 8cb25709ae | |||
| 221f2989b0 | |||
| 3d05207bc7 | |||
| 8c8497d885 | |||
| 56d083ced4 | |||
| a90b3544a7 | |||
| 08aea7fb26 | |||
| 13f7f9830b | |||
| 2f75039194 | |||
| 1e192e14f4 | |||
| 9cd1f931fc | |||
| 8d7235b535 | |||
| 8446abd484 | |||
| f67c0530f5 | |||
| 06db1d6a72 | |||
| 81775ab4d5 | |||
| 34877ecf9c | |||
| dbde144014 | |||
| 5361a4a4ee | |||
| 0997548d7f | |||
| 921de02a2b | |||
| 48e90a72dc | |||
| c0b7a98e6c | |||
| 6dc90186f9 | |||
| 0b0a65a3f3 | |||
| 6c5d82c4df | |||
| 5e66ffa366 | |||
| 4d88e19106 | |||
| 29e28b47ed | |||
| 1cb38bacdb | |||
| 169aafec50 | |||
| 3826c4b5be | |||
| e1410baaeb | |||
| c39712af67 | |||
| 53c35493a5 | |||
| af871fdacb | |||
| 2b93b59cdd | |||
| 2b2da1679e | |||
| 8cdb0b869e | |||
| 1e42b8dd21 | |||
| 842cb235b6 | |||
| e91d678bd1 | |||
| ef5739c32f | |||
| 88bf9b02e1 | |||
| 3803b5d351 | |||
| 14d58c8163 | |||
| 728fcdb375 | |||
| 1fc36263dc | |||
| 69420113f7 | |||
| 360fe03497 | |||
| 7557802933 | |||
| 2e9ba1e9b3 | |||
| 795bcdc5d2 | |||
| ad9b328ed5 | |||
| 3d5b57889a | |||
| 6b8e981bdc | |||
| 2f1eb4b004 | |||
| 3ee3d7d969 | |||
| 95eb350f15 | |||
| 1e5fcfe392 | |||
| 527f27d249 | |||
| 02557b2098 | |||
| 4c7a9ed195 | |||
| d5b30a7a08 | |||
| b7acef4d9d | |||
| fc43c26c48 | |||
| b12ce1eacd | |||
| ec6dbb099a | |||
| 2fbbbbe9a9 | |||
| 1e4f3c55d8 | |||
| a0f5454c2a | |||
| 4e7adacda9 | |||
| 4c64cf18a6 | |||
| 8a89f5c685 | |||
| cc0e4fee9d | |||
| 5861c9af29 | |||
| dd475c0ab3 | |||
| 407e9d3584 | |||
| d4f899b091 | |||
| 372923ae2f | |||
| 3bd01190bf | |||
| 1994b9895b | |||
| 03d979c089 | |||
| 798e6a4c00 | |||
| ffa2933873 | |||
| 7f47a3f00e | |||
| 1bcab9a9a5 | |||
| 1b2f424edc | |||
| 486b297409 | |||
| 75d7f06b25 | |||
| ea0944d743 | |||
| cb6ffe65c8 | |||
| 580dabd276 | |||
| 846862aa80 | |||
| e7a1f24c78 | |||
| 8ff0e029f0 | |||
| 0680b21938 | |||
| 0c8e7bfeca | |||
| badcd27b93 | |||
| 7d3ef3d67f | |||
| 5b89e253a6 | |||
| a90f4c2a2e | |||
| db7b917944 | |||
| 401b744808 | |||
| 0c83255573 | |||
| d55f0fc366 | |||
| 06b3ba91a0 | |||
| aa4125fe62 | |||
| d8c6ed9191 | |||
| cb47fa406f | |||
| c4d0f35008 | |||
| 0d3e8dd738 | |||
| 692355a08a | |||
| a370499aaa | |||
| 84f67d6608 | |||
| 4ac839cf49 | |||
| b96a5b1efd | |||
| 766c5e8580 | |||
| 3f493e043d | |||
| 3ddad9dee8 | |||
| 2c10c39bc4 | |||
| 0eb8f38792 | |||
| 402bf53a5c | |||
| 428a59dd3f | |||
| 153890b283 | |||
| a741c2ba4a | |||
| 741e5c719f | |||
| 34e4f93db9 | |||
| 3758135dc3 | |||
| 6794e6ff43 | |||
| 62f816e64a | |||
| e65478076b | |||
| ceeabded73 | |||
| 805634f9a9 | |||
| a92832d115 | |||
| 4c5f485587 | |||
| db3a577ae3 | |||
| e452917de9 | |||
| f37961b7d0 | |||
| 0157cbddaf | |||
| 65d872cc14 | |||
| 4ad2422810 | |||
| 8408b82e9c | |||
| cd3b1ab828 | |||
| ceebc56e62 | |||
| 70190e5230 |
@@ -11,22 +11,35 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: I've found a bug and checked that ...
|
label: Checklist prior issue creation
|
||||||
description: Prior to placing the issue, please check following:** *(fill out each checkbox with an `X` once done)*
|
description: Prior to creating the issue...
|
||||||
options:
|
options:
|
||||||
- label: ... I understand that not following the below instructions will result in immediate closure and/or deletion of my issue.
|
- label: I understand that failure to follow below instructions may cause this issue to be closed.
|
||||||
required: true
|
required: true
|
||||||
- label: ... I have understood that this bug report is dedicated for bugs, and not for support-related inquiries.
|
- label: I understand that vague, incomplete or inaccurate information may cause this issue to be closed.
|
||||||
required: true
|
required: true
|
||||||
- label: ... I have understood that answers are voluntary and community-driven, and not commercial support.
|
- label: I understand that this form is intended solely for reporting software bugs and not for support-related inquiries.
|
||||||
required: true
|
required: true
|
||||||
- label: ... I have verified that my issue has not been already answered in the past. I also checked previous [issues](https://github.com/mailcow/mailcow-dockerized/issues).
|
- label: I understand that all responses are voluntary and community-driven, and do not constitute commercial support.
|
||||||
|
required: true
|
||||||
|
- label: I confirm that I have reviewed previous [issues](https://github.com/mailcow/mailcow-dockerized/issues) to ensure this matter has not already been addressed.
|
||||||
|
required: true
|
||||||
|
- label: I confirm that my environment meets all [prerequisite requirements](https://docs.mailcow.email/getstarted/prerequisite-system/) as specified in the official documentation.
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Description
|
||||||
description: Please provide a brief description of the bug in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI.
|
description: Please provide a brief description of the bug. If applicable, add screenshots to help explain your problem. (Very useful for bugs in mailcow UI.)
|
||||||
render: plain text
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Steps to reproduce:"
|
||||||
|
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
|
||||||
|
placeholder: |-
|
||||||
|
1. ...
|
||||||
|
2. ...
|
||||||
|
3. ...
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
@@ -36,45 +49,36 @@ body:
|
|||||||
render: plain text
|
render: plain text
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: "Steps to reproduce:"
|
|
||||||
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
|
|
||||||
render: plain text
|
|
||||||
placeholder: |-
|
|
||||||
1. ...
|
|
||||||
2. ...
|
|
||||||
3. ...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
## System information
|
## System information
|
||||||
### In this stage we would kindly ask you to attach general system information about your setup.
|
In this stage we would kindly ask you to attach general system information about your setup.
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: "Which branch are you using?"
|
label: "Which branch are you using?"
|
||||||
description: "#### `git rev-parse --abbrev-ref HEAD`"
|
description: "#### Run: `git rev-parse --abbrev-ref HEAD`"
|
||||||
multiple: false
|
multiple: false
|
||||||
options:
|
options:
|
||||||
- master
|
- master (stable)
|
||||||
|
- staging
|
||||||
- nightly
|
- nightly
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: "Which architecture are you using?"
|
label: "Which architecture are you using?"
|
||||||
description: "#### `uname -m`"
|
description: "#### Run: `uname -m`"
|
||||||
multiple: false
|
multiple: false
|
||||||
options:
|
options:
|
||||||
- x86
|
- x86_64
|
||||||
- ARM64 (aarch64)
|
- ARM64 (aarch64)
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: "Operating System:"
|
label: "Operating System:"
|
||||||
|
description: "#### Run: `lsb_release -ds`"
|
||||||
placeholder: "e.g. Ubuntu 22.04 LTS"
|
placeholder: "e.g. Ubuntu 22.04 LTS"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
@@ -93,43 +97,44 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: "Virtualization technology:"
|
label: "Virtualization technology:"
|
||||||
placeholder: "KVM, VMware, Xen, etc - **LXC and OpenVZ are not supported**"
|
description: "LXC and OpenVZ are not supported!"
|
||||||
|
placeholder: "KVM, VMware ESXi, Xen, etc"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: "Docker version:"
|
label: "Docker version:"
|
||||||
description: "#### `docker version`"
|
description: "#### Run: `docker version`"
|
||||||
placeholder: "20.10.21"
|
placeholder: "20.10.21"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: "docker-compose version or docker compose version:"
|
label: "docker-compose version or docker compose version:"
|
||||||
description: "#### `docker-compose version` or `docker compose version`"
|
description: "#### Run: `docker-compose version` or `docker compose version`"
|
||||||
placeholder: "v2.12.2"
|
placeholder: "v2.12.2"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: "mailcow version:"
|
label: "mailcow version:"
|
||||||
description: "#### ```git describe --tags `git rev-list --tags --max-count=1` ```"
|
description: "#### Run: ```git describe --tags `git rev-list --tags --max-count=1` ```"
|
||||||
placeholder: "2022-08"
|
placeholder: "2022-08x"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: "Reverse proxy:"
|
label: "Reverse proxy:"
|
||||||
placeholder: "e.g. Nginx/Traefik"
|
placeholder: "e.g. nginx/Traefik, or none"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: "Logs of git diff:"
|
label: "Logs of git diff:"
|
||||||
description: "#### Output of `git diff origin/master`, any other changes to the code? If so, **please post them**:"
|
description: "#### Output of `git diff origin/master`, any other changes to the code? Sanitize if needed. If so, **please post them**:"
|
||||||
render: plain text
|
render: plain text
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: false
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: "Logs of iptables -L -vn:"
|
label: "Logs of iptables -L -vn:"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
||||||
uses: actions/stale@v9.1.0
|
uses: actions/stale@v10.1.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- "watchdog-mailcow"
|
- "watchdog-mailcow"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Setup Docker
|
- name: Setup Docker
|
||||||
run: |
|
run: |
|
||||||
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Run the Action
|
- name: Run the Action
|
||||||
uses: devops-infra/action-pull-request@v0.6.0
|
uses: devops-infra/action-pull-request@v1.0.2
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||||
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Generate postscreen_access.cidr
|
- name: Generate postscreen_access.cidr
|
||||||
run: |
|
run: |
|
||||||
bash helper-scripts/update_postscreen_whitelist.sh
|
bash helper-scripts/update_postscreen_whitelist.sh
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@v7
|
uses: peter-evans/create-pull-request@v8
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
|
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
|
||||||
commit-message: update postscreen_access.cidr
|
commit-message: update postscreen_access.cidr
|
||||||
|
|||||||
@@ -75,3 +75,4 @@ refresh_images.sh
|
|||||||
update_diffs/
|
update_diffs/
|
||||||
create_cold_standby.sh
|
create_cold_standby.sh
|
||||||
!data/conf/nginx/mailcow_auth.conf
|
!data/conf/nginx/mailcow_auth.conf
|
||||||
|
data/conf/postfix/postfix-tlspol
|
||||||
+7
-3
@@ -1,11 +1,11 @@
|
|||||||
# Contribution Guidelines
|
# Contribution Guidelines
|
||||||
**_Last modified on 15th August 2024_**
|
**_Last modified on 12th November 2025_**
|
||||||
|
|
||||||
First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow!
|
First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow!
|
||||||
|
|
||||||
As we want to keep mailcow's development structured we setup these Guidelines which helps you to create your issue/pull request accordingly.
|
As we want to keep mailcow's development structured we setup these Guidelines which helps you to create your issue/pull request accordingly.
|
||||||
|
|
||||||
**PLEASE NOTE, THAT WE MIGHT CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULLFIL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request.
|
**PLEASE NOTE, THAT WE WILL CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULFILL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request.
|
||||||
|
|
||||||
## Topics
|
## Topics
|
||||||
|
|
||||||
@@ -27,14 +27,18 @@ However, please note the following regarding pull requests:
|
|||||||
6. Please **ALWAYS** create the actual pull request against the staging branch and **NEVER** directly against the master branch. *If you forget to do this, our moobot will remind you to switch the branch to staging.*
|
6. Please **ALWAYS** create the actual pull request against the staging branch and **NEVER** directly against the master branch. *If you forget to do this, our moobot will remind you to switch the branch to staging.*
|
||||||
7. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project.
|
7. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project.
|
||||||
8. If you are planning larger and therefore more complex pull requests, it would be advisable to first announce this in a separate issue and then start implementing it after the idea has been accepted in order to avoid unnecessary frustration and effort!
|
8. If you are planning larger and therefore more complex pull requests, it would be advisable to first announce this in a separate issue and then start implementing it after the idea has been accepted in order to avoid unnecessary frustration and effort!
|
||||||
|
9. If your PR requires a Docker image rebuild (changes to Dockerfiles or files in data/Dockerfiles/), update the image tag in docker-compose.yml. Use the base-image versioning (e.g. ghcr.io/mailcow/sogo:5.12.4 → :5.12.5 for version bumps; append a letter for patch fixes, e.g. :5.12.4a). Follow this scheme.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Issue Reporting
|
## Issue Reporting
|
||||||
**_Last modified on 15th August 2024_**
|
**_Last modified on 12th November 2025_**
|
||||||
|
|
||||||
If you plan to report a issue within mailcow please read and understand the following rules:
|
If you plan to report a issue within mailcow please read and understand the following rules:
|
||||||
|
|
||||||
|
### Security disclosures / Security-related fixes
|
||||||
|
- Security vulnerabilities and security fixes must always be reported confidentially first to the contact address specified in SECURITY.md before they are integrated, published, or publicly disclosed in issues/PRs. Please wait for a response from the specified contact to ensure coordinated and responsible disclosure.
|
||||||
|
|
||||||
### Issue Reporting Guidelines
|
### Issue Reporting Guidelines
|
||||||
|
|
||||||
1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support).
|
1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support).
|
||||||
|
|||||||
@@ -13,6 +13,22 @@ You can also [get a SAL](https://www.servercow.de/mailcow?lang=en#sal) which is
|
|||||||
|
|
||||||
Or just spread the word: moo.
|
Or just spread the word: moo.
|
||||||
|
|
||||||
|
## Many thanks to our GitHub Sponsors ❤️
|
||||||
|
A big thank you to everyone supporting us on GitHub Sponsors—your contributions mean the world to us! Special thanks to the following amazing supporters:
|
||||||
|
|
||||||
|
### 100$/Month Sponsors
|
||||||
|
<a href="https://www.colba.net/" target=_blank><img
|
||||||
|
src="https://avatars.githubusercontent.com/u/204464723" height="58"
|
||||||
|
/></a>
|
||||||
|
<a href="https://www.maehdros.com/" target=_blank><img
|
||||||
|
src="https://avatars.githubusercontent.com/u/173894712" height="58"
|
||||||
|
/></a>
|
||||||
|
|
||||||
|
### 50$/Month Sponsors
|
||||||
|
<a href="https://github.com/vnukhr" target=_blank><img
|
||||||
|
src="https://avatars.githubusercontent.com/u/7805987?s=52&v=4" height="58"
|
||||||
|
/></a>
|
||||||
|
|
||||||
## Info, documentation and support
|
## Info, documentation and support
|
||||||
|
|
||||||
Please see [the official documentation](https://docs.mailcow.email/) for installation and support instructions. 🐄
|
Please see [the official documentation](https://docs.mailcow.email/) for installation and support instructions. 🐄
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# _modules/scripts/core.sh
|
||||||
|
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||||
|
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
||||||
|
|
||||||
|
# ANSI color for red errors
|
||||||
|
RED='\e[31m'
|
||||||
|
GREEN='\e[32m'
|
||||||
|
YELLOW='\e[33m'
|
||||||
|
BLUE='\e[34m'
|
||||||
|
MAGENTA='\e[35m'
|
||||||
|
LIGHT_RED='\e[91m'
|
||||||
|
LIGHT_GREEN='\e[92m'
|
||||||
|
NC='\e[0m'
|
||||||
|
|
||||||
|
caller="${BASH_SOURCE[1]##*/}"
|
||||||
|
|
||||||
|
get_installed_tools(){
|
||||||
|
for bin in openssl curl docker git awk sha1sum grep cut jq; do
|
||||||
|
if [[ -z $(command -v ${bin}) ]]; then
|
||||||
|
echo "Error: Cannot find command '${bin}'. Cannot proceed."
|
||||||
|
echo "Solution: Please review system requirements and install requirements. Then, re-run the script."
|
||||||
|
echo "See System Requirements: https://docs.mailcow.email/getstarted/install/"
|
||||||
|
echo "Exiting..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"${NC}"; exit 1; fi
|
||||||
|
# This will also cover sort
|
||||||
|
if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\"${NC}"; exit 1; fi
|
||||||
|
if sed --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox sed detected, please install gnu sed, \"apk add --no-cache --upgrade sed\"${NC}"; exit 1; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
get_docker_version(){
|
||||||
|
# Check Docker Version (need at least 24.X)
|
||||||
|
docker_version=$(docker version --format '{{.Server.Version}}' | cut -d '.' -f 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
get_compose_type(){
|
||||||
|
if docker compose > /dev/null 2>&1; then
|
||||||
|
if docker compose version --short | grep -e "^[2-9]\." -e "^v[2-9]\." -e "^[1-9][0-9]\." -e "^v[1-9][0-9]\." > /dev/null 2>&1; then
|
||||||
|
COMPOSE_VERSION=native
|
||||||
|
COMPOSE_COMMAND="docker compose"
|
||||||
|
if [[ "$caller" == "update.sh" ]]; then
|
||||||
|
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf"
|
||||||
|
fi
|
||||||
|
echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
|
||||||
|
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
|
||||||
|
sleep 2
|
||||||
|
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
|
||||||
|
else
|
||||||
|
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||||
|
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
elif docker-compose > /dev/null 2>&1; then
|
||||||
|
if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
|
||||||
|
if docker-compose version --short | grep -e "^[2-9]\." -e "^[1-9][0-9]\." > /dev/null 2>&1; then
|
||||||
|
COMPOSE_VERSION=standalone
|
||||||
|
COMPOSE_COMMAND="docker-compose"
|
||||||
|
if [[ "$caller" == "update.sh" ]]; then
|
||||||
|
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' "$SCRIPT_DIR/mailcow.conf"
|
||||||
|
fi
|
||||||
|
echo -e "\e[33mFound Docker Compose Standalone.\e[0m"
|
||||||
|
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
|
||||||
|
sleep 2
|
||||||
|
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
|
||||||
|
else
|
||||||
|
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
|
||||||
|
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||||
|
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_bad_asn() {
|
||||||
|
echo -e "\e[33mDetecting if your IP is listed on Spamhaus Bad ASN List...\e[0m"
|
||||||
|
response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email")
|
||||||
|
if [ "$response" -eq 503 ]; then
|
||||||
|
if [ -z "$SPAMHAUS_DQS_KEY" ]; then
|
||||||
|
echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
|
||||||
|
echo -e "\e[33mmailcow did not detected a value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf!\e[0m"
|
||||||
|
sleep 2
|
||||||
|
echo ""
|
||||||
|
echo -e "\e[33mTo use the Spamhaus DNS Blocklists again, you will need to create a FREE account for their Data Query Service (DQS) at: https://www.spamhaus.com/free-trial/sign-up-for-a-free-data-query-service-account\e[0m"
|
||||||
|
echo -e "\e[33mOnce done, enter your DQS API key in mailcow.conf and mailcow will do the rest for you!\e[0m"
|
||||||
|
echo ""
|
||||||
|
sleep 2
|
||||||
|
else
|
||||||
|
echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
|
||||||
|
echo -e "\e[32mmailcow detected a Value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf. Postfix will use DQS with the given API key...\e[0m"
|
||||||
|
fi
|
||||||
|
elif [ "$response" -eq 200 ]; then
|
||||||
|
echo -e "\e[33mCheck completed! Your IP is \e[32mclean\e[0m"
|
||||||
|
elif [ "$response" -eq 429 ]; then
|
||||||
|
echo -e "\e[33mCheck completed! \e[31mYour IP seems to be rate limited on the ASN Check service... please try again later!\e[0m"
|
||||||
|
else
|
||||||
|
echo -e "\e[31mCheck failed! \e[0mMaybe a DNS or Network problem?\e[0m"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_online_status() {
|
||||||
|
CHECK_ONLINE_DOMAINS=('https://github.com' 'https://hub.docker.com')
|
||||||
|
for domain in "${CHECK_ONLINE_DOMAINS[@]}"; do
|
||||||
|
if timeout 6 curl --head --silent --output /dev/null ${domain}; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
prefetch_images() {
|
||||||
|
[[ -z ${BRANCH} ]] && { echo -e "\e[33m\nUnknown branch...\e[0m"; exit 1; }
|
||||||
|
git fetch origin #${BRANCH}
|
||||||
|
while read image; do
|
||||||
|
RET_C=0
|
||||||
|
until docker pull "${image}"; do
|
||||||
|
RET_C=$((RET_C + 1))
|
||||||
|
echo -e "\e[33m\nError pulling $image, retrying...\e[0m"
|
||||||
|
[ ${RET_C} -gt 3 ] && { echo -e "\e[31m\nToo many failed retries, exiting\e[0m"; exit 1; }
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
done < <(git show "origin/${BRANCH}:docker-compose.yml" | grep "image:" | awk '{ gsub("image:","", $3); print $2 }')
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_garbage() {
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
|
||||||
|
IMGS_TO_DELETE=()
|
||||||
|
|
||||||
|
declare -A IMAGES_INFO
|
||||||
|
COMPOSE_IMAGES=($(grep -oP "image: \K(ghcr\.io/)?mailcow.+" "${SCRIPT_DIR}/docker-compose.yml"))
|
||||||
|
|
||||||
|
for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep -E '(mailcow/|ghcr\.io/mailcow/)'); do
|
||||||
|
ID=$(echo "$existing_image" | cut -d ':' -f 1)
|
||||||
|
REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2)
|
||||||
|
TAG=$(echo "$existing_image" | cut -d ':' -f 3)
|
||||||
|
|
||||||
|
if [[ "$REPOSITORY" == "mailcow/backup" || "$REPOSITORY" == "ghcr.io/mailcow/backup" ]]; then
|
||||||
|
if [[ "$TAG" != "<none>" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
IMGS_TO_DELETE+=("$ID")
|
||||||
|
IMAGES_INFO["$ID"]="$REPOSITORY:$TAG"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then
|
||||||
|
echo "The following unused mailcow images were found:"
|
||||||
|
for id in "${IMGS_TO_DELETE[@]}"; do
|
||||||
|
echo " ${IMAGES_INFO[$id]} ($id)"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$FORCE" ]; then
|
||||||
|
read -r -p "Do you want to delete them to free up some space? [y/N] " response
|
||||||
|
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
|
docker rmi ${IMGS_TO_DELETE[*]}
|
||||||
|
else
|
||||||
|
echo "OK, skipped."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Running in forced mode! Force removing old mailcow images..."
|
||||||
|
docker rmi ${IMGS_TO_DELETE[*]}
|
||||||
|
fi
|
||||||
|
echo -e "\e[32mFurther cleanup...\e[0m"
|
||||||
|
echo "If you want to cleanup further garbage collected by Docker, please make sure all containers are up and running before cleaning your system by executing \"docker system prune\""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
in_array() {
|
||||||
|
local e match="$1"
|
||||||
|
shift
|
||||||
|
for e; do [[ "$e" == "$match" ]] && return 0; done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_major_update() {
|
||||||
|
if [ ${BRANCH} == "master" ]; then
|
||||||
|
# Array with major versions
|
||||||
|
# Add major versions here
|
||||||
|
MAJOR_VERSIONS=(
|
||||||
|
"2025-02"
|
||||||
|
"2025-03"
|
||||||
|
"2025-09"
|
||||||
|
)
|
||||||
|
|
||||||
|
current_version=""
|
||||||
|
if [[ -f "${SCRIPT_DIR}/data/web/inc/app_info.inc.php" ]]; then
|
||||||
|
current_version=$(grep 'MAILCOW_GIT_VERSION' ${SCRIPT_DIR}/data/web/inc/app_info.inc.php | sed -E 's/.*MAILCOW_GIT_VERSION="([^"]+)".*/\1/')
|
||||||
|
fi
|
||||||
|
if [[ -z "$current_version" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag"
|
||||||
|
|
||||||
|
updates_to_apply=()
|
||||||
|
|
||||||
|
for version in "${MAJOR_VERSIONS[@]}"; do
|
||||||
|
if [[ "$current_version" < "$version" ]]; then
|
||||||
|
updates_to_apply+=("$version")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#updates_to_apply[@]} -gt 0 ]]; then
|
||||||
|
echo -e "\e[33m\nMAJOR UPDATES to be applied:\e[0m"
|
||||||
|
for update in "${updates_to_apply[@]}"; do
|
||||||
|
echo "$update - $release_url/$update"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "\nPlease read the release notes before proceeding."
|
||||||
|
read -p "Do you want to proceed with the update? [y/n] " response
|
||||||
|
if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
|
echo "Proceeding with the update..."
|
||||||
|
else
|
||||||
|
echo "Update canceled. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# _modules/scripts/ipv6_controller.sh
|
||||||
|
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||||
|
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
||||||
|
|
||||||
|
# 1) Check if the host supports IPv6
|
||||||
|
get_ipv6_support() {
|
||||||
|
# ---- helper: probe external IPv6 connectivity without DNS ----
|
||||||
|
_probe_ipv6_connectivity() {
|
||||||
|
# Use literal, always-on IPv6 echo responders (no DNS required)
|
||||||
|
local PROBE_IPS=("2001:4860:4860::8888" "2606:4700:4700::1111")
|
||||||
|
local ip rc=1
|
||||||
|
|
||||||
|
for ip in "${PROBE_IPS[@]}"; do
|
||||||
|
if command -v ping6 &>/dev/null; then
|
||||||
|
ping6 -c1 -W2 "$ip" &>/dev/null || ping6 -c1 -w2 "$ip" &>/dev/null
|
||||||
|
rc=$?
|
||||||
|
elif command -v ping &>/dev/null; then
|
||||||
|
ping -6 -c1 -W2 "$ip" &>/dev/null || ping -6 -c1 -w2 "$ip" &>/dev/null
|
||||||
|
rc=$?
|
||||||
|
else
|
||||||
|
rc=1
|
||||||
|
fi
|
||||||
|
[[ $rc -eq 0 ]] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ! -f /proc/net/if_inet6 ]] || grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null; then
|
||||||
|
DETECTED_IPV6=false
|
||||||
|
echo -e "${YELLOW}IPv6 not detected on host – ${LIGHT_RED}IPv6 is administratively disabled${YELLOW}.${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ip -6 route show default 2>/dev/null | grep -qE '^default'; then
|
||||||
|
echo -e "${YELLOW}Default IPv6 route found – testing external IPv6 connectivity...${NC}"
|
||||||
|
if _probe_ipv6_connectivity; then
|
||||||
|
DETECTED_IPV6=true
|
||||||
|
echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
|
||||||
|
else
|
||||||
|
DETECTED_IPV6=false
|
||||||
|
echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ip -6 addr show scope global 2>/dev/null | grep -q 'inet6'; then
|
||||||
|
DETECTED_IPV6=false
|
||||||
|
echo -e "${YELLOW}Global IPv6 address present but no default route – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ip -6 addr show scope link 2>/dev/null | grep -q 'inet6'; then
|
||||||
|
echo -e "${YELLOW}Only link-local IPv6 addresses found – testing external IPv6 connectivity...${NC}"
|
||||||
|
if _probe_ipv6_connectivity; then
|
||||||
|
DETECTED_IPV6=true
|
||||||
|
echo -e "External IPv6 connectivity available – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
|
||||||
|
else
|
||||||
|
DETECTED_IPV6=false
|
||||||
|
echo -e "${YELLOW}Only link-local IPv6 present and no external connectivity – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
DETECTED_IPV6=false
|
||||||
|
echo -e "${YELLOW}IPv6 not detected on host – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2) Ensure Docker daemon.json has (or create) the required IPv6 settings
|
||||||
|
docker_daemon_edit(){
|
||||||
|
DOCKER_DAEMON_CONFIG="/etc/docker/daemon.json"
|
||||||
|
DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1)
|
||||||
|
MISSING=()
|
||||||
|
|
||||||
|
_has_kv() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; }
|
||||||
|
|
||||||
|
if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then
|
||||||
|
|
||||||
|
# reject empty or whitespace-only file immediately
|
||||||
|
if [[ ! -s "$DOCKER_DAEMON_CONFIG" ]] || ! grep -Eq '[{}]' "$DOCKER_DAEMON_CONFIG"; then
|
||||||
|
echo -e "${RED}ERROR: $DOCKER_DAEMON_CONFIG exists but is empty or contains no JSON braces – please initialize it with valid JSON (e.g. {}).${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate JSON if jq is present
|
||||||
|
if command -v jq &>/dev/null && ! jq empty "$DOCKER_DAEMON_CONFIG" &>/dev/null; then
|
||||||
|
echo -e "${RED}ERROR: Invalid JSON in $DOCKER_DAEMON_CONFIG – please correct manually.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Gather missing keys
|
||||||
|
! _has_kv ipv6 true && MISSING+=("ipv6: true")
|
||||||
|
|
||||||
|
# For Docker < 28, keep requiring fixed-cidr-v6 (default bridge needs it on old engines)
|
||||||
|
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
|
||||||
|
! grep -Eq '"fixed-cidr-v6"[[:space:]]*:[[:space:]]*".+"' "$DOCKER_DAEMON_CONFIG" \
|
||||||
|
&& MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Docker < 27, ip6tables needed and was tied to experimental in older releases
|
||||||
|
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
|
||||||
|
_has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true")
|
||||||
|
! _has_kv experimental true && MISSING+=("experimental: true")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fix if needed
|
||||||
|
if ((${#MISSING[@]}>0)); then
|
||||||
|
echo -e "${MAGENTA}Your daemon.json is missing: ${YELLOW}${MISSING[*]}${NC}"
|
||||||
|
if [[ -n "$FORCE" ]]; then
|
||||||
|
ans=Y
|
||||||
|
else
|
||||||
|
read -p "Would you like to update $DOCKER_DAEMON_CONFIG now? [Y/n] " ans
|
||||||
|
ans=${ans:-Y}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $ans =~ ^[Yy]$ ]]; then
|
||||||
|
cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak"
|
||||||
|
if command -v jq &>/dev/null; then
|
||||||
|
TMP=$(mktemp)
|
||||||
|
# Base filter: ensure ipv6 = true
|
||||||
|
JQ_FILTER='.ipv6 = true'
|
||||||
|
|
||||||
|
# Add fixed-cidr-v6 only for Docker < 28
|
||||||
|
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
|
||||||
|
JQ_FILTER+=' | .["fixed-cidr-v6"] = (.["fixed-cidr-v6"] // "fd00:dead:beef:c0::/80")'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add ip6tables/experimental only for Docker < 27
|
||||||
|
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
|
||||||
|
JQ_FILTER+=' | .ip6tables = true | .experimental = true'
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG"
|
||||||
|
echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}"
|
||||||
|
(command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
|
||||||
|
echo -e "${YELLOW}Docker restarted.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Please install jq or manually update daemon.json and restart Docker.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}User declined Docker update – please insert these changes manually:${NC}"
|
||||||
|
echo "${MISSING[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
# Create new daemon.json if missing
|
||||||
|
if [[ -n "$FORCE" ]]; then
|
||||||
|
ans=Y
|
||||||
|
else
|
||||||
|
read -p "$DOCKER_DAEMON_CONFIG not found. Create it with IPv6 settings? [Y/n] " ans
|
||||||
|
ans=${ans:-Y}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $ans =~ ^[Yy]$ ]]; then
|
||||||
|
mkdir -p "$(dirname "$DOCKER_DAEMON_CONFIG")"
|
||||||
|
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
|
||||||
|
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
|
||||||
|
{
|
||||||
|
"ipv6": true,
|
||||||
|
"fixed-cidr-v6": "fd00:dead:beef:c0::/80",
|
||||||
|
"ip6tables": true,
|
||||||
|
"experimental": true
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
elif [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
|
||||||
|
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
|
||||||
|
{
|
||||||
|
"ipv6": true,
|
||||||
|
"fixed-cidr-v6": "fd00:dead:beef:c0::/80"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
# Docker 28+: ipv6 works without fixed-cidr-v6
|
||||||
|
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
|
||||||
|
{
|
||||||
|
"ipv6": true
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}"
|
||||||
|
echo "Restarting Docker..."
|
||||||
|
(command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
|
||||||
|
echo "Docker restarted."
|
||||||
|
else
|
||||||
|
echo "User declined to create daemon.json – please manually merge the docker daemon with these configs:"
|
||||||
|
echo "${MISSING[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3) Main wrapper for generate_config.sh and update.sh
|
||||||
|
configure_ipv6() {
|
||||||
|
# detect manual override if mailcow.conf is present
|
||||||
|
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
||||||
|
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
|
||||||
|
elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
|
||||||
|
MANUAL_SETTING="$ENABLE_IPV6"
|
||||||
|
else
|
||||||
|
MANUAL_SETTING=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
get_ipv6_support
|
||||||
|
|
||||||
|
# if user manually set it, check for mismatch
|
||||||
|
if [[ "$DETECTED_IPV6" != "true" ]]; then
|
||||||
|
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
|
||||||
|
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
||||||
|
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF"
|
||||||
|
else
|
||||||
|
echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
export IPV6_BOOL=false
|
||||||
|
fi
|
||||||
|
echo "Skipping Docker IPv6 configuration because host does not support IPv6."
|
||||||
|
echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
|
||||||
|
echo "IPv6 configuration complete: ENABLE_IPV6=false"
|
||||||
|
sleep 2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker_daemon_edit
|
||||||
|
|
||||||
|
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
|
||||||
|
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
|
||||||
|
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF"
|
||||||
|
else
|
||||||
|
echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
export IPV6_BOOL=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "IPv6 configuration complete: ENABLE_IPV6=true"
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# _modules/scripts/migrate_options.sh
|
||||||
|
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||||
|
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
||||||
|
|
||||||
|
migrate_config_options() {
|
||||||
|
|
||||||
|
sed -i --follow-symlinks '$a\' mailcow.conf
|
||||||
|
|
||||||
|
KEYS=(
|
||||||
|
SOLR_HEAP
|
||||||
|
SKIP_SOLR
|
||||||
|
SOLR_PORT
|
||||||
|
FLATCURVE_EXPERIMENTAL
|
||||||
|
DISABLE_IPv6
|
||||||
|
ACME_CONTACT
|
||||||
|
)
|
||||||
|
|
||||||
|
for key in "${KEYS[@]}"; do
|
||||||
|
if grep -q "${key}" mailcow.conf; then
|
||||||
|
case "${key}" in
|
||||||
|
SOLR_HEAP)
|
||||||
|
echo "Removing ${key} in mailcow.conf"
|
||||||
|
sed -i '/# Solr heap size in MB\b/d' mailcow.conf
|
||||||
|
sed -i '/# Solr is a prone to run\b/d' mailcow.conf
|
||||||
|
sed -i '/SOLR_HEAP\b/d' mailcow.conf
|
||||||
|
;;
|
||||||
|
SKIP_SOLR)
|
||||||
|
echo "Removing ${key} in mailcow.conf"
|
||||||
|
sed -i '/\bSkip Solr on low-memory\b/d' mailcow.conf
|
||||||
|
sed -i '/\bSolr is disabled by default\b/d' mailcow.conf
|
||||||
|
sed -i '/\bDisable Solr or\b/d' mailcow.conf
|
||||||
|
sed -i '/\bSKIP_SOLR\b/d' mailcow.conf
|
||||||
|
;;
|
||||||
|
SOLR_PORT)
|
||||||
|
echo "Removing ${key} in mailcow.conf"
|
||||||
|
sed -i '/\bSOLR_PORT\b/d' mailcow.conf
|
||||||
|
;;
|
||||||
|
FLATCURVE_EXPERIMENTAL)
|
||||||
|
echo "Removing ${key} in mailcow.conf"
|
||||||
|
sed -i '/\bFLATCURVE_EXPERIMENTAL\b/d' mailcow.conf
|
||||||
|
;;
|
||||||
|
DISABLE_IPv6)
|
||||||
|
echo "Migrating ${key} to ENABLE_IPv6 in mailcow.conf"
|
||||||
|
local old=$(grep '^DISABLE_IPv6=' "mailcow.conf" | cut -d'=' -f2)
|
||||||
|
local new
|
||||||
|
if [[ "$old" == "y" ]]; then
|
||||||
|
new="false"
|
||||||
|
else
|
||||||
|
new="true"
|
||||||
|
fi
|
||||||
|
sed -i '/^DISABLE_IPv6=/d' "mailcow.conf"
|
||||||
|
echo "ENABLE_IPV6=$new" >> "mailcow.conf"
|
||||||
|
;;
|
||||||
|
ACME_CONTACT)
|
||||||
|
echo "Deleting obsoleted ${key} in mailcow.conf"
|
||||||
|
sed -i '/^# Lets Encrypt registration contact information/d' mailcow.conf
|
||||||
|
sed -i '/^# Optional: Leave empty for none/d' mailcow.conf
|
||||||
|
sed -i '/^# This value is only used on first order!/d' mailcow.conf
|
||||||
|
sed -i '/^# Setting it at a later point will require the following steps:/d' mailcow.conf
|
||||||
|
sed -i '/^# https:\/\/docs.mailcow.email\/troubleshooting\/debug-reset_tls\//d' mailcow.conf
|
||||||
|
sed -i '/^ACME_CONTACT=.*/d' mailcow.conf
|
||||||
|
sed -i '/^#ACME_CONTACT=.*/d' mailcow.conf
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
solr_volume=$(docker volume ls -qf name=^${COMPOSE_PROJECT_NAME}_solr-vol-1)
|
||||||
|
if [[ -n $solr_volume ]]; then
|
||||||
|
echo -e "\e[34mSolr has been replaced within mailcow since 2025-01.\nThe volume $solr_volume is unused.\e[0m"
|
||||||
|
sleep 1
|
||||||
|
if [ ! "$FORCE" ]; then
|
||||||
|
read -r -p "Remove $solr_volume? [y/N] " response
|
||||||
|
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
|
echo -e "\e[33mRemoving $solr_volume...\e[0m"
|
||||||
|
docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m"
|
||||||
|
echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m"
|
||||||
|
else
|
||||||
|
echo -e "Not removing $solr_volume. Run \`docker volume rm $solr_volume\` manually if needed."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "\e[33mForce removing $solr_volume...\e[0m"
|
||||||
|
docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m"
|
||||||
|
echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete old fts.conf before forced switch to flatcurve to ensure update is working properly
|
||||||
|
FTS_CONF_PATH="${SCRIPT_DIR}/data/conf/dovecot/conf.d/fts.conf"
|
||||||
|
if [[ -f "$FTS_CONF_PATH" ]]; then
|
||||||
|
if grep -q "Autogenerated by mailcow" "$FTS_CONF_PATH"; then
|
||||||
|
rm -rf $FTS_CONF_PATH
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# _modules/scripts/new_options.sh
|
||||||
|
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
|
||||||
|
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
|
||||||
|
|
||||||
|
adapt_new_options() {
|
||||||
|
|
||||||
|
CONFIG_ARRAY=(
|
||||||
|
"AUTODISCOVER_SAN"
|
||||||
|
"SKIP_LETS_ENCRYPT"
|
||||||
|
"SKIP_SOGO"
|
||||||
|
"USE_WATCHDOG"
|
||||||
|
"WATCHDOG_NOTIFY_EMAIL"
|
||||||
|
"WATCHDOG_NOTIFY_WEBHOOK"
|
||||||
|
"WATCHDOG_NOTIFY_WEBHOOK_BODY"
|
||||||
|
"WATCHDOG_NOTIFY_BAN"
|
||||||
|
"WATCHDOG_NOTIFY_START"
|
||||||
|
"WATCHDOG_EXTERNAL_CHECKS"
|
||||||
|
"WATCHDOG_SUBJECT"
|
||||||
|
"SKIP_CLAMD"
|
||||||
|
"SKIP_OLEFY"
|
||||||
|
"SKIP_IP_CHECK"
|
||||||
|
"ADDITIONAL_SAN"
|
||||||
|
"DOVEADM_PORT"
|
||||||
|
"IPV4_NETWORK"
|
||||||
|
"IPV6_NETWORK"
|
||||||
|
"LOG_LINES"
|
||||||
|
"SNAT_TO_SOURCE"
|
||||||
|
"SNAT6_TO_SOURCE"
|
||||||
|
"COMPOSE_PROJECT_NAME"
|
||||||
|
"DOCKER_COMPOSE_VERSION"
|
||||||
|
"SQL_PORT"
|
||||||
|
"API_KEY"
|
||||||
|
"API_KEY_READ_ONLY"
|
||||||
|
"API_ALLOW_FROM"
|
||||||
|
"MAILDIR_GC_TIME"
|
||||||
|
"MAILDIR_SUB"
|
||||||
|
"ACL_ANYONE"
|
||||||
|
"FTS_HEAP"
|
||||||
|
"FTS_PROCS"
|
||||||
|
"SKIP_FTS"
|
||||||
|
"ENABLE_SSL_SNI"
|
||||||
|
"ALLOW_ADMIN_EMAIL_LOGIN"
|
||||||
|
"SKIP_HTTP_VERIFICATION"
|
||||||
|
"SOGO_EXPIRE_SESSION"
|
||||||
|
"SOGO_URL_ENCRYPTION_KEY"
|
||||||
|
"REDIS_PORT"
|
||||||
|
"REDISPASS"
|
||||||
|
"DOVECOT_MASTER_USER"
|
||||||
|
"DOVECOT_MASTER_PASS"
|
||||||
|
"MAILCOW_PASS_SCHEME"
|
||||||
|
"ADDITIONAL_SERVER_NAMES"
|
||||||
|
"WATCHDOG_VERBOSE"
|
||||||
|
"WEBAUTHN_ONLY_TRUSTED_VENDORS"
|
||||||
|
"SPAMHAUS_DQS_KEY"
|
||||||
|
"SKIP_UNBOUND_HEALTHCHECK"
|
||||||
|
"DISABLE_NETFILTER_ISOLATION_RULE"
|
||||||
|
"HTTP_REDIRECT"
|
||||||
|
"ENABLE_IPV6"
|
||||||
|
)
|
||||||
|
|
||||||
|
sed -i --follow-symlinks '$a\' mailcow.conf
|
||||||
|
for option in ${CONFIG_ARRAY[@]}; do
|
||||||
|
if grep -q "${option}" mailcow.conf; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Adding new option \"${option}\" to mailcow.conf"
|
||||||
|
|
||||||
|
case "${option}" in
|
||||||
|
AUTODISCOVER_SAN)
|
||||||
|
echo '# Obtain certificates for autodiscover.* and autoconfig.* domains.' >> mailcow.conf
|
||||||
|
echo '# This can be useful to switch off in case you are in a scenario where a reverse proxy already handles those.' >> mailcow.conf
|
||||||
|
echo '# There are mixed scenarios where ports 80,443 are occupied and you do not want to share certs' >> mailcow.conf
|
||||||
|
echo '# between services. So acme-mailcow obtains for maildomains and all web-things get handled' >> mailcow.conf
|
||||||
|
echo '# in the reverse proxy.' >> mailcow.conf
|
||||||
|
echo 'AUTODISCOVER_SAN=y' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
|
||||||
|
DOCKER_COMPOSE_VERSION)
|
||||||
|
echo "# Used Docker Compose version" >> mailcow.conf
|
||||||
|
echo "# Switch here between native (compose plugin) and standalone" >> mailcow.conf
|
||||||
|
echo "# For more informations take a look at the mailcow docs regarding the configuration options." >> mailcow.conf
|
||||||
|
echo "# Normally this should be untouched but if you decided to use either of those you can switch it manually here." >> mailcow.conf
|
||||||
|
echo "# Please be aware that at least one of those variants should be installed on your machine or mailcow will fail." >> mailcow.conf
|
||||||
|
echo "" >> mailcow.conf
|
||||||
|
echo "DOCKER_COMPOSE_VERSION=${DOCKER_COMPOSE_VERSION}" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
|
||||||
|
DOVEADM_PORT)
|
||||||
|
echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
|
||||||
|
LOG_LINES)
|
||||||
|
echo '# Max log lines per service to keep in Redis logs' >> mailcow.conf
|
||||||
|
echo "LOG_LINES=9999" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
IPV4_NETWORK)
|
||||||
|
echo '# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)' >> mailcow.conf
|
||||||
|
echo "IPV4_NETWORK=172.22.1" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
IPV6_NETWORK)
|
||||||
|
echo '# Internal IPv6 subnet in fc00::/7' >> mailcow.conf
|
||||||
|
echo "IPV6_NETWORK=fd4d:6169:6c63:6f77::/64" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SQL_PORT)
|
||||||
|
echo '# Bind SQL to 127.0.0.1 on port 13306' >> mailcow.conf
|
||||||
|
echo "SQL_PORT=127.0.0.1:13306" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
API_KEY)
|
||||||
|
echo '# Create or override API key for web UI' >> mailcow.conf
|
||||||
|
echo "#API_KEY=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
API_KEY_READ_ONLY)
|
||||||
|
echo '# Create or override read-only API key for web UI' >> mailcow.conf
|
||||||
|
echo "#API_KEY_READ_ONLY=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
API_ALLOW_FROM)
|
||||||
|
echo '# Must be set for API_KEY to be active' >> mailcow.conf
|
||||||
|
echo '# IPs only, no networks (networks can be set via UI)' >> mailcow.conf
|
||||||
|
echo "#API_ALLOW_FROM=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SNAT_TO_SOURCE)
|
||||||
|
echo '# Use this IPv4 for outgoing connections (SNAT)' >> mailcow.conf
|
||||||
|
echo "#SNAT_TO_SOURCE=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SNAT6_TO_SOURCE)
|
||||||
|
echo '# Use this IPv6 for outgoing connections (SNAT)' >> mailcow.conf
|
||||||
|
echo "#SNAT6_TO_SOURCE=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
MAILDIR_GC_TIME)
|
||||||
|
echo '# Garbage collector cleanup' >> mailcow.conf
|
||||||
|
echo '# Deleted domains and mailboxes are moved to /var/vmail/_garbage/timestamp_sanitizedstring' >> mailcow.conf
|
||||||
|
echo '# How long should objects remain in the garbage until they are being deleted? (value in minutes)' >> mailcow.conf
|
||||||
|
echo '# Check interval is hourly' >> mailcow.conf
|
||||||
|
echo 'MAILDIR_GC_TIME=1440' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
ACL_ANYONE)
|
||||||
|
echo '# Set this to "allow" to enable the anyone pseudo user. Disabled by default.' >> mailcow.conf
|
||||||
|
echo '# When enabled, ACL can be created, that apply to "All authenticated users"' >> mailcow.conf
|
||||||
|
echo '# This should probably only be activated on mail hosts, that are used exclusively by one organisation.' >> mailcow.conf
|
||||||
|
echo '# Otherwise a user might share data with too many other users.' >> mailcow.conf
|
||||||
|
echo 'ACL_ANYONE=disallow' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
FTS_HEAP)
|
||||||
|
echo '# Dovecot Indexing (FTS) Process maximum heap size in MB, there is no recommendation, please see Dovecot docs.' >> mailcow.conf
|
||||||
|
echo '# Flatcurve is used as FTS Engine. It is supposed to be pretty efficient in CPU and RAM consumption.' >> mailcow.conf
|
||||||
|
echo '# Please always monitor your Resource consumption!' >> mailcow.conf
|
||||||
|
echo "FTS_HEAP=128" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SKIP_FTS)
|
||||||
|
echo '# Skip FTS (Fulltext Search) for Dovecot on low-memory, low-threaded systems or if you simply want to disable it.' >> mailcow.conf
|
||||||
|
echo "# Dovecot inside mailcow use Flatcurve as FTS Backend." >> mailcow.conf
|
||||||
|
echo "SKIP_FTS=y" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
FTS_PROCS)
|
||||||
|
echo '# Controls how many processes the Dovecot indexing process can spawn at max.' >> mailcow.conf
|
||||||
|
echo '# Too many indexing processes can use a lot of CPU and Disk I/O' >> mailcow.conf
|
||||||
|
echo '# Please visit: https://doc.dovecot.org/configuration_manual/service_configuration/#indexer-worker for more informations' >> mailcow.conf
|
||||||
|
echo "FTS_PROCS=1" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
ENABLE_SSL_SNI)
|
||||||
|
echo '# Create seperate certificates for all domains - y/n' >> mailcow.conf
|
||||||
|
echo '# this will allow adding more than 100 domains, but some email clients will not be able to connect with alternative hostnames' >> mailcow.conf
|
||||||
|
echo '# see https://wiki.dovecot.org/SSL/SNIClientSupport' >> mailcow.conf
|
||||||
|
echo "ENABLE_SSL_SNI=n" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SKIP_SOGO)
|
||||||
|
echo '# Skip SOGo: Will disable SOGo integration and therefore webmail, DAV protocols and ActiveSync support (experimental, unsupported, not fully implemented) - y/n' >> mailcow.conf
|
||||||
|
echo "SKIP_SOGO=n" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
MAILDIR_SUB)
|
||||||
|
echo '# MAILDIR_SUB defines a path in a users virtual home to keep the maildir in. Leave empty for updated setups.' >> mailcow.conf
|
||||||
|
echo "#MAILDIR_SUB=Maildir" >> mailcow.conf
|
||||||
|
echo "MAILDIR_SUB=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
WATCHDOG_NOTIFY_WEBHOOK)
|
||||||
|
echo '# Send notifications to a webhook URL that receives a POST request with the content type "application/json".' >> mailcow.conf
|
||||||
|
echo '# You can use this to send notifications to services like Discord, Slack and others.' >> mailcow.conf
|
||||||
|
echo '#WATCHDOG_NOTIFY_WEBHOOK=https://discord.com/api/webhooks/XXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
WATCHDOG_NOTIFY_WEBHOOK_BODY)
|
||||||
|
echo '# JSON body included in the webhook POST request. Needs to be in single quotes.' >> mailcow.conf
|
||||||
|
echo '# Following variables are available: SUBJECT, BODY' >> mailcow.conf
|
||||||
|
WEBHOOK_BODY='{"username": "mailcow Watchdog", "content": "**${SUBJECT}**\n${BODY}"}'
|
||||||
|
echo "#WATCHDOG_NOTIFY_WEBHOOK_BODY='${WEBHOOK_BODY}'" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
WATCHDOG_NOTIFY_BAN)
|
||||||
|
echo '# Notify about banned IP. Includes whois lookup.' >> mailcow.conf
|
||||||
|
echo "WATCHDOG_NOTIFY_BAN=y" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
WATCHDOG_NOTIFY_START)
|
||||||
|
echo '# Send a notification when the watchdog is started.' >> mailcow.conf
|
||||||
|
echo "WATCHDOG_NOTIFY_START=y" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
WATCHDOG_SUBJECT)
|
||||||
|
echo '# Subject for watchdog mails. Defaults to "Watchdog ALERT" followed by the error message.' >> mailcow.conf
|
||||||
|
echo "#WATCHDOG_SUBJECT=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
WATCHDOG_EXTERNAL_CHECKS)
|
||||||
|
echo '# Checks if mailcow is an open relay. Requires a SAL. More checks will follow.' >> mailcow.conf
|
||||||
|
echo '# No data is collected. Opt-in and anonymous.' >> mailcow.conf
|
||||||
|
echo '# Will only work with unmodified mailcow setups.' >> mailcow.conf
|
||||||
|
echo "WATCHDOG_EXTERNAL_CHECKS=n" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SOGO_EXPIRE_SESSION)
|
||||||
|
echo '# SOGo session timeout in minutes' >> mailcow.conf
|
||||||
|
echo "SOGO_EXPIRE_SESSION=480" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
REDIS_PORT)
|
||||||
|
echo "REDIS_PORT=127.0.0.1:7654" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
DOVECOT_MASTER_USER)
|
||||||
|
echo '# DOVECOT_MASTER_USER and _PASS must _both_ be provided. No special chars.' >> mailcow.conf
|
||||||
|
echo '# Empty by default to auto-generate master user and password on start.' >> mailcow.conf
|
||||||
|
echo '# User expands to DOVECOT_MASTER_USER@mailcow.local' >> mailcow.conf
|
||||||
|
echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf
|
||||||
|
echo "DOVECOT_MASTER_USER=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
DOVECOT_MASTER_PASS)
|
||||||
|
echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf
|
||||||
|
echo "DOVECOT_MASTER_PASS=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
MAILCOW_PASS_SCHEME)
|
||||||
|
echo '# Password hash algorithm' >> mailcow.conf
|
||||||
|
echo '# Only certain password hash algorithm are supported. For a fully list of supported schemes,' >> mailcow.conf
|
||||||
|
echo '# see https://docs.mailcow.email/models/model-passwd/' >> mailcow.conf
|
||||||
|
echo "MAILCOW_PASS_SCHEME=BLF-CRYPT" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
ADDITIONAL_SERVER_NAMES)
|
||||||
|
echo '# Additional server names for mailcow UI' >> mailcow.conf
|
||||||
|
echo '#' >> mailcow.conf
|
||||||
|
echo '# Specify alternative addresses for the mailcow UI to respond to' >> mailcow.conf
|
||||||
|
echo '# This is useful when you set mail.* as ADDITIONAL_SAN and want to make sure mail.maildomain.com will always point to the mailcow UI.' >> mailcow.conf
|
||||||
|
echo '# If the server name does not match a known site, Nginx decides by best-guess and may redirect users to the wrong web root.' >> mailcow.conf
|
||||||
|
echo '# You can understand this as server_name directive in Nginx.' >> mailcow.conf
|
||||||
|
echo '# Comma separated list without spaces! Example: ADDITIONAL_SERVER_NAMES=a.b.c,d.e.f' >> mailcow.conf
|
||||||
|
echo 'ADDITIONAL_SERVER_NAMES=' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
WEBAUTHN_ONLY_TRUSTED_VENDORS)
|
||||||
|
echo "# WebAuthn device manufacturer verification" >> mailcow.conf
|
||||||
|
echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf
|
||||||
|
echo '# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates' >> mailcow.conf
|
||||||
|
echo 'WEBAUTHN_ONLY_TRUSTED_VENDORS=n' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SPAMHAUS_DQS_KEY)
|
||||||
|
echo "# Spamhaus Data Query Service Key" >> mailcow.conf
|
||||||
|
echo '# Optional: Leave empty for none' >> mailcow.conf
|
||||||
|
echo '# Enter your key here if you are using a blocked ASN (OVH, AWS, Cloudflare e.g) for the unregistered Spamhaus Blocklist.' >> mailcow.conf
|
||||||
|
echo '# If empty, it will completely disable Spamhaus blocklists if it detects that you are running on a server using a blocked AS.' >> mailcow.conf
|
||||||
|
echo '# Otherwise it will work as usual.' >> mailcow.conf
|
||||||
|
echo 'SPAMHAUS_DQS_KEY=' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
WATCHDOG_VERBOSE)
|
||||||
|
echo '# Enable watchdog verbose logging' >> mailcow.conf
|
||||||
|
echo 'WATCHDOG_VERBOSE=n' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SKIP_UNBOUND_HEALTHCHECK)
|
||||||
|
echo '# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) - y/n' >> mailcow.conf
|
||||||
|
echo 'SKIP_UNBOUND_HEALTHCHECK=n' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
DISABLE_NETFILTER_ISOLATION_RULE)
|
||||||
|
echo '# Prevent netfilter from setting an iptables/nftables rule to isolate the mailcow docker network - y/n' >> mailcow.conf
|
||||||
|
echo '# CAUTION: Disabling this may expose container ports to other neighbors on the same subnet, even if the ports are bound to localhost' >> mailcow.conf
|
||||||
|
echo 'DISABLE_NETFILTER_ISOLATION_RULE=n' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
HTTP_REDIRECT)
|
||||||
|
echo '# Redirect HTTP connections to HTTPS - y/n' >> mailcow.conf
|
||||||
|
echo 'HTTP_REDIRECT=n' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
ENABLE_IPV6)
|
||||||
|
echo '# IPv6 Controller Section' >> mailcow.conf
|
||||||
|
echo '# This variable controls the usage of IPv6 within mailcow.' >> mailcow.conf
|
||||||
|
echo '# Can either be true or false | Defaults to true' >> mailcow.conf
|
||||||
|
echo '# WARNING: MAKE SURE TO PROPERLY CONFIGURE IPv6 ON YOUR HOST FIRST BEFORE ENABLING THIS AS FAULTY CONFIGURATIONS CAN LEAD TO OPEN RELAYS!' >> mailcow.conf
|
||||||
|
echo '# A COMPLETE DOCKER STACK REBUILD (compose down && compose up -d) IS NEEDED TO APPLY THIS.' >> mailcow.conf
|
||||||
|
echo ENABLE_IPV6=${IPV6_BOOL} >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SKIP_CLAMD)
|
||||||
|
echo '# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n' >> mailcow.conf
|
||||||
|
echo 'SKIP_CLAMD=n' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SKIP_OLEFY)
|
||||||
|
echo '# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n' >> mailcow.conf
|
||||||
|
echo 'SKIP_OLEFY=n' >> mailcow.conf
|
||||||
|
;;
|
||||||
|
REDISPASS)
|
||||||
|
echo "REDISPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 28)" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
SOGO_URL_ENCRYPTION_KEY)
|
||||||
|
echo '# SOGo URL encryption key (exactly 16 characters, limited to A–Z, a–z, 0–9)' >> mailcow.conf
|
||||||
|
echo '# This key is used to encrypt email addresses within SOGo URLs' >> mailcow.conf
|
||||||
|
echo "SOGO_URL_ENCRYPTION_KEY=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 16)" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "${option}=" >> mailcow.conf
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
@@ -159,18 +159,6 @@ while true; do
|
|||||||
fi
|
fi
|
||||||
if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then
|
if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then
|
||||||
log_f "Generating missing Lets Encrypt account key..."
|
log_f "Generating missing Lets Encrypt account key..."
|
||||||
if [[ ! -z ${ACME_CONTACT} ]]; then
|
|
||||||
if ! verify_email "${ACME_CONTACT}"; then
|
|
||||||
log_f "Invalid email address, will not start registration!"
|
|
||||||
sleep 365d
|
|
||||||
exec $(readlink -f "$0")
|
|
||||||
else
|
|
||||||
ACME_CONTACT_PARAMETER="--contact mailto:${ACME_CONTACT}"
|
|
||||||
log_f "Valid email address, using ${ACME_CONTACT} for registration"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
ACME_CONTACT_PARAMETER=""
|
|
||||||
fi
|
|
||||||
openssl genrsa 4096 > ${ACME_BASE}/acme/account.pem
|
openssl genrsa 4096 > ${ACME_BASE}/acme/account.pem
|
||||||
else
|
else
|
||||||
log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem"
|
log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem"
|
||||||
@@ -218,7 +206,7 @@ while true; do
|
|||||||
|
|
||||||
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
|
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
|
||||||
# Fetch certs for autoconfig and autodiscover subdomains
|
# Fetch certs for autoconfig and autodiscover subdomains
|
||||||
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
|
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig' 'mta-sts')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
|
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
|
||||||
@@ -299,7 +287,7 @@ while true; do
|
|||||||
VALIDATED_CERTIFICATES+=("${CERT_NAME}")
|
VALIDATED_CERTIFICATES+=("${CERT_NAME}")
|
||||||
|
|
||||||
# obtain server certificate if required
|
# obtain server certificate if required
|
||||||
ACME_CONTACT_PARAMETER=${ACME_CONTACT_PARAMETER} DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa
|
DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa
|
||||||
RETURN="$?"
|
RETURN="$?"
|
||||||
if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully
|
if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully
|
||||||
CERT_AMOUNT_CHANGED=1
|
CERT_AMOUNT_CHANGED=1
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do
|
|||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
log_f "Resolver OK"
|
log_f "Resolver OK"
|
||||||
log_f "Using command acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/"
|
log_f "Using command acme-tiny ${DIRECTORY_URL} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/"
|
||||||
ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} \
|
ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} \
|
||||||
--account-key ${ACME_BASE}/acme/account.pem \
|
--account-key ${ACME_BASE}/acme/account.pem \
|
||||||
--disable-check \
|
--disable-check \
|
||||||
--csr ${CSR} \
|
--csr ${CSR} \
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
FROM debian:bookworm-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
RUN apt update && apt install pigz -y --no-install-recommends
|
RUN apt update && apt install pigz zstd -y --no-install-recommends
|
||||||
@@ -8,7 +8,7 @@ fi
|
|||||||
|
|
||||||
# Cleaning up garbage
|
# Cleaning up garbage
|
||||||
echo "Cleaning up tmp files..."
|
echo "Cleaning up tmp files..."
|
||||||
rm -rf /var/lib/clamav/clamav-*.tmp
|
rm -rf /var/lib/clamav/tmp.*
|
||||||
|
|
||||||
# Prepare whitelist
|
# Prepare whitelist
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM alpine:3.21
|
|||||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||||
ARG GOSU_VERSION=1.16
|
ARG GOSU_VERSION=1.17
|
||||||
|
|
||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
ENV LC_ALL=C.UTF-8
|
ENV LC_ALL=C.UTF-8
|
||||||
|
|||||||
@@ -204,16 +204,17 @@ EOF
|
|||||||
# Create random master Password for SOGo SSO
|
# Create random master Password for SOGo SSO
|
||||||
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
|
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
|
||||||
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
|
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
|
||||||
# Creating additional creds file for SOGo notify crons (calendars, etc)
|
|
||||||
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
|
|
||||||
cat <<EOF > /etc/dovecot/sogo-sso.conf
|
cat <<EOF > /etc/dovecot/sogo-sso.conf
|
||||||
# Autogenerated by mailcow
|
# Autogenerated by mailcow
|
||||||
passdb {
|
passdb {
|
||||||
driver = static
|
driver = static
|
||||||
args = allow_real_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
|
args = allow_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Creating additional creds file for SOGo notify crons (calendars, etc) (dummy user, sso password)
|
||||||
|
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
|
||||||
|
|
||||||
if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then
|
if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then
|
||||||
# Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated
|
# Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated
|
||||||
cat <<'EOF' > /usr/local/bin/quota_notify.py
|
cat <<'EOF' > /usr/local/bin/quota_notify.py
|
||||||
|
|||||||
@@ -132,8 +132,8 @@ while ($row = $sth->fetchrow_arrayref()) {
|
|||||||
"--tmpdir", "/tmp",
|
"--tmpdir", "/tmp",
|
||||||
"--nofoldersizes",
|
"--nofoldersizes",
|
||||||
"--addheader",
|
"--addheader",
|
||||||
($timeout1 gt "0" ? () : ('--timeout1', $timeout1)),
|
($timeout1 le "0" ? () : ('--timeout1', $timeout1)),
|
||||||
($timeout2 gt "0" ? () : ('--timeout2', $timeout2)),
|
($timeout2 le "0" ? () : ('--timeout2', $timeout2)),
|
||||||
($exclude eq "" ? () : ("--exclude", $exclude)),
|
($exclude eq "" ? () : ("--exclude", $exclude)),
|
||||||
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
|
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
|
||||||
($maxage eq "0" ? () : ('--maxage', $maxage)),
|
($maxage eq "0" ? () : ('--maxage', $maxage)),
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ from email.mime.multipart import MIMEMultipart
|
|||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.utils import COMMASPACE, formatdate
|
from email.utils import COMMASPACE, formatdate
|
||||||
import jinja2
|
import jinja2
|
||||||
from jinja2 import Template
|
from jinja2 import TemplateError
|
||||||
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
import json
|
import json
|
||||||
import redis
|
import redis
|
||||||
import time
|
import time
|
||||||
@@ -75,22 +76,27 @@ try:
|
|||||||
|
|
||||||
def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
|
def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
|
||||||
if category == "add_header": category = "add header"
|
if category == "add_header": category = "add header"
|
||||||
meta_query = query_mysql('SELECT SHA2(CONCAT(id, qid), 256) AS qhash, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
|
meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
|
||||||
print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
|
print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
|
||||||
if len(meta_query) == 0:
|
if len(meta_query) == 0:
|
||||||
return
|
return
|
||||||
msg_count = len(meta_query)
|
msg_count = len(meta_query)
|
||||||
|
env = SandboxedEnvironment()
|
||||||
if r.get('Q_HTML'):
|
if r.get('Q_HTML'):
|
||||||
try:
|
try:
|
||||||
template = Template(r.get('Q_HTML'))
|
template = env.from_string(r.get('Q_HTML'))
|
||||||
except:
|
except Exception:
|
||||||
print("Error: Cannot parse quarantine template, falling back to default template.")
|
print("Error: Cannot parse quarantine template, falling back to default template.")
|
||||||
with open('/templates/quarantine.tpl') as file_:
|
with open('/templates/quarantine.tpl') as file_:
|
||||||
template = Template(file_.read())
|
template = env.from_string(file_.read())
|
||||||
else:
|
else:
|
||||||
with open('/templates/quarantine.tpl') as file_:
|
with open('/templates/quarantine.tpl') as file_:
|
||||||
template = Template(file_.read())
|
template = env.from_string(file_.read())
|
||||||
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
|
try:
|
||||||
|
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
|
||||||
|
except (jinja2.exceptions.SecurityError, TemplateError) as ex:
|
||||||
|
print(f"SecurityError or TemplateError in template rendering: {ex}")
|
||||||
|
return
|
||||||
text = html2text.html2text(html)
|
text = html2text.html2text(html)
|
||||||
count = 0
|
count = 0
|
||||||
while count < 15:
|
while count < 15:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from email.mime.multipart import MIMEMultipart
|
|||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.utils import COMMASPACE, formatdate
|
from email.utils import COMMASPACE, formatdate
|
||||||
import jinja2
|
import jinja2
|
||||||
from jinja2 import Template
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
import redis
|
import redis
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
@@ -33,16 +33,24 @@ while True:
|
|||||||
|
|
||||||
if r.get('QW_HTML'):
|
if r.get('QW_HTML'):
|
||||||
try:
|
try:
|
||||||
template = Template(r.get('QW_HTML'))
|
env = SandboxedEnvironment()
|
||||||
except:
|
template = env.from_string(r.get('QW_HTML'))
|
||||||
print("Error: Cannot parse quarantine template, falling back to default template.")
|
except Exception:
|
||||||
|
print("Error: Cannot parse quota template, falling back to default template.")
|
||||||
with open('/templates/quota.tpl') as file_:
|
with open('/templates/quota.tpl') as file_:
|
||||||
template = Template(file_.read())
|
env = SandboxedEnvironment()
|
||||||
|
template = env.from_string(file_.read())
|
||||||
else:
|
else:
|
||||||
with open('/templates/quota.tpl') as file_:
|
with open('/templates/quota.tpl') as file_:
|
||||||
template = Template(file_.read())
|
env = SandboxedEnvironment()
|
||||||
|
template = env.from_string(file_.read())
|
||||||
|
|
||||||
|
try:
|
||||||
|
html = template.render(username=username, percent=percent)
|
||||||
|
except (jinja2.exceptions.SecurityError, jinja2.TemplateError) as ex:
|
||||||
|
print(f"SecurityError or TemplateError in template rendering: {ex}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
html = template.render(username=username, percent=percent)
|
|
||||||
text = html2text.html2text(html)
|
text = html2text.html2text(html)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
backend=iptables
|
backend=nftables
|
||||||
|
|
||||||
nft list table ip filter &>/dev/null
|
nft list table ip filter &>/dev/null
|
||||||
nftables_found=$?
|
nftables_found=$?
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -20,10 +22,13 @@ from modules.Logger import Logger
|
|||||||
from modules.IPTables import IPTables
|
from modules.IPTables import IPTables
|
||||||
from modules.NFTables import NFTables
|
from modules.NFTables import NFTables
|
||||||
|
|
||||||
|
def logdebug(msg):
|
||||||
|
if DEBUG:
|
||||||
|
logger.logInfo("DEBUG: %s" % msg)
|
||||||
|
|
||||||
# globals
|
# Globals
|
||||||
WHITELIST = []
|
WHITELIST = []
|
||||||
BLACKLIST= []
|
BLACKLIST = []
|
||||||
bans = {}
|
bans = {}
|
||||||
quit_now = False
|
quit_now = False
|
||||||
exit_code = 0
|
exit_code = 0
|
||||||
@@ -33,12 +38,10 @@ r = None
|
|||||||
pubsub = None
|
pubsub = None
|
||||||
clear_before_quit = False
|
clear_before_quit = False
|
||||||
|
|
||||||
|
|
||||||
def refreshF2boptions():
|
def refreshF2boptions():
|
||||||
global f2boptions
|
global f2boptions
|
||||||
global quit_now
|
global quit_now
|
||||||
global exit_code
|
global exit_code
|
||||||
|
|
||||||
f2boptions = {}
|
f2boptions = {}
|
||||||
|
|
||||||
if not r.get('F2B_OPTIONS'):
|
if not r.get('F2B_OPTIONS'):
|
||||||
@@ -52,8 +55,9 @@ def refreshF2boptions():
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
||||||
except ValueError:
|
except ValueError as e:
|
||||||
logger.logCrit('Error loading F2B options: F2B_OPTIONS is not json')
|
logger.logCrit(
|
||||||
|
'Error loading F2B options: F2B_OPTIONS is not json. Exception: %s' % e)
|
||||||
quit_now = True
|
quit_now = True
|
||||||
exit_code = 2
|
exit_code = 2
|
||||||
|
|
||||||
@@ -61,15 +65,15 @@ def refreshF2boptions():
|
|||||||
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
|
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
|
||||||
|
|
||||||
def verifyF2boptions(f2boptions):
|
def verifyF2boptions(f2boptions):
|
||||||
verifyF2boption(f2boptions,'ban_time', 1800)
|
verifyF2boption(f2boptions, 'ban_time', 1800)
|
||||||
verifyF2boption(f2boptions,'max_ban_time', 10000)
|
verifyF2boption(f2boptions, 'max_ban_time', 10000)
|
||||||
verifyF2boption(f2boptions,'ban_time_increment', True)
|
verifyF2boption(f2boptions, 'ban_time_increment', True)
|
||||||
verifyF2boption(f2boptions,'max_attempts', 10)
|
verifyF2boption(f2boptions, 'max_attempts', 10)
|
||||||
verifyF2boption(f2boptions,'retry_window', 600)
|
verifyF2boption(f2boptions, 'retry_window', 600)
|
||||||
verifyF2boption(f2boptions,'netban_ipv4', 32)
|
verifyF2boption(f2boptions, 'netban_ipv4', 32)
|
||||||
verifyF2boption(f2boptions,'netban_ipv6', 128)
|
verifyF2boption(f2boptions, 'netban_ipv6', 128)
|
||||||
verifyF2boption(f2boptions,'banlist_id', str(uuid.uuid4()))
|
verifyF2boption(f2boptions, 'banlist_id', str(uuid.uuid4()))
|
||||||
verifyF2boption(f2boptions,'manage_external', 0)
|
verifyF2boption(f2boptions, 'manage_external', 0)
|
||||||
|
|
||||||
def verifyF2boption(f2boptions, f2boption, f2bdefault):
|
def verifyF2boption(f2boptions, f2boption, f2bdefault):
|
||||||
f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
|
f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
|
||||||
@@ -111,7 +115,7 @@ def get_ip(address):
|
|||||||
def ban(address):
|
def ban(address):
|
||||||
global f2boptions
|
global f2boptions
|
||||||
global lock
|
global lock
|
||||||
|
logdebug("ban() called with address=%s" % address)
|
||||||
refreshF2boptions()
|
refreshF2boptions()
|
||||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||||
RETRY_WINDOW = int(f2boptions['retry_window'])
|
RETRY_WINDOW = int(f2boptions['retry_window'])
|
||||||
@@ -119,31 +123,43 @@ def ban(address):
|
|||||||
NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6'])
|
NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6'])
|
||||||
|
|
||||||
ip = get_ip(address)
|
ip = get_ip(address)
|
||||||
if not ip: return
|
if not ip:
|
||||||
|
logdebug("No valid IP -- skipping ban()")
|
||||||
|
return
|
||||||
address = str(ip)
|
address = str(ip)
|
||||||
self_network = ipaddress.ip_network(address)
|
self_network = ipaddress.ip_network(address)
|
||||||
|
|
||||||
with lock:
|
with lock:
|
||||||
temp_whitelist = set(WHITELIST)
|
temp_whitelist = set(WHITELIST)
|
||||||
if temp_whitelist:
|
logdebug("Checking if %s overlaps with any WHITELIST entries" % self_network)
|
||||||
for wl_key in temp_whitelist:
|
if temp_whitelist:
|
||||||
wl_net = ipaddress.ip_network(wl_key, False)
|
for wl_key in temp_whitelist:
|
||||||
if wl_net.overlaps(self_network):
|
wl_net = ipaddress.ip_network(wl_key, False)
|
||||||
logger.logInfo('Address %s is whitelisted by rule %s' % (self_network, wl_net))
|
logdebug("Checking overlap between %s and %s" % (self_network, wl_net))
|
||||||
return
|
if wl_net.overlaps(self_network):
|
||||||
|
logger.logInfo(
|
||||||
|
'Address %s is allowlisted by rule %s' % (self_network, wl_net))
|
||||||
|
return
|
||||||
|
|
||||||
net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
|
net = ipaddress.ip_network(
|
||||||
|
(address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
|
||||||
net = str(net)
|
net = str(net)
|
||||||
|
logdebug("Ban net: %s" % net)
|
||||||
|
|
||||||
if not net in bans:
|
if not net in bans:
|
||||||
bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0}
|
bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0}
|
||||||
|
logdebug("Initing new ban counter for %s" % net)
|
||||||
|
|
||||||
current_attempt = time.time()
|
current_attempt = time.time()
|
||||||
|
logdebug("Current attempt ts=%s, previous: %s, retry_window: %s" %
|
||||||
|
(current_attempt, bans[net]['last_attempt'], RETRY_WINDOW))
|
||||||
if current_attempt - bans[net]['last_attempt'] > RETRY_WINDOW:
|
if current_attempt - bans[net]['last_attempt'] > RETRY_WINDOW:
|
||||||
bans[net]['attempts'] = 0
|
bans[net]['attempts'] = 0
|
||||||
|
logdebug("Ban counter for %s reset as window expired" % net)
|
||||||
|
|
||||||
bans[net]['attempts'] += 1
|
bans[net]['attempts'] += 1
|
||||||
bans[net]['last_attempt'] = current_attempt
|
bans[net]['last_attempt'] = current_attempt
|
||||||
|
logdebug("%s attempts now %d" % (net, bans[net]['attempts']))
|
||||||
|
|
||||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||||
cur_time = int(round(time.time()))
|
cur_time = int(round(time.time()))
|
||||||
@@ -151,34 +167,41 @@ def ban(address):
|
|||||||
logger.logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 ))
|
logger.logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 ))
|
||||||
if type(ip) is ipaddress.IPv4Address and int(f2boptions['manage_external']) != 1:
|
if type(ip) is ipaddress.IPv4Address and int(f2boptions['manage_external']) != 1:
|
||||||
with lock:
|
with lock:
|
||||||
|
logdebug("Calling tables.banIPv4(%s)" % net)
|
||||||
tables.banIPv4(net)
|
tables.banIPv4(net)
|
||||||
elif int(f2boptions['manage_external']) != 1:
|
elif int(f2boptions['manage_external']) != 1:
|
||||||
with lock:
|
with lock:
|
||||||
|
logdebug("Calling tables.banIPv6(%s)" % net)
|
||||||
tables.banIPv6(net)
|
tables.banIPv6(net)
|
||||||
|
|
||||||
|
logdebug("Updating F2B_ACTIVE_BANS[%s]=%d" %
|
||||||
|
(net, cur_time + NET_BAN_TIME))
|
||||||
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
|
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
|
||||||
else:
|
else:
|
||||||
logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
|
logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (
|
||||||
|
MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
|
||||||
|
|
||||||
def unban(net):
|
def unban(net):
|
||||||
global lock
|
global lock
|
||||||
|
logdebug("Calling unban() with net=%s" % net)
|
||||||
if not net in bans:
|
if not net in bans:
|
||||||
logger.logInfo('%s is not banned, skipping unban and deleting from queue (if any)' % net)
|
logger.logInfo(
|
||||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
'%s is not banned, skipping unban and deleting from queue (if any)' % net)
|
||||||
return
|
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||||
|
return
|
||||||
logger.logInfo('Unbanning %s' % net)
|
logger.logInfo('Unbanning %s' % net)
|
||||||
if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network:
|
if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network:
|
||||||
with lock:
|
with lock:
|
||||||
|
logdebug("Calling tables.unbanIPv4(%s)" % net)
|
||||||
tables.unbanIPv4(net)
|
tables.unbanIPv4(net)
|
||||||
else:
|
else:
|
||||||
with lock:
|
with lock:
|
||||||
|
logdebug("Calling tables.unbanIPv6(%s)" % net)
|
||||||
tables.unbanIPv6(net)
|
tables.unbanIPv6(net)
|
||||||
|
|
||||||
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
||||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||||
if net in bans:
|
if net in bans:
|
||||||
|
logdebug("Unban for %s, setting attempts=0, ban_counter+=1" % net)
|
||||||
bans[net]['attempts'] = 0
|
bans[net]['attempts'] = 0
|
||||||
bans[net]['ban_counter'] += 1
|
bans[net]['ban_counter'] += 1
|
||||||
|
|
||||||
@@ -204,17 +227,19 @@ def permBan(net, unban=False):
|
|||||||
|
|
||||||
if is_unbanned:
|
if is_unbanned:
|
||||||
r.hdel('F2B_PERM_BANS', '%s' % net)
|
r.hdel('F2B_PERM_BANS', '%s' % net)
|
||||||
logger.logCrit('Removed host/network %s from blacklist' % net)
|
logger.logCrit('Removed host/network %s from denylist' % net)
|
||||||
elif is_banned:
|
elif is_banned:
|
||||||
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
||||||
logger.logCrit('Added host/network %s to blacklist' % net)
|
logger.logCrit('Added host/network %s to denylist' % net)
|
||||||
|
|
||||||
def clear():
|
def clear():
|
||||||
global lock
|
global lock
|
||||||
logger.logInfo('Clearing all bans')
|
logger.logInfo('Clearing all bans')
|
||||||
for net in bans.copy():
|
for net in bans.copy():
|
||||||
|
logdebug("Unbanning net: %s" % net)
|
||||||
unban(net)
|
unban(net)
|
||||||
with lock:
|
with lock:
|
||||||
|
logdebug("Clearing IPv4/IPv6 table")
|
||||||
tables.clearIPv4Table()
|
tables.clearIPv4Table()
|
||||||
tables.clearIPv6Table()
|
tables.clearIPv6Table()
|
||||||
try:
|
try:
|
||||||
@@ -275,21 +300,35 @@ def snat6(snat_target):
|
|||||||
|
|
||||||
def autopurge():
|
def autopurge():
|
||||||
global f2boptions
|
global f2boptions
|
||||||
|
logdebug("autopurge thread started")
|
||||||
while not quit_now:
|
while not quit_now:
|
||||||
|
logdebug("autopurge tick")
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
refreshF2boptions()
|
refreshF2boptions()
|
||||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||||
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
||||||
|
logdebug("QUEUE_UNBAN: %s" % QUEUE_UNBAN)
|
||||||
if QUEUE_UNBAN:
|
if QUEUE_UNBAN:
|
||||||
for net in QUEUE_UNBAN:
|
for net in QUEUE_UNBAN:
|
||||||
|
logdebug("Autopurge: unbanning queued net: %s" % net)
|
||||||
unban(str(net))
|
unban(str(net))
|
||||||
for net in bans.copy():
|
# Only check expiry for actively banned IPs:
|
||||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
active_bans = r.hgetall('F2B_ACTIVE_BANS')
|
||||||
NET_BAN_TIME = calcNetBanTime(bans[net]['ban_counter'])
|
now = time.time()
|
||||||
TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt']
|
for net_str, expire_str in active_bans.items():
|
||||||
if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME:
|
logdebug("Checking ban expiry for (actively banned): %s" % net_str)
|
||||||
unban(net)
|
# Defensive: always process if timer missing or expired
|
||||||
|
try:
|
||||||
|
expire = float(expire_str)
|
||||||
|
except Exception:
|
||||||
|
logdebug("Invalid expire time for %s; unbanning" % net_str)
|
||||||
|
unban(net_str)
|
||||||
|
continue
|
||||||
|
time_left = expire - now
|
||||||
|
logdebug("Time left for %s: %.1f seconds" % (net_str, time_left))
|
||||||
|
if time_left <= 0:
|
||||||
|
logdebug("Ban expired for %s" % net_str)
|
||||||
|
unban(net_str)
|
||||||
|
|
||||||
def mailcowChainOrder():
|
def mailcowChainOrder():
|
||||||
global lock
|
global lock
|
||||||
@@ -359,7 +398,7 @@ def whitelistUpdate():
|
|||||||
with lock:
|
with lock:
|
||||||
if Counter(new_whitelist) != Counter(WHITELIST):
|
if Counter(new_whitelist) != Counter(WHITELIST):
|
||||||
WHITELIST = new_whitelist
|
WHITELIST = new_whitelist
|
||||||
logger.logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST))
|
logger.logInfo('Allowlist was changed, it has %s entries' % len(WHITELIST))
|
||||||
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
|
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
|
||||||
|
|
||||||
def blacklistUpdate():
|
def blacklistUpdate():
|
||||||
@@ -375,7 +414,7 @@ def blacklistUpdate():
|
|||||||
addban = set(new_blacklist).difference(BLACKLIST)
|
addban = set(new_blacklist).difference(BLACKLIST)
|
||||||
delban = set(BLACKLIST).difference(new_blacklist)
|
delban = set(BLACKLIST).difference(new_blacklist)
|
||||||
BLACKLIST = new_blacklist
|
BLACKLIST = new_blacklist
|
||||||
logger.logInfo('Blacklist was changed, it has %s entries' % len(BLACKLIST))
|
logger.logInfo('Denylist was changed, it has %s entries' % len(BLACKLIST))
|
||||||
if addban:
|
if addban:
|
||||||
for net in addban:
|
for net in addban:
|
||||||
permBan(net=net)
|
permBan(net=net)
|
||||||
@@ -386,42 +425,43 @@ def blacklistUpdate():
|
|||||||
|
|
||||||
def sigterm_quit(signum, frame):
|
def sigterm_quit(signum, frame):
|
||||||
global clear_before_quit
|
global clear_before_quit
|
||||||
|
logdebug("SIGTERM received, setting clear_before_quit to True and exiting")
|
||||||
clear_before_quit = True
|
clear_before_quit = True
|
||||||
sys.exit(exit_code)
|
sys.exit(exit_code)
|
||||||
|
|
||||||
def berfore_quit():
|
def before_quit():
|
||||||
|
logdebug("before_quit called, clear_before_quit=%s" % clear_before_quit)
|
||||||
if clear_before_quit:
|
if clear_before_quit:
|
||||||
clear()
|
clear()
|
||||||
if pubsub is not None:
|
if pubsub is not None:
|
||||||
pubsub.unsubscribe()
|
pubsub.unsubscribe()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
atexit.register(berfore_quit)
|
logger = Logger()
|
||||||
|
logdebug("Sys.argv: %s" % sys.argv)
|
||||||
|
atexit.register(before_quit)
|
||||||
signal.signal(signal.SIGTERM, sigterm_quit)
|
signal.signal(signal.SIGTERM, sigterm_quit)
|
||||||
|
|
||||||
# init Logger
|
|
||||||
logger = Logger()
|
|
||||||
|
|
||||||
# init backend
|
|
||||||
backend = sys.argv[1]
|
backend = sys.argv[1]
|
||||||
|
logdebug("Backend: %s" % backend)
|
||||||
if backend == "nftables":
|
if backend == "nftables":
|
||||||
logger.logInfo('Using NFTables backend')
|
logger.logInfo('Using NFTables backend')
|
||||||
tables = NFTables(chain_name, logger)
|
tables = NFTables(chain_name, logger)
|
||||||
else:
|
else:
|
||||||
logger.logInfo('Using IPTables backend')
|
logger.logInfo('Using IPTables backend')
|
||||||
|
logger.logWarn(
|
||||||
|
"DEPRECATION: iptables-legacy is deprecated and will be removed in future releases. "
|
||||||
|
"Please switch to nftables on your host to ensure complete compatibility."
|
||||||
|
)
|
||||||
|
time.sleep(5)
|
||||||
tables = IPTables(chain_name, logger)
|
tables = IPTables(chain_name, logger)
|
||||||
|
|
||||||
# In case a previous session was killed without cleanup
|
|
||||||
clear()
|
clear()
|
||||||
|
|
||||||
# Reinit MAILCOW chain
|
|
||||||
# Is called before threads start, no locking
|
|
||||||
logger.logInfo("Initializing mailcow netfilter chain")
|
logger.logInfo("Initializing mailcow netfilter chain")
|
||||||
tables.initChainIPv4()
|
tables.initChainIPv4()
|
||||||
tables.initChainIPv6()
|
tables.initChainIPv6()
|
||||||
|
|
||||||
if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE").lower() in ("y", "yes"):
|
if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE", "").lower() in ("y", "yes"):
|
||||||
logger.logInfo(f"Skipping {chain_name} isolation")
|
logger.logInfo(f"Skipping {chain_name} isolation")
|
||||||
else:
|
else:
|
||||||
logger.logInfo(f"Setting {chain_name} isolation")
|
logger.logInfo(f"Setting {chain_name} isolation")
|
||||||
@@ -432,23 +472,28 @@ if __name__ == '__main__':
|
|||||||
try:
|
try:
|
||||||
redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '')
|
redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '')
|
||||||
redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '')
|
redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '')
|
||||||
|
logdebug(
|
||||||
|
"Connecting redis (SLAVEOF_IP:%s, PORT:%s)" % (redis_slaveof_ip, redis_slaveof_port))
|
||||||
if "".__eq__(redis_slaveof_ip):
|
if "".__eq__(redis_slaveof_ip):
|
||||||
r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
|
r = redis.StrictRedis(
|
||||||
|
host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
|
||||||
else:
|
else:
|
||||||
r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS'])
|
r = redis.StrictRedis(
|
||||||
|
host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS'])
|
||||||
r.ping()
|
r.ping()
|
||||||
pubsub = r.pubsub()
|
pubsub = r.pubsub()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('%s - trying again in 3 seconds' % (ex))
|
logdebug(
|
||||||
|
'Redis connection failed: %s - trying again in 3 seconds' % (ex))
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
logger.set_redis(r)
|
logger.set_redis(r)
|
||||||
|
logdebug("Redis connection established, setting up F2B keys")
|
||||||
|
|
||||||
# rename fail2ban to netfilter
|
|
||||||
if r.exists('F2B_LOG'):
|
if r.exists('F2B_LOG'):
|
||||||
|
logdebug("Renaming F2B_LOG to NETFILTER_LOG")
|
||||||
r.rename('F2B_LOG', 'NETFILTER_LOG')
|
r.rename('F2B_LOG', 'NETFILTER_LOG')
|
||||||
# clear bans in redis
|
|
||||||
r.delete('F2B_ACTIVE_BANS')
|
r.delete('F2B_ACTIVE_BANS')
|
||||||
r.delete('F2B_PERM_BANS')
|
r.delete('F2B_PERM_BANS')
|
||||||
|
|
||||||
@@ -463,7 +508,7 @@ if __name__ == '__main__':
|
|||||||
snat_ip = os.getenv('SNAT_TO_SOURCE')
|
snat_ip = os.getenv('SNAT_TO_SOURCE')
|
||||||
snat_ipo = ipaddress.ip_address(snat_ip)
|
snat_ipo = ipaddress.ip_address(snat_ip)
|
||||||
if type(snat_ipo) is ipaddress.IPv4Address:
|
if type(snat_ipo) is ipaddress.IPv4Address:
|
||||||
snat4_thread = Thread(target=snat4,args=(snat_ip,))
|
snat4_thread = Thread(target=snat4, args=(snat_ip,))
|
||||||
snat4_thread.daemon = True
|
snat4_thread.daemon = True
|
||||||
snat4_thread.start()
|
snat4_thread.start()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -499,4 +544,5 @@ if __name__ == '__main__':
|
|||||||
while not quit_now:
|
while not quit_now:
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
sys.exit(exit_code)
|
logdebug("Exiting with code %s" % exit_code)
|
||||||
|
sys.exit(exit_code)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
class Logger:
|
class Logger:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -8,17 +9,28 @@ class Logger:
|
|||||||
def set_redis(self, redis):
|
def set_redis(self, redis):
|
||||||
self.r = redis
|
self.r = redis
|
||||||
|
|
||||||
|
def _format_timestamp(self):
|
||||||
|
# Local time with milliseconds
|
||||||
|
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
def log(self, priority, message):
|
def log(self, priority, message):
|
||||||
tolog = {}
|
# build redis-friendly dict
|
||||||
tolog['time'] = int(round(time.time()))
|
tolog = {
|
||||||
tolog['priority'] = priority
|
'time': int(round(time.time())), # keep raw timestamp for Redis
|
||||||
tolog['message'] = message
|
'priority': priority,
|
||||||
print(message)
|
'message': message
|
||||||
|
}
|
||||||
|
|
||||||
|
# print human-readable message with timestamp
|
||||||
|
ts = self._format_timestamp()
|
||||||
|
print(f"{ts} {priority.upper()}: {message}", flush=True)
|
||||||
|
|
||||||
|
# also push JSON to Redis if connected
|
||||||
if self.r is not None:
|
if self.r is not None:
|
||||||
try:
|
try:
|
||||||
self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False))
|
self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('Failed logging to redis: %s' % (ex))
|
print(f'{ts} WARN: Failed logging to redis: {ex}', flush=True)
|
||||||
|
|
||||||
def logWarn(self, message):
|
def logWarn(self, message):
|
||||||
self.log('warn', message)
|
self.log('warn', message)
|
||||||
@@ -27,4 +39,4 @@ class Logger:
|
|||||||
self.log('crit', message)
|
self.log('crit', message)
|
||||||
|
|
||||||
def logInfo(self, message):
|
def logInfo(self, message):
|
||||||
self.log('info', message)
|
self.log('info', message)
|
||||||
@@ -10,7 +10,7 @@ def includes_conf(env, template_vars):
|
|||||||
server_name_config = f"server_name {template_vars['MAILCOW_HOSTNAME']} autodiscover.* autoconfig.* {' '.join(template_vars['ADDITIONAL_SERVER_NAMES'])};"
|
server_name_config = f"server_name {template_vars['MAILCOW_HOSTNAME']} autodiscover.* autoconfig.* {' '.join(template_vars['ADDITIONAL_SERVER_NAMES'])};"
|
||||||
listen_plain_config = f"listen {template_vars['HTTP_PORT']};"
|
listen_plain_config = f"listen {template_vars['HTTP_PORT']};"
|
||||||
listen_ssl_config = f"listen {template_vars['HTTPS_PORT']};"
|
listen_ssl_config = f"listen {template_vars['HTTPS_PORT']};"
|
||||||
if not template_vars['DISABLE_IPv6']:
|
if template_vars['ENABLE_IPV6']:
|
||||||
listen_plain_config += f"\nlisten [::]:{template_vars['HTTP_PORT']};"
|
listen_plain_config += f"\nlisten [::]:{template_vars['HTTP_PORT']};"
|
||||||
listen_ssl_config += f"\nlisten [::]:{template_vars['HTTPS_PORT']} ssl;"
|
listen_ssl_config += f"\nlisten [::]:{template_vars['HTTPS_PORT']} ssl;"
|
||||||
listen_ssl_config += "\nhttp2 on;"
|
listen_ssl_config += "\nhttp2 on;"
|
||||||
@@ -58,7 +58,7 @@ def prepare_template_vars():
|
|||||||
'SOGOHOST': os.getenv("SOGOHOST", ipv4_network + ".248"),
|
'SOGOHOST': os.getenv("SOGOHOST", ipv4_network + ".248"),
|
||||||
'RSPAMDHOST': os.getenv("RSPAMDHOST", "rspamd-mailcow"),
|
'RSPAMDHOST': os.getenv("RSPAMDHOST", "rspamd-mailcow"),
|
||||||
'PHPFPMHOST': os.getenv("PHPFPMHOST", "php-fpm-mailcow"),
|
'PHPFPMHOST': os.getenv("PHPFPMHOST", "php-fpm-mailcow"),
|
||||||
'DISABLE_IPv6': os.getenv("DISABLE_IPv6", "n").lower() in ("y", "yes"),
|
'ENABLE_IPV6': os.getenv("ENABLE_IPV6", "true").lower() != "false",
|
||||||
'HTTP_REDIRECT': os.getenv("HTTP_REDIRECT", "n").lower() in ("y", "yes"),
|
'HTTP_REDIRECT': os.getenv("HTTP_REDIRECT", "n").lower() in ("y", "yes"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ import time
|
|||||||
import magic
|
import magic
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
skip_olefy = os.getenv('SKIP_OLEFY', '')
|
||||||
|
|
||||||
|
if skip_olefy.lower() in ['yes', 'y']:
|
||||||
|
print("SKIP_OLEFY=y, skipping Olefy...")
|
||||||
|
time.sleep(365 * 24 * 60 * 60)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# merge variables from /etc/olefy.conf and the defaults
|
# merge variables from /etc/olefy.conf and the defaults
|
||||||
olefy_listen_addr_string = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1,::1')
|
olefy_listen_addr_string = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1,::1')
|
||||||
olefy_listen_port = int(os.getenv('OLEFY_BINDPORT', '10050'))
|
olefy_listen_port = int(os.getenv('OLEFY_BINDPORT', '10050'))
|
||||||
@@ -113,7 +120,7 @@ def oletools( stream, tmp_file_name, lid ):
|
|||||||
out = bytes(out.decode('utf-8', 'ignore').replace(' ', ' ').replace('\t', '').replace('\n', '').replace('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)', ''), encoding="utf-8")
|
out = bytes(out.decode('utf-8', 'ignore').replace(' ', ' ').replace('\t', '').replace('\n', '').replace('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)', ''), encoding="utf-8")
|
||||||
failed = False
|
failed = False
|
||||||
if out.__len__() < 30:
|
if out.__len__() < 30:
|
||||||
logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode,
|
logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode,
|
||||||
out.decode('utf-8', 'ignore'), err.decode('utf-8', 'ignore')))
|
out.decode('utf-8', 'ignore'), err.decode('utf-8', 'ignore')))
|
||||||
out = b'[ { "error": "Unhandled error - too short olevba response" } ]'
|
out = b'[ { "error": "Unhandled error - too short olevba response" } ]'
|
||||||
failed = True
|
failed = True
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ FROM php:8.2-fpm-alpine3.21
|
|||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG APCU_PECL_VERSION=5.1.24
|
ARG APCU_PECL_VERSION=5.1.27
|
||||||
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG IMAGICK_PECL_VERSION=3.7.0
|
ARG IMAGICK_PECL_VERSION=3.8.0
|
||||||
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG MAILPARSE_PECL_VERSION=3.1.8
|
ARG MAILPARSE_PECL_VERSION=3.1.9
|
||||||
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||||
ARG MEMCACHED_PECL_VERSION=3.2.0
|
ARG MEMCACHED_PECL_VERSION=3.3.0
|
||||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG REDIS_PECL_VERSION=6.1.0
|
ARG REDIS_PECL_VERSION=6.2.0
|
||||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||||
ARG COMPOSER_VERSION=2.8.6
|
ARG COMPOSER_VERSION=2.8.6
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ DELIMITER //
|
|||||||
CREATE EVENT clean_spamalias
|
CREATE EVENT clean_spamalias
|
||||||
ON SCHEDULE EVERY 1 DAY DO
|
ON SCHEDULE EVERY 1 DAY DO
|
||||||
BEGIN
|
BEGIN
|
||||||
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP();
|
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0;
|
||||||
END;
|
END;
|
||||||
//
|
//
|
||||||
DELIMITER ;
|
DELIMITER ;
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
FROM golang:1.25-bookworm AS builder
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=0 \
|
||||||
|
GO111MODULE=on \
|
||||||
|
NOOPT=1 \
|
||||||
|
VERSION=1.8.22
|
||||||
|
|
||||||
|
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
||||||
|
cd /src/postfix-tlspol && \
|
||||||
|
scripts/build.sh build-only
|
||||||
|
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV LC_ALL=C
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
dirmngr \
|
||||||
|
dnsutils \
|
||||||
|
iputils-ping \
|
||||||
|
sudo \
|
||||||
|
supervisor \
|
||||||
|
redis-tools \
|
||||||
|
syslog-ng \
|
||||||
|
syslog-ng-core \
|
||||||
|
syslog-ng-mod-redis \
|
||||||
|
tzdata \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& touch /etc/default/locale
|
||||||
|
|
||||||
|
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||||
|
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
|
||||||
|
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
|
||||||
|
COPY postfix-tlspol.sh /opt/postfix-tlspol.sh
|
||||||
|
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
|
||||||
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
COPY --from=builder /src/postfix-tlspol/build/postfix-tlspol /usr/local/bin/postfix-tlspol
|
||||||
|
|
||||||
|
RUN chmod +x /opt/postfix-tlspol.sh \
|
||||||
|
/usr/local/sbin/stop-supervisor.sh \
|
||||||
|
/docker-entrypoint.sh
|
||||||
|
RUN rm -rf /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||||
|
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
LOGLVL=info
|
||||||
|
|
||||||
|
if [ ${DEV_MODE} != "n" ]; then
|
||||||
|
echo -e "\e[31mEnabling debug mode\e[0m"
|
||||||
|
set -x
|
||||||
|
LOGLVL=debug
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ ! -d /etc/postfix-tlspol ]] && mkdir -p /etc/postfix-tlspol
|
||||||
|
[[ ! -d /var/lib/postfix-tlspol ]] && mkdir -p /var/lib/postfix-tlspol
|
||||||
|
|
||||||
|
until dig +short mailcow.email > /dev/null; do
|
||||||
|
echo "Waiting for DNS..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Do not attempt to write to slave
|
||||||
|
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||||
|
export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
|
||||||
|
else
|
||||||
|
export REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning"
|
||||||
|
fi
|
||||||
|
|
||||||
|
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
|
||||||
|
echo "Waiting for Redis..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Waiting for Postfix..."
|
||||||
|
until ping postfix -c1 > /dev/null; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo "Postfix OK"
|
||||||
|
|
||||||
|
cat <<EOF > /etc/postfix-tlspol/config.yaml
|
||||||
|
server:
|
||||||
|
address: 0.0.0.0:8642
|
||||||
|
|
||||||
|
log-level: ${LOGLVL}
|
||||||
|
|
||||||
|
prefetch: true
|
||||||
|
|
||||||
|
cache-file: /var/lib/postfix-tlspol/cache.db
|
||||||
|
|
||||||
|
dns:
|
||||||
|
# must support DNSSEC
|
||||||
|
address: 127.0.0.11:53
|
||||||
|
EOF
|
||||||
|
|
||||||
|
/usr/local/bin/postfix-tlspol -config /etc/postfix-tlspol/config.yaml
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
printf "READY\n";
|
||||||
|
|
||||||
|
while read line; do
|
||||||
|
echo "Processing Event: $line" >&2;
|
||||||
|
kill -3 $(cat "/var/run/supervisord.pid")
|
||||||
|
done < /dev/stdin
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
[supervisord]
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
|
||||||
|
[program:syslog-ng]
|
||||||
|
command=/usr/sbin/syslog-ng --foreground --no-caps
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
autostart=true
|
||||||
|
|
||||||
|
[program:postfix-tlspol]
|
||||||
|
startsecs=10
|
||||||
|
autorestart=true
|
||||||
|
command=/opt/postfix-tlspol.sh
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[eventlistener:processes]
|
||||||
|
command=/usr/local/sbin/stop-supervisor.sh
|
||||||
|
events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
@version: 3.38
|
||||||
|
@include "scl.conf"
|
||||||
|
options {
|
||||||
|
chain_hostnames(off);
|
||||||
|
flush_lines(0);
|
||||||
|
use_dns(no);
|
||||||
|
dns_cache(no);
|
||||||
|
use_fqdn(no);
|
||||||
|
owner("root"); group("adm"); perm(0640);
|
||||||
|
stats_freq(0);
|
||||||
|
bad_hostname("^gconfd$");
|
||||||
|
};
|
||||||
|
source s_src {
|
||||||
|
unix-stream("/dev/log");
|
||||||
|
internal();
|
||||||
|
};
|
||||||
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
|
destination d_redis_ui_log {
|
||||||
|
redis(
|
||||||
|
host("`REDIS_SLAVEOF_IP`")
|
||||||
|
persist-name("redis1")
|
||||||
|
port(`REDIS_SLAVEOF_PORT`)
|
||||||
|
auth("`REDISPASS`")
|
||||||
|
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
filter f_mail { facility(mail); };
|
||||||
|
# start
|
||||||
|
# overriding warnings are still displayed when the entrypoint runs its initial check
|
||||||
|
# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs
|
||||||
|
# Some other warnings are ignored
|
||||||
|
filter f_ignore {
|
||||||
|
not match("overriding earlier entry" value("MESSAGE"));
|
||||||
|
not match("TLS SNI from checks.mailcow.email" value("MESSAGE"));
|
||||||
|
not match("no SASL support" value("MESSAGE"));
|
||||||
|
not facility (local0, local1, local2, local3, local4, local5, local6, local7);
|
||||||
|
};
|
||||||
|
# end
|
||||||
|
log {
|
||||||
|
source(s_src);
|
||||||
|
filter(f_ignore);
|
||||||
|
destination(d_stdout);
|
||||||
|
filter(f_mail);
|
||||||
|
destination(d_redis_ui_log);
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
@version: 3.38
|
||||||
|
@include "scl.conf"
|
||||||
|
options {
|
||||||
|
chain_hostnames(off);
|
||||||
|
flush_lines(0);
|
||||||
|
use_dns(no);
|
||||||
|
dns_cache(no);
|
||||||
|
use_fqdn(no);
|
||||||
|
owner("root"); group("adm"); perm(0640);
|
||||||
|
stats_freq(0);
|
||||||
|
bad_hostname("^gconfd$");
|
||||||
|
};
|
||||||
|
source s_src {
|
||||||
|
unix-stream("/dev/log");
|
||||||
|
internal();
|
||||||
|
};
|
||||||
|
destination d_stdout { pipe("/dev/stdout"); };
|
||||||
|
destination d_redis_ui_log {
|
||||||
|
redis(
|
||||||
|
host("redis-mailcow")
|
||||||
|
persist-name("redis1")
|
||||||
|
port(6379)
|
||||||
|
auth("`REDISPASS`")
|
||||||
|
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
filter f_mail { facility(mail); };
|
||||||
|
# start
|
||||||
|
# overriding warnings are still displayed when the entrypoint runs its initial check
|
||||||
|
# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs
|
||||||
|
# Some other warnings are ignored
|
||||||
|
filter f_ignore {
|
||||||
|
not match("overriding earlier entry" value("MESSAGE"));
|
||||||
|
not match("TLS SNI from checks.mailcow.email" value("MESSAGE"));
|
||||||
|
not match("no SASL support" value("MESSAGE"));
|
||||||
|
not facility (local0, local1, local2, local3, local4, local5, local6, local7);
|
||||||
|
};
|
||||||
|
# end
|
||||||
|
log {
|
||||||
|
source(s_src);
|
||||||
|
filter(f_ignore);
|
||||||
|
destination(d_stdout);
|
||||||
|
filter(f_mail);
|
||||||
|
destination(d_redis_ui_log);
|
||||||
|
};
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
FROM debian:bookworm-slim
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ENV LC_ALL C
|
ENV LC_ALL=C
|
||||||
|
|
||||||
RUN dpkg-divert --local --rename --add /sbin/initctl \
|
RUN dpkg-divert --local --rename --add /sbin/initctl \
|
||||||
&& ln -sf /bin/true /sbin/initctl \
|
&& ln -sf /bin/true /sbin/initctl \
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ hosts = unix:/var/run/mysqld/mysqld.sock
|
|||||||
dbname = ${DBNAME}
|
dbname = ${DBNAME}
|
||||||
query = SELECT goto FROM spamalias
|
query = SELECT goto FROM spamalias
|
||||||
WHERE address='%s'
|
WHERE address='%s'
|
||||||
AND validity >= UNIX_TIMESTAMP()
|
AND (validity >= UNIX_TIMESTAMP() OR permanent != 0)
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then
|
if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
FROM debian:bookworm-slim
|
FROM debian:trixie-slim
|
||||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
ARG RSPAMD_VER=rspamd_3.11.1-1~ab0b44951
|
ARG RSPAMD_VER=rspamd_3.14.2-82~90302bc
|
||||||
ARG CODENAME=bookworm
|
ARG CODENAME=trixie
|
||||||
ENV LC_ALL=C
|
ENV LC_ALL=C
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
@@ -14,8 +14,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
dnsutils \
|
dnsutils \
|
||||||
netcat-traditional \
|
netcat-traditional \
|
||||||
wget \
|
wget \
|
||||||
redis-tools \
|
redis-tools \
|
||||||
procps \
|
procps \
|
||||||
nano \
|
nano \
|
||||||
lua-cjson \
|
lua-cjson \
|
||||||
&& arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \
|
&& arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \
|
||||||
|
|||||||
@@ -81,6 +81,29 @@ EOF
|
|||||||
redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF NO ONE
|
redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF NO ONE
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
|
if [[ -f /etc/rspamd/local.d/external_services.conf ]]; then
|
||||||
|
rm /etc/rspamd/local.d/external_services.conf
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ ! -f /etc/rspamd/local.d/external_services.conf ]]; then
|
||||||
|
cat <<EOF > /etc/rspamd/local.d/external_services.conf
|
||||||
|
oletools {
|
||||||
|
# default olefy settings
|
||||||
|
servers = "olefy:10055";
|
||||||
|
# needs to be set explicitly for Rspamd < 1.9.5
|
||||||
|
scan_mime_parts = true;
|
||||||
|
# mime-part regex matching in content-type or filename
|
||||||
|
# block all macros
|
||||||
|
extended = true;
|
||||||
|
max_size = 3145728;
|
||||||
|
timeout = 20.0;
|
||||||
|
retransmits = 1;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Provide additional lua modules
|
# Provide additional lua modules
|
||||||
ln -s /usr/lib/$(uname -m)-linux-gnu/liblua5.1-cjson.so.0.0.0 /usr/lib/rspamd/cjson.so
|
ln -s /usr/lib/$(uname -m)-linux-gnu/liblua5.1-cjson.so.0.0.0 /usr/lib/rspamd/cjson.so
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
|
|||||||
done
|
done
|
||||||
echo "DB schema is ${DBV_NOW}"
|
echo "DB schema is ${DBV_NOW}"
|
||||||
|
|
||||||
|
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
|
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
|
||||||
|
fi
|
||||||
|
|
||||||
# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
|
# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
|
||||||
RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
|
RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
59,65d58
|
60,65d58
|
||||||
< ng-show="::!activeUser.isSuperUser"
|
|
||||||
< var:ng-click="navButtonClick"
|
< var:ng-click="navButtonClick"
|
||||||
< ng-href="/user">
|
< ng-href="/user">
|
||||||
< <md-icon>build</md-icon>
|
< <md-icon>build</md-icon>
|
||||||
< <md-tooltip><var:string label:value="mailcow"/></md-tooltip>
|
< <md-tooltip>mailcow <var:string label:value="Preferences"/></md-tooltip>
|
||||||
< </md-button>
|
< </md-button>
|
||||||
< <md-button class="md-icon-button"
|
< <md-button class="md-icon-button"
|
||||||
83c76
|
83c76
|
||||||
< onclick="document.getElementById('mc_logout').setAttribute('action', '/'); document.getElementById('mc_logout').submit();"
|
< onclick="mc_logout();"
|
||||||
---
|
---
|
||||||
> ng-show="::activeUser.path.logoff.length"
|
> ng-show="::activeUser.path.logoff.length"
|
||||||
85c78
|
85c78
|
||||||
< ng-href="#">
|
< ng-href="#">
|
||||||
---
|
---
|
||||||
> ng-href="{{::activeUser.path.logoff}}">
|
> ng-href="{{::activeUser.path.logoff}}">
|
||||||
89,91d81
|
|
||||||
< <form method="POST" id="mc_logout" action="user">
|
|
||||||
< <input type="hidden" name="logout" value="1">
|
|
||||||
< </form>
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ RUN apk add --update \
|
|||||||
fcgi \
|
fcgi \
|
||||||
openssl \
|
openssl \
|
||||||
nagios-plugins-mysql \
|
nagios-plugins-mysql \
|
||||||
nagios-plugins-dns \
|
|
||||||
nagios-plugins-disk \
|
nagios-plugins-disk \
|
||||||
bind-tools \
|
bind-tools \
|
||||||
redis \
|
redis \
|
||||||
@@ -32,9 +31,11 @@ RUN apk add --update \
|
|||||||
tzdata \
|
tzdata \
|
||||||
whois \
|
whois \
|
||||||
&& curl https://raw.githubusercontent.com/mludvig/smtp-cli/v3.10/smtp-cli -o /smtp-cli \
|
&& curl https://raw.githubusercontent.com/mludvig/smtp-cli/v3.10/smtp-cli -o /smtp-cli \
|
||||||
&& chmod +x smtp-cli
|
&& chmod +x smtp-cli \
|
||||||
|
&& mkdir /usr/lib/mailcow
|
||||||
|
|
||||||
COPY watchdog.sh /watchdog.sh
|
COPY watchdog.sh /watchdog.sh
|
||||||
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
|
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
|
||||||
|
COPY check_dns.sh /usr/lib/mailcow/check_dns.sh
|
||||||
|
|
||||||
CMD ["/watchdog.sh"]
|
CMD ["/watchdog.sh"]
|
||||||
|
|||||||
Executable
+39
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
while getopts "H:s:" opt; do
|
||||||
|
case "$opt" in
|
||||||
|
H) HOST="$OPTARG" ;;
|
||||||
|
s) SERVER="$OPTARG" ;;
|
||||||
|
*) echo "Usage: $0 -H host -s server"; exit 3 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$SERVER" ]; then
|
||||||
|
echo "No DNS Server provided"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$HOST" ]; then
|
||||||
|
echo "No host to test provided"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# run dig and measure the time it takes to run
|
||||||
|
START_TIME=$(date +%s%3N)
|
||||||
|
dig_output=$(dig +short +timeout=2 +tries=1 "$HOST" @"$SERVER" 2>/dev/null)
|
||||||
|
dig_rc=$?
|
||||||
|
dig_output_ips=$(echo "$dig_output" | grep -E '^[0-9.]+$' | sort | paste -sd ',' -)
|
||||||
|
END_TIME=$(date +%s%3N)
|
||||||
|
ELAPSED_TIME=$((END_TIME - START_TIME))
|
||||||
|
|
||||||
|
# validate and perform nagios like output and exit codes
|
||||||
|
if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then
|
||||||
|
echo "Domain $HOST was not found by the server"
|
||||||
|
exit 2
|
||||||
|
elif [ $dig_rc -eq 0 ]; then
|
||||||
|
echo "DNS OK: $ELAPSED_TIME ms response time. $HOST returns $dig_output_ips"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Unknown error"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ "${DEV_MODE}" != "n" ]; then
|
||||||
|
echo -e "\e[31mEnabled Debug Mode\e[0m"
|
||||||
|
set -x
|
||||||
|
fi
|
||||||
|
|
||||||
trap "exit" INT TERM
|
trap "exit" INT TERM
|
||||||
trap "kill 0" EXIT
|
trap "kill 0" EXIT
|
||||||
|
|
||||||
@@ -297,7 +302,7 @@ unbound_checks() {
|
|||||||
touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow
|
touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow
|
||||||
host_ip=$(get_container_ip unbound-mailcow)
|
host_ip=$(get_container_ip unbound-mailcow)
|
||||||
err_c_cur=${err_count}
|
err_c_cur=${err_count}
|
||||||
/usr/lib/nagios/plugins/check_dns -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
/usr/lib/mailcow/check_dns.sh -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
||||||
DNSSEC=$(dig com +dnssec | egrep 'flags:.+ad')
|
DNSSEC=$(dig com +dnssec | egrep 'flags:.+ad')
|
||||||
if [[ -z ${DNSSEC} ]]; then
|
if [[ -z ${DNSSEC} ]]; then
|
||||||
echo "DNSSEC failure" 2>> /tmp/unbound-mailcow 1>&2
|
echo "DNSSEC failure" 2>> /tmp/unbound-mailcow 1>&2
|
||||||
@@ -445,6 +450,31 @@ postfix_checks() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postfix-tlspol_checks() {
|
||||||
|
err_count=0
|
||||||
|
diff_c=0
|
||||||
|
THRESHOLD=${POSTFIX_TLSPOL_THRESHOLD}
|
||||||
|
# Reduce error count by 2 after restarting an unhealthy container
|
||||||
|
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
|
||||||
|
while [ ${err_count} -lt ${THRESHOLD} ]; do
|
||||||
|
touch /tmp/postfix-tlspol-mailcow; echo "$(tail -50 /tmp/postfix-tlspol-mailcow)" > /tmp/postfix-tlspol-mailcow
|
||||||
|
host_ip=$(get_container_ip postfix-tlspol-mailcow)
|
||||||
|
err_c_cur=${err_count}
|
||||||
|
/usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 8642 2>> /tmp/postfix-tlspol-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
|
||||||
|
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
|
||||||
|
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
|
||||||
|
progress "Postfix TLS Policy companion" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
|
||||||
|
if [[ $? == 10 ]]; then
|
||||||
|
diff_c=0
|
||||||
|
sleep 1
|
||||||
|
else
|
||||||
|
diff_c=0
|
||||||
|
sleep $(( ( RANDOM % 60 ) + 20 ))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
clamd_checks() {
|
clamd_checks() {
|
||||||
err_count=0
|
err_count=0
|
||||||
diff_c=0
|
diff_c=0
|
||||||
@@ -922,6 +952,18 @@ PID=$!
|
|||||||
echo "Spawned mailq_checks with PID ${PID}"
|
echo "Spawned mailq_checks with PID ${PID}"
|
||||||
BACKGROUND_TASKS+=(${PID})
|
BACKGROUND_TASKS+=(${PID})
|
||||||
|
|
||||||
|
(
|
||||||
|
while true; do
|
||||||
|
if ! postfix-tlspol_checks; then
|
||||||
|
log_msg "Postfix TLS Policy hit error limit"
|
||||||
|
echo postfix-tlspol-mailcow > /tmp/com_pipe
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
PID=$!
|
||||||
|
echo "Spawned postfix-tlspol_checks with PID ${PID}"
|
||||||
|
BACKGROUND_TASKS+=(${PID})
|
||||||
|
|
||||||
(
|
(
|
||||||
while true; do
|
while true; do
|
||||||
if ! dovecot_checks; then
|
if ! dovecot_checks; then
|
||||||
@@ -994,6 +1036,7 @@ PID=$!
|
|||||||
echo "Spawned cert_checks with PID ${PID}"
|
echo "Spawned cert_checks with PID ${PID}"
|
||||||
BACKGROUND_TASKS+=(${PID})
|
BACKGROUND_TASKS+=(${PID})
|
||||||
|
|
||||||
|
if [[ "${SKIP_OLEFY}" =~ ^([nN][oO]|[nN])+$ ]]; then
|
||||||
(
|
(
|
||||||
while true; do
|
while true; do
|
||||||
if ! olefy_checks; then
|
if ! olefy_checks; then
|
||||||
@@ -1005,6 +1048,7 @@ done
|
|||||||
PID=$!
|
PID=$!
|
||||||
echo "Spawned olefy_checks with PID ${PID}"
|
echo "Spawned olefy_checks with PID ${PID}"
|
||||||
BACKGROUND_TASKS+=(${PID})
|
BACKGROUND_TASKS+=(${PID})
|
||||||
|
fi
|
||||||
|
|
||||||
(
|
(
|
||||||
while true; do
|
while true; do
|
||||||
|
|||||||
@@ -69,36 +69,43 @@ require_once 'functions.acl.inc.php';
|
|||||||
|
|
||||||
$isSOGoRequest = $post['real_rip'] == getenv('IPV4_NETWORK') . '.248';
|
$isSOGoRequest = $post['real_rip'] == getenv('IPV4_NETWORK') . '.248';
|
||||||
$result = false;
|
$result = false;
|
||||||
$protocol = $post['protocol'];
|
|
||||||
if ($isSOGoRequest) {
|
if ($isSOGoRequest) {
|
||||||
$protocol = null;
|
|
||||||
// This is a SOGo Auth request. First check for SSO password.
|
// This is a SOGo Auth request. First check for SSO password.
|
||||||
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
|
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
|
||||||
if ($sogo_sso_pass === $post['password']){
|
if ($sogo_sso_pass === $post['password']){
|
||||||
error_log('MAILCOWAUTH: SOGo SSO auth for user ' . $post['username']);
|
error_log('MAILCOWAUTH: SOGo SSO auth for user ' . $post['username']);
|
||||||
|
set_sasl_log($post['username'], $post['real_rip'], "SOGO");
|
||||||
$result = true;
|
$result = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($result === false){
|
if ($result === false){
|
||||||
$result = apppass_login($post['username'], $post['password'], $protocol, array(
|
// If it's a SOGo Request, don't check for protocol access
|
||||||
|
$service = ($isSOGoRequest) ? false : array($post['service'] => true);
|
||||||
|
$result = apppass_login($post['username'], $post['password'], $service, array(
|
||||||
'is_internal' => true,
|
'is_internal' => true,
|
||||||
'remote_addr' => $post['real_rip']
|
'remote_addr' => $post['real_rip']
|
||||||
));
|
));
|
||||||
if ($result) error_log('MAILCOWAUTH: App auth for user ' . $post['username']);
|
if ($result) {
|
||||||
|
error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
|
||||||
|
set_sasl_log($post['username'], $post['real_rip'], $post['service']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($result === false){
|
if ($result === false){
|
||||||
// Init Identity Provider
|
// Init Identity Provider
|
||||||
$iam_provider = identity_provider('init');
|
$iam_provider = identity_provider('init');
|
||||||
$iam_settings = identity_provider('get');
|
$iam_settings = identity_provider('get');
|
||||||
$result = user_login($post['username'], $post['password'], array('is_internal' => true));
|
$result = user_login($post['username'], $post['password'], array('is_internal' => true, 'service' => $post['service']));
|
||||||
if ($result) error_log('MAILCOWAUTH: User auth for user ' . $post['username']);
|
if ($result) {
|
||||||
|
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
|
||||||
|
set_sasl_log($post['username'], $post['real_rip'], $post['service']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
http_response_code(200); // OK
|
http_response_code(200); // OK
|
||||||
$return['success'] = true;
|
$return['success'] = true;
|
||||||
} else {
|
} else {
|
||||||
error_log("MAILCOWAUTH: Login failed for user " . $post['username']);
|
error_log("MAILCOWAUTH: Login failed for user " . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
|
||||||
http_response_code(401); // Unauthorized
|
http_response_code(401); // Unauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,21 +3,20 @@ function auth_password_verify(request, password)
|
|||||||
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
|
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
|
||||||
end
|
end
|
||||||
|
|
||||||
json = require "cjson"
|
local json = require "cjson"
|
||||||
ltn12 = require "ltn12"
|
local ltn12 = require "ltn12"
|
||||||
https = require "ssl.https"
|
local https = require "ssl.https"
|
||||||
https.TIMEOUT = 5
|
https.TIMEOUT = 30
|
||||||
|
|
||||||
local req = {
|
local req = {
|
||||||
username = request.user,
|
username = request.user,
|
||||||
password = password,
|
password = password,
|
||||||
real_rip = request.real_rip,
|
real_rip = request.real_rip,
|
||||||
protocol = {}
|
service = request.service
|
||||||
}
|
}
|
||||||
req.protocol[request.service] = true
|
|
||||||
local req_json = json.encode(req)
|
local req_json = json.encode(req)
|
||||||
local res = {}
|
local res = {}
|
||||||
|
|
||||||
local b, c = https.request {
|
local b, c = https.request {
|
||||||
method = "POST",
|
method = "POST",
|
||||||
url = "https://nginx:9082",
|
url = "https://nginx:9082",
|
||||||
@@ -29,11 +28,27 @@ function auth_password_verify(request, password)
|
|||||||
sink = ltn12.sink.table(res),
|
sink = ltn12.sink.table(res),
|
||||||
insecure = true
|
insecure = true
|
||||||
}
|
}
|
||||||
local api_response = json.decode(table.concat(res))
|
|
||||||
if api_response.success == true then
|
-- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry.
|
||||||
|
-- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry,
|
||||||
|
-- even if the TTL has expired. Useful to avoid cache eviction during backend issues.
|
||||||
|
if c ~= 200 and c ~= 401 then
|
||||||
|
dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user)
|
||||||
|
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error"
|
||||||
|
end
|
||||||
|
|
||||||
|
local response_str = table.concat(res)
|
||||||
|
local is_response_valid, response_json = pcall(json.decode, response_str)
|
||||||
|
|
||||||
|
if not is_response_valid then
|
||||||
|
dovecot.i_info("Invalid JSON received: " .. response_str)
|
||||||
|
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Invalid response format"
|
||||||
|
end
|
||||||
|
|
||||||
|
if response_json.success == true then
|
||||||
return dovecot.auth.PASSDB_RESULT_OK, ""
|
return dovecot.auth.PASSDB_RESULT_OK, ""
|
||||||
end
|
end
|
||||||
|
|
||||||
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
|
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ mail_shared_explicit_inbox = yes
|
|||||||
mail_prefetch_count = 30
|
mail_prefetch_count = 30
|
||||||
passdb {
|
passdb {
|
||||||
driver = lua
|
driver = lua
|
||||||
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes cache_key=%u:%w
|
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes cache_key=%s:%u:%w
|
||||||
result_success = return-ok
|
result_success = return-ok
|
||||||
result_failure = continue
|
result_failure = continue
|
||||||
result_internalfail = continue
|
result_internalfail = continue
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ events {
|
|||||||
http {
|
http {
|
||||||
include /etc/nginx/mime.types;
|
include /etc/nginx/mime.types;
|
||||||
default_type application/octet-stream;
|
default_type application/octet-stream;
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
@@ -48,13 +49,21 @@ http {
|
|||||||
listen {{ HTTP_PORT }} default_server;
|
listen {{ HTTP_PORT }} default_server;
|
||||||
listen [::]:{{ HTTP_PORT }} default_server;
|
listen [::]:{{ HTTP_PORT }} default_server;
|
||||||
|
|
||||||
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }};
|
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }};
|
||||||
|
|
||||||
if ( $request_uri ~* "%0A|%0D" ) { return 403; }
|
if ( $request_uri ~* "%0A|%0D" ) { return 403; }
|
||||||
location ^~ /.well-known/acme-challenge/ {
|
location ^~ /.well-known/acme-challenge/ {
|
||||||
allow all;
|
allow all;
|
||||||
default_type "text/plain";
|
default_type "text/plain";
|
||||||
}
|
}
|
||||||
|
location ^~ /.well-known/mta-sts.txt {
|
||||||
|
allow all;
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass {{ PHPFPMHOST }}:9002;
|
||||||
|
include /etc/nginx/fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
}
|
||||||
location / {
|
location / {
|
||||||
return 301 https://$host$uri$is_args$args;
|
return 301 https://$host$uri$is_args$args;
|
||||||
}
|
}
|
||||||
@@ -70,7 +79,7 @@ http {
|
|||||||
{%endif%}
|
{%endif%}
|
||||||
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
||||||
|
|
||||||
{% if not DISABLE_IPv6 %}
|
{% if ENABLE_IPV6 %}
|
||||||
{% if not HTTP_REDIRECT %}
|
{% if not HTTP_REDIRECT %}
|
||||||
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
||||||
{%endif%}
|
{%endif%}
|
||||||
@@ -82,7 +91,7 @@ http {
|
|||||||
ssl_certificate /etc/ssl/mail/cert.pem;
|
ssl_certificate /etc/ssl/mail/cert.pem;
|
||||||
ssl_certificate_key /etc/ssl/mail/key.pem;
|
ssl_certificate_key /etc/ssl/mail/key.pem;
|
||||||
|
|
||||||
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.*;
|
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.*;
|
||||||
|
|
||||||
include /etc/nginx/includes/sites-default.conf;
|
include /etc/nginx/includes/sites-default.conf;
|
||||||
}
|
}
|
||||||
@@ -97,7 +106,7 @@ http {
|
|||||||
{%endif%}
|
{%endif%}
|
||||||
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
||||||
|
|
||||||
{% if not DISABLE_IPv6 %}
|
{% if ENABLE_IPV6 %}
|
||||||
{% if not HTTP_REDIRECT %}
|
{% if not HTTP_REDIRECT %}
|
||||||
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
||||||
{%endif%}
|
{%endif%}
|
||||||
@@ -118,7 +127,7 @@ http {
|
|||||||
# rspamd dynmaps:
|
# rspamd dynmaps:
|
||||||
server {
|
server {
|
||||||
listen 8081;
|
listen 8081;
|
||||||
{% if not DISABLE_IPv6 %}
|
{% if ENABLE_IPV6 %}
|
||||||
listen [::]:8081;
|
listen [::]:8081;
|
||||||
{%endif%}
|
{%endif%}
|
||||||
index index.php index.html;
|
index index.php index.html;
|
||||||
@@ -191,7 +200,7 @@ http {
|
|||||||
{%endif%}
|
{%endif%}
|
||||||
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
||||||
|
|
||||||
{% if not DISABLE_IPv6 %}
|
{% if ENABLE_IPV6 %}
|
||||||
{% if not HTTP_REDIRECT %}
|
{% if not HTTP_REDIRECT %}
|
||||||
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
||||||
{%endif%}
|
{%endif%}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ ssl_session_tickets off;
|
|||||||
|
|
||||||
add_header Strict-Transport-Security "max-age=15768000;";
|
add_header Strict-Transport-Security "max-age=15768000;";
|
||||||
add_header X-Content-Type-Options nosniff;
|
add_header X-Content-Type-Options nosniff;
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
|
||||||
add_header X-Robots-Tag none;
|
add_header X-Robots-Tag none;
|
||||||
add_header X-Download-Options noopen;
|
add_header X-Download-Options noopen;
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
@@ -76,6 +75,14 @@ location ^~ /.well-known/acme-challenge/ {
|
|||||||
allow all;
|
allow all;
|
||||||
default_type "text/plain";
|
default_type "text/plain";
|
||||||
}
|
}
|
||||||
|
location ^~ /.well-known/mta-sts.txt {
|
||||||
|
allow all;
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass {{ PHPFPMHOST }}:9002;
|
||||||
|
include /etc/nginx/fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
}
|
||||||
|
|
||||||
rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent;
|
rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent;
|
||||||
rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent;
|
rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent;
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ while (true) {
|
|||||||
logMsg("err", "Could not create user " . $user['email']);
|
logMsg("err", "Could not create user " . $user['email']);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if ($row && intval($iam_settings['periodic_sync']) == 1) {
|
} else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "keycloak") {
|
||||||
if ($mapper_key === false){
|
if ($mapper_key === false){
|
||||||
logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
|
logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ foreach ($response as $user) {
|
|||||||
logMsg("err", "Could not create user " . $user[$iam_settings['username_field']][0]);
|
logMsg("err", "Could not create user " . $user[$iam_settings['username_field']][0]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if ($row && intval($iam_settings['periodic_sync']) == 1) {
|
} else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "ldap") {
|
||||||
if ($mapper_key === false){
|
if ($mapper_key === false){
|
||||||
logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
|
logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
|
; NOTE: Restart phpfpm on ANY manual changes to PHP files!
|
||||||
|
|
||||||
|
; opcache
|
||||||
opcache.enable=1
|
opcache.enable=1
|
||||||
opcache.enable_cli=1
|
opcache.enable_cli=1
|
||||||
opcache.interned_strings_buffer=16
|
opcache.interned_strings_buffer=16
|
||||||
opcache.max_accelerated_files=10000
|
opcache.max_accelerated_files=10000
|
||||||
opcache.memory_consumption=128
|
opcache.memory_consumption=128
|
||||||
opcache.save_comments=1
|
opcache.save_comments=1
|
||||||
opcache.revalidate_freq=1
|
opcache.validate_timestamps=0
|
||||||
|
|
||||||
|
; JIT
|
||||||
|
; Disabled for now due to some PHP segmentation faults observed
|
||||||
|
; in certain environments. Possibly some PHP or PHP extension bug.
|
||||||
|
opcache.jit=disable
|
||||||
|
opcache.jit_buffer_size=0
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ smtp_sasl_auth_enable = yes
|
|||||||
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
|
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
|
||||||
smtp_sasl_security_options =
|
smtp_sasl_security_options =
|
||||||
smtp_sasl_mechanism_filter = plain, login
|
smtp_sasl_mechanism_filter = plain, login
|
||||||
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf
|
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY
|
||||||
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
|
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
|
||||||
mail_name = Postcow
|
mail_name = Postcow
|
||||||
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
|
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
# Whitelist generated by Postwhite v3.4 on Sat Mar 1 00:19:29 UTC 2025
|
# Whitelist generated by Postwhite v3.4 on Mon Dec 1 00:24:43 UTC 2025
|
||||||
# https://github.com/stevejenkins/postwhite/
|
# https://github.com/stevejenkins/postwhite/
|
||||||
# 2000 total rules
|
# 2186 total rules
|
||||||
2a00:1450:4000::/36 permit
|
2a00:1450:4000::/36 permit
|
||||||
2a01:111:f400::/48 permit
|
2a01:111:f400::/48 permit
|
||||||
2a01:111:f403:8000::/50 permit
|
2a01:111:f403:2800::/53 permit
|
||||||
2a01:111:f403:8000::/51 permit
|
2a01:111:f403:8000::/51 permit
|
||||||
2a01:111:f403::/49 permit
|
2a01:111:f403::/49 permit
|
||||||
2a01:111:f403:c000::/51 permit
|
2a01:111:f403:c000::/51 permit
|
||||||
|
2a01:111:f403:d000::/53 permit
|
||||||
2a01:111:f403:f000::/52 permit
|
2a01:111:f403:f000::/52 permit
|
||||||
|
2a01:238:20a:202:5370::1 permit
|
||||||
|
2a01:238:20a:202:5372::1 permit
|
||||||
|
2a01:238:20a:202:5373::1 permit
|
||||||
|
2a01:238:400:101:53::1 permit
|
||||||
|
2a01:238:400:102:53::1 permit
|
||||||
|
2a01:238:400:103:53::1 permit
|
||||||
|
2a01:238:400:301:53::1 permit
|
||||||
|
2a01:238:400:302:53::1 permit
|
||||||
|
2a01:238:400:303:53::1 permit
|
||||||
|
2a01:238:400:470:53::1 permit
|
||||||
|
2a01:238:400:471:53::1 permit
|
||||||
|
2a01:238:400:472:53::1 permit
|
||||||
2a01:b747:3000:200::/56 permit
|
2a01:b747:3000:200::/56 permit
|
||||||
2a01:b747:3001:200::/56 permit
|
2a01:b747:3001:200::/56 permit
|
||||||
2a01:b747:3002:200::/56 permit
|
2a01:b747:3002:200::/56 permit
|
||||||
@@ -16,23 +29,43 @@
|
|||||||
2a01:b747:3005:200::/56 permit
|
2a01:b747:3005:200::/56 permit
|
||||||
2a01:b747:3006:200::/56 permit
|
2a01:b747:3006:200::/56 permit
|
||||||
2a02:a60:0:5::/64 permit
|
2a02:a60:0:5::/64 permit
|
||||||
|
2a0f:f640::/56 permit
|
||||||
2c0f:fb50:4000::/36 permit
|
2c0f:fb50:4000::/36 permit
|
||||||
2.207.151.53 permit
|
2.207.151.53 permit
|
||||||
|
2.207.217.30 permit
|
||||||
|
3.64.237.68 permit
|
||||||
|
3.65.3.180 permit
|
||||||
3.70.123.177 permit
|
3.70.123.177 permit
|
||||||
|
3.72.182.33 permit
|
||||||
|
3.74.81.189 permit
|
||||||
|
3.74.125.228 permit
|
||||||
|
3.75.33.185 permit
|
||||||
3.93.157.0/24 permit
|
3.93.157.0/24 permit
|
||||||
3.94.40.108 permit
|
3.94.40.108 permit
|
||||||
|
3.121.107.214 permit
|
||||||
3.129.120.190 permit
|
3.129.120.190 permit
|
||||||
3.210.190.0/24 permit
|
3.210.190.0/24 permit
|
||||||
|
3.211.80.218 permit
|
||||||
|
3.216.221.67 permit
|
||||||
|
3.221.209.22 permit
|
||||||
8.20.114.31 permit
|
8.20.114.31 permit
|
||||||
8.25.194.0/23 permit
|
8.25.194.0/23 permit
|
||||||
8.25.196.0/23 permit
|
8.25.196.0/23 permit
|
||||||
|
8.36.116.0/24 permit
|
||||||
|
8.39.144.0/24 permit
|
||||||
12.130.86.238 permit
|
12.130.86.238 permit
|
||||||
|
13.107.213.69 permit
|
||||||
|
13.107.246.69 permit
|
||||||
|
13.108.16.0/20 permit
|
||||||
13.110.208.0/21 permit
|
13.110.208.0/21 permit
|
||||||
13.110.209.0/24 permit
|
13.110.209.0/24 permit
|
||||||
13.110.216.0/22 permit
|
13.110.216.0/22 permit
|
||||||
13.110.224.0/20 permit
|
13.110.224.0/20 permit
|
||||||
13.111.0.0/16 permit
|
13.111.0.0/16 permit
|
||||||
13.111.191.0/24 permit
|
13.111.191.0/24 permit
|
||||||
|
13.216.7.111 permit
|
||||||
|
13.216.54.180 permit
|
||||||
|
13.247.164.219 permit
|
||||||
15.200.21.50 permit
|
15.200.21.50 permit
|
||||||
15.200.44.248 permit
|
15.200.44.248 permit
|
||||||
15.200.201.185 permit
|
15.200.201.185 permit
|
||||||
@@ -45,23 +78,26 @@
|
|||||||
18.97.1.184/29 permit
|
18.97.1.184/29 permit
|
||||||
18.97.2.64/26 permit
|
18.97.2.64/26 permit
|
||||||
18.156.89.250 permit
|
18.156.89.250 permit
|
||||||
|
18.156.205.64 permit
|
||||||
|
18.157.70.148 permit
|
||||||
|
18.157.114.255 permit
|
||||||
18.157.243.190 permit
|
18.157.243.190 permit
|
||||||
|
18.158.153.154 permit
|
||||||
18.194.95.56 permit
|
18.194.95.56 permit
|
||||||
|
18.197.217.180 permit
|
||||||
18.198.96.88 permit
|
18.198.96.88 permit
|
||||||
|
18.199.210.3 permit
|
||||||
|
18.207.52.234 permit
|
||||||
18.208.124.128/25 permit
|
18.208.124.128/25 permit
|
||||||
18.216.232.154 permit
|
18.216.232.154 permit
|
||||||
18.235.27.253 permit
|
18.235.27.253 permit
|
||||||
18.236.40.242 permit
|
18.236.40.242 permit
|
||||||
18.236.56.161 permit
|
|
||||||
20.51.6.32/30 permit
|
20.51.6.32/30 permit
|
||||||
20.51.98.61 permit
|
|
||||||
20.52.52.2 permit
|
20.52.52.2 permit
|
||||||
20.52.128.133 permit
|
20.52.128.133 permit
|
||||||
20.59.80.4/30 permit
|
20.59.80.4/30 permit
|
||||||
20.63.210.192/28 permit
|
20.63.210.192/28 permit
|
||||||
20.69.8.108/30 permit
|
20.69.8.108/30 permit
|
||||||
20.70.246.20 permit
|
|
||||||
20.76.201.171 permit
|
|
||||||
20.83.222.104/30 permit
|
20.83.222.104/30 permit
|
||||||
20.88.157.184/30 permit
|
20.88.157.184/30 permit
|
||||||
20.94.180.64/28 permit
|
20.94.180.64/28 permit
|
||||||
@@ -70,14 +106,11 @@
|
|||||||
20.98.194.68/30 permit
|
20.98.194.68/30 permit
|
||||||
20.105.209.76/30 permit
|
20.105.209.76/30 permit
|
||||||
20.107.239.64/30 permit
|
20.107.239.64/30 permit
|
||||||
20.112.250.133 permit
|
|
||||||
20.118.139.208/30 permit
|
20.118.139.208/30 permit
|
||||||
20.141.10.196 permit
|
20.141.10.196 permit
|
||||||
20.185.214.0/27 permit
|
20.185.214.0/27 permit
|
||||||
20.185.214.32/27 permit
|
20.185.214.32/27 permit
|
||||||
20.185.214.64/27 permit
|
20.185.214.64/27 permit
|
||||||
20.231.239.246 permit
|
|
||||||
20.236.44.162 permit
|
|
||||||
23.103.224.0/19 permit
|
23.103.224.0/19 permit
|
||||||
23.249.208.0/20 permit
|
23.249.208.0/20 permit
|
||||||
23.251.224.0/19 permit
|
23.251.224.0/19 permit
|
||||||
@@ -89,6 +122,7 @@
|
|||||||
23.253.183.147 permit
|
23.253.183.147 permit
|
||||||
23.253.183.148 permit
|
23.253.183.148 permit
|
||||||
23.253.183.150 permit
|
23.253.183.150 permit
|
||||||
|
24.110.64.0/18 permit
|
||||||
27.123.204.128/30 permit
|
27.123.204.128/30 permit
|
||||||
27.123.204.132/31 permit
|
27.123.204.132/31 permit
|
||||||
27.123.204.148/30 permit
|
27.123.204.148/30 permit
|
||||||
@@ -101,19 +135,50 @@
|
|||||||
27.123.206.56/29 permit
|
27.123.206.56/29 permit
|
||||||
27.123.206.76/30 permit
|
27.123.206.76/30 permit
|
||||||
27.123.206.80/28 permit
|
27.123.206.80/28 permit
|
||||||
31.25.48.222 permit
|
|
||||||
31.47.251.17 permit
|
31.47.251.17 permit
|
||||||
|
31.186.239.0/24 permit
|
||||||
|
34.2.64.0/22 permit
|
||||||
|
34.2.68.0/23 permit
|
||||||
|
34.2.70.0/23 permit
|
||||||
|
34.2.71.64/26 permit
|
||||||
|
34.2.72.0/22 permit
|
||||||
|
34.2.75.0/26 permit
|
||||||
|
34.2.78.0/23 permit
|
||||||
|
34.2.80.0/23 permit
|
||||||
|
34.2.82.0/23 permit
|
||||||
|
34.2.84.0/24 permit
|
||||||
|
34.2.84.64/26 permit
|
||||||
|
34.2.85.0/24 permit
|
||||||
|
34.2.85.64/26 permit
|
||||||
|
34.2.86.0/23 permit
|
||||||
|
34.2.88.0/23 permit
|
||||||
|
34.2.90.0/23 permit
|
||||||
|
34.2.92.0/23 permit
|
||||||
|
34.2.94.0/23 permit
|
||||||
|
34.70.158.162 permit
|
||||||
|
34.74.74.140 permit
|
||||||
|
34.83.159.189 permit
|
||||||
|
34.141.160.224 permit
|
||||||
|
34.193.58.168 permit
|
||||||
34.195.217.107 permit
|
34.195.217.107 permit
|
||||||
|
34.197.10.50 permit
|
||||||
|
34.197.254.9 permit
|
||||||
|
34.198.94.229 permit
|
||||||
|
34.198.218.121 permit
|
||||||
34.212.163.75 permit
|
34.212.163.75 permit
|
||||||
34.215.104.144 permit
|
34.215.104.144 permit
|
||||||
34.218.116.3 permit
|
34.218.115.239 permit
|
||||||
34.225.212.172 permit
|
34.225.212.172 permit
|
||||||
|
35.83.148.184 permit
|
||||||
|
35.155.198.111 permit
|
||||||
|
35.158.23.94 permit
|
||||||
35.161.32.253 permit
|
35.161.32.253 permit
|
||||||
|
35.162.73.231 permit
|
||||||
35.167.93.243 permit
|
35.167.93.243 permit
|
||||||
|
35.174.145.124 permit
|
||||||
35.176.132.251 permit
|
35.176.132.251 permit
|
||||||
35.190.247.0/24 permit
|
|
||||||
35.191.0.0/16 permit
|
|
||||||
35.205.92.9 permit
|
35.205.92.9 permit
|
||||||
|
35.228.216.85 permit
|
||||||
35.242.169.159 permit
|
35.242.169.159 permit
|
||||||
37.188.97.188 permit
|
37.188.97.188 permit
|
||||||
37.218.248.47 permit
|
37.218.248.47 permit
|
||||||
@@ -128,11 +193,19 @@
|
|||||||
40.233.83.78 permit
|
40.233.83.78 permit
|
||||||
40.233.88.28 permit
|
40.233.88.28 permit
|
||||||
44.206.138.57 permit
|
44.206.138.57 permit
|
||||||
|
44.210.169.44 permit
|
||||||
44.217.45.156 permit
|
44.217.45.156 permit
|
||||||
44.236.56.93 permit
|
44.236.56.93 permit
|
||||||
44.238.220.251 permit
|
44.238.220.251 permit
|
||||||
|
44.245.243.92 permit
|
||||||
|
44.246.1.125 permit
|
||||||
|
44.246.68.102 permit
|
||||||
|
44.246.77.92 permit
|
||||||
45.14.148.0/22 permit
|
45.14.148.0/22 permit
|
||||||
46.19.170.16 permit
|
45.143.132.0/24 permit
|
||||||
|
45.143.133.0/24 permit
|
||||||
|
45.143.134.0/24 permit
|
||||||
|
45.143.135.0/24 permit
|
||||||
46.226.48.0/21 permit
|
46.226.48.0/21 permit
|
||||||
46.228.36.37 permit
|
46.228.36.37 permit
|
||||||
46.228.36.38/31 permit
|
46.228.36.38/31 permit
|
||||||
@@ -183,6 +256,7 @@
|
|||||||
46.243.88.177 permit
|
46.243.88.177 permit
|
||||||
46.243.95.179 permit
|
46.243.95.179 permit
|
||||||
46.243.95.180 permit
|
46.243.95.180 permit
|
||||||
|
50.16.246.183 permit
|
||||||
50.18.45.249 permit
|
50.18.45.249 permit
|
||||||
50.18.121.236 permit
|
50.18.121.236 permit
|
||||||
50.18.121.248 permit
|
50.18.121.248 permit
|
||||||
@@ -196,14 +270,23 @@
|
|||||||
50.56.130.220 permit
|
50.56.130.220 permit
|
||||||
50.56.130.221 permit
|
50.56.130.221 permit
|
||||||
50.56.130.222 permit
|
50.56.130.222 permit
|
||||||
|
50.112.246.219 permit
|
||||||
52.1.14.157 permit
|
52.1.14.157 permit
|
||||||
52.5.230.59 permit
|
52.5.230.59 permit
|
||||||
|
52.12.53.23 permit
|
||||||
|
52.13.214.179 permit
|
||||||
|
52.26.1.71 permit
|
||||||
52.27.5.72 permit
|
52.27.5.72 permit
|
||||||
52.27.28.47 permit
|
52.27.28.47 permit
|
||||||
52.28.63.81 permit
|
52.28.63.81 permit
|
||||||
|
52.28.197.132 permit
|
||||||
|
52.34.181.151 permit
|
||||||
|
52.35.192.45 permit
|
||||||
52.36.138.31 permit
|
52.36.138.31 permit
|
||||||
52.37.142.146 permit
|
52.37.142.146 permit
|
||||||
|
52.42.203.116 permit
|
||||||
52.50.24.208 permit
|
52.50.24.208 permit
|
||||||
|
52.57.120.243 permit
|
||||||
52.58.216.183 permit
|
52.58.216.183 permit
|
||||||
52.59.143.3 permit
|
52.59.143.3 permit
|
||||||
52.60.41.5 permit
|
52.60.41.5 permit
|
||||||
@@ -216,7 +299,6 @@
|
|||||||
52.96.91.34 permit
|
52.96.91.34 permit
|
||||||
52.96.111.82 permit
|
52.96.111.82 permit
|
||||||
52.96.172.98 permit
|
52.96.172.98 permit
|
||||||
52.96.214.50 permit
|
|
||||||
52.96.222.194 permit
|
52.96.222.194 permit
|
||||||
52.96.222.226 permit
|
52.96.222.226 permit
|
||||||
52.96.223.2 permit
|
52.96.223.2 permit
|
||||||
@@ -246,23 +328,23 @@
|
|||||||
54.174.63.0/24 permit
|
54.174.63.0/24 permit
|
||||||
54.186.193.102 permit
|
54.186.193.102 permit
|
||||||
54.191.223.56 permit
|
54.191.223.56 permit
|
||||||
|
54.211.126.101 permit
|
||||||
54.213.20.246 permit
|
54.213.20.246 permit
|
||||||
54.214.39.184 permit
|
54.214.39.184 permit
|
||||||
54.240.0.0/18 permit
|
54.240.0.0/18 permit
|
||||||
54.240.64.0/19 permit
|
54.240.64.0/18 permit
|
||||||
54.240.96.0/19 permit
|
|
||||||
54.241.16.209 permit
|
54.241.16.209 permit
|
||||||
54.244.54.130 permit
|
54.244.54.130 permit
|
||||||
54.244.242.0/24 permit
|
54.244.242.0/24 permit
|
||||||
54.255.61.23 permit
|
54.255.61.23 permit
|
||||||
57.103.64.0/18 permit
|
57.103.64.0/18 permit
|
||||||
|
57.129.93.249 permit
|
||||||
62.13.128.0/24 permit
|
62.13.128.0/24 permit
|
||||||
62.13.129.128/25 permit
|
62.13.129.128/25 permit
|
||||||
62.13.136.0/21 permit
|
62.13.136.0/21 permit
|
||||||
62.13.144.0/21 permit
|
62.13.144.0/21 permit
|
||||||
62.13.152.0/21 permit
|
62.13.152.0/21 permit
|
||||||
62.17.146.128/26 permit
|
62.17.146.128/26 permit
|
||||||
62.179.121.0/24 permit
|
|
||||||
62.201.172.0/27 permit
|
62.201.172.0/27 permit
|
||||||
62.201.172.32/27 permit
|
62.201.172.32/27 permit
|
||||||
62.253.227.114 permit
|
62.253.227.114 permit
|
||||||
@@ -270,6 +352,9 @@
|
|||||||
63.128.21.0/24 permit
|
63.128.21.0/24 permit
|
||||||
63.143.57.128/25 permit
|
63.143.57.128/25 permit
|
||||||
63.143.59.128/25 permit
|
63.143.59.128/25 permit
|
||||||
|
63.176.194.123 permit
|
||||||
|
63.178.132.221 permit
|
||||||
|
63.178.143.178 permit
|
||||||
64.18.0.0/20 permit
|
64.18.0.0/20 permit
|
||||||
64.20.241.45 permit
|
64.20.241.45 permit
|
||||||
64.69.212.0/24 permit
|
64.69.212.0/24 permit
|
||||||
@@ -282,6 +367,7 @@
|
|||||||
64.127.115.252 permit
|
64.127.115.252 permit
|
||||||
64.132.88.0/23 permit
|
64.132.88.0/23 permit
|
||||||
64.132.92.0/24 permit
|
64.132.92.0/24 permit
|
||||||
|
64.181.194.190 permit
|
||||||
64.207.219.7 permit
|
64.207.219.7 permit
|
||||||
64.207.219.8 permit
|
64.207.219.8 permit
|
||||||
64.207.219.9 permit
|
64.207.219.9 permit
|
||||||
@@ -291,9 +377,6 @@
|
|||||||
64.207.219.13 permit
|
64.207.219.13 permit
|
||||||
64.207.219.14 permit
|
64.207.219.14 permit
|
||||||
64.207.219.15 permit
|
64.207.219.15 permit
|
||||||
64.207.219.24 permit
|
|
||||||
64.207.219.25 permit
|
|
||||||
64.207.219.26 permit
|
|
||||||
64.207.219.71 permit
|
64.207.219.71 permit
|
||||||
64.207.219.72 permit
|
64.207.219.72 permit
|
||||||
64.207.219.73 permit
|
64.207.219.73 permit
|
||||||
@@ -303,9 +386,6 @@
|
|||||||
64.207.219.77 permit
|
64.207.219.77 permit
|
||||||
64.207.219.78 permit
|
64.207.219.78 permit
|
||||||
64.207.219.79 permit
|
64.207.219.79 permit
|
||||||
64.207.219.88 permit
|
|
||||||
64.207.219.89 permit
|
|
||||||
64.207.219.90 permit
|
|
||||||
64.207.219.135 permit
|
64.207.219.135 permit
|
||||||
64.207.219.136 permit
|
64.207.219.136 permit
|
||||||
64.207.219.137 permit
|
64.207.219.137 permit
|
||||||
@@ -317,23 +397,15 @@
|
|||||||
64.207.219.143 permit
|
64.207.219.143 permit
|
||||||
64.233.160.0/19 permit
|
64.233.160.0/19 permit
|
||||||
65.52.80.137 permit
|
65.52.80.137 permit
|
||||||
65.54.51.64/26 permit
|
|
||||||
65.54.61.64/26 permit
|
|
||||||
65.54.121.120/29 permit
|
65.54.121.120/29 permit
|
||||||
65.54.190.0/24 permit
|
|
||||||
65.54.241.0/24 permit
|
|
||||||
65.55.29.77 permit
|
65.55.29.77 permit
|
||||||
65.55.33.64/28 permit
|
65.55.33.64/28 permit
|
||||||
65.55.34.0/24 permit
|
|
||||||
65.55.42.224/28 permit
|
65.55.42.224/28 permit
|
||||||
65.55.52.224/27 permit
|
65.55.52.224/27 permit
|
||||||
65.55.78.128/25 permit
|
65.55.78.128/25 permit
|
||||||
65.55.81.48/28 permit
|
65.55.81.48/28 permit
|
||||||
65.55.90.0/24 permit
|
|
||||||
65.55.94.0/25 permit
|
65.55.94.0/25 permit
|
||||||
65.55.111.0/24 permit
|
|
||||||
65.55.113.64/26 permit
|
65.55.113.64/26 permit
|
||||||
65.55.116.0/25 permit
|
|
||||||
65.55.126.0/25 permit
|
65.55.126.0/25 permit
|
||||||
65.55.174.0/25 permit
|
65.55.174.0/25 permit
|
||||||
65.55.178.128/27 permit
|
65.55.178.128/27 permit
|
||||||
@@ -344,7 +416,6 @@
|
|||||||
65.212.180.36 permit
|
65.212.180.36 permit
|
||||||
66.102.0.0/20 permit
|
66.102.0.0/20 permit
|
||||||
66.119.150.192/26 permit
|
66.119.150.192/26 permit
|
||||||
66.162.193.226/31 permit
|
|
||||||
66.163.184.0/24 permit
|
66.163.184.0/24 permit
|
||||||
66.163.185.0/24 permit
|
66.163.185.0/24 permit
|
||||||
66.163.186.0/24 permit
|
66.163.186.0/24 permit
|
||||||
@@ -550,13 +621,11 @@
|
|||||||
74.86.241.250/31 permit
|
74.86.241.250/31 permit
|
||||||
74.112.67.243 permit
|
74.112.67.243 permit
|
||||||
74.125.0.0/16 permit
|
74.125.0.0/16 permit
|
||||||
74.202.227.40 permit
|
|
||||||
74.208.4.200 permit
|
74.208.4.200 permit
|
||||||
74.208.4.201 permit
|
74.208.4.201 permit
|
||||||
74.208.4.220 permit
|
74.208.4.220 permit
|
||||||
74.208.4.221 permit
|
74.208.4.221 permit
|
||||||
74.209.250.0/24 permit
|
74.209.250.0/24 permit
|
||||||
75.2.70.75 permit
|
|
||||||
76.223.128.0/19 permit
|
76.223.128.0/19 permit
|
||||||
76.223.176.0/20 permit
|
76.223.176.0/20 permit
|
||||||
77.238.176.0/24 permit
|
77.238.176.0/24 permit
|
||||||
@@ -579,25 +648,31 @@
|
|||||||
77.238.189.142 permit
|
77.238.189.142 permit
|
||||||
77.238.189.146/31 permit
|
77.238.189.146/31 permit
|
||||||
77.238.189.148/30 permit
|
77.238.189.148/30 permit
|
||||||
|
79.135.106.0/24 permit
|
||||||
|
79.135.107.0/24 permit
|
||||||
|
81.169.146.243 permit
|
||||||
|
81.169.146.245 permit
|
||||||
|
81.169.146.246 permit
|
||||||
81.223.46.0/27 permit
|
81.223.46.0/27 permit
|
||||||
82.165.159.2 permit
|
|
||||||
82.165.159.3 permit
|
|
||||||
82.165.159.4 permit
|
|
||||||
82.165.159.12 permit
|
82.165.159.12 permit
|
||||||
82.165.159.13 permit
|
82.165.159.13 permit
|
||||||
82.165.159.14 permit
|
82.165.159.14 permit
|
||||||
82.165.159.34 permit
|
|
||||||
82.165.159.35 permit
|
|
||||||
82.165.159.40 permit
|
82.165.159.40 permit
|
||||||
82.165.159.41 permit
|
82.165.159.41 permit
|
||||||
82.165.159.42 permit
|
82.165.159.42 permit
|
||||||
82.165.159.45 permit
|
|
||||||
82.165.159.130 permit
|
82.165.159.130 permit
|
||||||
82.165.159.131 permit
|
82.165.159.131 permit
|
||||||
84.116.6.0/23 permit
|
85.9.206.169 permit
|
||||||
84.116.36.0/24 permit
|
85.9.210.45 permit
|
||||||
84.116.50.0/23 permit
|
|
||||||
85.158.136.0/21 permit
|
85.158.136.0/21 permit
|
||||||
|
85.215.255.39 permit
|
||||||
|
85.215.255.40 permit
|
||||||
|
85.215.255.41 permit
|
||||||
|
85.215.255.45 permit
|
||||||
|
85.215.255.46 permit
|
||||||
|
85.215.255.47 permit
|
||||||
|
85.215.255.48 permit
|
||||||
|
85.215.255.49 permit
|
||||||
86.61.88.25 permit
|
86.61.88.25 permit
|
||||||
87.238.80.0/21 permit
|
87.238.80.0/21 permit
|
||||||
87.248.103.12 permit
|
87.248.103.12 permit
|
||||||
@@ -637,12 +712,13 @@
|
|||||||
87.248.117.205 permit
|
87.248.117.205 permit
|
||||||
87.253.232.0/21 permit
|
87.253.232.0/21 permit
|
||||||
89.22.108.0/24 permit
|
89.22.108.0/24 permit
|
||||||
|
91.198.2.0/24 permit
|
||||||
91.211.240.0/22 permit
|
91.211.240.0/22 permit
|
||||||
94.169.2.0/23 permit
|
|
||||||
94.236.119.0/26 permit
|
94.236.119.0/26 permit
|
||||||
94.245.112.0/27 permit
|
94.245.112.0/27 permit
|
||||||
94.245.112.10/31 permit
|
94.245.112.10/31 permit
|
||||||
95.131.104.0/21 permit
|
95.131.104.0/21 permit
|
||||||
|
95.217.114.154 permit
|
||||||
96.43.144.0/20 permit
|
96.43.144.0/20 permit
|
||||||
96.43.144.64/28 permit
|
96.43.144.64/28 permit
|
||||||
96.43.144.64/31 permit
|
96.43.144.64/31 permit
|
||||||
@@ -1133,7 +1209,6 @@
|
|||||||
98.139.245.208/30 permit
|
98.139.245.208/30 permit
|
||||||
98.139.245.212/31 permit
|
98.139.245.212/31 permit
|
||||||
99.78.197.208/28 permit
|
99.78.197.208/28 permit
|
||||||
99.83.190.102 permit
|
|
||||||
103.9.96.0/22 permit
|
103.9.96.0/22 permit
|
||||||
103.28.42.0/24 permit
|
103.28.42.0/24 permit
|
||||||
103.151.192.0/23 permit
|
103.151.192.0/23 permit
|
||||||
@@ -1142,9 +1217,6 @@
|
|||||||
104.43.243.237 permit
|
104.43.243.237 permit
|
||||||
104.44.112.128/25 permit
|
104.44.112.128/25 permit
|
||||||
104.47.0.0/17 permit
|
104.47.0.0/17 permit
|
||||||
104.47.20.0/23 permit
|
|
||||||
104.47.75.0/24 permit
|
|
||||||
104.47.108.0/23 permit
|
|
||||||
104.130.96.0/28 permit
|
104.130.96.0/28 permit
|
||||||
104.130.122.0/23 permit
|
104.130.122.0/23 permit
|
||||||
106.10.144.64/27 permit
|
106.10.144.64/27 permit
|
||||||
@@ -1270,6 +1342,7 @@
|
|||||||
106.50.16.0/28 permit
|
106.50.16.0/28 permit
|
||||||
107.20.18.111 permit
|
107.20.18.111 permit
|
||||||
107.20.210.250 permit
|
107.20.210.250 permit
|
||||||
|
107.22.191.150 permit
|
||||||
108.174.0.0/24 permit
|
108.174.0.0/24 permit
|
||||||
108.174.0.215 permit
|
108.174.0.215 permit
|
||||||
108.174.3.0/24 permit
|
108.174.3.0/24 permit
|
||||||
@@ -1278,9 +1351,8 @@
|
|||||||
108.174.6.215 permit
|
108.174.6.215 permit
|
||||||
108.175.18.45 permit
|
108.175.18.45 permit
|
||||||
108.175.30.45 permit
|
108.175.30.45 permit
|
||||||
108.177.8.0/21 permit
|
|
||||||
108.177.96.0/19 permit
|
|
||||||
108.179.144.0/20 permit
|
108.179.144.0/20 permit
|
||||||
|
109.224.244.0/24 permit
|
||||||
109.237.142.0/24 permit
|
109.237.142.0/24 permit
|
||||||
111.221.23.128/25 permit
|
111.221.23.128/25 permit
|
||||||
111.221.26.0/27 permit
|
111.221.26.0/27 permit
|
||||||
@@ -1331,6 +1403,7 @@
|
|||||||
128.245.248.0/21 permit
|
128.245.248.0/21 permit
|
||||||
129.41.77.70 permit
|
129.41.77.70 permit
|
||||||
129.41.169.249 permit
|
129.41.169.249 permit
|
||||||
|
129.77.16.0/20 permit
|
||||||
129.80.5.164 permit
|
129.80.5.164 permit
|
||||||
129.80.64.36 permit
|
129.80.64.36 permit
|
||||||
129.80.67.121 permit
|
129.80.67.121 permit
|
||||||
@@ -1352,7 +1425,6 @@
|
|||||||
129.213.195.191 permit
|
129.213.195.191 permit
|
||||||
130.61.9.72 permit
|
130.61.9.72 permit
|
||||||
130.162.39.83 permit
|
130.162.39.83 permit
|
||||||
130.211.0.0/22 permit
|
|
||||||
130.248.172.0/24 permit
|
130.248.172.0/24 permit
|
||||||
130.248.173.0/24 permit
|
130.248.173.0/24 permit
|
||||||
131.253.30.0/24 permit
|
131.253.30.0/24 permit
|
||||||
@@ -1361,12 +1433,25 @@
|
|||||||
132.226.26.225 permit
|
132.226.26.225 permit
|
||||||
132.226.49.32 permit
|
132.226.49.32 permit
|
||||||
132.226.56.24 permit
|
132.226.56.24 permit
|
||||||
|
134.128.64.0/19 permit
|
||||||
|
134.128.96.0/19 permit
|
||||||
134.170.27.8 permit
|
134.170.27.8 permit
|
||||||
134.170.113.0/26 permit
|
134.170.113.0/26 permit
|
||||||
134.170.141.64/26 permit
|
134.170.141.64/26 permit
|
||||||
134.170.143.0/24 permit
|
134.170.143.0/24 permit
|
||||||
134.170.174.0/24 permit
|
134.170.174.0/24 permit
|
||||||
135.84.216.0/22 permit
|
135.84.216.0/22 permit
|
||||||
|
136.143.160.0/24 permit
|
||||||
|
136.143.161.0/24 permit
|
||||||
|
136.143.162.0/24 permit
|
||||||
|
136.143.176.0/24 permit
|
||||||
|
136.143.177.0/24 permit
|
||||||
|
136.143.178.49 permit
|
||||||
|
136.143.182.0/23 permit
|
||||||
|
136.143.184.0/24 permit
|
||||||
|
136.143.188.0/24 permit
|
||||||
|
136.143.190.0/23 permit
|
||||||
|
136.146.128.0/20 permit
|
||||||
136.147.128.0/20 permit
|
136.147.128.0/20 permit
|
||||||
136.147.135.0/24 permit
|
136.147.135.0/24 permit
|
||||||
136.147.176.0/20 permit
|
136.147.176.0/20 permit
|
||||||
@@ -1415,6 +1500,7 @@
|
|||||||
146.20.215.0/24 permit
|
146.20.215.0/24 permit
|
||||||
146.20.215.182 permit
|
146.20.215.182 permit
|
||||||
146.88.28.0/24 permit
|
146.88.28.0/24 permit
|
||||||
|
146.148.116.76 permit
|
||||||
147.154.32.0/25 permit
|
147.154.32.0/25 permit
|
||||||
147.243.1.47 permit
|
147.243.1.47 permit
|
||||||
147.243.1.48 permit
|
147.243.1.48 permit
|
||||||
@@ -1424,7 +1510,7 @@
|
|||||||
148.105.0.0/16 permit
|
148.105.0.0/16 permit
|
||||||
148.105.8.0/21 permit
|
148.105.8.0/21 permit
|
||||||
149.72.0.0/16 permit
|
149.72.0.0/16 permit
|
||||||
149.72.223.204 permit
|
149.72.234.184 permit
|
||||||
149.72.248.236 permit
|
149.72.248.236 permit
|
||||||
149.97.173.180 permit
|
149.97.173.180 permit
|
||||||
150.230.98.160 permit
|
150.230.98.160 permit
|
||||||
@@ -1436,9 +1522,6 @@
|
|||||||
155.248.220.138 permit
|
155.248.220.138 permit
|
||||||
155.248.234.149 permit
|
155.248.234.149 permit
|
||||||
155.248.237.141 permit
|
155.248.237.141 permit
|
||||||
157.55.0.192/26 permit
|
|
||||||
157.55.1.128/26 permit
|
|
||||||
157.55.2.0/25 permit
|
|
||||||
157.55.9.128/25 permit
|
157.55.9.128/25 permit
|
||||||
157.55.11.0/25 permit
|
157.55.11.0/25 permit
|
||||||
157.55.49.0/25 permit
|
157.55.49.0/25 permit
|
||||||
@@ -1480,6 +1563,7 @@
|
|||||||
159.183.0.0/16 permit
|
159.183.0.0/16 permit
|
||||||
159.183.68.71 permit
|
159.183.68.71 permit
|
||||||
159.183.79.38 permit
|
159.183.79.38 permit
|
||||||
|
159.183.129.172 permit
|
||||||
160.1.62.192 permit
|
160.1.62.192 permit
|
||||||
161.38.192.0/20 permit
|
161.38.192.0/20 permit
|
||||||
161.38.204.0/22 permit
|
161.38.204.0/22 permit
|
||||||
@@ -1496,7 +1580,10 @@
|
|||||||
163.114.132.120 permit
|
163.114.132.120 permit
|
||||||
163.114.134.16 permit
|
163.114.134.16 permit
|
||||||
163.114.135.16 permit
|
163.114.135.16 permit
|
||||||
|
163.116.128.0/17 permit
|
||||||
|
163.192.116.87 permit
|
||||||
164.152.23.32 permit
|
164.152.23.32 permit
|
||||||
|
164.152.25.241 permit
|
||||||
164.177.132.168/30 permit
|
164.177.132.168/30 permit
|
||||||
166.78.68.0/22 permit
|
166.78.68.0/22 permit
|
||||||
166.78.68.221 permit
|
166.78.68.221 permit
|
||||||
@@ -1522,6 +1609,7 @@
|
|||||||
168.138.5.36 permit
|
168.138.5.36 permit
|
||||||
168.138.73.51 permit
|
168.138.73.51 permit
|
||||||
168.138.77.31 permit
|
168.138.77.31 permit
|
||||||
|
168.138.237.153 permit
|
||||||
168.245.0.0/17 permit
|
168.245.0.0/17 permit
|
||||||
168.245.12.252 permit
|
168.245.12.252 permit
|
||||||
168.245.46.9 permit
|
168.245.46.9 permit
|
||||||
@@ -1531,17 +1619,11 @@
|
|||||||
170.10.132.56/29 permit
|
170.10.132.56/29 permit
|
||||||
170.10.132.64/29 permit
|
170.10.132.64/29 permit
|
||||||
170.10.133.0/24 permit
|
170.10.133.0/24 permit
|
||||||
172.217.0.0/19 permit
|
|
||||||
172.217.32.0/20 permit
|
|
||||||
172.217.128.0/19 permit
|
|
||||||
172.217.160.0/20 permit
|
|
||||||
172.217.192.0/19 permit
|
|
||||||
172.253.56.0/21 permit
|
|
||||||
172.253.112.0/20 permit
|
|
||||||
173.0.84.0/29 permit
|
173.0.84.0/29 permit
|
||||||
173.0.84.224/27 permit
|
173.0.84.224/27 permit
|
||||||
173.0.94.244/30 permit
|
173.0.94.244/30 permit
|
||||||
173.194.0.0/16 permit
|
173.194.0.0/16 permit
|
||||||
|
173.194.0.0/17 permit
|
||||||
173.203.79.182 permit
|
173.203.79.182 permit
|
||||||
173.203.81.39 permit
|
173.203.81.39 permit
|
||||||
173.224.161.128/25 permit
|
173.224.161.128/25 permit
|
||||||
@@ -1565,9 +1647,14 @@
|
|||||||
182.50.78.64/28 permit
|
182.50.78.64/28 permit
|
||||||
183.240.219.64/29 permit
|
183.240.219.64/29 permit
|
||||||
185.4.120.0/22 permit
|
185.4.120.0/22 permit
|
||||||
|
185.11.253.128/27 permit
|
||||||
|
185.11.255.0/24 permit
|
||||||
185.12.80.0/22 permit
|
185.12.80.0/22 permit
|
||||||
185.28.196.0/22 permit
|
185.28.196.0/22 permit
|
||||||
185.58.84.93 permit
|
185.58.84.93 permit
|
||||||
|
185.70.40.0/24 permit
|
||||||
|
185.70.41.0/24 permit
|
||||||
|
185.70.43.0/24 permit
|
||||||
185.80.93.204 permit
|
185.80.93.204 permit
|
||||||
185.80.93.227 permit
|
185.80.93.227 permit
|
||||||
185.80.95.31 permit
|
185.80.95.31 permit
|
||||||
@@ -1575,6 +1662,8 @@
|
|||||||
185.138.56.128/25 permit
|
185.138.56.128/25 permit
|
||||||
185.189.236.0/22 permit
|
185.189.236.0/22 permit
|
||||||
185.211.120.0/22 permit
|
185.211.120.0/22 permit
|
||||||
|
185.233.188.0/23 permit
|
||||||
|
185.233.190.0/23 permit
|
||||||
185.250.236.0/22 permit
|
185.250.236.0/22 permit
|
||||||
185.250.239.148 permit
|
185.250.239.148 permit
|
||||||
185.250.239.168 permit
|
185.250.239.168 permit
|
||||||
@@ -1627,6 +1716,7 @@
|
|||||||
188.125.85.234/31 permit
|
188.125.85.234/31 permit
|
||||||
188.125.85.236/31 permit
|
188.125.85.236/31 permit
|
||||||
188.125.85.238 permit
|
188.125.85.238 permit
|
||||||
|
188.165.51.139 permit
|
||||||
188.172.128.0/20 permit
|
188.172.128.0/20 permit
|
||||||
192.0.64.0/18 permit
|
192.0.64.0/18 permit
|
||||||
192.18.139.154 permit
|
192.18.139.154 permit
|
||||||
@@ -1649,10 +1739,31 @@
|
|||||||
193.109.254.0/23 permit
|
193.109.254.0/23 permit
|
||||||
193.122.128.100 permit
|
193.122.128.100 permit
|
||||||
193.123.56.63 permit
|
193.123.56.63 permit
|
||||||
|
193.142.157.0/24 permit
|
||||||
|
193.142.157.191 permit
|
||||||
|
193.142.157.198 permit
|
||||||
194.19.134.0/25 permit
|
194.19.134.0/25 permit
|
||||||
|
194.25.134.16/28 permit
|
||||||
|
194.25.134.80/28 permit
|
||||||
194.64.234.129 permit
|
194.64.234.129 permit
|
||||||
|
194.97.196.0/24 permit
|
||||||
|
194.97.196.3 permit
|
||||||
|
194.97.196.4 permit
|
||||||
|
194.97.196.11 permit
|
||||||
|
194.97.196.12 permit
|
||||||
|
194.97.204.0/24 permit
|
||||||
|
194.97.204.3 permit
|
||||||
|
194.97.204.4 permit
|
||||||
|
194.97.204.11 permit
|
||||||
|
194.97.204.12 permit
|
||||||
|
194.97.212.0/24 permit
|
||||||
|
194.97.212.3 permit
|
||||||
|
194.97.212.4 permit
|
||||||
|
194.97.212.11 permit
|
||||||
|
194.97.212.12 permit
|
||||||
194.106.220.0/23 permit
|
194.106.220.0/23 permit
|
||||||
194.113.24.0/22 permit
|
194.113.24.0/22 permit
|
||||||
|
194.113.42.0/26 permit
|
||||||
194.154.193.192/27 permit
|
194.154.193.192/27 permit
|
||||||
195.4.92.0/23 permit
|
195.4.92.0/23 permit
|
||||||
195.54.172.0/23 permit
|
195.54.172.0/23 permit
|
||||||
@@ -1666,7 +1777,16 @@
|
|||||||
198.61.254.21 permit
|
198.61.254.21 permit
|
||||||
198.61.254.231 permit
|
198.61.254.231 permit
|
||||||
198.178.234.57 permit
|
198.178.234.57 permit
|
||||||
|
198.202.211.1 permit
|
||||||
198.244.48.0/20 permit
|
198.244.48.0/20 permit
|
||||||
|
198.244.56.107 permit
|
||||||
|
198.244.56.108 permit
|
||||||
|
198.244.56.109 permit
|
||||||
|
198.244.56.111 permit
|
||||||
|
198.244.56.112 permit
|
||||||
|
198.244.56.113 permit
|
||||||
|
198.244.56.114 permit
|
||||||
|
198.244.56.115 permit
|
||||||
198.244.59.30 permit
|
198.244.59.30 permit
|
||||||
198.244.59.33 permit
|
198.244.59.33 permit
|
||||||
198.244.59.35 permit
|
198.244.59.35 permit
|
||||||
@@ -1736,9 +1856,11 @@
|
|||||||
204.92.114.187 permit
|
204.92.114.187 permit
|
||||||
204.92.114.203 permit
|
204.92.114.203 permit
|
||||||
204.92.114.204/31 permit
|
204.92.114.204/31 permit
|
||||||
|
204.216.164.202 permit
|
||||||
204.220.160.0/21 permit
|
204.220.160.0/21 permit
|
||||||
204.220.168.0/21 permit
|
204.220.168.0/21 permit
|
||||||
204.220.176.0/20 permit
|
204.220.176.0/20 permit
|
||||||
|
204.220.181.105 permit
|
||||||
204.232.168.0/24 permit
|
204.232.168.0/24 permit
|
||||||
205.139.110.0/24 permit
|
205.139.110.0/24 permit
|
||||||
205.201.128.0/20 permit
|
205.201.128.0/20 permit
|
||||||
@@ -1766,7 +1888,6 @@
|
|||||||
207.46.52.79 permit
|
207.46.52.79 permit
|
||||||
207.46.58.128/25 permit
|
207.46.58.128/25 permit
|
||||||
207.46.116.128/29 permit
|
207.46.116.128/29 permit
|
||||||
207.46.117.0/24 permit
|
|
||||||
207.46.132.128/27 permit
|
207.46.132.128/27 permit
|
||||||
207.46.198.0/25 permit
|
207.46.198.0/25 permit
|
||||||
207.46.200.0/27 permit
|
207.46.200.0/27 permit
|
||||||
@@ -1813,8 +1934,6 @@
|
|||||||
208.71.42.212/31 permit
|
208.71.42.212/31 permit
|
||||||
208.71.42.214 permit
|
208.71.42.214 permit
|
||||||
208.72.249.240/29 permit
|
208.72.249.240/29 permit
|
||||||
208.74.204.5 permit
|
|
||||||
208.74.204.9 permit
|
|
||||||
208.75.120.0/22 permit
|
208.75.120.0/22 permit
|
||||||
208.76.62.0/24 permit
|
208.76.62.0/24 permit
|
||||||
208.76.63.0/24 permit
|
208.76.63.0/24 permit
|
||||||
@@ -1874,17 +1993,11 @@
|
|||||||
212.82.111.228/31 permit
|
212.82.111.228/31 permit
|
||||||
212.82.111.230 permit
|
212.82.111.230 permit
|
||||||
212.123.28.40 permit
|
212.123.28.40 permit
|
||||||
212.227.15.3 permit
|
212.227.15.7 permit
|
||||||
212.227.15.4 permit
|
212.227.15.8 permit
|
||||||
212.227.15.5 permit
|
|
||||||
212.227.15.6 permit
|
|
||||||
212.227.15.14 permit
|
|
||||||
212.227.15.15 permit
|
212.227.15.15 permit
|
||||||
212.227.15.18 permit
|
212.227.15.18 permit
|
||||||
212.227.15.19 permit
|
212.227.15.19 permit
|
||||||
212.227.15.25 permit
|
|
||||||
212.227.15.26 permit
|
|
||||||
212.227.15.29 permit
|
|
||||||
212.227.15.44 permit
|
212.227.15.44 permit
|
||||||
212.227.15.45 permit
|
212.227.15.45 permit
|
||||||
212.227.15.46 permit
|
212.227.15.46 permit
|
||||||
@@ -1892,23 +2005,32 @@
|
|||||||
212.227.15.50 permit
|
212.227.15.50 permit
|
||||||
212.227.15.52 permit
|
212.227.15.52 permit
|
||||||
212.227.15.53 permit
|
212.227.15.53 permit
|
||||||
212.227.15.54 permit
|
212.227.17.1 permit
|
||||||
212.227.15.55 permit
|
212.227.17.2 permit
|
||||||
212.227.17.11 permit
|
212.227.17.7 permit
|
||||||
212.227.17.12 permit
|
212.227.17.16 permit
|
||||||
212.227.17.18 permit
|
212.227.17.17 permit
|
||||||
212.227.17.19 permit
|
|
||||||
212.227.17.20 permit
|
212.227.17.20 permit
|
||||||
212.227.17.21 permit
|
212.227.17.21 permit
|
||||||
212.227.17.22 permit
|
212.227.17.22 permit
|
||||||
212.227.17.26 permit
|
212.227.17.26 permit
|
||||||
|
212.227.17.27 permit
|
||||||
212.227.17.28 permit
|
212.227.17.28 permit
|
||||||
212.227.17.29 permit
|
212.227.17.29 permit
|
||||||
|
212.227.126.206 permit
|
||||||
|
212.227.126.207 permit
|
||||||
|
212.227.126.208 permit
|
||||||
|
212.227.126.209 permit
|
||||||
|
212.227.126.220 permit
|
||||||
|
212.227.126.221 permit
|
||||||
|
212.227.126.222 permit
|
||||||
|
212.227.126.223 permit
|
||||||
212.227.126.224 permit
|
212.227.126.224 permit
|
||||||
212.227.126.225 permit
|
212.227.126.225 permit
|
||||||
212.227.126.226 permit
|
212.227.126.226 permit
|
||||||
212.227.126.227 permit
|
212.227.126.227 permit
|
||||||
213.46.255.0/24 permit
|
213.95.19.64/27 permit
|
||||||
|
213.95.135.4 permit
|
||||||
213.199.128.139 permit
|
213.199.128.139 permit
|
||||||
213.199.128.145 permit
|
213.199.128.145 permit
|
||||||
213.199.138.181 permit
|
213.199.138.181 permit
|
||||||
@@ -1918,6 +2040,7 @@
|
|||||||
216.17.150.242 permit
|
216.17.150.242 permit
|
||||||
216.17.150.251 permit
|
216.17.150.251 permit
|
||||||
216.24.224.0/20 permit
|
216.24.224.0/20 permit
|
||||||
|
216.27.86.152/31 permit
|
||||||
216.39.60.154/31 permit
|
216.39.60.154/31 permit
|
||||||
216.39.60.156/30 permit
|
216.39.60.156/30 permit
|
||||||
216.39.60.160/30 permit
|
216.39.60.160/30 permit
|
||||||
@@ -1955,6 +2078,8 @@
|
|||||||
216.99.5.68 permit
|
216.99.5.68 permit
|
||||||
216.109.114.32/27 permit
|
216.109.114.32/27 permit
|
||||||
216.109.114.64/29 permit
|
216.109.114.64/29 permit
|
||||||
|
216.113.162.65 permit
|
||||||
|
216.113.163.65 permit
|
||||||
216.128.126.97 permit
|
216.128.126.97 permit
|
||||||
216.136.162.65 permit
|
216.136.162.65 permit
|
||||||
216.136.162.120/29 permit
|
216.136.162.120/29 permit
|
||||||
@@ -1969,8 +2094,6 @@
|
|||||||
216.205.24.0/24 permit
|
216.205.24.0/24 permit
|
||||||
216.221.160.0/19 permit
|
216.221.160.0/19 permit
|
||||||
216.239.32.0/19 permit
|
216.239.32.0/19 permit
|
||||||
217.72.192.77 permit
|
|
||||||
217.72.192.78 permit
|
|
||||||
217.77.141.52 permit
|
217.77.141.52 permit
|
||||||
217.77.141.59 permit
|
217.77.141.59 permit
|
||||||
217.175.194.0/24 permit
|
217.175.194.0/24 permit
|
||||||
@@ -1982,6 +2105,21 @@
|
|||||||
2001:0868:0100:0600::/64 permit
|
2001:0868:0100:0600::/64 permit
|
||||||
2001:4860:4000::/36 permit
|
2001:4860:4000::/36 permit
|
||||||
2001:748:100:40::2:0/112 permit
|
2001:748:100:40::2:0/112 permit
|
||||||
|
2001:748:400:1300::3 permit
|
||||||
|
2001:748:400:1300::4 permit
|
||||||
|
2001:748:400:1301::0/64 permit
|
||||||
|
2001:748:400:1301::3 permit
|
||||||
|
2001:748:400:1301::4 permit
|
||||||
|
2001:748:400:2300::3 permit
|
||||||
|
2001:748:400:2300::4 permit
|
||||||
|
2001:748:400:2301::0/64 permit
|
||||||
|
2001:748:400:2301::3 permit
|
||||||
|
2001:748:400:2301::4 permit
|
||||||
|
2001:748:400:3300::3 permit
|
||||||
|
2001:748:400:3300::4 permit
|
||||||
|
2001:748:400:3301::0/64 permit
|
||||||
|
2001:748:400:3301::3 permit
|
||||||
|
2001:748:400:3301::4 permit
|
||||||
2404:6800:4000::/36 permit
|
2404:6800:4000::/36 permit
|
||||||
2603:1010:3:3::5b permit
|
2603:1010:3:3::5b permit
|
||||||
2603:1020:201:10::10f permit
|
2603:1020:201:10::10f permit
|
||||||
@@ -2001,4 +2139,5 @@
|
|||||||
2620:119:50c0:207::/64 permit
|
2620:119:50c0:207::/64 permit
|
||||||
2620:119:50c0:207::215 permit
|
2620:119:50c0:207::215 permit
|
||||||
2800:3f0:4000::/36 permit
|
2800:3f0:4000::/36 permit
|
||||||
194.25.134.0/24 permit # t-online.de
|
49.12.4.251 permit # checks.mailcow.email
|
||||||
|
2a01:4f8:c17:7906::10 permit # checks.mailcow.email
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
/.+\.guru$/i
|
/.+\.guru$/i
|
||||||
/.+\.icu$/i
|
/.+\.icu$/i
|
||||||
/.+\.id$/i
|
/.+\.id$/i
|
||||||
/.+\.info$/i
|
|
||||||
/.+\.in.net$/i
|
/.+\.in.net$/i
|
||||||
/.+\.ir$/i
|
/.+\.ir$/i
|
||||||
/.+\.jetzt$/i
|
/.+\.jetzt$/i
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ try {
|
|||||||
error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||||
$goto_branch_array = explode(',', $goto_branch);
|
$goto_branch_array = explode(',', $goto_branch);
|
||||||
} else {
|
} else {
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
||||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||||
if ($goto_branch) {
|
if ($goto_branch) {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ function normalize_email($email) {
|
|||||||
$email = explode('@', $email);
|
$email = explode('@', $email);
|
||||||
$email[0] = str_replace('.', '', $email[0]);
|
$email[0] = str_replace('.', '', $email[0]);
|
||||||
$email = implode('@', $email);
|
$email = implode('@', $email);
|
||||||
}
|
}
|
||||||
$gm_alt = "@googlemail.com";
|
$gm_alt = "@googlemail.com";
|
||||||
if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
|
if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
|
||||||
$email = explode('@', $email);
|
$email = explode('@', $email);
|
||||||
@@ -114,7 +114,7 @@ function ucl_rcpts($object, $type) {
|
|||||||
$rcpt[] = str_replace('/', '\/', $row['address']);
|
$rcpt[] = str_replace('/', '\/', $row['address']);
|
||||||
}
|
}
|
||||||
// Aliases by alias domains
|
// Aliases by alias domains
|
||||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
|
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
|
||||||
LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
|
LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
|
||||||
WHERE `mailbox`.`username` = :object");
|
WHERE `mailbox`.`username` = :object");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
@@ -184,7 +184,7 @@ while ($row = array_shift($rows)) {
|
|||||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
||||||
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
|
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
|
||||||
AND `object`= :object");
|
AND `object`= :object");
|
||||||
$stmt->execute(array(':object' => $row['object']));
|
$stmt->execute(array(':object' => $row['object']));
|
||||||
@@ -468,4 +468,36 @@ while ($row = array_shift($rows)) {
|
|||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Start internal aliases
|
||||||
|
|
||||||
|
$stmt = $pdo->query("SELECT `id`, `address`, `domain` FROM `alias` WHERE `active` = '1' AND `internal` = '1'");
|
||||||
|
$aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
while ($alias = array_shift($aliases)) {
|
||||||
|
// build allowed_domains regex and add target domain and alias domains
|
||||||
|
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `active` = '1' AND `target_domain` = :target_domain");
|
||||||
|
$stmt->execute(array(':target_domain' => $alias['domain']));
|
||||||
|
$allowed_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$allowed_domains = array_map(function($item) {
|
||||||
|
return str_replace('.', '\.', $item['alias_domain']);
|
||||||
|
}, $allowed_domains);
|
||||||
|
$allowed_domains[] = str_replace('.', '\.', $alias['domain']);
|
||||||
|
$allowed_domains = implode('|', $allowed_domains);
|
||||||
|
?>
|
||||||
|
internal_alias_<?=$alias['id'];?> {
|
||||||
|
priority = 10;
|
||||||
|
rcpt = "<?=$alias['address'];?>";
|
||||||
|
from = "/^((?!.*@(<?=$allowed_domains;?>)).)*$/";
|
||||||
|
apply "default" {
|
||||||
|
MAILCOW_INTERNAL_ALIAS = 9999.0;
|
||||||
|
}
|
||||||
|
symbols [
|
||||||
|
"MAILCOW_INTERNAL_ALIAS"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ VIRUS_FOUND {
|
|||||||
}
|
}
|
||||||
# Bad policy from free mail providers
|
# Bad policy from free mail providers
|
||||||
FREEMAIL_POLICY_FAILURE {
|
FREEMAIL_POLICY_FAILURE {
|
||||||
expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST& !WHITELISTED_FWD_HOST & -g+:policies";
|
expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST & !WHITELISTED_FWD_HOST & -g+:policies";
|
||||||
score = 16.0;
|
score = 16.0;
|
||||||
}
|
}
|
||||||
# Applies to freemail with undisclosed recipients
|
# Applies to freemail with undisclosed recipients
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
oletools {
|
|
||||||
# default olefy settings
|
|
||||||
servers = "olefy:10055";
|
|
||||||
# needs to be set explicitly for Rspamd < 1.9.5
|
|
||||||
scan_mime_parts = true;
|
|
||||||
# mime-part regex matching in content-type or filename
|
|
||||||
# block all macros
|
|
||||||
extended = true;
|
|
||||||
max_size = 3145728;
|
|
||||||
timeout = 20.0;
|
|
||||||
retransmits = 1;
|
|
||||||
}
|
|
||||||
@@ -102,7 +102,7 @@ rspamd_config:register_symbol({
|
|||||||
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
||||||
if #rcpt_split == 2 then
|
if #rcpt_split == 2 then
|
||||||
if rcpt_split[1] == 'postmaster' then
|
if rcpt_split[1] == 'postmaster' then
|
||||||
task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt')
|
task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt', 'postmaster')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -146,8 +146,171 @@ rspamd_config:register_symbol({
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Helper function to parse IPv6 into 8 segments
|
||||||
|
local function ipv6_to_segments(ip_str)
|
||||||
|
-- Remove zone identifier if present (e.g., %eth0)
|
||||||
|
ip_str = ip_str:gsub("%%.*$", "")
|
||||||
|
|
||||||
|
local segments = {}
|
||||||
|
|
||||||
|
-- Handle :: compression
|
||||||
|
if ip_str:find('::') then
|
||||||
|
local before, after = ip_str:match('^(.*)::(.*)$')
|
||||||
|
before = before or ''
|
||||||
|
after = after or ''
|
||||||
|
|
||||||
|
local before_parts = {}
|
||||||
|
local after_parts = {}
|
||||||
|
|
||||||
|
if before ~= '' then
|
||||||
|
for seg in before:gmatch('[^:]+') do
|
||||||
|
table.insert(before_parts, tonumber(seg, 16) or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if after ~= '' then
|
||||||
|
for seg in after:gmatch('[^:]+') do
|
||||||
|
table.insert(after_parts, tonumber(seg, 16) or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add before segments
|
||||||
|
for _, seg in ipairs(before_parts) do
|
||||||
|
table.insert(segments, seg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add compressed zeros
|
||||||
|
local zeros_needed = 8 - #before_parts - #after_parts
|
||||||
|
for i = 1, zeros_needed do
|
||||||
|
table.insert(segments, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add after segments
|
||||||
|
for _, seg in ipairs(after_parts) do
|
||||||
|
table.insert(segments, seg)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- No compression
|
||||||
|
for seg in ip_str:gmatch('[^:]+') do
|
||||||
|
table.insert(segments, tonumber(seg, 16) or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ensure we have exactly 8 segments
|
||||||
|
while #segments < 8 do
|
||||||
|
table.insert(segments, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
return segments
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Generate all common IPv6 notations
|
||||||
|
local function get_ipv6_variants(ip_str)
|
||||||
|
local variants = {}
|
||||||
|
local seen = {}
|
||||||
|
|
||||||
|
local function add_variant(v)
|
||||||
|
if v and not seen[v] then
|
||||||
|
table.insert(variants, v)
|
||||||
|
seen[v] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- For IPv4, just return the original
|
||||||
|
if not ip_str:find(':') then
|
||||||
|
add_variant(ip_str)
|
||||||
|
return variants
|
||||||
|
end
|
||||||
|
|
||||||
|
local segments = ipv6_to_segments(ip_str)
|
||||||
|
|
||||||
|
-- 1. Fully expanded form (all zeros shown as 0000)
|
||||||
|
local expanded_parts = {}
|
||||||
|
for _, seg in ipairs(segments) do
|
||||||
|
table.insert(expanded_parts, string.format('%04x', seg))
|
||||||
|
end
|
||||||
|
add_variant(table.concat(expanded_parts, ':'))
|
||||||
|
|
||||||
|
-- 2. Standard form (no leading zeros, but all segments present)
|
||||||
|
local standard_parts = {}
|
||||||
|
for _, seg in ipairs(segments) do
|
||||||
|
table.insert(standard_parts, string.format('%x', seg))
|
||||||
|
end
|
||||||
|
add_variant(table.concat(standard_parts, ':'))
|
||||||
|
|
||||||
|
-- 3. Find all possible :: compressions
|
||||||
|
-- RFC 5952: compress the longest run of consecutive zeros
|
||||||
|
-- But we need to check all possibilities since Redis might have any form
|
||||||
|
|
||||||
|
-- Find all zero runs
|
||||||
|
local zero_runs = {}
|
||||||
|
local in_run = false
|
||||||
|
local run_start = 0
|
||||||
|
local run_length = 0
|
||||||
|
|
||||||
|
for i = 1, 8 do
|
||||||
|
if segments[i] == 0 then
|
||||||
|
if not in_run then
|
||||||
|
in_run = true
|
||||||
|
run_start = i
|
||||||
|
run_length = 1
|
||||||
|
else
|
||||||
|
run_length = run_length + 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if in_run then
|
||||||
|
if run_length >= 1 then -- Allow single zero compression too
|
||||||
|
table.insert(zero_runs, {start = run_start, length = run_length})
|
||||||
|
end
|
||||||
|
in_run = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Don't forget the last run
|
||||||
|
if in_run and run_length >= 1 then
|
||||||
|
table.insert(zero_runs, {start = run_start, length = run_length})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Generate variant for each zero run compression
|
||||||
|
for _, run in ipairs(zero_runs) do
|
||||||
|
local parts = {}
|
||||||
|
|
||||||
|
-- Before compression
|
||||||
|
for i = 1, run.start - 1 do
|
||||||
|
table.insert(parts, string.format('%x', segments[i]))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The compression
|
||||||
|
if run.start == 1 then
|
||||||
|
table.insert(parts, '')
|
||||||
|
table.insert(parts, '')
|
||||||
|
elseif run.start + run.length - 1 == 8 then
|
||||||
|
table.insert(parts, '')
|
||||||
|
table.insert(parts, '')
|
||||||
|
else
|
||||||
|
table.insert(parts, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- After compression
|
||||||
|
for i = run.start + run.length, 8 do
|
||||||
|
table.insert(parts, string.format('%x', segments[i]))
|
||||||
|
end
|
||||||
|
|
||||||
|
local compressed = table.concat(parts, ':'):gsub('::+', '::')
|
||||||
|
add_variant(compressed)
|
||||||
|
end
|
||||||
|
|
||||||
|
return variants
|
||||||
|
end
|
||||||
|
|
||||||
local from_ip_string = tostring(ip)
|
local from_ip_string = tostring(ip)
|
||||||
ip_check_table = {from_ip_string}
|
local ip_check_table = {}
|
||||||
|
|
||||||
|
-- Add all variants of the exact IP
|
||||||
|
for _, variant in ipairs(get_ipv6_variants(from_ip_string)) do
|
||||||
|
table.insert(ip_check_table, variant)
|
||||||
|
end
|
||||||
|
|
||||||
local maxbits = 128
|
local maxbits = 128
|
||||||
local minbits = 32
|
local minbits = 32
|
||||||
@@ -155,10 +318,18 @@ rspamd_config:register_symbol({
|
|||||||
maxbits = 32
|
maxbits = 32
|
||||||
minbits = 8
|
minbits = 8
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Add all CIDR notations with variants
|
||||||
for i=maxbits,minbits,-1 do
|
for i=maxbits,minbits,-1 do
|
||||||
local nip = ip:apply_mask(i):to_string() .. "/" .. i
|
local masked_ip = ip:apply_mask(i)
|
||||||
table.insert(ip_check_table, nip)
|
local cidr_base = masked_ip:to_string()
|
||||||
|
|
||||||
|
for _, variant in ipairs(get_ipv6_variants(cidr_base)) do
|
||||||
|
local cidr = variant .. "/" .. i
|
||||||
|
table.insert(ip_check_table, cidr)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function keep_spam_cb(err, data)
|
local function keep_spam_cb(err, data)
|
||||||
if err then
|
if err then
|
||||||
rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
||||||
@@ -166,12 +337,15 @@ rspamd_config:register_symbol({
|
|||||||
else
|
else
|
||||||
for k,v in pairs(data) do
|
for k,v in pairs(data) do
|
||||||
if (v and v ~= userdata and v == '1') then
|
if (v and v ~= userdata and v == '1') then
|
||||||
rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result")
|
rspamd_logger.infox(rspamd_config, "found ip %s (checked as: %s) in keep_spam map, setting pre-result accept", from_ip_string, ip_check_table[k])
|
||||||
task:set_pre_result('accept', 'ip matched with forward hosts')
|
task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam')
|
||||||
|
task:set_flag('no_stat')
|
||||||
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(ip_check_table, 1, 'KEEP_SPAM')
|
table.insert(ip_check_table, 1, 'KEEP_SPAM')
|
||||||
local redis_ret_user = rspamd_redis_make_request(task,
|
local redis_ret_user = rspamd_redis_make_request(task,
|
||||||
redis_params, -- connect params
|
redis_params, -- connect params
|
||||||
@@ -210,6 +384,7 @@ rspamd_config:register_symbol({
|
|||||||
rspamd_config:register_symbol({
|
rspamd_config:register_symbol({
|
||||||
name = 'TAG_MOO',
|
name = 'TAG_MOO',
|
||||||
type = 'postfilter',
|
type = 'postfilter',
|
||||||
|
flags = 'ignore_passthrough',
|
||||||
callback = function(task)
|
callback = function(task)
|
||||||
local util = require("rspamd_util")
|
local util = require("rspamd_util")
|
||||||
local rspamd_logger = require "rspamd_logger"
|
local rspamd_logger = require "rspamd_logger"
|
||||||
@@ -218,9 +393,6 @@ rspamd_config:register_symbol({
|
|||||||
local rcpts = task:get_recipients('smtp')
|
local rcpts = task:get_recipients('smtp')
|
||||||
local lua_util = require "lua_util"
|
local lua_util = require "lua_util"
|
||||||
|
|
||||||
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
|
||||||
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
|
||||||
|
|
||||||
local function remove_moo_tag()
|
local function remove_moo_tag()
|
||||||
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
||||||
if moo_tag_header then
|
if moo_tag_header then
|
||||||
@@ -231,101 +403,149 @@ rspamd_config:register_symbol({
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then
|
-- Check if we have exactly one recipient
|
||||||
local tag = tagged_rcpt[1].options[1]
|
if not (rcpts and #rcpts == 1) then
|
||||||
rspamd_logger.infox("found tag: %s", tag)
|
rspamd_logger.infox("TAG_MOO: not exactly one rcpt (%s), removing moo tag", rcpts and #rcpts or 0)
|
||||||
local action = task:get_metric_action('default')
|
remove_moo_tag()
|
||||||
rspamd_logger.infox("metric action now: %s", action)
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if action ~= 'no action' and action ~= 'greylist' then
|
local rcpt_addr = rcpts[1]['addr']
|
||||||
rspamd_logger.infox("skipping tag handler for action: %s", action)
|
local rcpt_user = rcpts[1]['user']
|
||||||
remove_moo_tag()
|
local rcpt_domain = rcpts[1]['domain']
|
||||||
return true
|
|
||||||
|
-- Check if recipient has a tag (contains '+')
|
||||||
|
local tag = nil
|
||||||
|
if rcpt_user:find('%+') then
|
||||||
|
local base_user, tag_part = rcpt_user:match('^(.-)%+(.+)$')
|
||||||
|
if base_user and tag_part then
|
||||||
|
tag = tag_part
|
||||||
|
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function http_callback(err_message, code, body, headers)
|
if not tag then
|
||||||
if body ~= nil and body ~= "" then
|
rspamd_logger.infox("TAG_MOO: no tag found in recipient %s, removing moo tag", rcpt_addr)
|
||||||
rspamd_logger.infox(rspamd_config, "expanding rcpt to \"%s\"", body)
|
remove_moo_tag()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local function tag_callback_subject(err, data)
|
-- Optional: Check if domain is a mailcow domain
|
||||||
if err or type(data) ~= 'string' then
|
-- When KEEP_SPAM is active, RCPT_MAILCOW_DOMAIN might not be set
|
||||||
rspamd_logger.infox(rspamd_config, "subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err)
|
-- If the mail is being delivered, we can assume it's valid
|
||||||
|
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
||||||
|
if not mailcow_domain then
|
||||||
|
rspamd_logger.infox("TAG_MOO: RCPT_MAILCOW_DOMAIN not set (possibly due to pre-result), proceeding anyway for domain %s", rcpt_domain)
|
||||||
|
end
|
||||||
|
|
||||||
local function tag_callback_subfolder(err, data)
|
local action = task:get_metric_action('default')
|
||||||
if err or type(data) ~= 'string' then
|
rspamd_logger.infox("TAG_MOO: metric action: %s", action)
|
||||||
rspamd_logger.infox(rspamd_config, "subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
|
|
||||||
remove_moo_tag()
|
|
||||||
else
|
|
||||||
rspamd_logger.infox("Add X-Moo-Tag header")
|
|
||||||
task:set_milter_reply({
|
|
||||||
add_headers = {['X-Moo-Tag'] = 'YES'}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
-- Check if we have a pre-result (e.g., from KEEP_SPAM or POSTMASTER_HANDLER)
|
||||||
redis_params, -- connect params
|
local allow_processing = false
|
||||||
body, -- hash key
|
|
||||||
false, -- is write
|
if task.has_pre_result then
|
||||||
tag_callback_subfolder, --callback
|
local has_pre, pre_action = task:has_pre_result()
|
||||||
'HGET', -- command
|
if has_pre then
|
||||||
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
rspamd_logger.infox("TAG_MOO: pre-result detected: %s", tostring(pre_action))
|
||||||
)
|
if pre_action == 'accept' then
|
||||||
if not redis_ret_subfolder then
|
allow_processing = true
|
||||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
rspamd_logger.infox("TAG_MOO: pre-result is accept, will process")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Allow processing for mild actions or when we have pre-result accept
|
||||||
|
if not allow_processing and action ~= 'no action' and action ~= 'greylist' then
|
||||||
|
rspamd_logger.infox("TAG_MOO: skipping tag handler for action: %s", action)
|
||||||
|
remove_moo_tag()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
rspamd_logger.infox("TAG_MOO: processing allowed")
|
||||||
|
|
||||||
|
local function http_callback(err_message, code, body, headers)
|
||||||
|
if body ~= nil and body ~= "" then
|
||||||
|
rspamd_logger.infox(rspamd_config, "TAG_MOO: expanding rcpt to \"%s\"", body)
|
||||||
|
|
||||||
|
local function tag_callback_subject(err, data)
|
||||||
|
if err or type(data) ~= 'string' or data == '' then
|
||||||
|
rspamd_logger.infox(rspamd_config, "TAG_MOO: subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err)
|
||||||
|
|
||||||
|
local function tag_callback_subfolder(err, data)
|
||||||
|
if err or type(data) ~= 'string' or data == '' then
|
||||||
|
rspamd_logger.infox(rspamd_config, "TAG_MOO: subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
|
||||||
remove_moo_tag()
|
remove_moo_tag()
|
||||||
|
else
|
||||||
|
rspamd_logger.infox("TAG_MOO: User wants subfolder tag, adding X-Moo-Tag header")
|
||||||
|
task:set_milter_reply({
|
||||||
|
add_headers = {['X-Moo-Tag'] = 'YES'}
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
|
||||||
rspamd_logger.infox("user wants subject modified for tagged mail")
|
|
||||||
local sbj = task:get_header('Subject')
|
|
||||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
|
||||||
task:set_milter_reply({
|
|
||||||
remove_headers = {
|
|
||||||
['Subject'] = 1,
|
|
||||||
['X-Moo-Tag'] = 0
|
|
||||||
},
|
|
||||||
add_headers = {['Subject'] = new_sbj}
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
local redis_ret_subject = rspamd_redis_make_request(task,
|
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
||||||
redis_params, -- connect params
|
redis_params, -- connect params
|
||||||
body, -- hash key
|
body, -- hash key
|
||||||
false, -- is write
|
false, -- is write
|
||||||
tag_callback_subject, --callback
|
tag_callback_subfolder, --callback
|
||||||
'HGET', -- command
|
'HGET', -- command
|
||||||
{'RCPT_WANTS_SUBJECT_TAG', body} -- arguments
|
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
||||||
)
|
)
|
||||||
if not redis_ret_subject then
|
if not redis_ret_subfolder then
|
||||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt")
|
||||||
remove_moo_tag()
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if rcpts and #rcpts == 1 then
|
|
||||||
for _,rcpt in ipairs(rcpts) do
|
|
||||||
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
|
||||||
if #rcpt_split == 2 then
|
|
||||||
if rcpt_split[1] == 'postmaster' then
|
|
||||||
rspamd_logger.infox(rspamd_config, "not expanding postmaster alias")
|
|
||||||
remove_moo_tag()
|
remove_moo_tag()
|
||||||
else
|
|
||||||
rspamd_http.request({
|
|
||||||
task=task,
|
|
||||||
url='http://nginx:8081/aliasexp.php',
|
|
||||||
body='',
|
|
||||||
callback=http_callback,
|
|
||||||
headers={Rcpt=rcpt['addr']},
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail")
|
||||||
|
local sbj = task:get_header('Subject') or ''
|
||||||
|
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
||||||
|
task:set_milter_reply({
|
||||||
|
remove_headers = {
|
||||||
|
['Subject'] = 1,
|
||||||
|
['X-Moo-Tag'] = 0
|
||||||
|
},
|
||||||
|
add_headers = {['Subject'] = new_sbj}
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local redis_ret_subject = rspamd_redis_make_request(task,
|
||||||
|
redis_params, -- connect params
|
||||||
|
body, -- hash key
|
||||||
|
false, -- is write
|
||||||
|
tag_callback_subject, --callback
|
||||||
|
'HGET', -- command
|
||||||
|
{'RCPT_WANTS_SUBJECT_TAG', body} -- arguments
|
||||||
|
)
|
||||||
|
if not redis_ret_subject then
|
||||||
|
rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt")
|
||||||
|
remove_moo_tag()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
rspamd_logger.infox("TAG_MOO: alias expansion returned empty body")
|
||||||
|
remove_moo_tag()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local rcpt_split = rspamd_str_split(rcpt_addr, '@')
|
||||||
|
if #rcpt_split == 2 then
|
||||||
|
if rcpt_split[1]:match('^postmaster') then
|
||||||
|
rspamd_logger.infox(rspamd_config, "TAG_MOO: not expanding postmaster alias")
|
||||||
|
remove_moo_tag()
|
||||||
|
else
|
||||||
|
rspamd_logger.infox("TAG_MOO: requesting alias expansion for %s", rcpt_addr)
|
||||||
|
rspamd_http.request({
|
||||||
|
task=task,
|
||||||
|
url='http://nginx:8081/aliasexp.php',
|
||||||
|
body='',
|
||||||
|
callback=http_callback,
|
||||||
|
headers={Rcpt=rcpt_addr},
|
||||||
|
})
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
rspamd_logger.infox("TAG_MOO: invalid rcpt format")
|
||||||
remove_moo_tag()
|
remove_moo_tag()
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@@ -335,6 +555,7 @@ rspamd_config:register_symbol({
|
|||||||
rspamd_config:register_symbol({
|
rspamd_config:register_symbol({
|
||||||
name = 'BCC',
|
name = 'BCC',
|
||||||
type = 'postfilter',
|
type = 'postfilter',
|
||||||
|
flags = 'ignore_passthrough',
|
||||||
callback = function(task)
|
callback = function(task)
|
||||||
local util = require("rspamd_util")
|
local util = require("rspamd_util")
|
||||||
local rspamd_http = require "rspamd_http"
|
local rspamd_http = require "rspamd_http"
|
||||||
@@ -363,11 +584,13 @@ rspamd_config:register_symbol({
|
|||||||
local email_content = tostring(task:get_content())
|
local email_content = tostring(task:get_content())
|
||||||
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
||||||
-- send mail
|
-- send mail
|
||||||
|
local from_smtp = task:get_from('smtp')
|
||||||
|
local from_addr = (from_smtp and from_smtp[1] and from_smtp[1].addr) or 'mailer-daemon@localhost'
|
||||||
lua_smtp.sendmail({
|
lua_smtp.sendmail({
|
||||||
task = task,
|
task = task,
|
||||||
host = os.getenv("IPV4_NETWORK") .. '.253',
|
host = os.getenv("IPV4_NETWORK") .. '.253',
|
||||||
port = 591,
|
port = 591,
|
||||||
from = task:get_from(stp)[1].addr,
|
from = from_addr,
|
||||||
recipients = bcc_dest,
|
recipients = bcc_dest,
|
||||||
helo = 'bcc',
|
helo = 'bcc',
|
||||||
timeout = 20,
|
timeout = 20,
|
||||||
@@ -397,27 +620,41 @@ rspamd_config:register_symbol({
|
|||||||
end
|
end
|
||||||
|
|
||||||
local action = task:get_metric_action('default')
|
local action = task:get_metric_action('default')
|
||||||
rspamd_logger.infox("metric action now: %s", action)
|
rspamd_logger.infox("BCC: metric action: %s", action)
|
||||||
|
|
||||||
|
-- Check for pre-result accept (e.g., from KEEP_SPAM)
|
||||||
|
local allow_bcc = false
|
||||||
|
if task.has_pre_result then
|
||||||
|
local has_pre, pre_action = task:has_pre_result()
|
||||||
|
if has_pre and pre_action == 'accept' then
|
||||||
|
allow_bcc = true
|
||||||
|
rspamd_logger.infox("BCC: pre-result accept detected, will send BCC")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Allow BCC for mild actions or when we have pre-result accept
|
||||||
|
if not allow_bcc and action ~= 'no action' and action ~= 'add header' and action ~= 'rewrite subject' then
|
||||||
|
rspamd_logger.infox("BCC: skipping for action: %s", action)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local function rcpt_callback(err_message, code, body, headers)
|
local function rcpt_callback(err_message, code, body, headers)
|
||||||
if err_message == nil and code == 201 and body ~= nil then
|
if err_message == nil and code == 201 and body ~= nil then
|
||||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
rspamd_logger.infox("BCC: sending BCC to %s for rcpt match", body)
|
||||||
send_mail(task, body)
|
send_mail(task, body)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function from_callback(err_message, code, body, headers)
|
local function from_callback(err_message, code, body, headers)
|
||||||
if err_message == nil and code == 201 and body ~= nil then
|
if err_message == nil and code == 201 and body ~= nil then
|
||||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
rspamd_logger.infox("BCC: sending BCC to %s for from match", body)
|
||||||
send_mail(task, body)
|
send_mail(task, body)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if rcpt_table then
|
if rcpt_table then
|
||||||
for _,e in ipairs(rcpt_table) do
|
for _,e in ipairs(rcpt_table) do
|
||||||
rspamd_logger.infox(rspamd_config, "checking bcc for rcpt address %s", e)
|
rspamd_logger.infox(rspamd_config, "BCC: checking bcc for rcpt address %s", e)
|
||||||
rspamd_http.request({
|
rspamd_http.request({
|
||||||
task=task,
|
task=task,
|
||||||
url='http://nginx:8081/bcc.php',
|
url='http://nginx:8081/bcc.php',
|
||||||
@@ -430,7 +667,7 @@ rspamd_config:register_symbol({
|
|||||||
|
|
||||||
if from_table then
|
if from_table then
|
||||||
for _,e in ipairs(from_table) do
|
for _,e in ipairs(from_table) do
|
||||||
rspamd_logger.infox(rspamd_config, "checking bcc for from address %s", e)
|
rspamd_logger.infox(rspamd_config, "BCC: checking bcc for from address %s", e)
|
||||||
rspamd_http.request({
|
rspamd_http.request({
|
||||||
task=task,
|
task=task,
|
||||||
url='http://nginx:8081/bcc.php',
|
url='http://nginx:8081/bcc.php',
|
||||||
@@ -441,7 +678,7 @@ rspamd_config:register_symbol({
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
-- Don't return true to avoid symbol being logged
|
||||||
end,
|
end,
|
||||||
priority = 20
|
priority = 20
|
||||||
})
|
})
|
||||||
@@ -454,12 +691,18 @@ rspamd_config:register_symbol({
|
|||||||
local redis_params = rspamd_parse_redis_server('dyn_rl')
|
local redis_params = rspamd_parse_redis_server('dyn_rl')
|
||||||
local rspamd_logger = require "rspamd_logger"
|
local rspamd_logger = require "rspamd_logger"
|
||||||
local envfrom = task:get_from(1)
|
local envfrom = task:get_from(1)
|
||||||
|
local envrcpt = task:get_recipients(1) or {}
|
||||||
local uname = task:get_user()
|
local uname = task:get_user()
|
||||||
if not envfrom or not uname then
|
if not envfrom or not uname then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local uname = uname:lower()
|
local uname = uname:lower()
|
||||||
|
|
||||||
|
if #envrcpt == 1 and envrcpt[1].addr:lower() == uname then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
|
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
|
||||||
|
|
||||||
local function redis_cb_user(err, data)
|
local function redis_cb_user(err, data)
|
||||||
@@ -544,13 +787,13 @@ rspamd_config:register_symbol({
|
|||||||
-- determine newline type
|
-- determine newline type
|
||||||
local function newline(task)
|
local function newline(task)
|
||||||
local t = task:get_newlines_type()
|
local t = task:get_newlines_type()
|
||||||
|
|
||||||
if t == 'cr' then
|
if t == 'cr' then
|
||||||
return '\r'
|
return '\r'
|
||||||
elseif t == 'lf' then
|
elseif t == 'lf' then
|
||||||
return '\n'
|
return '\n'
|
||||||
end
|
end
|
||||||
|
|
||||||
return '\r\n'
|
return '\r\n'
|
||||||
end
|
end
|
||||||
-- retrieve footer
|
-- retrieve footer
|
||||||
@@ -558,7 +801,7 @@ rspamd_config:register_symbol({
|
|||||||
if err or type(data) ~= 'string' then
|
if err or type(data) ~= 'string' then
|
||||||
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
|
||||||
else
|
else
|
||||||
|
|
||||||
-- parse json string
|
-- parse json string
|
||||||
local footer = cjson.decode(data)
|
local footer = cjson.decode(data)
|
||||||
if not footer then
|
if not footer then
|
||||||
@@ -607,26 +850,30 @@ rspamd_config:register_symbol({
|
|||||||
if footer.plain and footer.plain ~= "" then
|
if footer.plain and footer.plain ~= "" then
|
||||||
footer.plain = lua_util.jinja_template(footer.plain, replacements, true)
|
footer.plain = lua_util.jinja_template(footer.plain, replacements, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add footer
|
-- add footer
|
||||||
local out = {}
|
local out = {}
|
||||||
local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
|
local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
|
||||||
|
|
||||||
local seen_cte
|
local seen_cte
|
||||||
local newline_s = newline(task)
|
local newline_s = newline(task)
|
||||||
|
|
||||||
local function rewrite_ct_cb(name, hdr)
|
local function rewrite_ct_cb(name, hdr)
|
||||||
if rewrite.need_rewrite_ct then
|
if rewrite.need_rewrite_ct then
|
||||||
if name:lower() == 'content-type' then
|
if name:lower() == 'content-type' then
|
||||||
local nct = string.format('%s: %s/%s; charset=utf-8',
|
-- include boundary if present
|
||||||
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
|
local boundary_part = rewrite.new_ct.boundary and
|
||||||
|
string.format('; boundary="%s"', rewrite.new_ct.boundary) or ''
|
||||||
|
local nct = string.format('%s: %s/%s; charset=utf-8%s',
|
||||||
|
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part)
|
||||||
out[#out + 1] = nct
|
out[#out + 1] = nct
|
||||||
-- update Content-Type header
|
-- update Content-Type header (include boundary if present)
|
||||||
task:set_milter_reply({
|
task:set_milter_reply({
|
||||||
remove_headers = {['Content-Type'] = 0},
|
remove_headers = {['Content-Type'] = 0},
|
||||||
})
|
})
|
||||||
task:set_milter_reply({
|
task:set_milter_reply({
|
||||||
add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8', rewrite.new_ct.type, rewrite.new_ct.subtype)}
|
add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8%s',
|
||||||
|
rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part)}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
elseif name:lower() == 'content-transfer-encoding' then
|
elseif name:lower() == 'content-transfer-encoding' then
|
||||||
@@ -645,16 +892,16 @@ rspamd_config:register_symbol({
|
|||||||
end
|
end
|
||||||
out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
|
out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
|
||||||
end
|
end
|
||||||
|
|
||||||
task:headers_foreach(rewrite_ct_cb, {full = true})
|
task:headers_foreach(rewrite_ct_cb, {full = true})
|
||||||
|
|
||||||
if not seen_cte and rewrite.need_rewrite_ct then
|
if not seen_cte and rewrite.need_rewrite_ct then
|
||||||
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
|
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
|
||||||
end
|
end
|
||||||
|
|
||||||
-- End of headers
|
-- End of headers
|
||||||
out[#out + 1] = newline_s
|
out[#out + 1] = newline_s
|
||||||
|
|
||||||
if rewrite.out then
|
if rewrite.out then
|
||||||
for _,o in ipairs(rewrite.out) do
|
for _,o in ipairs(rewrite.out) do
|
||||||
out[#out + 1] = o
|
out[#out + 1] = o
|
||||||
@@ -698,4 +945,4 @@ rspamd_config:register_symbol({
|
|||||||
return true
|
return true
|
||||||
end,
|
end,
|
||||||
priority = 1
|
priority = 1
|
||||||
})
|
})
|
||||||
@@ -182,7 +182,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||||
$goto_branch_array = explode(',', $goto_branch);
|
$goto_branch_array = explode(',', $goto_branch);
|
||||||
} else {
|
} else {
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
||||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||||
if ($goto_branch) {
|
if ($goto_branch) {
|
||||||
@@ -236,6 +236,9 @@ foreach ($rcpt_final_mailboxes as $rcpt_final) {
|
|||||||
':action' => $action,
|
':action' => $action,
|
||||||
':fuzzy_hashes' => $fuzzy
|
':fuzzy_hashes' => $fuzzy
|
||||||
));
|
));
|
||||||
|
$lastId = $pdo->lastInsertId();
|
||||||
|
$stmt_update = $pdo->prepare("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE `id` = :id");
|
||||||
|
$stmt_update->execute(array(':id' => $lastId));
|
||||||
$stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN (
|
$stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN (
|
||||||
SELECT `id`
|
SELECT `id`
|
||||||
FROM (
|
FROM (
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
|
|||||||
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
|
||||||
$goto_branch_array = explode(',', $goto_branch);
|
$goto_branch_array = explode(',', $goto_branch);
|
||||||
} else {
|
} else {
|
||||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
|
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
|
||||||
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
$stmt->execute(array(':domain' => $parsed_goto['domain']));
|
||||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||||
if ($goto_branch) {
|
if ($goto_branch) {
|
||||||
|
|||||||
@@ -5,6 +5,16 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
window.location.href = '/user';
|
window.location.href = '/user';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// logout function
|
||||||
|
function mc_logout() {
|
||||||
|
fetch("/", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
body: "logout=1"
|
||||||
|
}).then(() => window.location.href = '/');
|
||||||
|
}
|
||||||
|
|
||||||
// Custom SOGo JS
|
// Custom SOGo JS
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
SOGoFoldersSendEMailNotifications = YES;
|
SOGoFoldersSendEMailNotifications = YES;
|
||||||
SOGoForwardEnabled = YES;
|
SOGoForwardEnabled = YES;
|
||||||
|
|
||||||
|
// Added with SOGo 5.12 - Allows users to cleanup there maildirectories by deleting mails oder than X
|
||||||
|
SOGoEnableMailCleaning = YES;
|
||||||
|
|
||||||
// Fixes "MODIFICATION_FAILED" error (HTTP 412) in Clients when accepting invitations from external services
|
// Fixes "MODIFICATION_FAILED" error (HTTP 412) in Clients when accepting invitations from external services
|
||||||
SOGoDisableOrganizerEventCheck = YES;
|
SOGoDisableOrganizerEventCheck = YES;
|
||||||
|
|
||||||
@@ -83,6 +86,12 @@
|
|||||||
SOGoMaximumFailedLoginInterval = 900;
|
SOGoMaximumFailedLoginInterval = 900;
|
||||||
SOGoFailedLoginBlockInterval = 900;
|
SOGoFailedLoginBlockInterval = 900;
|
||||||
|
|
||||||
|
// Enable SOGo URL Description for GDPR compliance, this may cause some issues with calendars and contacts. Also uncomment the encryption key below to use it.
|
||||||
|
//SOGoURLEncryptionEnabled = NO;
|
||||||
|
|
||||||
|
// Set a 16 character encryption key for SOGo URL Description, change this to your own value
|
||||||
|
//SOGoURLPathEncryptionKey = "SOGoSuperSecret0";
|
||||||
|
|
||||||
GCSChannelCollectionTimer = 60;
|
GCSChannelCollectionTimer = 60;
|
||||||
GCSChannelExpireAge = 60;
|
GCSChannelExpireAge = 60;
|
||||||
|
|
||||||
@@ -91,7 +100,7 @@
|
|||||||
//SoDebugBaseURL = YES;
|
//SoDebugBaseURL = YES;
|
||||||
//ImapDebugEnabled = YES;
|
//ImapDebugEnabled = YES;
|
||||||
//SOGoEASDebugEnabled = YES;
|
//SOGoEASDebugEnabled = YES;
|
||||||
SOGoEASSearchInBody = YES; // Experimental. Enabled since 2023-10
|
SOGoEASSearchInBody = YES;
|
||||||
//LDAPDebugEnabled = YES;
|
//LDAPDebugEnabled = YES;
|
||||||
//PGDebugEnabled = YES;
|
//PGDebugEnabled = YES;
|
||||||
//MySQL4DebugEnabled = YES;
|
//MySQL4DebugEnabled = YES;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] !=
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
||||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||||
$clamd_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_CLAMD"])) ? false : true;
|
$clamd_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_CLAMD"])) ? false : true;
|
||||||
|
$olefy_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_OLEFY"])) ? false : true;
|
||||||
|
|
||||||
|
|
||||||
if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) {
|
if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) {
|
||||||
@@ -33,6 +34,7 @@ $vmail_df = explode(',', (string)json_decode(docker('post', 'dovecot-mailcow', '
|
|||||||
// containers
|
// containers
|
||||||
$containers_info = (array) docker('info');
|
$containers_info = (array) docker('info');
|
||||||
if ($clamd_status === false) unset($containers_info['clamd-mailcow']);
|
if ($clamd_status === false) unset($containers_info['clamd-mailcow']);
|
||||||
|
if ($olefy_status === false) unset($containers_info['olefy-mailcow']);
|
||||||
ksort($containers_info);
|
ksort($containers_info);
|
||||||
$containers = array();
|
$containers = array();
|
||||||
foreach ($containers_info as $container => $container_info) {
|
foreach ($containers_info as $container => $container_info) {
|
||||||
@@ -77,6 +79,7 @@ $template_data = [
|
|||||||
'gal' => @$_SESSION['gal'],
|
'gal' => @$_SESSION['gal'],
|
||||||
'license_guid' => license('guid'),
|
'license_guid' => license('guid'),
|
||||||
'clamd_status' => $clamd_status,
|
'clamd_status' => $clamd_status,
|
||||||
|
'olefy_status' => $olefy_status,
|
||||||
'containers' => $containers,
|
'containers' => $containers,
|
||||||
'ip_check' => customize('get', 'ip_check'),
|
'ip_check' => customize('get', 'ip_check'),
|
||||||
'lang_admin' => json_encode($lang['admin']),
|
'lang_admin' => json_encode($lang['admin']),
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
|
|||||||
|
|
||||||
$template = 'admin_index.twig';
|
$template = 'admin_index.twig';
|
||||||
$template_data = [
|
$template_data = [
|
||||||
'login_delay' => @$_SESSION['ldelay']
|
'login_delay' => @$_SESSION['ldelay'],
|
||||||
|
'custom_login' => customize('get', 'custom_login'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$js_minifier->add('/web/js/site/index.js');
|
$js_minifier->add('/web/js/site/index.js');
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ $template_data = [
|
|||||||
'logo_specs' => customize('get', 'main_logo_specs'),
|
'logo_specs' => customize('get', 'main_logo_specs'),
|
||||||
'logo_dark_specs' => customize('get', 'main_logo_dark_specs'),
|
'logo_dark_specs' => customize('get', 'main_logo_dark_specs'),
|
||||||
'ip_check' => customize('get', 'ip_check'),
|
'ip_check' => customize('get', 'ip_check'),
|
||||||
|
'custom_login' => customize('get', 'custom_login'),
|
||||||
'password_complexity' => password_complexity('get'),
|
'password_complexity' => password_complexity('get'),
|
||||||
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
||||||
'cors_settings' => $cors_settings,
|
'cors_settings' => $cors_settings,
|
||||||
|
|||||||
@@ -5847,6 +5847,7 @@ paths:
|
|||||||
client_id: "mailcow_client"
|
client_id: "mailcow_client"
|
||||||
client_secret: "*"
|
client_secret: "*"
|
||||||
redirect_url: "https://mail.mailcow.tld"
|
redirect_url: "https://mail.mailcow.tld"
|
||||||
|
redirect_url_extra: ["https://extramail.mailcow.tld"]
|
||||||
version: "26.1.3"
|
version: "26.1.3"
|
||||||
default_template: "Default"
|
default_template: "Default"
|
||||||
mappers:
|
mappers:
|
||||||
@@ -5900,6 +5901,9 @@ paths:
|
|||||||
redirect_url:
|
redirect_url:
|
||||||
description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc.
|
description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc.
|
||||||
type: string
|
type: string
|
||||||
|
redirect_url_extra:
|
||||||
|
description: Additional redirect URLs that OIDC Provider can use after authentication if valid.
|
||||||
|
type: array
|
||||||
version:
|
version:
|
||||||
description: Specifies the Keycloak version. Required if `authsource` is keycloak.
|
description: Specifies the Keycloak version. Required if `authsource` is keycloak.
|
||||||
type: string
|
type: string
|
||||||
@@ -5990,6 +5994,7 @@ paths:
|
|||||||
client_id: "mailcow_client"
|
client_id: "mailcow_client"
|
||||||
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
|
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
|
||||||
redirect_url: "https://mail.mailcow.tld"
|
redirect_url: "https://mail.mailcow.tld"
|
||||||
|
redirect_url_extra: ["https://extramail.mailcow.tld"]
|
||||||
version: "26.1.3"
|
version: "26.1.3"
|
||||||
default_template: "Default"
|
default_template: "Default"
|
||||||
mappers: ["small_mbox", "medium_mbox"]
|
mappers: ["small_mbox", "medium_mbox"]
|
||||||
@@ -6034,6 +6039,7 @@ paths:
|
|||||||
client_id: "mailcow_client"
|
client_id: "mailcow_client"
|
||||||
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
|
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
|
||||||
redirect_url: "https://mail.mailcow.tld"
|
redirect_url: "https://mail.mailcow.tld"
|
||||||
|
redirect_url_extra: ["https://extramail.mailcow.tld"]
|
||||||
client_scopes: "openid profile email mailcow_template"
|
client_scopes: "openid profile email mailcow_template"
|
||||||
default_template: "Default"
|
default_template: "Default"
|
||||||
mappers: ["small_mbox", "medium_mbox"]
|
mappers: ["small_mbox", "medium_mbox"]
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ if (count($records) == 0 || $records[0]['target'] != '') { ?>
|
|||||||
<authentication>password-cleartext</authentication>
|
<authentication>password-cleartext</authentication>
|
||||||
</outgoingServer>
|
</outgoingServer>
|
||||||
|
|
||||||
<enable visiturl="https://<?=$mailcow_hostname; ?><?php if ($port != 443) echo ':'.$port; ?>/admin.php">
|
<enable visiturl="https://<?=$mailcow_hostname; ?><?php if ($port != 443) echo ':'.$port; ?>/admin">
|
||||||
<instruction>If you didn't change the password given to you by the administrator or if you didn't change it in a long time, please consider doing that now.</instruction>
|
<instruction>If you didn't change the password given to you by the administrator or if you didn't change it in a long time, please consider doing that now.</instruction>
|
||||||
<instruction lang="de">Sollten Sie das Ihnen durch den Administrator vergebene Passwort noch nicht geändert haben, empfehlen wir dies nun zu tun. Auch ein altes Passwort sollte aus Sicherheitsgründen geändert werden.</instruction>
|
<instruction lang="de">Sollten Sie das Ihnen durch den Administrator vergebene Passwort noch nicht geändert haben, empfehlen wir dies nun zu tun. Auch ein altes Passwort sollte aus Sicherheitsgründen geändert werden.</instruction>
|
||||||
</enable>
|
</enable>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ if(file_exists('inc/vars.local.inc.php')) {
|
|||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
|
||||||
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
|
||||||
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
|
||||||
$default_autodiscover_config = $autodiscover_config;
|
$default_autodiscover_config = $autodiscover_config;
|
||||||
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
|
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
|
|||||||
$template = 'domainadmin_index.twig';
|
$template = 'domainadmin_index.twig';
|
||||||
$template_data = [
|
$template_data = [
|
||||||
'login_delay' => @$_SESSION['ldelay'],
|
'login_delay' => @$_SESSION['ldelay'],
|
||||||
|
'custom_login' => customize('get', 'custom_login'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$js_minifier->add('/web/js/site/index.js');
|
$js_minifier->add('/web/js/site/index.js');
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
|||||||
$rl = ratelimit('get', 'domain', $domain);
|
$rl = ratelimit('get', 'domain', $domain);
|
||||||
$rlyhosts = relayhost('get');
|
$rlyhosts = relayhost('get');
|
||||||
$domain_footer = mailbox('get', 'domain_wide_footer', $domain);
|
$domain_footer = mailbox('get', 'domain_wide_footer', $domain);
|
||||||
|
$mta_sts = mailbox('get', 'mta_sts', $domain);
|
||||||
|
if (count($mta_sts) == 0) {
|
||||||
|
$mta_sts = false;
|
||||||
|
} elseif (isset($mta_sts['mx'])) {
|
||||||
|
$mta_sts['mx'] = implode(',', $mta_sts['mx']);
|
||||||
|
}
|
||||||
$template = 'edit/domain.twig';
|
$template = 'edit/domain.twig';
|
||||||
$template_data = [
|
$template_data = [
|
||||||
'acl' => $_SESSION['acl'],
|
'acl' => $_SESSION['acl'],
|
||||||
@@ -58,6 +64,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
|||||||
'dkim' => dkim('details', $domain),
|
'dkim' => dkim('details', $domain),
|
||||||
'domain_details' => $result,
|
'domain_details' => $result,
|
||||||
'domain_footer' => $domain_footer,
|
'domain_footer' => $domain_footer,
|
||||||
|
'mta_sts' => $mta_sts,
|
||||||
'mailboxes' => mailbox('get', 'mailboxes', $_GET["domain"]),
|
'mailboxes' => mailbox('get', 'mailboxes', $_GET["domain"]),
|
||||||
'aliases' => mailbox('get', 'aliases', $_GET["domain"], 'address'),
|
'aliases' => mailbox('get', 'aliases', $_GET["domain"], 'address'),
|
||||||
'alias_domains' => mailbox('get', 'alias_domains', $_GET["domain"])
|
'alias_domains' => mailbox('get', 'alias_domains', $_GET["domain"])
|
||||||
@@ -125,6 +132,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
|||||||
'mailbox' => $mailbox,
|
'mailbox' => $mailbox,
|
||||||
'rl' => $rl,
|
'rl' => $rl,
|
||||||
'pushover_data' => $pushover_data,
|
'pushover_data' => $pushover_data,
|
||||||
|
'get_tagging_options' => mailbox('get', 'delimiter_action', $mailbox),
|
||||||
'quarantine_notification' => $quarantine_notification,
|
'quarantine_notification' => $quarantine_notification,
|
||||||
'quarantine_category' => $quarantine_category,
|
'quarantine_category' => $quarantine_category,
|
||||||
'get_tls_policy' => $get_tls_policy,
|
'get_tls_policy' => $get_tls_policy,
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 17 KiB |
@@ -71,6 +71,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
// Init records array
|
// Init records array
|
||||||
$spf_link = '<a href="http://www.open-spf.org/SPF_Record_Syntax/" target="_blank">SPF Record Syntax</a><br />';
|
$spf_link = '<a href="http://www.open-spf.org/SPF_Record_Syntax/" target="_blank">SPF Record Syntax</a><br />';
|
||||||
$dmarc_link = '<a href="https://www.kitterman.com/dmarc/assistant.html" target="_blank">DMARC Assistant</a>';
|
$dmarc_link = '<a href="https://www.kitterman.com/dmarc/assistant.html" target="_blank">DMARC Assistant</a>';
|
||||||
|
$mtasts_report_link = '<a href="https://mxtoolbox.com/dmarc/smtp-tls/how-to-setup-smtp-tls-reports" target="_blank">TLS Report Record Syntax</a>';
|
||||||
|
|
||||||
$records = array();
|
$records = array();
|
||||||
|
|
||||||
@@ -128,6 +129,27 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$mta_sts = mailbox('get', 'mta_sts', $domain);
|
||||||
|
if (count($mta_sts) > 0 && $mta_sts['active'] == 1) {
|
||||||
|
if (!in_array($domain, $alias_domains)) {
|
||||||
|
$records[] = array(
|
||||||
|
'mta-sts.' . $domain,
|
||||||
|
'CNAME',
|
||||||
|
$mailcow_hostname
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$records[] = array(
|
||||||
|
'_mta-sts.' . $domain,
|
||||||
|
'TXT',
|
||||||
|
"v={$mta_sts['version']};id={$mta_sts['id']};",
|
||||||
|
);
|
||||||
|
$records[] = array(
|
||||||
|
'_smtp._tls.' . $domain,
|
||||||
|
'TXT',
|
||||||
|
$mtasts_report_link,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$records[] = array(
|
$records[] = array(
|
||||||
$domain,
|
$domain,
|
||||||
'TXT',
|
'TXT',
|
||||||
@@ -341,15 +363,25 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($currents as &$current) {
|
foreach ($currents as &$current) {
|
||||||
|
if ($current['type'] == "TXT" &&
|
||||||
|
stripos(strtolower($current['txt']), 'v=sts') === 0) {
|
||||||
|
if (strtolower($current[$data_field[$current['type']]]) == strtolower($record[2])) {
|
||||||
|
$state = state_good;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$state = state_nomatch;
|
||||||
|
}
|
||||||
|
$state .= '<br />' . $current[$data_field[$current['type']]];
|
||||||
|
}
|
||||||
if ($current['type'] == 'TXT' &&
|
if ($current['type'] == 'TXT' &&
|
||||||
stripos($current['txt'], 'v=dmarc') === 0 &&
|
stripos($current['txt'], 'v=dmarc') === 0 &&
|
||||||
$record[2] == $dmarc_link) {
|
$record[2] == $dmarc_link) {
|
||||||
$current['txt'] = str_replace(' ', '', $current['txt']);
|
$current['txt'] = str_replace(' ', '', $current['txt']);
|
||||||
$state = $current[$data_field[$current['type']]] . state_optional;
|
$state = $current[$data_field[$current['type']]] . state_optional;
|
||||||
}
|
}
|
||||||
elseif ($current['type'] == 'TXT' &&
|
elseif ($current['type'] == 'TXT' &&
|
||||||
stripos($current['txt'], 'v=spf') === 0 &&
|
stripos($current['txt'], 'v=spf') === 0 &&
|
||||||
$record[2] == $spf_link) {
|
$record[2] == $spf_link) {
|
||||||
$state = state_nomatch;
|
$state = state_nomatch;
|
||||||
$rslt = get_spf_allowed_hosts($record[0], true);
|
$rslt = get_spf_allowed_hosts($record[0], true);
|
||||||
if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
|
if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
|
||||||
@@ -358,8 +390,8 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
$state .= '<br />' . $current[$data_field[$current['type']]] . state_optional;
|
$state .= '<br />' . $current[$data_field[$current['type']]] . state_optional;
|
||||||
}
|
}
|
||||||
elseif ($current['type'] == 'TXT' &&
|
elseif ($current['type'] == 'TXT' &&
|
||||||
stripos($current['txt'], 'v=dkim') === 0 &&
|
stripos($current['txt'], 'v=dkim') === 0 &&
|
||||||
stripos($record[2], 'v=dkim') === 0) {
|
stripos($record[2], 'v=dkim') === 0) {
|
||||||
preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $current[$data_field[$current['type']]], $dkim_matches_current);
|
preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $current[$data_field[$current['type']]], $dkim_matches_current);
|
||||||
preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $record[2], $dkim_matches_good);
|
preg_match('/v=DKIM1;.*k=rsa;.*p=([^;]*).*/i', $record[2], $dkim_matches_good);
|
||||||
if ($dkim_matches_current[1] == $dkim_matches_good[1]) {
|
if ($dkim_matches_current[1] == $dkim_matches_good[1]) {
|
||||||
@@ -367,7 +399,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif ($current['type'] != 'TXT' &&
|
elseif ($current['type'] != 'TXT' &&
|
||||||
isset($data_field[$current['type']]) && $state != state_good) {
|
isset($data_field[$current['type']]) && $state != state_good) {
|
||||||
$state = state_nomatch;
|
$state = state_nomatch;
|
||||||
if ($current[$data_field[$current['type']]] == $record[2]) {
|
if ($current[$data_field[$current['type']]] == $record[2]) {
|
||||||
$state = state_good;
|
$state = state_good;
|
||||||
|
|||||||
+19
-17
@@ -26,23 +26,25 @@ if (is_array($alertbox_log_parser)) {
|
|||||||
|
|
||||||
// map tfa details for twig
|
// map tfa details for twig
|
||||||
$pending_tfa_authmechs = [];
|
$pending_tfa_authmechs = [];
|
||||||
foreach($_SESSION['pending_tfa_methods'] as $authdata){
|
if (array_key_exists('pending_tfa_methods', $_SESSION)) {
|
||||||
$pending_tfa_authmechs[$authdata['authmech']] = false;
|
foreach($_SESSION['pending_tfa_methods'] as $authdata){
|
||||||
}
|
$pending_tfa_authmechs[$authdata['authmech']] = false;
|
||||||
if (isset($pending_tfa_authmechs['webauthn'])) {
|
}
|
||||||
$pending_tfa_authmechs['webauthn'] = true;
|
if (isset($pending_tfa_authmechs['webauthn'])) {
|
||||||
}
|
$pending_tfa_authmechs['webauthn'] = true;
|
||||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
}
|
||||||
&& isset($pending_tfa_authmechs['yubi_otp'])) {
|
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||||
$pending_tfa_authmechs['yubi_otp'] = true;
|
&& isset($pending_tfa_authmechs['yubi_otp'])) {
|
||||||
}
|
$pending_tfa_authmechs['yubi_otp'] = true;
|
||||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
}
|
||||||
&& !isset($pending_tfa_authmechs['yubi_otp'])
|
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||||
&& isset($pending_tfa_authmechs['totp'])) {
|
&& !isset($pending_tfa_authmechs['yubi_otp'])
|
||||||
$pending_tfa_authmechs['totp'] = true;
|
&& isset($pending_tfa_authmechs['totp'])) {
|
||||||
}
|
$pending_tfa_authmechs['totp'] = true;
|
||||||
if (isset($pending_tfa_authmechs['u2f'])) {
|
}
|
||||||
$pending_tfa_authmechs['u2f'] = true;
|
if (isset($pending_tfa_authmechs['u2f'])) {
|
||||||
|
$pending_tfa_authmechs['u2f'] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// globals
|
// globals
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
function app_passwd($_action, $_data = null) {
|
function app_passwd($_action, $_data = null) {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
global $lang;
|
global $lang;
|
||||||
$_data_log = $_data;
|
$_data_log = $_data;
|
||||||
!isset($_data_log['app_passwd']) ?: $_data_log['app_passwd'] = '*';
|
!isset($_data_log['app_passwd']) ?: $_data_log['app_passwd'] = '*';
|
||||||
!isset($_data_log['app_passwd2']) ?: $_data_log['app_passwd2'] = '*';
|
!isset($_data_log['app_passwd2']) ?: $_data_log['app_passwd2'] = '*';
|
||||||
@@ -43,20 +43,7 @@ function app_passwd($_action, $_data = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
if (password_check($password, $password2) !== true) {
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
|
||||||
'msg' => 'password_complexity'
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ($password != $password2) {
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
|
||||||
'msg' => 'password_mismatch'
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$password_hashed = hash_password($password);
|
$password_hashed = hash_password($password);
|
||||||
@@ -88,15 +75,15 @@ function app_passwd($_action, $_data = null) {
|
|||||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||||
'msg' => 'app_passwd_added'
|
'msg' => 'app_passwd_added'
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'edit':
|
case 'edit':
|
||||||
$ids = (array)$_data['id'];
|
$ids = (array)$_data['id'];
|
||||||
foreach ($ids as $id) {
|
foreach ($ids as $id) {
|
||||||
$is_now = app_passwd('details', $id);
|
$is_now = app_passwd('details', $id);
|
||||||
if (!empty($is_now)) {
|
if (!empty($is_now)) {
|
||||||
$app_name = (!empty($_data['app_name'])) ? $_data['app_name'] : $is_now['name'];
|
$app_name = (!empty($_data['app_name'])) ? $_data['app_name'] : $is_now['name'];
|
||||||
$password = (!empty($_data['password'])) ? $_data['password'] : null;
|
$password = (!empty($_data['app_passwd'])) ? $_data['app_passwd'] : null;
|
||||||
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
|
$password2 = (!empty($_data['app_passwd2'])) ? $_data['app_passwd2'] : null;
|
||||||
if (isset($_data['protocols'])) {
|
if (isset($_data['protocols'])) {
|
||||||
$protocols = (array)$_data['protocols'];
|
$protocols = (array)$_data['protocols'];
|
||||||
$imap_access = (in_array('imap_access', $protocols)) ? 1 : 0;
|
$imap_access = (in_array('imap_access', $protocols)) ? 1 : 0;
|
||||||
@@ -126,20 +113,7 @@ function app_passwd($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
$app_name = htmlspecialchars(trim($app_name));
|
$app_name = htmlspecialchars(trim($app_name));
|
||||||
if (!empty($password) && !empty($password2)) {
|
if (!empty($password) && !empty($password2)) {
|
||||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
if (password_check($password, $password2) !== true) {
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
|
||||||
'msg' => 'password_complexity'
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ($password != $password2) {
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
|
||||||
'msg' => 'password_mismatch'
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$password_hashed = hash_password($password);
|
$password_hashed = hash_password($password);
|
||||||
@@ -182,7 +156,7 @@ function app_passwd($_action, $_data = null) {
|
|||||||
'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids)))
|
'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
$ids = (array)$_data['id'];
|
$ids = (array)$_data['id'];
|
||||||
foreach ($ids as $id) {
|
foreach ($ids as $id) {
|
||||||
@@ -213,19 +187,17 @@ function app_passwd($_action, $_data = null) {
|
|||||||
'msg' => array('app_passwd_removed', htmlspecialchars($id))
|
'msg' => array('app_passwd_removed', htmlspecialchars($id))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'get':
|
case 'get':
|
||||||
$app_passwds = array();
|
$app_passwds = array();
|
||||||
$stmt = $pdo->prepare("SELECT `id`, `name` FROM `app_passwd` WHERE `mailbox` = :username");
|
$stmt = $pdo->prepare("SELECT `id`, `name` FROM `app_passwd` WHERE `mailbox` = :username");
|
||||||
$stmt->execute(array(':username' => $username));
|
$stmt->execute(array(':username' => $username));
|
||||||
$app_passwds = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$app_passwds = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
return $app_passwds;
|
return $app_passwds;
|
||||||
break;
|
break;
|
||||||
case 'details':
|
case 'details':
|
||||||
$app_passwd_data = array();
|
$app_passwd_data = array();
|
||||||
$stmt = $pdo->prepare("SELECT *
|
$stmt = $pdo->prepare("SELECT * FROM `app_passwd` WHERE `id` = :id");
|
||||||
FROM `app_passwd`
|
|
||||||
WHERE `id` = :id");
|
|
||||||
$stmt->execute(array(':id' => $_data));
|
$stmt->execute(array(':id' => $_data));
|
||||||
$app_passwd_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
$app_passwd_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (empty($app_passwd_data)) {
|
if (empty($app_passwd_data)) {
|
||||||
@@ -237,6 +209,6 @@ function app_passwd($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
$app_passwd_data['name'] = htmlspecialchars(trim($app_passwd_data['name']));
|
$app_passwd_data['name'] = htmlspecialchars(trim($app_passwd_data['name']));
|
||||||
return $app_passwd_data;
|
return $app_passwd_data;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,25 +9,52 @@ function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
|
|||||||
// Try validate admin
|
// Try validate admin
|
||||||
if (!isset($role) || $role == "admin") {
|
if (!isset($role) || $role == "admin") {
|
||||||
$result = admin_login($user, $pass);
|
$result = admin_login($user, $pass);
|
||||||
if ($result !== false) return $result;
|
if ($result !== false){
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try validate domain admin
|
// Try validate domain admin
|
||||||
if (!isset($role) || $role == "domain_admin") {
|
if (!isset($role) || $role == "domain_admin") {
|
||||||
$result = domainadmin_login($user, $pass);
|
$result = domainadmin_login($user, $pass);
|
||||||
if ($result !== false) return $result;
|
if ($result !== false) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Try validate app password
|
||||||
|
if (!isset($role) || $role == "app") {
|
||||||
|
$result = apppass_login($user, $pass, $app_passwd_data);
|
||||||
|
if ($result !== false) {
|
||||||
|
if ($app_passwd_data['eas'] === true) {
|
||||||
|
$service = 'EAS';
|
||||||
|
} elseif ($app_passwd_data['dav'] === true) {
|
||||||
|
$service = 'DAV';
|
||||||
|
} else {
|
||||||
|
$service = 'NONE';
|
||||||
|
}
|
||||||
|
$real_rip = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
|
||||||
|
set_sasl_log($user, $real_rip, $service, $pass);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try validate user
|
// Try validate user
|
||||||
if (!isset($role) || $role == "user") {
|
if (!isset($role) || $role == "user") {
|
||||||
$result = user_login($user, $pass);
|
$result = user_login($user, $pass);
|
||||||
if ($result !== false) return $result;
|
if ($result !== false) {
|
||||||
}
|
if ($app_passwd_data['eas'] === true) {
|
||||||
|
$service = 'EAS';
|
||||||
// Try validate app password
|
} elseif ($app_passwd_data['dav'] === true) {
|
||||||
if (!isset($role) || $role == "app") {
|
$service = 'DAV';
|
||||||
$result = apppass_login($user, $pass, $app_passwd_data);
|
} else {
|
||||||
if ($result !== false) return $result;
|
$service = 'MAILCOWUI';
|
||||||
|
}
|
||||||
|
$real_rip = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
|
||||||
|
set_sasl_log($user, $real_rip, $service);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip log and only return false if it's an internal request
|
// skip log and only return false if it's an internal request
|
||||||
@@ -166,6 +193,7 @@ function user_login($user, $pass, $extra = null){
|
|||||||
global $iam_settings;
|
global $iam_settings;
|
||||||
|
|
||||||
$is_internal = $extra['is_internal'];
|
$is_internal = $extra['is_internal'];
|
||||||
|
$service = $extra['service'];
|
||||||
|
|
||||||
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
|
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
|
||||||
if (!$is_internal){
|
if (!$is_internal){
|
||||||
@@ -208,6 +236,14 @@ function user_login($user, $pass, $extra = null){
|
|||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!empty($row)) {
|
if (!empty($row)) {
|
||||||
|
// check if user has access to service (imap, smtp, pop3, sieve) if service is set
|
||||||
|
$row['attributes'] = json_decode($row['attributes'], true);
|
||||||
|
if (isset($service)) {
|
||||||
|
$key = strtolower($service) . "_access";
|
||||||
|
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,6 +251,14 @@ function user_login($user, $pass, $extra = null){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if user has access to service (imap, smtp, pop3, sieve) if service is set
|
||||||
|
$row['attributes'] = json_decode($row['attributes'], true);
|
||||||
|
if (isset($service)) {
|
||||||
|
$key = strtolower($service) . "_access";
|
||||||
|
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
switch ($row['authsource']) {
|
switch ($row['authsource']) {
|
||||||
case 'keycloak':
|
case 'keycloak':
|
||||||
// user authsource is keycloak, try using via rest flow
|
// user authsource is keycloak, try using via rest flow
|
||||||
@@ -324,6 +368,11 @@ function user_login($user, $pass, $extra = null){
|
|||||||
}
|
}
|
||||||
// verify password
|
// verify password
|
||||||
if (verify_hash($row['password'], $pass) !== false) {
|
if (verify_hash($row['password'], $pass) !== false) {
|
||||||
|
|
||||||
|
if (intval($row['attributes']['force_pw_update']) == 1) {
|
||||||
|
$_SESSION['pending_pw_update'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
// check for tfa authenticators
|
// check for tfa authenticators
|
||||||
$authenticators = get_tfa($user);
|
$authenticators = get_tfa($user);
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
||||||
@@ -415,21 +464,7 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){
|
|||||||
|
|
||||||
// verify password
|
// verify password
|
||||||
if (verify_hash($row['password'], $pass) !== false) {
|
if (verify_hash($row['password'], $pass) !== false) {
|
||||||
if ($is_internal){
|
$_SESSION['app_passwd_id'] = $row['app_passwd_id'];
|
||||||
$remote_addr = $extra['remote_addr'];
|
|
||||||
} else {
|
|
||||||
$remote_addr = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$service = strtoupper($is_app_passwd);
|
|
||||||
$stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':service' => $service,
|
|
||||||
':app_id' => $row['app_passwd_id'],
|
|
||||||
':username' => $user,
|
|
||||||
':remote_addr' => $remote_addr
|
|
||||||
));
|
|
||||||
|
|
||||||
unset($_SESSION['ldelay']);
|
unset($_SESSION['ldelay']);
|
||||||
return "user";
|
return "user";
|
||||||
}
|
}
|
||||||
@@ -458,6 +493,9 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!$iam_provider) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// get access_token for service account of mailcow client
|
// get access_token for service account of mailcow client
|
||||||
$admin_token = identity_provider("get-keycloak-admin-token");
|
$admin_token = identity_provider("get-keycloak-admin-token");
|
||||||
@@ -527,6 +565,17 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
|
|||||||
return 'user';
|
return 'user';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if login provisioning is enabled before creating user
|
||||||
|
if (!$iam_settings['login_provisioning']){
|
||||||
|
if (!$is_internal){
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, "Auto-create users on login is deactivated"),
|
||||||
|
'msg' => 'login_failed'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// check if matching attribute exist
|
// check if matching attribute exist
|
||||||
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
|
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
|
||||||
if (!empty($iam_settings['default_template'])) {
|
if (!empty($iam_settings['default_template'])) {
|
||||||
@@ -640,10 +689,21 @@ function ldap_mbox_login($user, $pass, $extra = null){
|
|||||||
return 'user';
|
return 'user';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if login provisioning is enabled before creating user
|
||||||
|
if (!$iam_settings['login_provisioning']){
|
||||||
|
if (!$is_internal){
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, "Auto-create users on login is deactivated"),
|
||||||
|
'msg' => 'login_failed'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// check if matching attribute exist
|
// check if matching attribute exist
|
||||||
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
|
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
|
||||||
if (!empty($iam_settings['default_tempalte'])) {
|
if (!empty($iam_settings['default_template'])) {
|
||||||
$mbox_template = $iam_settings['default_tempalte'];
|
$mbox_template = $iam_settings['default_template'];
|
||||||
} else {
|
} else {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
|
|||||||
@@ -204,6 +204,35 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
'msg' => 'ip_check_opt_in_modified'
|
'msg' => 'ip_check_opt_in_modified'
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case 'custom_login':
|
||||||
|
$hide_user_quicklink = ($_data['hide_user_quicklink'] == "1") ? 1 : 0;
|
||||||
|
$hide_domainadmin_quicklink = ($_data['hide_domainadmin_quicklink'] == "1") ? 1 : 0;
|
||||||
|
$hide_admin_quicklink = ($_data['hide_admin_quicklink'] == "1") ? 1 : 0;
|
||||||
|
$force_sso = ($_data['force_sso'] == "1") ? 1 : 0;
|
||||||
|
|
||||||
|
$custom_login = array(
|
||||||
|
"hide_user_quicklink" => $hide_user_quicklink,
|
||||||
|
"hide_domainadmin_quicklink" => $hide_domainadmin_quicklink,
|
||||||
|
"hide_admin_quicklink" => $hide_admin_quicklink,
|
||||||
|
"force_sso" => $force_sso,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
$redis->set('CUSTOM_LOGIN', json_encode($custom_login));
|
||||||
|
}
|
||||||
|
catch (RedisException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
|
'msg' => array('redis_error', $e)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
|
'msg' => 'custom_login_modified'
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
@@ -264,7 +293,7 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (empty($app_links)){
|
if (empty($app_links)){
|
||||||
return false;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert from old style
|
// convert from old style
|
||||||
@@ -296,8 +325,10 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
break;
|
break;
|
||||||
case 'ui_texts':
|
case 'ui_texts':
|
||||||
try {
|
try {
|
||||||
$data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : 'mailcow UI';
|
$mailcow_hostname = strtolower(getenv("MAILCOW_HOSTNAME"));
|
||||||
$data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI';
|
|
||||||
|
$data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : "$mailcow_hostname - mail UI";
|
||||||
|
$data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : "$mailcow_hostname - mail UI";
|
||||||
$data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : $lang['header']['apps'];
|
$data['apps_name'] = ($apps_name = $redis->get('APPS_NAME')) ? $apps_name : $lang['header']['apps'];
|
||||||
$data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false;
|
$data['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false;
|
||||||
if (!empty($redis->get('UI_IMPRESS'))) {
|
if (!empty($redis->get('UI_IMPRESS'))) {
|
||||||
@@ -357,6 +388,20 @@ function customize($_action, $_item, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'custom_login':
|
||||||
|
try {
|
||||||
|
$custom_login = $redis->get('CUSTOM_LOGIN');
|
||||||
|
return $custom_login ? json_decode($custom_login, true) : array();
|
||||||
|
}
|
||||||
|
catch (RedisException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||||
|
'msg' => array('redis_error', $e)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,6 +350,34 @@ function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
function set_sasl_log($username, $real_rip, $service){
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!empty($_SESSION['app_passwd_id'])) {
|
||||||
|
$app_password = $_SESSION['app_passwd_id'];
|
||||||
|
} else {
|
||||||
|
$app_password = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare('REPLACE INTO `sasl_log` (`username`, `real_rip`, `service`, `app_password`) VALUES (:username, :real_rip, :service, :app_password)');
|
||||||
|
$stmt->execute(array(
|
||||||
|
':username' => $username,
|
||||||
|
':real_rip' => $real_rip,
|
||||||
|
':service' => $service,
|
||||||
|
':app_password' => $app_password
|
||||||
|
));
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_data_log),
|
||||||
|
'msg' => array('mysql_error', $e)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
function flush_memcached() {
|
function flush_memcached() {
|
||||||
try {
|
try {
|
||||||
$m = new Memcached();
|
$m = new Memcached();
|
||||||
@@ -786,6 +814,32 @@ function verify_hash($hash, $password) {
|
|||||||
$hash = $components[4];
|
$hash = $components[4];
|
||||||
return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash);
|
return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash);
|
||||||
|
|
||||||
|
case "PBKDF2-SHA512":
|
||||||
|
// Handle FreeIPA-style hash: {PBKDF2-SHA512}10000$<base64_salt>$<base64_hash>
|
||||||
|
$components = explode('$', $hash);
|
||||||
|
if (count($components) !== 3) return false;
|
||||||
|
|
||||||
|
// 1st part: iteration count (integer)
|
||||||
|
$iterations = intval($components[0]);
|
||||||
|
if ($iterations <= 0) return false;
|
||||||
|
|
||||||
|
// 2nd part: salt (base64-encoded)
|
||||||
|
$salt = $components[1];
|
||||||
|
// 3rd part: hash (base64-encoded)
|
||||||
|
$stored_hash_b64 = $components[2];
|
||||||
|
|
||||||
|
// Decode salt and hash from base64
|
||||||
|
$salt_bin = base64_decode($salt, true);
|
||||||
|
$hash_bin = base64_decode($stored_hash_b64, true);
|
||||||
|
if ($salt_bin === false || $hash_bin === false) return false;
|
||||||
|
// Get length of hash in bytes
|
||||||
|
$hash_len = strlen($hash_bin);
|
||||||
|
if ($hash_len === 0) return false;
|
||||||
|
|
||||||
|
// Calculate PBKDF2-SHA512 hash for provided password
|
||||||
|
$test_hash = hash_pbkdf2('sha512', $password, $salt_bin, $iterations, $hash_len, true);
|
||||||
|
return hash_equals($hash_bin, $test_hash);
|
||||||
|
|
||||||
case "PLAIN-MD4":
|
case "PLAIN-MD4":
|
||||||
return hash_equals(hash('md4', $password), $hash);
|
return hash_equals(hash('md4', $password), $hash);
|
||||||
|
|
||||||
@@ -973,11 +1027,12 @@ function edit_user_account($_data) {
|
|||||||
':password_hashed' => $password_hashed,
|
':password_hashed' => $password_hashed,
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
|
$_SESSION['pending_pw_update'] = false;
|
||||||
|
|
||||||
update_sogo_static_view();
|
update_sogo_static_view();
|
||||||
}
|
}
|
||||||
// edit password recovery email
|
// edit password recovery email
|
||||||
elseif (isset($pw_recovery_email)) {
|
elseif (!empty($password_old) && isset($pw_recovery_email)) {
|
||||||
if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) {
|
if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
@@ -987,6 +1042,21 @@ function edit_user_account($_data) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
||||||
|
WHERE `kind` NOT REGEXP 'location|thing|group'
|
||||||
|
AND `username` = :user AND authsource = 'mailcow'");
|
||||||
|
$stmt->execute(array(':user' => $username));
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!verify_hash($row['password'], $password_old)) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_data_log),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$pw_recovery_email = (!filter_var($pw_recovery_email, FILTER_VALIDATE_EMAIL)) ? '' : $pw_recovery_email;
|
$pw_recovery_email = (!filter_var($pw_recovery_email, FILTER_VALIDATE_EMAIL)) ? '' : $pw_recovery_email;
|
||||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
|
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
|
||||||
WHERE `username` = :username AND authsource = 'mailcow'");
|
WHERE `username` = :username AND authsource = 'mailcow'");
|
||||||
@@ -1078,11 +1148,21 @@ function user_get_alias_details($username) {
|
|||||||
}
|
}
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
function is_valid_domain_name($domain_name) {
|
function is_valid_domain_name($domain_name, $options = array()) {
|
||||||
if (empty($domain_name)) {
|
if (empty($domain_name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert domain name to ASCII for validation
|
||||||
$domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46);
|
$domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
|
|
||||||
|
if (isset($options['allow_wildcard']) && $options['allow_wildcard'] == true) {
|
||||||
|
// Remove '*.' if wildcard subdomains are allowed
|
||||||
|
if (strpos($domain_name, '*.') === 0) {
|
||||||
|
$domain_name = substr($domain_name, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name)
|
return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name)
|
||||||
&& preg_match("/^.{1,253}$/", $domain_name)
|
&& preg_match("/^.{1,253}$/", $domain_name)
|
||||||
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
|
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
|
||||||
@@ -2182,7 +2262,7 @@ function cors($action, $data = null) {
|
|||||||
$cors_settings['allowed_origins'] = $allowed_origins[0];
|
$cors_settings['allowed_origins'] = $allowed_origins[0];
|
||||||
if (in_array('*', $allowed_origins)){
|
if (in_array('*', $allowed_origins)){
|
||||||
$cors_settings['allowed_origins'] = '*';
|
$cors_settings['allowed_origins'] = '*';
|
||||||
} else if (in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
|
} else if (array_key_exists('HTTP_ORIGIN', $_SERVER) && in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
|
||||||
$cors_settings['allowed_origins'] = $_SERVER['HTTP_ORIGIN'];
|
$cors_settings['allowed_origins'] = $_SERVER['HTTP_ORIGIN'];
|
||||||
}
|
}
|
||||||
// always allow OPTIONS for preflight request
|
// always allow OPTIONS for preflight request
|
||||||
@@ -2258,12 +2338,14 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
foreach($rows as $row){
|
foreach($rows as $row){
|
||||||
switch ($row["key"]) {
|
switch ($row["key"]) {
|
||||||
|
case "redirect_url_extra":
|
||||||
case "mappers":
|
case "mappers":
|
||||||
case "templates":
|
case "templates":
|
||||||
$settings[$row["key"]] = json_decode($row["value"]);
|
$settings[$row["key"]] = json_decode($row["value"]);
|
||||||
break;
|
break;
|
||||||
case "use_ssl":
|
case "use_ssl":
|
||||||
case "use_tls":
|
case "use_tls":
|
||||||
|
case "login_provisioning":
|
||||||
case "ignore_ssl_errors":
|
case "ignore_ssl_errors":
|
||||||
$settings[$row["key"]] = boolval($row["value"]);
|
$settings[$row["key"]] = boolval($row["value"]);
|
||||||
break;
|
break;
|
||||||
@@ -2272,6 +2354,10 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// set login_provisioning if not exists
|
||||||
|
if (!array_key_exists('login_provisioning', $settings)) {
|
||||||
|
$settings['login_provisioning'] = 1;
|
||||||
|
}
|
||||||
// return default client_scopes for generic-oidc if none is set
|
// return default client_scopes for generic-oidc if none is set
|
||||||
if ($settings["authsource"] == "generic-oidc" && empty($settings["client_scopes"])){
|
if ($settings["authsource"] == "generic-oidc" && empty($settings["client_scopes"])){
|
||||||
$settings["client_scopes"] = "openid profile email mailcow_template";
|
$settings["client_scopes"] = "openid profile email mailcow_template";
|
||||||
@@ -2336,7 +2422,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$_data['ignore_ssl_error'] = isset($_data['ignore_ssl_error']) ? boolval($_data['ignore_ssl_error']) : false;
|
$_data['ignore_ssl_error'] = isset($_data['ignore_ssl_error']) ? boolval($_data['ignore_ssl_error']) : false;
|
||||||
|
$_data['login_provisioning'] = isset($_data['login_provisioning']) ? boolval($_data['login_provisioning']) : false;
|
||||||
switch ($_data['authsource']) {
|
switch ($_data['authsource']) {
|
||||||
case "keycloak":
|
case "keycloak":
|
||||||
$_data['server_url'] = (!empty($_data['server_url'])) ? rtrim($_data['server_url'], '/') : null;
|
$_data['server_url'] = (!empty($_data['server_url'])) ? rtrim($_data['server_url'], '/') : null;
|
||||||
@@ -2345,14 +2432,14 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
$_data['import_users'] = isset($_data['import_users']) ? intval($_data['import_users']) : 0;
|
$_data['import_users'] = isset($_data['import_users']) ? intval($_data['import_users']) : 0;
|
||||||
$_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15;
|
$_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15;
|
||||||
$_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval'];
|
$_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval'];
|
||||||
$required_settings = array('authsource', 'server_url', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version', 'mailpassword_flow', 'periodic_sync', 'import_users', 'sync_interval', 'ignore_ssl_error');
|
$required_settings = array('authsource', 'server_url', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version', 'mailpassword_flow', 'periodic_sync', 'import_users', 'sync_interval', 'ignore_ssl_error', 'login_provisioning');
|
||||||
break;
|
break;
|
||||||
case "generic-oidc":
|
case "generic-oidc":
|
||||||
$_data['authorize_url'] = (!empty($_data['authorize_url'])) ? $_data['authorize_url'] : null;
|
$_data['authorize_url'] = (!empty($_data['authorize_url'])) ? $_data['authorize_url'] : null;
|
||||||
$_data['token_url'] = (!empty($_data['token_url'])) ? $_data['token_url'] : null;
|
$_data['token_url'] = (!empty($_data['token_url'])) ? $_data['token_url'] : null;
|
||||||
$_data['userinfo_url'] = (!empty($_data['userinfo_url'])) ? $_data['userinfo_url'] : null;
|
$_data['userinfo_url'] = (!empty($_data['userinfo_url'])) ? $_data['userinfo_url'] : null;
|
||||||
$_data['client_scopes'] = (!empty($_data['client_scopes'])) ? $_data['client_scopes'] : "openid profile email mailcow_template";
|
$_data['client_scopes'] = (!empty($_data['client_scopes'])) ? $_data['client_scopes'] : "openid profile email mailcow_template";
|
||||||
$required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes', 'ignore_ssl_error');
|
$required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes', 'ignore_ssl_error', 'login_provisioning');
|
||||||
break;
|
break;
|
||||||
case "ldap":
|
case "ldap":
|
||||||
$_data['host'] = (!empty($_data['host'])) ? str_replace(" ", "", $_data['host']) : "";
|
$_data['host'] = (!empty($_data['host'])) ? str_replace(" ", "", $_data['host']) : "";
|
||||||
@@ -2366,7 +2453,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
$_data['use_tls'] = isset($_data['use_tls']) && !$_data['use_ssl'] ? boolval($_data['use_tls']) : false;
|
$_data['use_tls'] = isset($_data['use_tls']) && !$_data['use_ssl'] ? boolval($_data['use_tls']) : false;
|
||||||
$_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15;
|
$_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15;
|
||||||
$_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval'];
|
$_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval'];
|
||||||
$required_settings = array('authsource', 'host', 'port', 'basedn', 'username_field', 'filter', 'attribute_field', 'binddn', 'bindpass', 'periodic_sync', 'import_users', 'sync_interval', 'use_ssl', 'use_tls', 'ignore_ssl_error');
|
$required_settings = array('authsource', 'host', 'port', 'basedn', 'username_field', 'filter', 'attribute_field', 'binddn', 'bindpass', 'periodic_sync', 'import_users', 'sync_interval', 'use_ssl', 'use_tls', 'ignore_ssl_error', 'login_provisioning');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2390,6 +2477,18 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
}
|
}
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
|
||||||
|
// add redirect_url_extra
|
||||||
|
if (isset($_data['redirect_url_extra'])){
|
||||||
|
$_data['redirect_url_extra'] = (!is_array($_data['redirect_url_extra'])) ? array($_data['redirect_url_extra']) : $_data['redirect_url_extra'];
|
||||||
|
|
||||||
|
$redirect_url_extra = array_filter($_data['redirect_url_extra']);
|
||||||
|
$redirect_url_extra = json_encode($redirect_url_extra);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('redirect_url_extra', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
|
||||||
|
$stmt->bindParam(':value', $redirect_url_extra);
|
||||||
|
$stmt->execute();
|
||||||
|
}
|
||||||
|
|
||||||
// add default template
|
// add default template
|
||||||
if (isset($_data['default_template'])) {
|
if (isset($_data['default_template'])) {
|
||||||
$_data['default_template'] = (empty($_data['default_template'])) ? "" : $_data['default_template'];
|
$_data['default_template'] = (empty($_data['default_template'])) ? "" : $_data['default_template'];
|
||||||
@@ -2724,6 +2823,16 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// user doesn't exist, check if login provisioning is enabled
|
||||||
|
if (!$iam_settings['login_provisioning']){
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, "Auto-create users on login is deactivated"),
|
||||||
|
'msg' => 'login_failed'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($iam_settings['mappers']) || empty($user_template) || $mapper_key === false){
|
if (empty($iam_settings['mappers']) || empty($user_template) || $mapper_key === false){
|
||||||
if (!empty($iam_settings['default_template'])) {
|
if (!empty($iam_settings['default_template'])) {
|
||||||
$mbox_template = $iam_settings['default_template'];
|
$mbox_template = $iam_settings['default_template'];
|
||||||
@@ -2823,7 +2932,19 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
|||||||
case "get-redirect":
|
case "get-redirect":
|
||||||
if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc')
|
if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc')
|
||||||
return false;
|
return false;
|
||||||
$authUrl = $iam_provider->getAuthorizationUrl();
|
$options = [];
|
||||||
|
if (isset($iam_settings['redirect_url_extra'])) {
|
||||||
|
// check if the current domain is used in an extra redirect URL
|
||||||
|
$targetDomain = strtolower($_SERVER['HTTP_HOST']);
|
||||||
|
foreach ($iam_settings['redirect_url_extra'] as $testUrl) {
|
||||||
|
$testUrlParsed = parse_url($testUrl);
|
||||||
|
if (isset($testUrlParsed['host']) && strtolower($testUrlParsed['host']) == $targetDomain) {
|
||||||
|
$options['redirect_uri'] = $testUrl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$authUrl = $iam_provider->getAuthorizationUrl($options);
|
||||||
$_SESSION['oauth2state'] = $iam_provider->getState();
|
$_SESSION['oauth2state'] = $iam_provider->getState();
|
||||||
return $authUrl;
|
return $authUrl;
|
||||||
break;
|
break;
|
||||||
@@ -3276,6 +3397,8 @@ function set_user_loggedin_session($user) {
|
|||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
$_SESSION['mailcow_cc_username'] = $user;
|
$_SESSION['mailcow_cc_username'] = $user;
|
||||||
$_SESSION['mailcow_cc_role'] = 'user';
|
$_SESSION['mailcow_cc_role'] = 'user';
|
||||||
|
// Update User-Agent after session regeneration to prevent validation errors
|
||||||
|
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
|
||||||
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
|
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
|
||||||
$_SESSION['sogo-sso-user-allowed'][] = $user;
|
$_SESSION['sogo-sso-user-allowed'][] = $user;
|
||||||
$_SESSION['sogo-sso-pass'] = $sogo_sso_pass;
|
$_SESSION['sogo-sso-pass'] = $sogo_sso_pass;
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
// Default to 1 yr
|
// Default to 1 yr
|
||||||
$_data["validity"] = 8760;
|
$_data["validity"] = 8760;
|
||||||
}
|
}
|
||||||
|
if (isset($_data["permanent"]) && filter_var($_data["permanent"], FILTER_VALIDATE_BOOL)) {
|
||||||
|
$permanent = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$permanent = 0;
|
||||||
|
}
|
||||||
$domain = $_data['domain'];
|
$domain = $_data['domain'];
|
||||||
$description = $_data['description'];
|
$description = $_data['description'];
|
||||||
$valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain'];
|
$valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain'];
|
||||||
@@ -65,13 +71,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$validity = strtotime("+" . $_data["validity"] . " hour");
|
$validity = strtotime("+" . $_data["validity"] . " hour");
|
||||||
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`) VALUES
|
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`, `permanent`) VALUES
|
||||||
(:address, :description, :goto, :validity)");
|
(:address, :description, :goto, :validity, :permanent)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain,
|
':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain,
|
||||||
':description' => $description,
|
':description' => $description,
|
||||||
':goto' => $username,
|
':goto' => $username,
|
||||||
':validity' => $validity
|
':validity' => $validity,
|
||||||
|
':permanent' => $permanent
|
||||||
));
|
));
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
@@ -684,15 +691,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
case 'alias':
|
case 'alias':
|
||||||
$addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address']));
|
$addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address']));
|
||||||
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
|
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
|
||||||
$active = intval($_data['active']);
|
$internal = intval($_data['internal']);
|
||||||
$sogo_visible = intval($_data['sogo_visible']);
|
$active = intval($_data['active']);
|
||||||
$goto_null = intval($_data['goto_null']);
|
$sogo_visible = intval($_data['sogo_visible']);
|
||||||
$goto_spam = intval($_data['goto_spam']);
|
$goto_null = intval($_data['goto_null']);
|
||||||
$goto_ham = intval($_data['goto_ham']);
|
$goto_spam = intval($_data['goto_spam']);
|
||||||
|
$goto_ham = intval($_data['goto_ham']);
|
||||||
$private_comment = $_data['private_comment'];
|
$private_comment = $_data['private_comment'];
|
||||||
$public_comment = $_data['public_comment'];
|
$public_comment = $_data['public_comment'];
|
||||||
if (strlen($private_comment) > 160 | strlen($public_comment) > 160){
|
if (strlen($private_comment) > 160 | strlen($public_comment) > 160){
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
@@ -842,8 +850,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `active`)
|
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `internal`, `active`)
|
||||||
VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :active)");
|
VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :internal, :active)");
|
||||||
if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
|
if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':address' => '@'.$domain,
|
':address' => '@'.$domain,
|
||||||
@@ -853,6 +861,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':goto' => $goto,
|
':goto' => $goto,
|
||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':sogo_visible' => $sogo_visible,
|
':sogo_visible' => $sogo_visible,
|
||||||
|
':internal' => $internal,
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -864,6 +873,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':goto' => $goto,
|
':goto' => $goto,
|
||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':sogo_visible' => $sogo_visible,
|
':sogo_visible' => $sogo_visible,
|
||||||
|
':internal' => $internal,
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -1223,6 +1233,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username
|
':username' => $username
|
||||||
));
|
));
|
||||||
|
// save delimiter_action
|
||||||
|
if (isset($_data['tagged_mail_handler'])) {
|
||||||
|
mailbox('edit', 'delimiter_action', array(
|
||||||
|
'username' => $username,
|
||||||
|
'tagged_mail_handler' => $_data['tagged_mail_handler']
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// save tags
|
// save tags
|
||||||
foreach($tags as $index => $tag){
|
foreach($tags as $index => $tag){
|
||||||
if (empty($tag)) continue;
|
if (empty($tag)) continue;
|
||||||
@@ -1392,6 +1410,80 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
|
|
||||||
return mailbox('add', 'mailbox', $mailbox_attributes);
|
return mailbox('add', 'mailbox', $mailbox_attributes);
|
||||||
break;
|
break;
|
||||||
|
case 'mta_sts':
|
||||||
|
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
|
$version = strtolower($_data['version']);
|
||||||
|
$mode = strtolower($_data['mode']);
|
||||||
|
$mx = explode(",", preg_replace('/\s+/', '', $_data['mx']));
|
||||||
|
$max_age = intval($_data['max_age']);
|
||||||
|
$active = (intval($_data['active']) == 1) ? 1 : 0;
|
||||||
|
$id = date('YmdHis');
|
||||||
|
|
||||||
|
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (empty($version) || !in_array($version, array('stsv1'))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => array('version_invalid', htmlspecialchars($domain))
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (empty($mode) || !in_array($mode, array('enforce', 'testing', 'none'))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => array('mode_invalid', htmlspecialchars($domain))
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (empty($max_age) || $max_age < 0 || $max_age > 31536000) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => array('max_age_invalid', htmlspecialchars($domain))
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ($mx as $index => $mx_domain) {
|
||||||
|
$mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
|
if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => array('mx_invalid', htmlspecialchars($mx_domain))
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO `mta_sts` (`id`, `domain`, `version`, `mode`, `mx`, `max_age`, `active`)
|
||||||
|
VALUES (:id, :domain, :version, :mode, :mx, :max_age, :active)");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':id' => $id,
|
||||||
|
':domain' => $domain,
|
||||||
|
':version' => $version,
|
||||||
|
':mode' => $mode,
|
||||||
|
':mx' => implode(",", $mx),
|
||||||
|
':max_age' => $max_age,
|
||||||
|
':active' => $active
|
||||||
|
));
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data),
|
||||||
|
'msg' => $e->getMessage()
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'resource':
|
case 'resource':
|
||||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
$description = $_data['description'];
|
$description = $_data['description'];
|
||||||
@@ -1613,6 +1705,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$attr = array();
|
$attr = array();
|
||||||
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
|
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
|
||||||
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
|
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
|
||||||
|
$attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler']);
|
||||||
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
|
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
|
||||||
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
|
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
|
||||||
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
|
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
|
||||||
@@ -2017,15 +2110,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (empty($_data['validity'])) {
|
if (empty($_data['validity']) && empty($_data['permanent'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$validity = round((int)time() + ($_data['validity'] * 3600));
|
if (isset($_data['permanent']) && filter_var($_data['permanent'], FILTER_VALIDATE_BOOL)) {
|
||||||
$stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE
|
$permanent = 1;
|
||||||
|
$validity = 0;
|
||||||
|
}
|
||||||
|
else if (isset($_data['validity'])) {
|
||||||
|
$permanent = 0;
|
||||||
|
$validity = round((int)time() + ($_data['validity'] * 3600));
|
||||||
|
}
|
||||||
|
$stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity, `permanent` = :permanent WHERE
|
||||||
`address` = :address");
|
`address` = :address");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':address' => $address,
|
':address' => $address,
|
||||||
':validity' => $validity
|
':validity' => $validity,
|
||||||
|
':permanent' => $permanent
|
||||||
));
|
));
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'success',
|
'type' => 'success',
|
||||||
@@ -2398,6 +2499,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
foreach ($ids as $id) {
|
foreach ($ids as $id) {
|
||||||
$is_now = mailbox('get', 'alias_details', $id);
|
$is_now = mailbox('get', 'alias_details', $id);
|
||||||
if (!empty($is_now)) {
|
if (!empty($is_now)) {
|
||||||
|
$internal = (isset($_data['internal'])) ? intval($_data['internal']) : $is_now['internal'];
|
||||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||||
$sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible'];
|
$sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible'];
|
||||||
$goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0;
|
$goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0;
|
||||||
@@ -2583,6 +2685,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
`domain` = :domain,
|
`domain` = :domain,
|
||||||
`goto` = :goto,
|
`goto` = :goto,
|
||||||
`sogo_visible`= :sogo_visible,
|
`sogo_visible`= :sogo_visible,
|
||||||
|
`internal`= :internal,
|
||||||
`active`= :active
|
`active`= :active
|
||||||
WHERE `id` = :id");
|
WHERE `id` = :id");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
@@ -2592,6 +2695,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':goto' => $goto,
|
':goto' => $goto,
|
||||||
':sogo_visible' => $sogo_visible,
|
':sogo_visible' => $sogo_visible,
|
||||||
|
':internal' => $internal,
|
||||||
':active' => $active,
|
':active' => $active,
|
||||||
':id' => $is_now['id']
|
':id' => $is_now['id']
|
||||||
));
|
));
|
||||||
@@ -3259,6 +3363,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// save delimiter_action
|
||||||
|
if (isset($_data['tagged_mail_handler'])) {
|
||||||
|
mailbox('edit', 'delimiter_action', array(
|
||||||
|
'username' => $username,
|
||||||
|
'tagged_mail_handler' => $_data['tagged_mail_handler']
|
||||||
|
));
|
||||||
|
}
|
||||||
// save tags
|
// save tags
|
||||||
foreach($tags as $index => $tag){
|
foreach($tags as $index => $tag){
|
||||||
if (empty($tag)) continue;
|
if (empty($tag)) continue;
|
||||||
@@ -3604,6 +3715,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$attr = array();
|
$attr = array();
|
||||||
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
|
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
|
||||||
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags'];
|
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags'];
|
||||||
|
$attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : $is_now['tagged_mail_handler'];
|
||||||
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
|
$attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
|
||||||
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
|
$attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
|
||||||
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
|
$attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];
|
||||||
@@ -3724,6 +3836,125 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
break;
|
break;
|
||||||
|
case 'mta_sts':
|
||||||
|
if (!is_array($_data['domains'])) {
|
||||||
|
$domains = array();
|
||||||
|
$domains[] = $_data['domains'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$domains = $_data['domains'];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($domains as $domain) {
|
||||||
|
$domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
|
|
||||||
|
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_now = mailbox('get', 'mta_sts', $domain);
|
||||||
|
if (!empty($is_now)) {
|
||||||
|
$version = (isset($_data['version'])) ? strtolower($_data['version']) : $is_now['version'];
|
||||||
|
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||||
|
$active = ($active == 1) ? 1 : 0;
|
||||||
|
$mode = (isset($_data['mode'])) ? strtolower($_data['mode']) : $is_now['mode'];
|
||||||
|
$mx = (isset($_data['mx'])) ? explode(",", preg_replace('/\s+/', '', $_data['mx'])) : $is_now['mx'];
|
||||||
|
$max_age = (isset($_data['max_age'])) ? intval($_data['max_age']) : $is_now['max_age'];
|
||||||
|
|
||||||
|
// Update ID if neccesary
|
||||||
|
if ($version != strtolower($is_now['version']) ||
|
||||||
|
$mode != strtolower($is_now['mode']) ||
|
||||||
|
$mx != $is_now['mx'] ||
|
||||||
|
$max_age != $is_now['max_age']) {
|
||||||
|
$id = date('YmdHis');
|
||||||
|
} else {
|
||||||
|
$id = $is_now['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||||
|
'msg' => 'access_denied'
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($version) || !in_array($version, array('stsv1'))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => array('version_invalid', htmlspecialchars($version))
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (empty($mode) || !in_array($mode, array('enforce', 'testing', 'none'))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => array('mode_invalid', htmlspecialchars($domain))
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (empty($max_age) || $max_age < 0 || $max_age > 31557600) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => array('max_age_invalid', htmlspecialchars($domain))
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($mx as $index => $mx_domain) {
|
||||||
|
$mx_domain = idn_to_ascii(strtolower(trim($mx_domain)), 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
|
$invalid_mx = false;
|
||||||
|
if (!is_valid_domain_name($mx_domain, array('allow_wildcard' => true))) {
|
||||||
|
$invalid_mx = $mx_domain;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($invalid_mx) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => array('mx_invalid', htmlspecialchars($invalid_mx))
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("UPDATE `mta_sts` SET `id` = :id, `version` = :version, `mode` = :mode, `mx` = :mx, `max_age` = :max_age, `active` = :active WHERE `domain` = :domain");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':id' => $id,
|
||||||
|
':domain' => $domain,
|
||||||
|
':version' => $version,
|
||||||
|
':mode' => $mode,
|
||||||
|
':mx' => implode(",", $mx),
|
||||||
|
':max_age' => $max_age,
|
||||||
|
':active' => $active
|
||||||
|
));
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data),
|
||||||
|
'msg' => $e->getMessage()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $_action, $_type, $_data, $_attr),
|
||||||
|
'msg' => array('object_modified', $domain)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
case 'resource':
|
case 'resource':
|
||||||
if (!is_array($_data['name'])) {
|
if (!is_array($_data['name'])) {
|
||||||
$names = array();
|
$names = array();
|
||||||
@@ -4368,10 +4599,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
`description`,
|
`description`,
|
||||||
`validity`,
|
`validity`,
|
||||||
`created`,
|
`created`,
|
||||||
`modified`
|
`modified`,
|
||||||
|
`permanent`
|
||||||
FROM `spamalias`
|
FROM `spamalias`
|
||||||
WHERE `goto` = :username
|
WHERE `goto` = :username
|
||||||
AND `validity` >= :unixnow");
|
AND (`validity` >= :unixnow
|
||||||
|
OR `permanent` != 0)");
|
||||||
$stmt->execute(array(':username' => $_data, ':unixnow' => time()));
|
$stmt->execute(array(':username' => $_data, ':unixnow' => time()));
|
||||||
$tladata = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$tladata = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
return $tladata;
|
return $tladata;
|
||||||
@@ -4490,6 +4723,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
`address`,
|
`address`,
|
||||||
`public_comment`,
|
`public_comment`,
|
||||||
`private_comment`,
|
`private_comment`,
|
||||||
|
`internal`,
|
||||||
`active`,
|
`active`,
|
||||||
`sogo_visible`,
|
`sogo_visible`,
|
||||||
`created`,
|
`created`,
|
||||||
@@ -4520,6 +4754,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$aliasdata['goto'] = $row['goto'];
|
$aliasdata['goto'] = $row['goto'];
|
||||||
$aliasdata['address'] = $row['address'];
|
$aliasdata['address'] = $row['address'];
|
||||||
(!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0;
|
(!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0;
|
||||||
|
$aliasdata['internal'] = $row['internal'];
|
||||||
$aliasdata['active'] = $row['active'];
|
$aliasdata['active'] = $row['active'];
|
||||||
$aliasdata['active_int'] = $row['active'];
|
$aliasdata['active_int'] = $row['active'];
|
||||||
$aliasdata['sogo_visible'] = $row['sogo_visible'];
|
$aliasdata['sogo_visible'] = $row['sogo_visible'];
|
||||||
@@ -4944,7 +5179,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username");
|
$stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username");
|
||||||
$stmt->execute(array(':domain' => $row['domain'], ':username' => $_data));
|
$stmt->execute(array(':domain' => $row['domain'], ':username' => $_data));
|
||||||
$MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
$MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow");
|
$stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND (`validity` >= :unixnow OR `permanent` != 0)");
|
||||||
$stmt->execute(array(':address' => $_data, ':unixnow' => time()));
|
$stmt->execute(array(':address' => $_data, ':unixnow' => time()));
|
||||||
$SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
$SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];
|
$mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];
|
||||||
@@ -5012,6 +5247,20 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'mta_sts':
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM `mta_sts` WHERE `domain` = :domain");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':domain' => $_data,
|
||||||
|
));
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (empty($row)){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$row['mx'] = explode(',', $row['mx']);
|
||||||
|
$row['version'] = strtoupper(substr($row['version'], 0, 3)) . substr($row['version'], 3);
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
break;
|
||||||
case 'resource_details':
|
case 'resource_details':
|
||||||
$resourcedata = array();
|
$resourcedata = array();
|
||||||
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
|
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
|
||||||
@@ -5397,6 +5646,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
|||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
));
|
));
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM `mta_sts` WHERE `domain` = :domain");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':domain' => $domain,
|
||||||
|
));
|
||||||
$stmt = $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);");
|
$stmt = $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);");
|
||||||
$stmt = $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);");
|
$stmt = $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);");
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
||||||
WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash
|
WHERE `qhash` = :hash
|
||||||
AND user_acl.quarantine = 1
|
AND user_acl.quarantine = 1
|
||||||
AND rcpt IN (SELECT username FROM mailbox)');
|
AND rcpt IN (SELECT username FROM mailbox)');
|
||||||
$stmt->execute(array(':hash' => $hash));
|
$stmt->execute(array(':hash' => $hash));
|
||||||
@@ -65,7 +65,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
$stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
|
||||||
WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash
|
WHERE `qhash` = :hash
|
||||||
AND `user_acl`.`quarantine` = 1
|
AND `user_acl`.`quarantine` = 1
|
||||||
AND `username` IN (SELECT `username` FROM `mailbox`)');
|
AND `username` IN (SELECT `username` FROM `mailbox`)');
|
||||||
$stmt->execute(array(':hash' => $hash));
|
$stmt->execute(array(':hash' => $hash));
|
||||||
@@ -169,7 +169,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif ($release_format == 'raw') {
|
elseif ($release_format == 'raw') {
|
||||||
$detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $detail_row['msg']);
|
$detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']);
|
||||||
$postfix_talk = array(
|
$postfix_talk = array(
|
||||||
array('220', 'HELO quarantine' . chr(10)),
|
array('220', 'HELO quarantine' . chr(10)),
|
||||||
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
||||||
@@ -464,7 +464,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif ($release_format == 'raw') {
|
elseif ($release_format == 'raw') {
|
||||||
$row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $row['msg']);
|
$row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']);
|
||||||
$postfix_talk = array(
|
$postfix_talk = array(
|
||||||
array('220', 'HELO quarantine' . chr(10)),
|
array('220', 'HELO quarantine' . chr(10)),
|
||||||
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
||||||
@@ -833,7 +833,7 @@ function quarantine($_action, $_data = null) {
|
|||||||
)));
|
)));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash');
|
$stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash');
|
||||||
$stmt->execute(array(':hash' => $hash));
|
$stmt->execute(array(':hash' => $hash));
|
||||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -62,7 +62,11 @@ if ($app_links_processed){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workaround to get text with <br> straight to twig.
|
||||||
|
// Using "nl2br" doesn't work with Twig as it would escape everything by default.
|
||||||
|
if (isset($UI_TEXTS["ui_footer"])) {
|
||||||
|
$UI_TEXTS["ui_footer"] = nl2br($UI_TEXTS["ui_footer"]);
|
||||||
|
}
|
||||||
|
|
||||||
$globalVariables = [
|
$globalVariables = [
|
||||||
'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'),
|
'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ function init_db_schema()
|
|||||||
try {
|
try {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
|
|
||||||
$db_version = "27012025_1555";
|
$db_version = "10312025_0525";
|
||||||
|
|
||||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
@@ -184,6 +184,7 @@ function init_db_schema()
|
|||||||
"private_comment" => "TEXT",
|
"private_comment" => "TEXT",
|
||||||
"public_comment" => "TEXT",
|
"public_comment" => "TEXT",
|
||||||
"sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
"sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||||
|
"internal" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||||
),
|
),
|
||||||
"keys" => array(
|
"keys" => array(
|
||||||
@@ -345,10 +346,14 @@ function init_db_schema()
|
|||||||
"notified" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
"notified" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||||
"user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'",
|
"user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'",
|
||||||
|
"qhash" => "VARCHAR(64)",
|
||||||
),
|
),
|
||||||
"keys" => array(
|
"keys" => array(
|
||||||
"primary" => array(
|
"primary" => array(
|
||||||
"" => array("id")
|
"" => array("id")
|
||||||
|
),
|
||||||
|
"key" => array(
|
||||||
|
"qhash" => array("qhash")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||||
@@ -471,6 +476,23 @@ function init_db_schema()
|
|||||||
),
|
),
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||||
),
|
),
|
||||||
|
"mta_sts" => array(
|
||||||
|
"cols" => array(
|
||||||
|
"id" => "BIGINT NOT NULL",
|
||||||
|
"domain" => "VARCHAR(255) NOT NULL",
|
||||||
|
"version" => "VARCHAR(255) NOT NULL",
|
||||||
|
"mode" => "VARCHAR(255) NOT NULL",
|
||||||
|
"mx" => "VARCHAR(255) NOT NULL",
|
||||||
|
"max_age" => "VARCHAR(255) NOT NULL",
|
||||||
|
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||||
|
),
|
||||||
|
"keys" => array(
|
||||||
|
"primary" => array(
|
||||||
|
"" => array("domain")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||||
|
),
|
||||||
"user_acl" => array(
|
"user_acl" => array(
|
||||||
"cols" => array(
|
"cols" => array(
|
||||||
"username" => "VARCHAR(255) NOT NULL",
|
"username" => "VARCHAR(255) NOT NULL",
|
||||||
@@ -532,7 +554,8 @@ function init_db_schema()
|
|||||||
"description" => "TEXT NOT NULL",
|
"description" => "TEXT NOT NULL",
|
||||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
|
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
|
||||||
"validity" => "INT(11)"
|
"validity" => "INT(11)",
|
||||||
|
"permanent" => "TINYINT(1) NOT NULL DEFAULT '0'"
|
||||||
),
|
),
|
||||||
"keys" => array(
|
"keys" => array(
|
||||||
"primary" => array(
|
"primary" => array(
|
||||||
@@ -1315,6 +1338,14 @@ function init_db_schema()
|
|||||||
$pdo->query($create);
|
$pdo->query($create);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear old app_passwd log entries
|
||||||
|
$pdo->exec("DELETE FROM logs
|
||||||
|
WHERE role != 'unauthenticated'
|
||||||
|
AND JSON_EXTRACT(`call`, '$[0]') = 'app_passwd'
|
||||||
|
AND JSON_EXTRACT(`call`, '$[1]') = 'edit'
|
||||||
|
AND (JSON_CONTAINS_PATH(`call`, 'one', '$[2].password')
|
||||||
|
OR JSON_CONTAINS_PATH(`call`, 'one', '$[2].password2'));");
|
||||||
|
|
||||||
// Mitigate imapsync argument injection issue
|
// Mitigate imapsync argument injection issue
|
||||||
$pdo->query("UPDATE `imapsync` SET `custom_params` = ''
|
$pdo->query("UPDATE `imapsync` SET `custom_params` = ''
|
||||||
WHERE `custom_params` LIKE '%pipemess%'
|
WHERE `custom_params` LIKE '%pipemess%'
|
||||||
@@ -1482,6 +1513,10 @@ function init_db_schema()
|
|||||||
'msg' => 'db_init_complete'
|
'msg' => 'db_init_complete'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fill quarantine.qhash
|
||||||
|
$pdo->query("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE ISNULL(`qhash`)");
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
if (php_sapi_name() == "cli") {
|
if (php_sapi_name() == "cli") {
|
||||||
echo "DB initialization failed: " . print_r($e, true) . PHP_EOL;
|
echo "DB initialization failed: " . print_r($e, true) . PHP_EOL;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
// Start session
|
// Start session
|
||||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
|
session_name($SESSION_NAME);
|
||||||
ini_set("session.cookie_httponly", 1);
|
ini_set("session.cookie_httponly", 1);
|
||||||
|
ini_set("session.cookie_samesite", $SESSION_SAMESITE_POLICY);
|
||||||
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
|
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +43,9 @@ if (!isset($_SESSION['SESS_REMOTE_UA'])) {
|
|||||||
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) {
|
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) {
|
||||||
session_unset();
|
session_unset();
|
||||||
session_destroy();
|
session_destroy();
|
||||||
|
session_start();
|
||||||
|
// After destroying session, we need to reset the User-Agent for the new session
|
||||||
|
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
|
||||||
}
|
}
|
||||||
$_SESSION['LAST_ACTIVITY'] = time();
|
$_SESSION['LAST_ACTIVITY'] = time();
|
||||||
|
|
||||||
@@ -132,6 +137,12 @@ function session_check() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) {
|
if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) {
|
||||||
|
// In development mode, allow User-Agent changes (e.g., for responsive testing in dev tools)
|
||||||
|
// Validate UA is not empty and has reasonable length (most UAs are under 200 chars, 500 is safe upper limit)
|
||||||
|
if (isset($GLOBALS['DEV_MODE']) && $GLOBALS['DEV_MODE'] && !empty($_SERVER['HTTP_USER_AGENT']) && strlen($_SERVER['HTTP_USER_AGENT']) < 500) {
|
||||||
|
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'warning',
|
'type' => 'warning',
|
||||||
'msg' => 'session_ua'
|
'msg' => 'session_ua'
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
|||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
$_SESSION['mailcow_cc_username'] = $login_user;
|
$_SESSION['mailcow_cc_username'] = $login_user;
|
||||||
$_SESSION['mailcow_cc_role'] = "admin";
|
$_SESSION['mailcow_cc_role'] = "admin";
|
||||||
|
// Update User-Agent after session regeneration to prevent validation errors
|
||||||
|
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
|
||||||
header("Location: /admin/dashboard");
|
header("Location: /admin/dashboard");
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ if (!empty($_GET['sso_token'])) {
|
|||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
$_SESSION['mailcow_cc_username'] = $username;
|
$_SESSION['mailcow_cc_username'] = $username;
|
||||||
$_SESSION['mailcow_cc_role'] = 'domainadmin';
|
$_SESSION['mailcow_cc_role'] = 'domainadmin';
|
||||||
|
// Update User-Agent after session regeneration to prevent validation errors
|
||||||
|
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
|
||||||
header('Location: /domainadmin/mailbox');
|
header('Location: /domainadmin/mailbox');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +63,8 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
|||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
$_SESSION['mailcow_cc_username'] = $login_user;
|
$_SESSION['mailcow_cc_username'] = $login_user;
|
||||||
$_SESSION['mailcow_cc_role'] = "domainadmin";
|
$_SESSION['mailcow_cc_role'] = "domainadmin";
|
||||||
|
// Update User-Agent after session regeneration to prevent validation errors
|
||||||
|
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
|
||||||
header("Location: /domainadmin/mailbox");
|
header("Location: /domainadmin/mailbox");
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,11 @@ if (isset($_POST["verify_tfa_login"])) {
|
|||||||
|
|
||||||
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
|
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
|
||||||
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
|
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
|
||||||
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
|
if (intval($user_details['attributes']['sogo_access']) == 1 &&
|
||||||
header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}");
|
intval($user_details['attributes']['force_pw_update']) != 1 &&
|
||||||
|
getenv('SKIP_SOGO') != "y" &&
|
||||||
|
!$is_dual) {
|
||||||
|
header("Location: /SOGo/so/");
|
||||||
die();
|
die();
|
||||||
} else {
|
} else {
|
||||||
header("Location: /user");
|
header("Location: /user");
|
||||||
@@ -139,8 +142,11 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
|||||||
|
|
||||||
$user_details = mailbox("get", "mailbox_details", $login_user);
|
$user_details = mailbox("get", "mailbox_details", $login_user);
|
||||||
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
|
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
|
||||||
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
|
if (intval($user_details['attributes']['sogo_access']) == 1 &&
|
||||||
header("Location: /SOGo/so/{$login_user}");
|
intval($user_details['attributes']['force_pw_update']) != 1 &&
|
||||||
|
getenv('SKIP_SOGO') != "y" &&
|
||||||
|
!$is_dual) {
|
||||||
|
header("Location: /SOGo/so/");
|
||||||
die();
|
die();
|
||||||
} else {
|
} else {
|
||||||
header("Location: /user");
|
header("Location: /user");
|
||||||
|
|||||||
+93
-78
@@ -83,8 +83,9 @@ $DEFAULT_LANG = 'en-gb';
|
|||||||
// https://en.wikipedia.org/wiki/IETF_language_tag
|
// https://en.wikipedia.org/wiki/IETF_language_tag
|
||||||
$AVAILABLE_LANGUAGES = array(
|
$AVAILABLE_LANGUAGES = array(
|
||||||
// 'ca-es' => 'Català (Catalan)',
|
// 'ca-es' => 'Català (Catalan)',
|
||||||
|
'bg-bg' => 'Български (Bulgarian)',
|
||||||
'cs-cz' => 'Čeština (Czech)',
|
'cs-cz' => 'Čeština (Czech)',
|
||||||
'da-dk' => 'Danish (Dansk)',
|
'da-dk' => 'Dansk (Danish)',
|
||||||
'de-de' => 'Deutsch (German)',
|
'de-de' => 'Deutsch (German)',
|
||||||
'en-gb' => 'English',
|
'en-gb' => 'English',
|
||||||
'es-es' => 'Español (Spanish)',
|
'es-es' => 'Español (Spanish)',
|
||||||
@@ -109,6 +110,7 @@ $AVAILABLE_LANGUAGES = array(
|
|||||||
'sv-se' => 'Svenska (Swedish)',
|
'sv-se' => 'Svenska (Swedish)',
|
||||||
'tr-tr' => 'Türkçe (Turkish)',
|
'tr-tr' => 'Türkçe (Turkish)',
|
||||||
'uk-ua' => 'Українська (Ukrainian)',
|
'uk-ua' => 'Українська (Ukrainian)',
|
||||||
|
'vi-vn' => 'Tiếng Việt (Vietnamese)',
|
||||||
'zh-cn' => '简体中文 (Simplified Chinese)',
|
'zh-cn' => '简体中文 (Simplified Chinese)',
|
||||||
'zh-tw' => '繁體中文 (Traditional Chinese)',
|
'zh-tw' => '繁體中文 (Traditional Chinese)',
|
||||||
);
|
);
|
||||||
@@ -152,6 +154,13 @@ $LOG_PAGINATION_SIZE = 50;
|
|||||||
// Session lifetime in seconds
|
// Session lifetime in seconds
|
||||||
$SESSION_LIFETIME = 10800;
|
$SESSION_LIFETIME = 10800;
|
||||||
|
|
||||||
|
// Session SameSite Policy
|
||||||
|
// Use "None", "Lax" or "Strict"
|
||||||
|
$SESSION_SAMESITE_POLICY = "Lax";
|
||||||
|
|
||||||
|
// Name of the session cookie
|
||||||
|
$SESSION_NAME = "MCSESSID";
|
||||||
|
|
||||||
// Label for OTP devices
|
// Label for OTP devices
|
||||||
$OTP_LABEL = "mailcow UI";
|
$OTP_LABEL = "mailcow UI";
|
||||||
|
|
||||||
@@ -185,6 +194,12 @@ $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false;
|
|||||||
// Enable SOGo access - Users will be redirected to SOGo after login (set to false to disable redirect by default)
|
// Enable SOGo access - Users will be redirected to SOGo after login (set to false to disable redirect by default)
|
||||||
$MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true;
|
$MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true;
|
||||||
|
|
||||||
|
// How to handle tagged emails
|
||||||
|
// none - No special handling
|
||||||
|
// subfolder - Create subfolder under INBOX (e.g. "INBOX/Facebook")
|
||||||
|
// subject - Add tag to subject (e.g. "[Facebook] Subject")
|
||||||
|
$MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler'] = "none";
|
||||||
|
|
||||||
// Send notification when quarantine is not empty (never, hourly, daily, weekly)
|
// Send notification when quarantine is not empty (never, hourly, daily, weekly)
|
||||||
$MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'] = 'hourly';
|
$MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'] = 'hourly';
|
||||||
|
|
||||||
@@ -237,12 +252,12 @@ $FIDO2_FORMATS = array('apple', 'android-key', 'android-safetynet', 'fido-u2f',
|
|||||||
// Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing
|
// Set visible Rspamd maps in mailcow UI, do not change unless you know what you are doing
|
||||||
$RSPAMD_MAPS = array(
|
$RSPAMD_MAPS = array(
|
||||||
'regex' => array(
|
'regex' => array(
|
||||||
'Header-From: Blacklist' => 'global_mime_from_blacklist.map',
|
'Header-From: Denylist' => 'global_mime_from_blacklist.map',
|
||||||
'Header-From: Whitelist' => 'global_mime_from_whitelist.map',
|
'Header-From: Allowlist' => 'global_mime_from_whitelist.map',
|
||||||
'Envelope Sender Blacklist' => 'global_smtp_from_blacklist.map',
|
'Envelope Sender Denylist' => 'global_smtp_from_blacklist.map',
|
||||||
'Envelope Sender Whitelist' => 'global_smtp_from_whitelist.map',
|
'Envelope Sender Allowlist' => 'global_smtp_from_whitelist.map',
|
||||||
'Recipient Blacklist' => 'global_rcpt_blacklist.map',
|
'Recipient Denylist' => 'global_rcpt_blacklist.map',
|
||||||
'Recipient Whitelist' => 'global_rcpt_whitelist.map',
|
'Recipient Allowlist' => 'global_rcpt_whitelist.map',
|
||||||
'Fishy TLDS (only fired in combination with bad words)' => 'fishy_tlds.map',
|
'Fishy TLDS (only fired in combination with bad words)' => 'fishy_tlds.map',
|
||||||
'Bad Words (only fired in combination with fishy TLDs)' => 'bad_words.map',
|
'Bad Words (only fired in combination with fishy TLDs)' => 'bad_words.map',
|
||||||
'Bad Words DE (only fired in combination with fishy TLDs)' => 'bad_words_de.map',
|
'Bad Words DE (only fired in combination with fishy TLDs)' => 'bad_words_de.map',
|
||||||
@@ -256,57 +271,57 @@ $RSPAMD_MAPS = array(
|
|||||||
|
|
||||||
$IMAPSYNC_OPTIONS = array(
|
$IMAPSYNC_OPTIONS = array(
|
||||||
'whitelist' => array(
|
'whitelist' => array(
|
||||||
'abort',
|
'abort',
|
||||||
'authmd51',
|
'authmd51',
|
||||||
'authmd52',
|
'authmd52',
|
||||||
'authmech1',
|
'authmech1',
|
||||||
'authmech2',
|
'authmech2',
|
||||||
'authuser1',
|
'authuser1',
|
||||||
'authuser2',
|
'authuser2',
|
||||||
'debug',
|
'debug',
|
||||||
'debugcontent',
|
'debugcontent',
|
||||||
'debugcrossduplicates',
|
'debugcrossduplicates',
|
||||||
'debugflags',
|
'debugflags',
|
||||||
'debugfolders',
|
'debugfolders',
|
||||||
'debugimap',
|
'debugimap',
|
||||||
'debugimap1',
|
'debugimap1',
|
||||||
'debugimap2',
|
'debugimap2',
|
||||||
'debugmemory',
|
'debugmemory',
|
||||||
'debugssl',
|
'debugssl',
|
||||||
'delete1emptyfolders',
|
'delete1emptyfolders',
|
||||||
'delete2folders',
|
'delete2folders',
|
||||||
'disarmreadreceipts',
|
'disarmreadreceipts',
|
||||||
'domain1',
|
'domain1',
|
||||||
'domain2',
|
'domain2',
|
||||||
'domino1',
|
'domino1',
|
||||||
'domino2',
|
'domino2',
|
||||||
'dry',
|
'dry',
|
||||||
'errorsmax',
|
'errorsmax',
|
||||||
'exchange1',
|
'exchange1',
|
||||||
'exchange2',
|
'exchange2',
|
||||||
'exitwhenover',
|
'exitwhenover',
|
||||||
'expunge1',
|
'expunge1',
|
||||||
'f1f2',
|
'f1f2',
|
||||||
'filterbuggyflags',
|
'filterbuggyflags',
|
||||||
'folder',
|
'folder',
|
||||||
'folderfirst',
|
'folderfirst',
|
||||||
'folderlast',
|
'folderlast',
|
||||||
'folderrec',
|
'folderrec',
|
||||||
'gmail1',
|
'gmail1',
|
||||||
'gmail2',
|
'gmail2',
|
||||||
'idatefromheader',
|
'idatefromheader',
|
||||||
'include',
|
'include',
|
||||||
'inet4',
|
'inet4',
|
||||||
'inet6',
|
'inet6',
|
||||||
'justconnect',
|
'justconnect',
|
||||||
'justfolders',
|
'justfolders',
|
||||||
'justfoldersizes',
|
'justfoldersizes',
|
||||||
'justlogin',
|
'justlogin',
|
||||||
'keepalive1',
|
'keepalive1',
|
||||||
'keepalive2',
|
'keepalive2',
|
||||||
'log',
|
'log',
|
||||||
'logdir',
|
'logdir',
|
||||||
'logfile',
|
'logfile',
|
||||||
'maxbytesafter',
|
'maxbytesafter',
|
||||||
'maxlinelength',
|
'maxlinelength',
|
||||||
'maxmessagespersecond',
|
'maxmessagespersecond',
|
||||||
@@ -314,62 +329,62 @@ $IMAPSYNC_OPTIONS = array(
|
|||||||
'maxsleep',
|
'maxsleep',
|
||||||
'minage',
|
'minage',
|
||||||
'minsize',
|
'minsize',
|
||||||
'noabletosearch',
|
'noabletosearch',
|
||||||
'noabletosearch1',
|
'noabletosearch1',
|
||||||
'noabletosearch2',
|
'noabletosearch2',
|
||||||
'noexpunge1',
|
'noexpunge1',
|
||||||
'noexpunge2',
|
'noexpunge2',
|
||||||
'nofoldersizesatend',
|
'nofoldersizesatend',
|
||||||
'noid',
|
'noid',
|
||||||
'nolog',
|
'nolog',
|
||||||
'nomixfolders',
|
'nomixfolders',
|
||||||
'noresyncflags',
|
'noresyncflags',
|
||||||
'nossl1',
|
'nossl1',
|
||||||
'nossl2',
|
'nossl2',
|
||||||
'nosyncacls',
|
'nosyncacls',
|
||||||
'notls1',
|
'notls1',
|
||||||
'notls2',
|
'notls2',
|
||||||
'nouidexpunge2',
|
'nouidexpunge2',
|
||||||
'nousecache',
|
'nousecache',
|
||||||
'oauthaccesstoken1',
|
'oauthaccesstoken1',
|
||||||
'oauthaccesstoken2',
|
'oauthaccesstoken2',
|
||||||
'oauthdirect1',
|
'oauthdirect1',
|
||||||
'oauthdirect2',
|
'oauthdirect2',
|
||||||
'office1',
|
'office1',
|
||||||
'office2',
|
'office2',
|
||||||
'pidfile',
|
'pidfile',
|
||||||
'pidfilelocking',
|
'pidfilelocking',
|
||||||
'prefix1',
|
'prefix1',
|
||||||
'prefix2',
|
'prefix2',
|
||||||
'proxyauth1',
|
'proxyauth1',
|
||||||
'proxyauth2',
|
'proxyauth2',
|
||||||
'resyncflags',
|
'resyncflags',
|
||||||
'resynclabels',
|
'resynclabels',
|
||||||
'search',
|
'search',
|
||||||
'search1',
|
'search1',
|
||||||
'search2',
|
'search2',
|
||||||
'sep1',
|
'sep1',
|
||||||
'sep2',
|
'sep2',
|
||||||
'showpasswords',
|
'showpasswords',
|
||||||
'skipemptyfolders',
|
'skipemptyfolders',
|
||||||
'ssl2',
|
'ssl2',
|
||||||
'sslargs1',
|
'sslargs1',
|
||||||
'sslargs2',
|
'sslargs2',
|
||||||
'subfolder1',
|
'subfolder1',
|
||||||
'subscribe',
|
'subscribe',
|
||||||
'subscribed',
|
'subscribed',
|
||||||
'syncacls',
|
'syncacls',
|
||||||
'syncduplicates',
|
'syncduplicates',
|
||||||
'syncinternaldates',
|
'syncinternaldates',
|
||||||
'synclabels',
|
'synclabels',
|
||||||
'tests',
|
'tests',
|
||||||
'testslive',
|
'testslive',
|
||||||
'testslive6',
|
'testslive6',
|
||||||
'tls2',
|
'tls2',
|
||||||
'truncmess',
|
'truncmess',
|
||||||
'usecache',
|
'usecache',
|
||||||
'useheader',
|
'useheader',
|
||||||
'useuid'
|
'useuid'
|
||||||
),
|
),
|
||||||
'blacklist' => array(
|
'blacklist' => array(
|
||||||
'skipmess',
|
'skipmess',
|
||||||
|
|||||||
+7
-5
@@ -11,8 +11,8 @@ if (isset($_SESSION['mailcow_cc_role']) && isset($_SESSION['oauth2_request'])) {
|
|||||||
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
|
||||||
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
|
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
|
||||||
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
|
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
|
||||||
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
|
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") {
|
||||||
header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}");
|
header("Location: /SOGo/so/");
|
||||||
} else {
|
} else {
|
||||||
header("Location: /user");
|
header("Location: /user");
|
||||||
}
|
}
|
||||||
@@ -33,16 +33,18 @@ $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
|
|||||||
|
|
||||||
$has_iam_sso = false;
|
$has_iam_sso = false;
|
||||||
if ($iam_provider){
|
if ($iam_provider){
|
||||||
$has_iam_sso = identity_provider("get-redirect") ? true : false;
|
$iam_redirect_url = identity_provider("get-redirect");
|
||||||
|
$has_iam_sso = $iam_redirect_url ? true : false;
|
||||||
}
|
}
|
||||||
|
$custom_login = customize('get', 'custom_login');
|
||||||
|
|
||||||
$template = 'user_index.twig';
|
$template = 'user_index.twig';
|
||||||
$template_data = [
|
$template_data = [
|
||||||
'oauth2_request' => @$_SESSION['oauth2_request'],
|
'oauth2_request' => @$_SESSION['oauth2_request'],
|
||||||
'is_mobileconfig' => str_contains($_SESSION['index_query_string'], 'mobileconfig'),
|
'is_mobileconfig' => str_contains($_SESSION['index_query_string'], 'mobileconfig'),
|
||||||
'login_delay' => @$_SESSION['ldelay'],
|
'login_delay' => @$_SESSION['ldelay'],
|
||||||
'has_iam_sso' => $has_iam_sso
|
'has_iam_sso' => $has_iam_sso,
|
||||||
|
'custom_login' => $custom_login,
|
||||||
];
|
];
|
||||||
|
|
||||||
$js_minifier->add('/web/js/site/index.js');
|
$js_minifier->add('/web/js/site/index.js');
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ $(document).ready(function() {
|
|||||||
$.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}});
|
$.notify({message: msg},{z_index: 20000, delay: auto_hide, type: type,placement: {from: "bottom",align: "right"},animate: {enter: 'animated fadeInUp',exit: 'animated fadeOutDown'}});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(".generate_password").click(async function( event ) {
|
$(".generate_password").click(async function( event ) {
|
||||||
try {
|
try {
|
||||||
var password_policy = await window.fetch("/api/v1/get/passwordpolicy", { method:'GET', cache:'no-cache' });
|
var password_policy = await window.fetch("/api/v1/get/passwordpolicy", { method:'GET', cache:'no-cache' });
|
||||||
var password_policy = await password_policy.json();
|
var password_policy = await password_policy.json();
|
||||||
random_passwd_length = password_policy.length;
|
random_passwd_length = password_policy.length;
|
||||||
@@ -48,7 +48,11 @@ $(document).ready(function() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
$(".rot-enc").html(function(){
|
$(".rot-enc").html(function(){
|
||||||
return str_rot13($(this).html())
|
footer_html = $(this).html();
|
||||||
|
footer_html = footer_html.replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
.replace(/&/g, '&').replace(/&nzc;/g, '&')
|
||||||
|
.replace(/"/g, '"').replace(/'/g, "'");
|
||||||
|
return str_rot13(footer_html)
|
||||||
});
|
});
|
||||||
// https://stackoverflow.com/questions/4399005/implementing-jquerys-shake-effect-with-animate
|
// https://stackoverflow.com/questions/4399005/implementing-jquerys-shake-effect-with-animate
|
||||||
function shake(div,interval,distance,times) {
|
function shake(div,interval,distance,times) {
|
||||||
@@ -125,7 +129,7 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// responsive tabs, scroll to opened tab
|
// responsive tabs, scroll to opened tab
|
||||||
$(document).on("shown.bs.collapse shown.bs.tab", function (e) {
|
$(document).on("shown.bs.collapse shown.bs.tab", function (e) {
|
||||||
var target = $(e.target);
|
var target = $(e.target);
|
||||||
@@ -409,4 +413,4 @@ function copyToClipboard(id) {
|
|||||||
// only works with https connections
|
// only works with https connections
|
||||||
navigator.clipboard.writeText(copyText.value);
|
navigator.clipboard.writeText(copyText.value);
|
||||||
mailcow_alert_box(lang.copy_to_clipboard, "success");
|
mailcow_alert_box(lang.copy_to_clipboard, "success");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,19 @@ jQuery(function($){
|
|||||||
$('.submit_rspamd_regex').attr({"disabled": true});
|
$('.submit_rspamd_regex').attr({"disabled": true});
|
||||||
});
|
});
|
||||||
$("#show_rspamd_global_filters").click(function() {
|
$("#show_rspamd_global_filters").click(function() {
|
||||||
$.get("inc/ajax/show_rspamd_global_filters.php");
|
$.get("/inc/ajax/show_rspamd_global_filters.php");
|
||||||
$("#confirm_show_rspamd_global_filters").hide();
|
$("#confirm_show_rspamd_global_filters").hide();
|
||||||
$("#rspamd_global_filters").removeClass("d-none");
|
$("#rspamd_global_filters").removeClass("d-none");
|
||||||
|
localStorage.setItem('rspamd_global_filters_confirmed', 'true');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
if (localStorage.getItem('rspamd_global_filters_confirmed') === 'true') {
|
||||||
|
$("#confirm_show_rspamd_global_filters").hide();
|
||||||
|
$("#rspamd_global_filters").removeClass("d-none");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$("#super_delete").click(function() { return confirm(lang.queue_ays); });
|
$("#super_delete").click(function() { return confirm(lang.queue_ays); });
|
||||||
|
|
||||||
$(".refresh_table").on('click', function(e) {
|
$(".refresh_table").on('click', function(e) {
|
||||||
@@ -558,7 +567,7 @@ jQuery(function($){
|
|||||||
} else if (table == 'oauth2clientstable') {
|
} else if (table == 'oauth2clientstable') {
|
||||||
$.each(data, function (i, item) {
|
$.each(data, function (i, item) {
|
||||||
item.action = '<div class="btn-group">' +
|
item.action = '<div class="btn-group">' +
|
||||||
'<a href="/edit.php?oauth2client=' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
'<a href="/edit/oauth2client/' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||||
'<a href="#" data-action="delete_selected" data-id="single-oauth2-client" data-api-url="delete/oauth2-client" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
'<a href="#" data-action="delete_selected" data-id="single-oauth2-client" data-api-url="delete/oauth2-client" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-xs-lg btn-xs-half btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
item.scope = "profile";
|
item.scope = "profile";
|
||||||
@@ -573,7 +582,7 @@ jQuery(function($){
|
|||||||
item.action = '<div class="btn-group">' +
|
item.action = '<div class="btn-group">' +
|
||||||
'<a href="/edit/domainadmin/' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
'<a href="/edit/domainadmin/' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
|
||||||
'<a href="#" data-action="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
'<a href="#" data-action="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-danger"><i class="bi bi-trash"></i> ' + lang.remove + '</a>' +
|
||||||
'<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-success"><i class="bi bi-person-fill"></i> Login</a>' +
|
'<a href="/domainadmin/?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-xs-lg btn-xs-third btn-success"><i class="bi bi-person-fill"></i> Login</a>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
});
|
});
|
||||||
} else if (table == 'adminstable') {
|
} else if (table == 'adminstable') {
|
||||||
@@ -655,7 +664,7 @@ jQuery(function($){
|
|||||||
$(this).html('<i class="bi bi-arrow-repeat icon-spin"></i> ');
|
$(this).html('<i class="bi bi-arrow-repeat icon-spin"></i> ');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: 'inc/ajax/relay_check.php',
|
url: '/inc/ajax/relay_check.php',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
data: $('#test_relayhost_form').serialize(),
|
data: $('#test_relayhost_form').serialize(),
|
||||||
complete: function (data) {
|
complete: function (data) {
|
||||||
@@ -681,7 +690,7 @@ jQuery(function($){
|
|||||||
$(this).html('<div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div> ');
|
$(this).html('<div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div> ');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: 'inc/ajax/transport_check.php',
|
url: '/inc/ajax/transport_check.php',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
data: $('#test_transport_form').serialize(),
|
data: $('#test_transport_form').serialize(),
|
||||||
complete: function (data) {
|
complete: function (data) {
|
||||||
@@ -715,7 +724,6 @@ jQuery(function($){
|
|||||||
$('.app_hide').off('change');
|
$('.app_hide').off('change');
|
||||||
$('.app_hide').on('change', function (e) {
|
$('.app_hide').on('change', function (e) {
|
||||||
var value = $(this).is(':checked') ? '1' : '0';
|
var value = $(this).is(':checked') ? '1' : '0';
|
||||||
console.log(value)
|
|
||||||
$(this).parent().children(':first-child').val(value);
|
$(this).parent().children(':first-child').val(value);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -789,6 +797,18 @@ jQuery(function($){
|
|||||||
$('.iam_ldap_rolemap_del').click(async function(e){
|
$('.iam_ldap_rolemap_del').click(async function(e){
|
||||||
deleteAttributeMappingRow(this, e);
|
deleteAttributeMappingRow(this, e);
|
||||||
});
|
});
|
||||||
|
$('.iam_redirect_add_keycloak').click(async function(e){
|
||||||
|
addRedirectUrlRow('#iam_keycloak_redirect_list', '.iam_keycloak_redirect_del', e);
|
||||||
|
});
|
||||||
|
$('.iam_redirect_add_generic').click(async function(e){
|
||||||
|
addRedirectUrlRow('#iam_generic_redirect_list', '.iam_generic_redirect_del', e);
|
||||||
|
});
|
||||||
|
$('.iam_keycloak_redirect_del').click(async function(e){
|
||||||
|
deleteRedirectUrlRow(this, e);
|
||||||
|
});
|
||||||
|
$('.iam_generic_redirect_del').click(async function(e){
|
||||||
|
deleteRedirectUrlRow(this, e);
|
||||||
|
});
|
||||||
// selecting identity provider
|
// selecting identity provider
|
||||||
$('#iam_provider').on('change', function(){
|
$('#iam_provider').on('change', function(){
|
||||||
// toggle password fields
|
// toggle password fields
|
||||||
@@ -833,4 +853,22 @@ jQuery(function($){
|
|||||||
if ($(elem).parent().parent().parent().parent().children().length > 1)
|
if ($(elem).parent().parent().parent().parent().children().length > 1)
|
||||||
$(elem).parent().parent().parent().remove();
|
$(elem).parent().parent().parent().remove();
|
||||||
}
|
}
|
||||||
|
function addRedirectUrlRow(list_id, del_class, e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var parent = $(list_id)
|
||||||
|
$(parent).children().last().clone().appendTo(parent);
|
||||||
|
var newChild = $(parent).children().last();
|
||||||
|
$(newChild).find('input').val('');
|
||||||
|
|
||||||
|
$(del_class).off('click');
|
||||||
|
$(del_class).click(async function(e){
|
||||||
|
deleteRedirectUrlRow(this, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function deleteRedirectUrlRow(elem, e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if ($(elem).parent().parent().parent().parent().children().length > 2)
|
||||||
|
$(elem).parent().parent().parent().remove();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ $(document).ready(function() {
|
|||||||
window.fetch("/api/v1/get/status/host/ip", { method:'GET', cache:'no-cache' }).then(function(response) {
|
window.fetch("/api/v1/get/status/host/ip", { method:'GET', cache:'no-cache' }).then(function(response) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}).then(function(data) {
|
}).then(function(data) {
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
// display host ips
|
// display host ips
|
||||||
if (data.ipv4)
|
if (data.ipv4)
|
||||||
$("#host_ipv4").text(data.ipv4);
|
$("#host_ipv4").text(data.ipv4);
|
||||||
@@ -1007,7 +1005,7 @@ jQuery(function($){
|
|||||||
"data-order": cellData.sortBy,
|
"data-order": cellData.sortBy,
|
||||||
"data-sort": cellData.sortBy
|
"data-sort": cellData.sortBy
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render: function (data) {
|
render: function (data) {
|
||||||
return data.value;
|
return data.value;
|
||||||
}
|
}
|
||||||
@@ -1032,7 +1030,7 @@ jQuery(function($){
|
|||||||
"data-order": cellData.sortBy,
|
"data-order": cellData.sortBy,
|
||||||
"data-sort": cellData.sortBy
|
"data-sort": cellData.sortBy
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render: function (data) {
|
render: function (data) {
|
||||||
return data.value;
|
return data.value;
|
||||||
}
|
}
|
||||||
@@ -1348,8 +1346,6 @@ function update_stats(timeout=5){
|
|||||||
window.fetch("/api/v1/get/status/host", {method:'GET',cache:'no-cache'}).then(function(response) {
|
window.fetch("/api/v1/get/status/host", {method:'GET',cache:'no-cache'}).then(function(response) {
|
||||||
return response.json();
|
return response.json();
|
||||||
}).then(function(data) {
|
}).then(function(data) {
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
if (data){
|
if (data){
|
||||||
// display table data
|
// display table data
|
||||||
$("#host_date").text(data.system_time);
|
$("#host_date").text(data.system_time);
|
||||||
@@ -1399,8 +1395,6 @@ function update_container_stats(timeout=5){
|
|||||||
var diskIOCtx = Chart.getChart(container + "_DiskIOChart");
|
var diskIOCtx = Chart.getChart(container + "_DiskIOChart");
|
||||||
var netIOCtx = Chart.getChart(container + "_NetIOChart");
|
var netIOCtx = Chart.getChart(container + "_NetIOChart");
|
||||||
|
|
||||||
console.log(container);
|
|
||||||
console.log(data);
|
|
||||||
prev_stats = null;
|
prev_stats = null;
|
||||||
if (data.length >= 2){
|
if (data.length >= 2){
|
||||||
prev_stats = data[data.length -2];
|
prev_stats = data[data.length -2];
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ $(document).ready(function() {
|
|||||||
// load tags
|
// load tags
|
||||||
if ($('#tags').length){
|
if ($('#tags').length){
|
||||||
var tagsEl = $('#tags').parent().find('.tag-values')[0];
|
var tagsEl = $('#tags').parent().find('.tag-values')[0];
|
||||||
console.log($(tagsEl).val())
|
|
||||||
var tags = JSON.parse($(tagsEl).val());
|
var tags = JSON.parse($(tagsEl).val());
|
||||||
$(tagsEl).val("");
|
$(tagsEl).val("");
|
||||||
|
|
||||||
|
|||||||
@@ -269,6 +269,24 @@ $(document).ready(function() {
|
|||||||
function setMailboxTemplateData(template){
|
function setMailboxTemplateData(template){
|
||||||
$("#addInputQuota").val(template.quota / 1048576);
|
$("#addInputQuota").val(template.quota / 1048576);
|
||||||
|
|
||||||
|
if (template.tagged_mail_handler === "subfolder"){
|
||||||
|
$('#tagged_mail_handler_subfolder').prop('checked', true);
|
||||||
|
$('#tagged_mail_handler_subject').prop('checked', false);
|
||||||
|
$('#tagged_mail_handler_none').prop('checked', false);
|
||||||
|
} else if(template.tagged_mail_handler === "subject"){
|
||||||
|
$('#tagged_mail_handler_subfolder').prop('checked', false);
|
||||||
|
$('#tagged_mail_handler_subject').prop('checked', true);
|
||||||
|
$('#tagged_mail_handler_none').prop('checked', false);
|
||||||
|
} else if(template.tagged_mail_handler === "none"){
|
||||||
|
$('#tagged_mail_handler_subfolder').prop('checked', false);
|
||||||
|
$('#tagged_mail_handler_subject').prop('checked', false);
|
||||||
|
$('#tagged_mail_handler_none').prop('checked', true);
|
||||||
|
} else {
|
||||||
|
$('#tagged_mail_handler_subfolder').prop('checked', false);
|
||||||
|
$('#tagged_mail_handler_subject').prop('checked', false);
|
||||||
|
$('#tagged_mail_handler_none').prop('checked', true);
|
||||||
|
}
|
||||||
|
|
||||||
if (template.quarantine_notification === "never"){
|
if (template.quarantine_notification === "never"){
|
||||||
$('#quarantine_notification_never').prop('checked', true);
|
$('#quarantine_notification_never').prop('checked', true);
|
||||||
$('#quarantine_notification_hourly').prop('checked', false);
|
$('#quarantine_notification_hourly').prop('checked', false);
|
||||||
@@ -1931,11 +1949,6 @@ jQuery(function($){
|
|||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
responsivePriority: 5,
|
responsivePriority: 5,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: lang.bcc_destinations,
|
|
||||||
data: 'bcc_dest',
|
|
||||||
defaultContent: ''
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: lang.sogo_visible,
|
title: lang.sogo_visible,
|
||||||
data: 'sogo_visible',
|
data: 'sogo_visible',
|
||||||
@@ -1954,6 +1967,15 @@ jQuery(function($){
|
|||||||
data: 'private_comment',
|
data: 'private_comment',
|
||||||
defaultContent: ''
|
defaultContent: ''
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: lang.internal,
|
||||||
|
data: 'internal',
|
||||||
|
defaultContent: '',
|
||||||
|
responsivePriority: 6,
|
||||||
|
render: function (data, type) {
|
||||||
|
return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: lang.active,
|
title: lang.active,
|
||||||
data: 'active',
|
data: 'active',
|
||||||
|
|||||||
+23
-19
@@ -90,31 +90,24 @@ jQuery(function($){
|
|||||||
console.log('error reading last logins');
|
console.log('error reading last logins');
|
||||||
},
|
},
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
$('.last-ui-login').html('');
|
|
||||||
$('.last-sasl-login').html('');
|
$('.last-sasl-login').html('');
|
||||||
if (data.ui.time) {
|
|
||||||
$('.last-ui-login').html('<i class="bi bi-person-fill"></i> ' + lang.last_ui_login + ': ' + unix_time_format(data.ui.time));
|
|
||||||
} else {
|
|
||||||
$('.last-ui-login').text(lang.no_last_login);
|
|
||||||
}
|
|
||||||
if (data.sasl) {
|
if (data.sasl) {
|
||||||
$('.last-sasl-login').append('<ul class="list-group">');
|
$('.last-sasl-login').append('<ul class="list-group">');
|
||||||
$.each(data.sasl, function (i, item) {
|
$.each(data.sasl, function (i, item) {
|
||||||
var datetime = new Date(item.datetime.replace(/-/g, "/"));
|
var datetime = new Date(item.datetime.replace(/-/g, "/"));
|
||||||
var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"});
|
||||||
var service = '<div class="badge bg-secondary">' + item.service.toUpperCase() + '</div>';
|
var service = '<div class="badge bg-secondary">' + item.service.toUpperCase() + '</div>';
|
||||||
var app_password = item.app_password ? ' <a href="/edit/app-passwd/' + item.app_password + '"><i class="bi bi-app-indicator"></i> ' + escapeHtml(item.app_password_name || "App") + '</a>' : '';
|
var app_password = item.app_password ? ' <a href="/edit/app-passwd/' + item.app_password + '"><i class="bi bi-key-fill"></i><span class="ms-1">' + escapeHtml(item.app_password_name || "App") + '</span></a>' : '';
|
||||||
var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '<a href="https://bgp.he.net/ip/' + item.real_rip + '" target="_blank">' + item.real_rip + "</a>";
|
var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '<a href="https://bgp.tools/prefix/' + item.real_rip + '" target="_blank">' + item.real_rip + "</a>";
|
||||||
var ip_location = item.location ? ' <span class="flag-icon flag-icon-' + item.location.toLowerCase() + '"></span>' : '';
|
var ip_location = item.location ? ' <span class="flag-icon flag-icon-' + item.location.toLowerCase() + '"></span>' : '';
|
||||||
var ip_data = real_rip + ip_location + app_password;
|
var ip_data = real_rip + ip_location + app_password;
|
||||||
|
|
||||||
$(".last-sasl-login").append(`
|
$(".last-sasl-login").append(`
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||||
<div class="ms-2 me-auto d-flex flex-column">
|
<div class="ms-2 me-auto d-flex flex-column">
|
||||||
<div class="fw-bold">` + real_rip + `</div>
|
<div class="fw-bold">` + ip_location + real_rip + `</div>
|
||||||
<small class="fst-italic mt-2">` + service + ` ` + local_datetime + `</small>
|
<small class="fst-italic mt-2">` + service + ` ` + local_datetime + `</small>` + app_password + `
|
||||||
</div>
|
</div>
|
||||||
<span>` + ip_location + `</span>
|
|
||||||
</li>
|
</li>
|
||||||
`);
|
`);
|
||||||
})
|
})
|
||||||
@@ -175,7 +168,6 @@ jQuery(function($){
|
|||||||
type: "GET",
|
type: "GET",
|
||||||
url: "/api/v1/get/time_limited_aliases",
|
url: "/api/v1/get/time_limited_aliases",
|
||||||
dataSrc: function(data){
|
dataSrc: function(data){
|
||||||
console.log(data);
|
|
||||||
$.each(data, function (i, item) {
|
$.each(data, function (i, item) {
|
||||||
if (acl_data.spam_alias === 1) {
|
if (acl_data.spam_alias === 1) {
|
||||||
item.action = '<div class="btn-group">' +
|
item.action = '<div class="btn-group">' +
|
||||||
@@ -183,6 +175,10 @@ jQuery(function($){
|
|||||||
'</div>';
|
'</div>';
|
||||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />';
|
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />';
|
||||||
item.address = escapeHtml(item.address);
|
item.address = escapeHtml(item.address);
|
||||||
|
item.validity = {
|
||||||
|
value: item.validity,
|
||||||
|
permanent: item.permanent
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
|
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
|
||||||
@@ -226,9 +222,21 @@ jQuery(function($){
|
|||||||
title: lang.alias_valid_until,
|
title: lang.alias_valid_until,
|
||||||
data: 'validity',
|
data: 'validity',
|
||||||
defaultContent: '',
|
defaultContent: '',
|
||||||
createdCell: function(td, cellData) {
|
render: function (data, type) {
|
||||||
createSortableDate(td, cellData)
|
var date = new Date(data.value ? data.value * 1000 : 0);
|
||||||
}
|
switch (type) {
|
||||||
|
case "sort":
|
||||||
|
if (data.permanent) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return date.getTime();
|
||||||
|
default:
|
||||||
|
if (data.permanent) {
|
||||||
|
return lang.forever;
|
||||||
|
}
|
||||||
|
return date.toLocaleDateString(LOCALE, DATETIME_FORMAT);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: lang.created_on,
|
title: lang.created_on,
|
||||||
@@ -268,7 +276,6 @@ jQuery(function($){
|
|||||||
type: "GET",
|
type: "GET",
|
||||||
url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
|
url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
|
||||||
dataSrc: function(data){
|
dataSrc: function(data){
|
||||||
console.log(data);
|
|
||||||
$.each(data, function (i, item) {
|
$.each(data, function (i, item) {
|
||||||
item.user1 = escapeHtml(item.user1);
|
item.user1 = escapeHtml(item.user1);
|
||||||
item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>'
|
item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>'
|
||||||
@@ -424,7 +431,6 @@ jQuery(function($){
|
|||||||
type: "GET",
|
type: "GET",
|
||||||
url: '/api/v1/get/app-passwd/all',
|
url: '/api/v1/get/app-passwd/all',
|
||||||
dataSrc: function(data){
|
dataSrc: function(data){
|
||||||
console.log(data);
|
|
||||||
$.each(data, function (i, item) {
|
$.each(data, function (i, item) {
|
||||||
item.name = escapeHtml(item.name)
|
item.name = escapeHtml(item.name)
|
||||||
item.protocols = []
|
item.protocols = []
|
||||||
@@ -520,7 +526,6 @@ jQuery(function($){
|
|||||||
type: "GET",
|
type: "GET",
|
||||||
url: '/api/v1/get/policy_wl_mailbox',
|
url: '/api/v1/get/policy_wl_mailbox',
|
||||||
dataSrc: function(data){
|
dataSrc: function(data){
|
||||||
console.log(data);
|
|
||||||
$.each(data, function (i, item) {
|
$.each(data, function (i, item) {
|
||||||
if (validateEmail(item.object)) {
|
if (validateEmail(item.object)) {
|
||||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_wl_mailbox" name="multi_select" value="' + item.prefid + '" />';
|
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_wl_mailbox" name="multi_select" value="' + item.prefid + '" />';
|
||||||
@@ -591,7 +596,6 @@ jQuery(function($){
|
|||||||
type: "GET",
|
type: "GET",
|
||||||
url: '/api/v1/get/policy_bl_mailbox',
|
url: '/api/v1/get/policy_bl_mailbox',
|
||||||
dataSrc: function(data){
|
dataSrc: function(data){
|
||||||
console.log(data);
|
|
||||||
$.each(data, function (i, item) {
|
$.each(data, function (i, item) {
|
||||||
if (validateEmail(item.object)) {
|
if (validateEmail(item.object)) {
|
||||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_bl_mailbox" name="multi_select" value="' + item.prefid + '" />';
|
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_bl_mailbox" name="multi_select" value="' + item.prefid + '" />';
|
||||||
|
|||||||
@@ -324,6 +324,9 @@ if (isset($_GET['query'])) {
|
|||||||
case "app-passwd":
|
case "app-passwd":
|
||||||
process_add_return(app_passwd('add', $attr));
|
process_add_return(app_passwd('add', $attr));
|
||||||
break;
|
break;
|
||||||
|
case "mta-sts":
|
||||||
|
process_add_return(mailbox('add', 'mta_sts', $attr));
|
||||||
|
break;
|
||||||
// return no route found if no case is matched
|
// return no route found if no case is matched
|
||||||
default:
|
default:
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
@@ -1976,6 +1979,9 @@ if (isset($_GET['query'])) {
|
|||||||
case "ip_check":
|
case "ip_check":
|
||||||
process_edit_return(customize('edit', 'ip_check', $attr));
|
process_edit_return(customize('edit', 'ip_check', $attr));
|
||||||
break;
|
break;
|
||||||
|
case "custom_login":
|
||||||
|
process_edit_return(customize('edit', 'custom_login', $attr));
|
||||||
|
break;
|
||||||
case "self":
|
case "self":
|
||||||
if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
||||||
process_edit_return(domain_admin('edit', $attr));
|
process_edit_return(domain_admin('edit', $attr));
|
||||||
@@ -1986,6 +1992,7 @@ if (isset($_GET['query'])) {
|
|||||||
break;
|
break;
|
||||||
case "cors":
|
case "cors":
|
||||||
process_edit_return(cors('edit', $attr));
|
process_edit_return(cors('edit', $attr));
|
||||||
|
break;
|
||||||
case "identity-provider":
|
case "identity-provider":
|
||||||
process_edit_return(identity_provider('edit', $attr));
|
process_edit_return(identity_provider('edit', $attr));
|
||||||
break;
|
break;
|
||||||
@@ -1998,6 +2005,9 @@ if (isset($_GET['query'])) {
|
|||||||
case "reset-password-notification":
|
case "reset-password-notification":
|
||||||
process_edit_return(reset_password('edit_notification', $attr));
|
process_edit_return(reset_password('edit_notification', $attr));
|
||||||
break;
|
break;
|
||||||
|
case "mta-sts":
|
||||||
|
process_edit_return(mailbox('edit', 'mta_sts', array_merge(array('domains' => $items), $attr)));
|
||||||
|
break;
|
||||||
// return no route found if no case is matched
|
// return no route found if no case is matched
|
||||||
default:
|
default:
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,16 @@
|
|||||||
"spam_alias": "Àlies temporals",
|
"spam_alias": "Àlies temporals",
|
||||||
"spam_score": "Puntuació de correu brossa",
|
"spam_score": "Puntuació de correu brossa",
|
||||||
"tls_policy": "Política TLS",
|
"tls_policy": "Política TLS",
|
||||||
"unlimited_quota": "Quota ilimitada per bústies de correo"
|
"unlimited_quota": "Quota ilimitada per bústies de correo",
|
||||||
|
"delimiter_action": "Acció delimitadora",
|
||||||
|
"domain_relayhost": "Canviar relayhost per un domini",
|
||||||
|
"extend_sender_acl": "Permetre extendre l'ACL del remitent per adreces externes",
|
||||||
|
"mailbox_relayhost": "Canvia el host de reenviament per una bústia",
|
||||||
|
"pushover": "Pushover",
|
||||||
|
"pw_reset": "Permetre el restabliment de la contrasenya de l'usuari mailcow",
|
||||||
|
"ratelimit": "Límit de peticions",
|
||||||
|
"smtp_ip_access": "Canvia hosts permesos per SMTP",
|
||||||
|
"sogo_access": "Permetre la gestió d'accés a SOGo"
|
||||||
},
|
},
|
||||||
"add": {
|
"add": {
|
||||||
"activate_filter_warn": "All other filters will be deactivated, when active is checked.",
|
"activate_filter_warn": "All other filters will be deactivated, when active is checked.",
|
||||||
@@ -73,7 +82,25 @@
|
|||||||
"validate": "Validar",
|
"validate": "Validar",
|
||||||
"validation_success": "Validated successfully",
|
"validation_success": "Validated successfully",
|
||||||
"app_name": "Nom de l'aplicació",
|
"app_name": "Nom de l'aplicació",
|
||||||
"app_password": "Afegir contrasenya a l'aplicació"
|
"app_password": "Afegir contrasenya a l'aplicació",
|
||||||
|
"app_passwd_protocols": "Protocols autoritzats per la contrasenya de l'aplicació",
|
||||||
|
"bcc_dest_format": "La destinació c/o ha de ser una única adreça de correu vàlida.<br>Si necessiteu enviar una còpia a diverses adreces, creeu un àlies i utilitzeu-lo aquí.",
|
||||||
|
"comment_info": "Els comentaris privats no són visibles per l'usuari, mentre que els comentaris públics apareixen com una descripció emergent a la informació de l'usuari",
|
||||||
|
"custom_params": "Paràmetres personalitzats",
|
||||||
|
"custom_params_hint": "Correcte: --param=xy, incorrecte: --param xy",
|
||||||
|
"destination": "Destí",
|
||||||
|
"disable_login": "No permetre l'inici de sessió (els missatges entrants continuen sent acceptats)",
|
||||||
|
"domain_matches_hostname": "El domini %s coincideix amb el nom del servidor",
|
||||||
|
"dry": "Simular la sincronització",
|
||||||
|
"gal": "Llista d'adreces global",
|
||||||
|
"generate": "genereu",
|
||||||
|
"inactive": "Inactiu",
|
||||||
|
"internal": "Intern",
|
||||||
|
"internal_info": "Els àlies interns són només accessibles des del mateix domini o els àlies de dominis.",
|
||||||
|
"mailbox_quota_def": "Quota per defecte de la bústia",
|
||||||
|
"nexthop": "Següent salt",
|
||||||
|
"private_comment": "Comentari privat",
|
||||||
|
"public_comment": "Comentari púlbic"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"access": "Accés",
|
"access": "Accés",
|
||||||
@@ -557,4 +584,4 @@
|
|||||||
"week": "Setmana",
|
"week": "Setmana",
|
||||||
"weeks": "Setmanes"
|
"weeks": "Setmanes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+178
-58
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"acl": {
|
"acl": {
|
||||||
"alias_domains": "Doménové aliasy",
|
"alias_domains": "Doménové aliasy",
|
||||||
"app_passwds": "Hesla aplikací",
|
"app_passwds": "Správa hesel aplikací",
|
||||||
"bcc_maps": "BCC mapy",
|
"bcc_maps": "BCC mapy",
|
||||||
"delimiter_action": "Zacházení s označkovanou poštou",
|
"delimiter_action": "Zacházení s označkovanou poštou",
|
||||||
"domain_desc": "Změnit popis domény",
|
"domain_desc": "Změnit popis domény",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"sogo_access": "Správa přístupu do SOGo",
|
"sogo_access": "Správa přístupu do SOGo",
|
||||||
"sogo_profile_reset": "Resetování profilu SOGo",
|
"sogo_profile_reset": "Resetování profilu SOGo",
|
||||||
"spam_alias": "Dočasné aliasy",
|
"spam_alias": "Dočasné aliasy",
|
||||||
"spam_policy": "Blacklist/Whitelist",
|
"spam_policy": "Denylist/Allowlist",
|
||||||
"spam_score": "Skóre spamu",
|
"spam_score": "Skóre spamu",
|
||||||
"syncjobs": "Synchronizační úlohy",
|
"syncjobs": "Synchronizační úlohy",
|
||||||
"tls_policy": "Pravidla TLS",
|
"tls_policy": "Pravidla TLS",
|
||||||
@@ -82,12 +82,12 @@
|
|||||||
"password": "Heslo",
|
"password": "Heslo",
|
||||||
"password_repeat": "Potvrzení nového hesla (opakujte)",
|
"password_repeat": "Potvrzení nového hesla (opakujte)",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"post_domain_add": "Po přidání nové domény je nutné restartovat SOGo kontejner!",
|
"post_domain_add": "Po přidání nové domény se musí restartovat kontejner SOGo!<br><br>Je také třeba ověřit nastavení DNS nové domény. Po ověření restartujte kontejner \"acme-mailcow\", aby se vygenerovaly certifikáty domény (autoconfig.<domain>, autodiscover.<domain>).<br>Tento krok je volitelný, a provede se automaticky každých 24 hodin.",
|
||||||
"private_comment": "Soukromý komentář",
|
"private_comment": "Soukromý komentář",
|
||||||
"public_comment": "Veřejný komentář",
|
"public_comment": "Veřejný komentář",
|
||||||
"quota_mb": "Kvóta (MiB)",
|
"quota_mb": "Kvóta (MiB)",
|
||||||
"relay_all": "Předávání všech příjemců",
|
"relay_all": "Předávání všech příjemců",
|
||||||
"relay_all_info": "<small>Pokud se rozhodnete <b>nepředávat</b> všechny příjemce, musíte přidat prázdnou mailovou schránku pro každého příjemce, který se má předávat.</small>",
|
"relay_all_info": "↪Pokud se rozhodnete <b>nepředávat</b> všechny příjemce, musíte přidat prázdnou mailovou schránku pro každého příjemce, který se má předávat.",
|
||||||
"relay_domain": "Předávání domény",
|
"relay_domain": "Předávání domény",
|
||||||
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> U této domény lze pro konkrétní cíl nastavit transportní mapu. Není-li nastavena, použije se MX záznam.",
|
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> U této domény lze pro konkrétní cíl nastavit transportní mapu. Není-li nastavena, použije se MX záznam.",
|
||||||
"relay_unknown_only": "Předávat jen neexistující schránky. Doručení do existujících proběhne lokálně.",
|
"relay_unknown_only": "Předávat jen neexistující schránky. Doručení do existujících proběhne lokálně.",
|
||||||
@@ -109,7 +109,9 @@
|
|||||||
"validate": "Ověřit",
|
"validate": "Ověřit",
|
||||||
"validation_success": "Úspěšně ověřeno",
|
"validation_success": "Úspěšně ověřeno",
|
||||||
"tags": "Štítky",
|
"tags": "Štítky",
|
||||||
"dry": "Simulovat synchronizaci"
|
"dry": "Simulovat synchronizaci",
|
||||||
|
"internal": "Interní",
|
||||||
|
"internal_info": "Interní aliasy jsou přístupné jen z vlastních domén nebo jejich aliasů."
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"access": "Přístupy",
|
"access": "Přístupy",
|
||||||
@@ -147,7 +149,7 @@
|
|||||||
"arrival_time": "Čas zařazení do fronty (čas na serveru)",
|
"arrival_time": "Čas zařazení do fronty (čas na serveru)",
|
||||||
"authed_user": "Přihlášený uživatel",
|
"authed_user": "Přihlášený uživatel",
|
||||||
"ays": "Opravdu chcete pokračovat?",
|
"ays": "Opravdu chcete pokračovat?",
|
||||||
"ban_list_info": "Seznam blokovaných IP adres je zobrazen níže: <b>síť (zbývající čas blokování) - [akce]</b>.<br />IP adresy zařazené pro odblokování budou z aktivního seznamu odebrány během několika sekund.<br />Červeně označené položky jsou pernamentní bloky z blacklistu.",
|
"ban_list_info": "Viz seznam zablokovaných IP níže: <b>síť (zbývající doba zablokování) - [akce]</b>.<br />IP adresy zařazené pro odblokování budou z aktivního seznamu odebrány během pár sekund.<br />Červeně označeny jsou položky z trvalých seznamů.",
|
||||||
"change_logo": "Změnit logo",
|
"change_logo": "Změnit logo",
|
||||||
"logo_normal_label": "Normální",
|
"logo_normal_label": "Normální",
|
||||||
"logo_dark_label": "Inverzní pro tmavý režim",
|
"logo_dark_label": "Inverzní pro tmavý režim",
|
||||||
@@ -181,16 +183,16 @@
|
|||||||
"empty": "Žádné výsledky",
|
"empty": "Žádné výsledky",
|
||||||
"excludes": "Vyloučit tyto příjemce",
|
"excludes": "Vyloučit tyto příjemce",
|
||||||
"f2b_ban_time": "Doba blokování (s)",
|
"f2b_ban_time": "Doba blokování (s)",
|
||||||
"f2b_blacklist": "Sítě/hostitelé na blacklistu",
|
"f2b_blacklist": "Sítě či hostitelé na seznamu zákazů",
|
||||||
"f2b_filter": "Regex filtre",
|
"f2b_filter": "Regex filtre",
|
||||||
"f2b_list_info": "Síť nebo hostitelé na blacklistu mají vždy větší váhu než položky na whitelistu. Blacklist se sestavuje vždy při startu kontejneru.",
|
"f2b_list_info": "Sítě či hostitelé na seznamu zákazů mají vždy větší váhu než položky na seznamu povolení. <b>Každá úprava seznamu trvá pár sekund.</b>",
|
||||||
"f2b_max_attempts": "Max. pokusů",
|
"f2b_max_attempts": "Max. pokusů",
|
||||||
"f2b_netban_ipv4": "Rozsah IPv4 podsítě k zablokování (8-32)",
|
"f2b_netban_ipv4": "Rozsah IPv4 podsítě k zablokování (8-32)",
|
||||||
"f2b_netban_ipv6": "Rozsah IPv6 podsítě k zablokování (8-128)",
|
"f2b_netban_ipv6": "Rozsah IPv6 podsítě k zablokování (8-128)",
|
||||||
"f2b_parameters": "Parametry automatického firewallu",
|
"f2b_parameters": "Parametry automatického firewallu",
|
||||||
"f2b_regex_info": "Záznamy které se berou v úvahu: SOGo, Postfix, Dovecot, PHP-FPM.",
|
"f2b_regex_info": "Záznamy které se berou v úvahu: SOGo, Postfix, Dovecot, PHP-FPM.",
|
||||||
"f2b_retry_window": "Časový horizont pro maximum pokusů (s)",
|
"f2b_retry_window": "Časový horizont pro maximum pokusů (s)",
|
||||||
"f2b_whitelist": "Sítě/hostitelé na whitelistu",
|
"f2b_whitelist": "Sítě či hostitelé na seznamu povolení",
|
||||||
"filter_table": "Tabulka filtrů",
|
"filter_table": "Tabulka filtrů",
|
||||||
"forwarding_hosts": "Předávající servery",
|
"forwarding_hosts": "Předávající servery",
|
||||||
"forwarding_hosts_add_hint": "Lze zadat IPv4/IPv6 adresy, sítě ve formátu CIDR, názvy serverů (budou převedeny na IP adresy) nebo názvy domén (budou převedeny na IP pomocí SPF záznamů, příp. MX záznamů).",
|
"forwarding_hosts_add_hint": "Lze zadat IPv4/IPv6 adresy, sítě ve formátu CIDR, názvy serverů (budou převedeny na IP adresy) nebo názvy domén (budou převedeny na IP pomocí SPF záznamů, příp. MX záznamů).",
|
||||||
@@ -256,7 +258,7 @@
|
|||||||
"quarantine_exclude_domains": "Vyloučené domény a doménové aliasy",
|
"quarantine_exclude_domains": "Vyloučené domény a doménové aliasy",
|
||||||
"quarantine_max_age": "Maximální stáří ve dnech<br><small>Hodnota musí být rovna nebo větší než 1 den.</small>",
|
"quarantine_max_age": "Maximální stáří ve dnech<br><small>Hodnota musí být rovna nebo větší než 1 den.</small>",
|
||||||
"quarantine_max_score": "Neposílat notifikace pokud je spam skóre větší než hodnota:<br><small>Výchozí je 9999.0</small>",
|
"quarantine_max_score": "Neposílat notifikace pokud je spam skóre větší než hodnota:<br><small>Výchozí je 9999.0</small>",
|
||||||
"quarantine_max_size": "Maximální velikost v MiB (větší prvky budou smazány)<br />0 <b>neznamená</b> neomezeno.",
|
"quarantine_max_size": "Maximální velikost v MiB (větší prvky budou smazány)<br /><small>0 <b>neznamená</b> neomezeno.</small>",
|
||||||
"quarantine_notification_html": "Šablona upozornění:<br><small>Ponechte prázdné, aby se obnovila výchozí šablona.</small>",
|
"quarantine_notification_html": "Šablona upozornění:<br><small>Ponechte prázdné, aby se obnovila výchozí šablona.</small>",
|
||||||
"quarantine_notification_sender": "Odesílatel upozornění",
|
"quarantine_notification_sender": "Odesílatel upozornění",
|
||||||
"quarantine_notification_subject": "Předmět upozornění",
|
"quarantine_notification_subject": "Předmět upozornění",
|
||||||
@@ -264,7 +266,7 @@
|
|||||||
"quarantine_release_format": "Formát propuštěných položek",
|
"quarantine_release_format": "Formát propuštěných položek",
|
||||||
"quarantine_release_format_att": "Jako příloha",
|
"quarantine_release_format_att": "Jako příloha",
|
||||||
"quarantine_release_format_raw": "Nezměněný originál",
|
"quarantine_release_format_raw": "Nezměněný originál",
|
||||||
"quarantine_retention_size": "Počet zadržených zpráv na mailovou schránku<br />0 znamená <b>neaktivní</b>.",
|
"quarantine_retention_size": "Počet zadržených zpráv na mailovou schránku<br /><small>0 znamená <b>neaktivní</b>.</small>",
|
||||||
"quota_notification_html": "Šablona upozornění:<br><small>Ponechte prázdné, aby se obnovila výchozí šablona.</small>",
|
"quota_notification_html": "Šablona upozornění:<br><small>Ponechte prázdné, aby se obnovila výchozí šablona.</small>",
|
||||||
"quota_notification_sender": "Odesílatel upozornění",
|
"quota_notification_sender": "Odesílatel upozornění",
|
||||||
"quota_notification_subject": "Předmět upozornění",
|
"quota_notification_subject": "Předmět upozornění",
|
||||||
@@ -283,7 +285,7 @@
|
|||||||
"relay_rcpt": "\"Komu:\" adresa",
|
"relay_rcpt": "\"Komu:\" adresa",
|
||||||
"relay_run": "Provést test",
|
"relay_run": "Provést test",
|
||||||
"relayhosts": "Transporty podle odesílatele",
|
"relayhosts": "Transporty podle odesílatele",
|
||||||
"relayhosts_hint": "Zde definujte transporty podle odesílatele, jež pak můžete použít v nastavení domény.<br>\r\nProtokol transportu je vždy \"smtp:\". Bere se v potaz uživatelské nastavení odchozího TLS.",
|
"relayhosts_hint": "Zde definujte transporty podle odesílatele, jež pak můžete použít v nastavení domény.<br>\nProtokol transportu je vždy \"smtp:\" a použije se TLS, je-li nabídnuto. Zabalené TLS (SMTPS) se nepodporuje. Bere se v potaz uživatelské nastavení odchozího TLS.<br>\nTýká se vybraných domén včetně doménových aliasů.",
|
||||||
"remove": "Smazat",
|
"remove": "Smazat",
|
||||||
"remove_row": "Smazat řádek",
|
"remove_row": "Smazat řádek",
|
||||||
"reset_default": "Obnovit výchozí nastavení",
|
"reset_default": "Obnovit výchozí nastavení",
|
||||||
@@ -299,11 +301,11 @@
|
|||||||
"rsettings_preset_2": "Postmasteři chtějí dostávat spam",
|
"rsettings_preset_2": "Postmasteři chtějí dostávat spam",
|
||||||
"rsettings_preset_3": "Povolit jen určité odesílatele pro schránku (např. jen interní schránka)",
|
"rsettings_preset_3": "Povolit jen určité odesílatele pro schránku (např. jen interní schránka)",
|
||||||
"rsettings_preset_4": "Deaktivujte Rspamd pro doménu",
|
"rsettings_preset_4": "Deaktivujte Rspamd pro doménu",
|
||||||
"rspamd_com_settings": "<a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd dokumentace</a>\r\n - Název nastavení bude automaticky vygenerován, viz níže uvedené předvolby.",
|
"rspamd_com_settings": "Název nastavení se vygeneruje automaticky, viz ukázky nastavení níže. Více informací viz <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd dokumentace</a>",
|
||||||
"rspamd_global_filters": "Mapa globálních filtrů",
|
"rspamd_global_filters": "Mapa globálních filtrů",
|
||||||
"rspamd_global_filters_agree": "Budu opatrný!",
|
"rspamd_global_filters_agree": "Budu opatrný!",
|
||||||
"rspamd_global_filters_info": "Mapa globálních filtrů obsahuje jiné globální black- a whitelisty.",
|
"rspamd_global_filters_info": "Mapa globálních filtrů obsahuje různé seznamy povolených a zakázaných serverů",
|
||||||
"rspamd_global_filters_regex": "Názvy jsou dostatečným vysvětlením. Musí obsahovat jen platné regulární výrazy ve formátu \"/vyraz/parametry\" (e.g. <code>/.+@domena\\.tld/i</code>).<br>\r\n Každý výraz bude podroben základní kontrole, přesto je možné Rspamd 'rozbít', nebude-li syntax zcela korektní.<br>\r\n Rspamd se pokusí načíst mapu po každé změně. V případě potíží, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restartujte Rspamd</a>, aby se konfigurace načetla explicitně.",
|
"rspamd_global_filters_regex": "Názvy stačí k vysvětlení. Položky musejí obsahovat jen platné regulární výrazy ve tvaru \"/vyraz/parametry\" (e.g. <code>/.+@domena\\.tld/i</code>).<br>\n Každý výraz bude podroben základní kontrole, přesto je možné Rspamd 'rozbít', nebude-li syntax zcela korektní.<br>\n Rspamd se pokusí po každé změně načíst mapu znovu. V případě potíží <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restartujte Rspamd</a>, aby se konfigurace načetla explicitně.",
|
||||||
"rspamd_settings_map": "Nastavení Rspamd",
|
"rspamd_settings_map": "Nastavení Rspamd",
|
||||||
"sal_level": "Úroveň 'Moo'",
|
"sal_level": "Úroveň 'Moo'",
|
||||||
"save": "Uložit změny",
|
"save": "Uložit změny",
|
||||||
@@ -324,8 +326,8 @@
|
|||||||
"to_top": "Zpět na začátek",
|
"to_top": "Zpět na začátek",
|
||||||
"transport_dest_format": "Formát: example.org, .example.org, *, box@example.org (vícero položek lze oddělit čárkou)",
|
"transport_dest_format": "Formát: example.org, .example.org, *, box@example.org (vícero položek lze oddělit čárkou)",
|
||||||
"transport_maps": "Transportní mapy",
|
"transport_maps": "Transportní mapy",
|
||||||
"transport_test_rcpt_info": "• Na otestování odchozí pošty je možné použít null@hosted.mailcow.de jako adresáta",
|
"transport_test_rcpt_info": "• K otestování předávání pošty ven použijte null@hosted.mailcow.de.",
|
||||||
"transports_hint": "→ Položka transportní mapy <b>přebíjí</b> transportní mapu podle odesílatele</b>.<br>\r\n→ Uživatelské nastavení odchozího TLS se ignoruje a lze je výhradně vynutit mapováním TLS pravidel.<br>\r\n→ Protokol transportu je vždy \"smtp:\".<br>\r\n→ Adresy, jež odpovídají výrazu \"/localhost$/\", se vždy předají přes \"local:\", takže nejsou zahrnuty do definice cíle \"*\".<br>\r\n→ Pro stanovení přihlašovacích údajů dalšího skoku, např. \"[host]:25\", bude Postfix <b>vždy</b> hledat nejdříve \"host\" a teprve pak \"[host]:25\". Kvůli tomu nelze použít současně \"host\" a \"[host]:25\"",
|
"transports_hint": "• Položka transportní mapy <b>přebíjí</b> transportní mapu podle odesílatele</b>.<br>\n• Transporty založené na MX mají přednost.<br>\n• Uživatelské nastavení odchozího TLS se ignoruje a lze je vynutit výhradně mapou TLS pravidel.<br>\n• Transportní služnou pro tyto transporty je vždy \"smtp:\" a použije se TLS, je-li nabídnuto. Zabalené TLS (SMTPS) se nepodporuje.<br>\n• Adresy, jež odpovídají výrazu \"/localhost$/\", se vždy předají přes \"local:\", takže nejsou zahrnuty do definice cíle \"*\".<br>\n• Pro stanovení přihlašovacích údajů dalšího skoku, např. \"[host]:25\", bude Postfix <b>vždy</b> hledat nejdříve \"host\" a teprve pak \"[host]:25\". Kvůli tomu nelze použít současně \"host\" a \"[host]:25\".",
|
||||||
"ui_footer": "Pata stránka (HTML povoleno)",
|
"ui_footer": "Pata stránka (HTML povoleno)",
|
||||||
"ui_header_announcement": "Oznámení",
|
"ui_header_announcement": "Oznámení",
|
||||||
"ui_header_announcement_active": "Nastavit jako aktivní",
|
"ui_header_announcement_active": "Nastavit jako aktivní",
|
||||||
@@ -344,17 +346,72 @@
|
|||||||
"validate_license_now": "Ověřit GUID na licenčním serveru",
|
"validate_license_now": "Ověřit GUID na licenčním serveru",
|
||||||
"verify": "Ověřit",
|
"verify": "Ověřit",
|
||||||
"yes": "✓",
|
"yes": "✓",
|
||||||
"f2b_ban_time_increment": "Délka banu je prodlužována s každým dalším banem",
|
"f2b_ban_time_increment": "Délka bloku se prodlužuje s každým dalším zablokováním",
|
||||||
"f2b_max_ban_time": "Maximální délka banu (s)",
|
"f2b_max_ban_time": "Maximální délka bloku (s)",
|
||||||
"cors_settings": "Nastavení CORS",
|
"cors_settings": "Nastavení CORS",
|
||||||
"queue_unban": "zrušit ban",
|
"queue_unban": "odblokovat",
|
||||||
"password_reset_info": "Pokud není zadán žádný e-mail pro obnovení, nelze tuto funkci použít.",
|
"password_reset_info": "Pokud není zadán žádný e-mail pro obnovení, nelze tuto funkci použít.",
|
||||||
"password_reset_settings": "Nastavení obnovení hesla",
|
"password_reset_settings": "Nastavení obnovení hesla",
|
||||||
"password_settings": "Nastavení hesel",
|
"password_settings": "Nastavení hesel",
|
||||||
"password_reset_tmpl_html": "HTML šablona",
|
"password_reset_tmpl_html": "HTML šablona",
|
||||||
"password_reset_tmpl_text": "Textová šablona",
|
"password_reset_tmpl_text": "Textová šablona",
|
||||||
"reset_password_vars": "<code>{{link}}</code> Vygenerovaný odkaz pro obnovení hesla<br><code>{{username}}</code> Název mailboxu uživatele, který požádal o resetování hesla.<br><code>{{username2}}</code> Název schránky pro obnovení<br><code>{{date}}</code> Datum podání žádosti o obnovení hesla<br><code>{{token_lifetime}}</code> Délka životnosti tokenu v minutách<br><code>{{hostname}}</code> Název serveru mailcow",
|
"reset_password_vars": "<code>{{link}}</code> Vygenerovaný odkaz pro obnovení hesla<br><code>{{username}}</code> Název mailboxu uživatele, který požádal o resetování hesla.<br><code>{{username2}}</code> Název schránky pro obnovení<br><code>{{date}}</code> Datum podání žádosti o obnovení hesla<br><code>{{token_lifetime}}</code> Délka životnosti tokenu v minutách<br><code>{{hostname}}</code> Název serveru mailcow",
|
||||||
"restore_template": "Ponechte prázdné pro obnovení výchozí šablony."
|
"restore_template": "Ponechte prázdné pro obnovení výchozí šablony.",
|
||||||
|
"copy_to_clipboard": "Text zkopírován do schránky!",
|
||||||
|
"iam_login_provisioning": "Automaticky vytvořit uživatele při přihlášení",
|
||||||
|
"user_quicklink": "Skrýt zkratku na přihlášení uživatele",
|
||||||
|
"domainadmin_quicklink": "Skrýt zkratku na přihlášení správce domény",
|
||||||
|
"iam_auth_flow_info": "Kromě metody autorizačního kódu (Authorization Code Flow, výchozího v Keycloaku), jež se používá pro SSO, podporuje mailcow také metody autentizaci přímo pomocí přihlašovacích údajů. The metoda Mailpassword Flow se pokusí ověřit přihlašovací údaje uživatele přímo v Admin REST API Keycloaku. mailcow získá hash hesla z atributu <code>mailcow_password</code> , namapovaného v Keycloaku.",
|
||||||
|
"iam_default_template_description": "Nemá-li uživatel přiřazenu šablonu, použije se výchozí šablona k vytvoření schránky, ale ne k její úpravě či aktualizaci.",
|
||||||
|
"iam_userinfo_url": "Koncový bod pro informace o uživatelích",
|
||||||
|
"iam_redirect_url": "URL přesměrování",
|
||||||
|
"user_link": "Odkaz pro uživatele",
|
||||||
|
"force_sso_text": "Je-li nastaven externí poskytovatel OIDC, zapnutím této volby skryjete výchozí přihlašovací formulář. Zůstane vidět jen tlačítko pro SSO",
|
||||||
|
"iam_mapping": "Mapování atributů",
|
||||||
|
"iam_bindpass": "Heslo pro bind",
|
||||||
|
"iam_periodic_full_sync": "Pravidelná úplná synchronizace",
|
||||||
|
"iam_port": "Port",
|
||||||
|
"iam_realm": "Realm",
|
||||||
|
"iam_rest_flow": "Mailpassword Flow",
|
||||||
|
"iam_server_url": "URL serveru",
|
||||||
|
"iam_sso": "Single Sign-On",
|
||||||
|
"iam_sync_interval": "Interval synchronizace/importu (min)",
|
||||||
|
"iam_test_connection": "Test spojení",
|
||||||
|
"iam_token_url": "Koncový bod pro tokeny",
|
||||||
|
"iam_username_field": "Pole uživatelského jména",
|
||||||
|
"iam_binddn": "Doména pro bind",
|
||||||
|
"iam_use_ssl": "Používat SSL",
|
||||||
|
"iam_use_tls": "Používat StartTLS",
|
||||||
|
"iam_version": "Verze",
|
||||||
|
"quicklink_text": "Zobrazení zkratek k dalším přihlašovacím stránkám",
|
||||||
|
"iam_use_tls_info": "Je-li zapnuto TLS, musí se používat standardní port pro LDAP (389). Port SSL nelze použít.",
|
||||||
|
"ignore_ssl_error": "Ignorovat chyby SSL",
|
||||||
|
"iam_use_ssl_info": "Je-li zapnuto SSL a nastaven port 389, použije se automaticky port 636.",
|
||||||
|
"task": "Úloha",
|
||||||
|
"app_hide": "Skrýt při přihlášení",
|
||||||
|
"admin_quicklink": "Skrýt zkratku na přihlášení správce",
|
||||||
|
"allowed_methods": "Access-Control-Allow-Methods",
|
||||||
|
"allowed_origins": "Access-Control-Allow-Origin",
|
||||||
|
"login_page": "Přihlašovací stránka",
|
||||||
|
"f2b_manage_external": "Spravovat Fail2Ban externě",
|
||||||
|
"f2b_manage_external_info": "Fail2ban bude udržovat seznam zakázaných adres, ale nebude aktivně nastavovat pravidla blokování. Pro blokování použijte seznam adres níže.",
|
||||||
|
"filter": "Filtr",
|
||||||
|
"force_sso": "Vypnout přihlášení mailcow a ponechat jen SSO",
|
||||||
|
"iam": "Poskytovatel identity",
|
||||||
|
"iam_attribute_field": "Pole atributu",
|
||||||
|
"iam_authorize_url": "Autorizační koncový bod",
|
||||||
|
"iam_basedn": "Doména (Base DN)",
|
||||||
|
"iam_client_id": "ID klienta",
|
||||||
|
"iam_client_secret": "Tajný kód klienta",
|
||||||
|
"iam_client_scopes": "Scopes klienta",
|
||||||
|
"iam_default_template": "Výchozí šablona",
|
||||||
|
"iam_description": "Nastavení externího poskytovatele ověření<br>Schránky uživatele se vytvoří po prvním přihlášení automaticky, pokud je tedy nastaveno mapování atributů.",
|
||||||
|
"iam_extra_permission": "Aby vše fungovalo, musí mít mailcow klient v Keycloaku nastavený <code>servisní účet</code> a povolení <code>view-users</code>.",
|
||||||
|
"iam_host": "Hostitel",
|
||||||
|
"iam_host_info": "Zadejte jeden či více hostitelů, oddělte čárkou.",
|
||||||
|
"iam_import_users": "Importovat uživatele",
|
||||||
|
"iam_auth_flow": "Proces autentizace",
|
||||||
|
"needs_restart": "potřebuje restart"
|
||||||
},
|
},
|
||||||
"danger": {
|
"danger": {
|
||||||
"access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",
|
"access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",
|
||||||
@@ -408,7 +465,7 @@
|
|||||||
"is_alias": "%s je již známa jako adresa aliasu",
|
"is_alias": "%s je již známa jako adresa aliasu",
|
||||||
"is_alias_or_mailbox": "%s je již známa jako adresa aliasu, mailové schránky nebo aliasu rozvedeného z aliasu domény.",
|
"is_alias_or_mailbox": "%s je již známa jako adresa aliasu, mailové schránky nebo aliasu rozvedeného z aliasu domény.",
|
||||||
"is_spam_alias": "%s je již známa jako adresa spamového aliasu",
|
"is_spam_alias": "%s je již známa jako adresa spamového aliasu",
|
||||||
"last_key": "Nelze smazat poslední klíč",
|
"last_key": "Nelze smazat poslední klíč, vypněte tedy celé TFA.",
|
||||||
"login_failed": "Přihlášení selhalo",
|
"login_failed": "Přihlášení selhalo",
|
||||||
"mailbox_defquota_exceeds_mailbox_maxquota": "Výchozí kvóta překračuje maximální kvótu schránky\"",
|
"mailbox_defquota_exceeds_mailbox_maxquota": "Výchozí kvóta překračuje maximální kvótu schránky\"",
|
||||||
"mailbox_invalid": "Název mailové schránky je neplatný",
|
"mailbox_invalid": "Název mailové schránky je neplatný",
|
||||||
@@ -486,7 +543,20 @@
|
|||||||
"demo_mode_enabled": "Demo režim je zapnutý",
|
"demo_mode_enabled": "Demo režim je zapnutý",
|
||||||
"recovery_email_failed": "Nepodařilo se odeslat e-mail pro obnovení. Obraťte se prosím na svého správce.",
|
"recovery_email_failed": "Nepodařilo se odeslat e-mail pro obnovení. Obraťte se prosím na svého správce.",
|
||||||
"password_reset_invalid_user": "Mailbox nebyl nalezen nebo není nastaven žádný e-mail pro obnovu",
|
"password_reset_invalid_user": "Mailbox nebyl nalezen nebo není nastaven žádný e-mail pro obnovu",
|
||||||
"password_reset_na": "Obnovení hesla není v současné době k dispozici. Obraťte se prosím na svého správce."
|
"password_reset_na": "Obnovení hesla není v současné době k dispozici. Obraťte se prosím na svého správce.",
|
||||||
|
"generic_server_error": "Došlo k nečekané chybě. Obraťte se na vašeho správce.",
|
||||||
|
"to_invalid": "Adresát nemůže být prázdný",
|
||||||
|
"authsource_in_use": "Poskytovatele identity nelze změnit nebo odstranit, neboť se právě používá pro jednoho či více uživatelů.",
|
||||||
|
"iam_test_connection": "Spojení selhalo",
|
||||||
|
"img_dimensions_exceeded": "Obrázek je větší než povolené rozměry",
|
||||||
|
"img_size_exceeded": "Obrázek má větší než povolenou velikost souboru",
|
||||||
|
"invalid_reset_token": "Neplatný resetovací token",
|
||||||
|
"required_data_missing": "Chybí potřebný údaj %s",
|
||||||
|
"reset_token_limit_exceeded": "Byl překročen limit na reset tokeny. Zkuste to později.",
|
||||||
|
"max_age_invalid": "Maximální životnost %s není platná",
|
||||||
|
"mode_invalid": "Mód %s není platný",
|
||||||
|
"mx_invalid": "Záznam MX %s není platný",
|
||||||
|
"version_invalid": "Verze %s není platná"
|
||||||
},
|
},
|
||||||
"datatables": {
|
"datatables": {
|
||||||
"emptyTable": "Tabulka neobsahuje žádná data",
|
"emptyTable": "Tabulka neobsahuje žádná data",
|
||||||
@@ -548,7 +618,8 @@
|
|||||||
"update_failed": "Nepodařilo se zkontrolovat aktualizace",
|
"update_failed": "Nepodařilo se zkontrolovat aktualizace",
|
||||||
"wip": "Nedokončená vývojová verze",
|
"wip": "Nedokončená vývojová verze",
|
||||||
"memory": "Paměť",
|
"memory": "Paměť",
|
||||||
"container_disabled": "Kontejner je zastaven nebo zakázán"
|
"container_disabled": "Kontejner je zastaven nebo zakázán",
|
||||||
|
"cores": "jádra"
|
||||||
},
|
},
|
||||||
"diagnostics": {
|
"diagnostics": {
|
||||||
"cname_from_a": "Hodnota odvozena z A/AAAA záznamu. Lze použít, pokud záznam ukazuje na správný zdroj.",
|
"cname_from_a": "Hodnota odvozena z A/AAAA záznamu. Lze použít, pokud záznam ukazuje na správný zdroj.",
|
||||||
@@ -569,7 +640,7 @@
|
|||||||
"alias": "Upravit alias",
|
"alias": "Upravit alias",
|
||||||
"allow_from_smtp": "Umožnit pouze těmto IP adresám používat <b>SMTP</b>",
|
"allow_from_smtp": "Umožnit pouze těmto IP adresám používat <b>SMTP</b>",
|
||||||
"allow_from_smtp_info": "Nechte prázdné pro povolení všech odesílatelů.<br>IPv4/IPv6 adresy a sítě.",
|
"allow_from_smtp_info": "Nechte prázdné pro povolení všech odesílatelů.<br>IPv4/IPv6 adresy a sítě.",
|
||||||
"allowed_protocols": "Povolené protokoly",
|
"allowed_protocols": "Povolené protokoly pro přímá spojení (netýká se protokolů na změnu hesla)",
|
||||||
"app_name": "Název aplikace",
|
"app_name": "Název aplikace",
|
||||||
"app_passwd": "Heslo aplikace",
|
"app_passwd": "Heslo aplikace",
|
||||||
"app_passwd_protocols": "Povolené protokoly pro hesla aplikací",
|
"app_passwd_protocols": "Povolené protokoly pro hesla aplikací",
|
||||||
@@ -607,7 +678,7 @@
|
|||||||
"inactive": "Neaktivní",
|
"inactive": "Neaktivní",
|
||||||
"kind": "Druh",
|
"kind": "Druh",
|
||||||
"last_modified": "Naposledy změněn",
|
"last_modified": "Naposledy změněn",
|
||||||
"lookup_mx": "Cíl je regulární výraz který se shoduje s MX záznamem (<code>.*\\.google\\.com</code> směřuje veškerou poštu na MX které jsou cílem pro google.com přes tento skok)",
|
"lookup_mx": "Cíl je regulární výraz, jenž se porovná s MX záznamem (např. <code>.*\\.google\\.com</code> na tento skok nasměruje veškerou poštu s MX, jež končí na *.google.com)",
|
||||||
"mailbox": "Úprava mailové schránky",
|
"mailbox": "Úprava mailové schránky",
|
||||||
"mailbox_quota_def": "Výchozí kvóta schránky",
|
"mailbox_quota_def": "Výchozí kvóta schránky",
|
||||||
"mailbox_relayhost_info": "Aplikované jen na uživatelskou schránku a přímé aliasy, přepisuje předávající server domény.",
|
"mailbox_relayhost_info": "Aplikované jen na uživatelskou schránku a přímé aliasy, přepisuje předávající server domény.",
|
||||||
@@ -641,7 +712,7 @@
|
|||||||
"ratelimit": "Omezení přenosu",
|
"ratelimit": "Omezení přenosu",
|
||||||
"redirect_uri": "URL přesměrování/odvolání",
|
"redirect_uri": "URL přesměrování/odvolání",
|
||||||
"relay_all": "Předávání všech příjemců",
|
"relay_all": "Předávání všech příjemců",
|
||||||
"relay_all_info": "<small>Pokud se rozhodnete <b>nepředávat</b> všechny příjemce, musíte přidat prázdnou mailovou schránku pro každého příjemce, který se má předávat.</small>",
|
"relay_all_info": "↪ Pokud se rozhodnete <b>nepředávat</b> všechny příjemce, musíte přidat prázdnou mailovou schránku pro každého příjemce, který se má předávat.",
|
||||||
"relay_domain": "Předávání domény",
|
"relay_domain": "Předávání domény",
|
||||||
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> U této domény lze pro konkrétní cíl nastavit transportní mapu. Není-li nastavena, použije se MX záznam.",
|
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Info</div> U této domény lze pro konkrétní cíl nastavit transportní mapu. Není-li nastavena, použije se MX záznam.",
|
||||||
"relay_unknown_only": "Předávat jen neexistující schránky. Doručení do existujících proběhne lokálně.",
|
"relay_unknown_only": "Předávat jen neexistující schránky. Doručení do existujících proběhne lokálně.",
|
||||||
@@ -662,11 +733,11 @@
|
|||||||
"sogo_visible_info": "Tato volba určuje objekty, jež lze zobrazit v SOGo (sdílené nebo nesdílené aliasy, jež ukazuje alespoň na jednu schránku).",
|
"sogo_visible_info": "Tato volba určuje objekty, jež lze zobrazit v SOGo (sdílené nebo nesdílené aliasy, jež ukazuje alespoň na jednu schránku).",
|
||||||
"spam_alias": "Vytvořit nebo změnit dočasné aliasy",
|
"spam_alias": "Vytvořit nebo změnit dočasné aliasy",
|
||||||
"spam_filter": "Spam filtr",
|
"spam_filter": "Spam filtr",
|
||||||
"spam_policy": "Přidat nebo odebrat položky whitelistu/blacklistu",
|
"spam_policy": "Přidat nebo odebrat položky seznamu",
|
||||||
"spam_score": "Nastavte vlastní skóre spamu",
|
"spam_score": "Nastavte vlastní skóre spamu",
|
||||||
"subfolder2": "Synchronizace do podsložky v cílovém umístění<br><small>(prázdné = nepoužívat podsložku)</small>",
|
"subfolder2": "Synchronizace do podsložky v cílovém umístění<br><small>(prázdné = nepoužívat podsložku)</small>",
|
||||||
"syncjob": "Upravit synchronizační úlohu",
|
"syncjob": "Upravit synchronizační úlohu",
|
||||||
"target_address": "Cílová adresa/y<br /> <small>(oddělte čárkou)</small>",
|
"target_address": "Cílová adresa/y <small>(oddělte čárkou)</small>",
|
||||||
"target_domain": "Cílová doména",
|
"target_domain": "Cílová doména",
|
||||||
"timeout1": "Časový limit pro připojení ke vzdálenému serveru",
|
"timeout1": "Časový limit pro připojení ke vzdálenému serveru",
|
||||||
"timeout2": "Časový limit pro připojení k lokálnímu serveru",
|
"timeout2": "Časový limit pro připojení k lokálnímu serveru",
|
||||||
@@ -690,7 +761,26 @@
|
|||||||
"custom_attributes": "Vlastní atributy",
|
"custom_attributes": "Vlastní atributy",
|
||||||
"footer_exclude": "Vyloučit ze zápatí",
|
"footer_exclude": "Vyloučit ze zápatí",
|
||||||
"domain_footer_skip_replies": "Ignorovat patičku u odpovědí na e-maily",
|
"domain_footer_skip_replies": "Ignorovat patičku u odpovědí na e-maily",
|
||||||
"password_recovery_email": "E-mail pro obnovu hesla"
|
"password_recovery_email": "E-mail pro obnovu hesla",
|
||||||
|
"mailbox_rename": "Přejmenovat schránku",
|
||||||
|
"mailbox_rename_agree": "Mám vytvořenou zálohu.",
|
||||||
|
"mailbox_rename_warning": "DŮLEŽITÉ! Vytvořte si zálohu schránky, než ji přejmenujete.",
|
||||||
|
"mailbox_rename_alias": "Automaticky vytvořit alias",
|
||||||
|
"mailbox_rename_title": "Nový název zdejší schránky",
|
||||||
|
"pushover": "Pushover",
|
||||||
|
"internal": "Interní",
|
||||||
|
"internal_info": "Interní aliasy jsou přístupné jen z vlastních domén nebo jejich aliasů.",
|
||||||
|
"mta_sts": "MTA-STS",
|
||||||
|
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> je standard, jenž říká poštovním serverům, aby komunikovaly pomocí TLS s platnými certifikáty. <br>Používá se, pokud není k dispozici <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a>, např. chybí-li či není podporováno DNSSEC.<br><b>Pozn.</b>: Podporuje-li přijímající doména DANE a DNSSEC, bude <b>vždy</b> použito DANE; MTA-STS zůstane jako plán B.",
|
||||||
|
"mta_sts_version": "Verze",
|
||||||
|
"mta_sts_version_info": "Určuje verzi standardu MTA-STS – zatím je podporována jen <code>STSv1</code>.",
|
||||||
|
"mta_sts_mode": "Mód",
|
||||||
|
"mta_sts_mode_info": "K dispozici jsou tři módy:<ul><li><em>testing</em> – pravidlo se jen sleduje, porušení je bez následků.</li><li><em>enforce</em> – pravidlo je důsledně dodržováno, spojení bez platného TLS jsou odmítána.</li><li><em>none</em> – pravidlo je zveřejněno, ale neuplatňuje se.</li></ul>",
|
||||||
|
"mta_sts_max_age": "Maximální životnost",
|
||||||
|
"mta_sts_max_age_info": "Doba v sekundách, po niž poštovní servery mohou toho pravidlo držet v mezipaměti bez nutnosti obnovení.",
|
||||||
|
"mta_sts_mx": "Server MX",
|
||||||
|
"mta_sts_mx_info": "Dovoluje odesílání jen výslovně vypsaným poštovním serverům; odesílající server kontroluje, že server MX určený v DNS odpovídá pravidlu, a povolí doručení jen s platným certifikátem TLS (chrání přes útokem typu MITM).",
|
||||||
|
"mta_sts_mx_notice": "Lze zadat více serverů MX (oddělte čárkou)."
|
||||||
},
|
},
|
||||||
"fido2": {
|
"fido2": {
|
||||||
"confirm": "Potvrdit",
|
"confirm": "Potvrdit",
|
||||||
@@ -711,7 +801,7 @@
|
|||||||
"cancel": "Zrušit",
|
"cancel": "Zrušit",
|
||||||
"confirm_delete": "Potvdit smazání",
|
"confirm_delete": "Potvdit smazání",
|
||||||
"delete_now": "Smazat",
|
"delete_now": "Smazat",
|
||||||
"delete_these_items": "Prosím potvrďte změny objektu id:",
|
"delete_these_items": "Prosím potvrďte změny objektu id",
|
||||||
"hibp_check": "Ověřit heslo v databázi hacknutých hesel haveibeenpwned.com",
|
"hibp_check": "Ověřit heslo v databázi hacknutých hesel haveibeenpwned.com",
|
||||||
"hibp_nok": "Nalezeno! Toto je potenciálně nebezpečné heslo!",
|
"hibp_nok": "Nalezeno! Toto je potenciálně nebezpečné heslo!",
|
||||||
"hibp_ok": "Nebyla nalezena žádná shoda.",
|
"hibp_ok": "Nebyla nalezena žádná shoda.",
|
||||||
@@ -720,12 +810,12 @@
|
|||||||
"restart_container": "Restartovat kontejner",
|
"restart_container": "Restartovat kontejner",
|
||||||
"restart_container_info": "<b>Důležité:</b> Šetrný restart může chvíli trvat, prosím čekejte...",
|
"restart_container_info": "<b>Důležité:</b> Šetrný restart může chvíli trvat, prosím čekejte...",
|
||||||
"restart_now": "Restartovat nyní",
|
"restart_now": "Restartovat nyní",
|
||||||
"restarting_container": "Restartuje se kontejner, může to chvilku trvat..."
|
"restarting_container": "Restartuje se kontejner, může to chvilku trvat"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"administration": "Hlavní nastavení",
|
"administration": "Hlavní nastavení",
|
||||||
"apps": "Aplikace",
|
"apps": "Aplikace",
|
||||||
"debug": "Systémové informace",
|
"debug": "Informace",
|
||||||
"email": "E-Mail",
|
"email": "E-Mail",
|
||||||
"mailcow_system": "Systém",
|
"mailcow_system": "Systém",
|
||||||
"mailcow_config": "Nastavení",
|
"mailcow_config": "Nastavení",
|
||||||
@@ -753,7 +843,15 @@
|
|||||||
"new_password": "Nové heslo",
|
"new_password": "Nové heslo",
|
||||||
"new_password_confirm": "Ověření nového hesla",
|
"new_password_confirm": "Ověření nového hesla",
|
||||||
"reset_password": "Obnovit heslo",
|
"reset_password": "Obnovit heslo",
|
||||||
"request_reset_password": "Požádat o změnu hesla"
|
"request_reset_password": "Požádat o změnu hesla",
|
||||||
|
"login_domainadmintext": "Přihlášení správce domény",
|
||||||
|
"login_linkstext": "Hledáte jinou přihlašovací stránku?",
|
||||||
|
"login_usertext": "Přihlášení uživatele",
|
||||||
|
"login_admintext": "Přihlášení správce",
|
||||||
|
"login_user": "Přihlášení uživatele",
|
||||||
|
"login_dadmin": "Přihlášení správce domény",
|
||||||
|
"login_admin": "Přihlášení správce",
|
||||||
|
"email": "Mailová adresa"
|
||||||
},
|
},
|
||||||
"mailbox": {
|
"mailbox": {
|
||||||
"action": "Akce",
|
"action": "Akce",
|
||||||
@@ -785,7 +883,7 @@
|
|||||||
"bcc": "BCC",
|
"bcc": "BCC",
|
||||||
"bcc_destination": "Cíl kopie",
|
"bcc_destination": "Cíl kopie",
|
||||||
"bcc_destinations": "Cíl kopií",
|
"bcc_destinations": "Cíl kopií",
|
||||||
"bcc_info": "Skryté kopie (Mapa BCC) se používá pro tiché předávání kopií všech zpráv na jinou adresu. Při použití skryté kopie typu <i>Přijatý e-mail</i> budou přeposlány všechny maily směřující na dotyčnou adresu nebo doménu.\nU typu <i>Odeslaný e-mail</i> budou přeposlány všechny maily odeslané z dotyčné adresy nebo domény.\nPokud selže přeposlání na cílovou adresu, tak odesílatel o tom nebude informován.",
|
"bcc_info": "Skrytá kopie (mapa BCC) se používá pro tiché předávání kopií všech zpráv na jinou adresu. Mapa příjemců se použije, funguje-li je místní cíl jako adresát zprávy. Totéž platí pro mapy odesílatelů.<br/>\n Místní cíl se nedozví, selže-li doručení na cíl BCC.",
|
||||||
"bcc_local_dest": "Týká se",
|
"bcc_local_dest": "Týká se",
|
||||||
"bcc_map": "Skrytá kopie",
|
"bcc_map": "Skrytá kopie",
|
||||||
"bcc_map_type": "Typ skryté kopie",
|
"bcc_map_type": "Typ skryté kopie",
|
||||||
@@ -840,7 +938,7 @@
|
|||||||
"last_run_reset": "Znovu naplánovat",
|
"last_run_reset": "Znovu naplánovat",
|
||||||
"mailbox": "Mailová schránka",
|
"mailbox": "Mailová schránka",
|
||||||
"mailbox_defaults": "Výchozí nastavení",
|
"mailbox_defaults": "Výchozí nastavení",
|
||||||
"mailbox_defaults_info": "Definuje výchozí nastavení pro nové schránky",
|
"mailbox_defaults_info": "Definuje výchozí nastavení pro nové schránky.",
|
||||||
"mailbox_defquota": "Výchozí velikost schránky",
|
"mailbox_defquota": "Výchozí velikost schránky",
|
||||||
"mailbox_templates": "Šablony schránek",
|
"mailbox_templates": "Šablony schránek",
|
||||||
"mailbox_quota": "Max. velikost schránky",
|
"mailbox_quota": "Max. velikost schránky",
|
||||||
@@ -860,7 +958,7 @@
|
|||||||
"private_comment": "Soukromý komentář",
|
"private_comment": "Soukromý komentář",
|
||||||
"public_comment": "Veřejný komentář",
|
"public_comment": "Veřejný komentář",
|
||||||
"q_add_header": "Složka nevyžádaná pošta",
|
"q_add_header": "Složka nevyžádaná pošta",
|
||||||
"q_all": "Všechny kategorie",
|
"q_all": " Nevyžádaná pošta a Odmítnuta",
|
||||||
"q_reject": "Odmítnuta",
|
"q_reject": "Odmítnuta",
|
||||||
"quarantine_category": "Kategorie oznámení karantény",
|
"quarantine_category": "Kategorie oznámení karantény",
|
||||||
"quarantine_notification": "Upozornění z karantény",
|
"quarantine_notification": "Upozornění z karantény",
|
||||||
@@ -869,7 +967,7 @@
|
|||||||
"recipient_map": "Mapa příjemce",
|
"recipient_map": "Mapa příjemce",
|
||||||
"recipient_map_info": "Mapy příjemců slouží k nahrazení cílové adresy zprávy před doručením.",
|
"recipient_map_info": "Mapy příjemců slouží k nahrazení cílové adresy zprávy před doručením.",
|
||||||
"recipient_map_new": "Nový přijemce",
|
"recipient_map_new": "Nový přijemce",
|
||||||
"recipient_map_new_info": "Cílová adresa mapy příjemce musí být emailová adresa nebo název domény.",
|
"recipient_map_new_info": "Cílovou adresou mapy příjemců musí být emailová adresa nebo název domény.",
|
||||||
"recipient_map_old": "Původní příjemce",
|
"recipient_map_old": "Původní příjemce",
|
||||||
"recipient_map_old_info": "Původní příjemce musí být platná emailová adresa nebo název domény.",
|
"recipient_map_old_info": "Původní příjemce musí být platná emailová adresa nebo název domény.",
|
||||||
"recipient_maps": "Mapy příjemců",
|
"recipient_maps": "Mapy příjemců",
|
||||||
@@ -888,7 +986,7 @@
|
|||||||
"sieve_preset_5": "Automatický odpovídač (dovolená)",
|
"sieve_preset_5": "Automatický odpovídač (dovolená)",
|
||||||
"sieve_preset_6": "Odmítnout zprávu s odpovědí",
|
"sieve_preset_6": "Odmítnout zprávu s odpovědí",
|
||||||
"sieve_preset_7": "Přesměrovat a ponechat/zahodit",
|
"sieve_preset_7": "Přesměrovat a ponechat/zahodit",
|
||||||
"sieve_preset_8": "Zahodit zprávu poslanou na alias, do něhož patří i odesílatel",
|
"sieve_preset_8": "Zprávu od určitého odesílatele přesměrovat, označit jako přečtenou a uložit do složky",
|
||||||
"sieve_preset_header": "Vizte následující ukázková pravidla. Více informací na <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Wikipedii</a>.",
|
"sieve_preset_header": "Vizte následující ukázková pravidla. Více informací na <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Wikipedii</a>.",
|
||||||
"sogo_visible": "Alias dostupný v SOGo",
|
"sogo_visible": "Alias dostupný v SOGo",
|
||||||
"sogo_visible_n": "Skrýt alias v SOGo",
|
"sogo_visible_n": "Skrýt alias v SOGo",
|
||||||
@@ -928,7 +1026,9 @@
|
|||||||
"waiting": "Čekání",
|
"waiting": "Čekání",
|
||||||
"weekly": "Každý týden",
|
"weekly": "Každý týden",
|
||||||
"yes": "✓",
|
"yes": "✓",
|
||||||
"relay_unknown": "Předávání neexistujících schránek"
|
"relay_unknown": "Předávání neexistujících schránek",
|
||||||
|
"iam": "Poskytovatel identity",
|
||||||
|
"internal": "Interní"
|
||||||
},
|
},
|
||||||
"oauth2": {
|
"oauth2": {
|
||||||
"access_denied": "K udělení přístupu se přihlašte jako vlastník mailové schránky.",
|
"access_denied": "K udělení přístupu se přihlašte jako vlastník mailové schránky.",
|
||||||
@@ -936,7 +1036,7 @@
|
|||||||
"deny": "Zamítnout",
|
"deny": "Zamítnout",
|
||||||
"permit": "Ověřit aplikaci",
|
"permit": "Ověřit aplikaci",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"profile_desc": "Zobrazit osobní údaje: uživ. jméno, jméno, datum vytvoření a úpravy, stav",
|
"profile_desc": "Zobrazit osobní údaje: uživ. jméno, celé jméno, datum vytvoření a úpravy, stav",
|
||||||
"scope_ask_permission": "Aplikace požádala o následující oprávnění"
|
"scope_ask_permission": "Aplikace požádala o následující oprávnění"
|
||||||
},
|
},
|
||||||
"quarantine": {
|
"quarantine": {
|
||||||
@@ -960,7 +1060,7 @@
|
|||||||
"notified": "Oznámeno",
|
"notified": "Oznámeno",
|
||||||
"qhandler_success": "Požadavek úspěšně přijat. Můžete nyní zavřít okno.",
|
"qhandler_success": "Požadavek úspěšně přijat. Můžete nyní zavřít okno.",
|
||||||
"qid": "Rspamd QID",
|
"qid": "Rspamd QID",
|
||||||
"qinfo": "Karanténní systém uloží odmítnutou poštu do databáze (odesílatel se <em>nedozví</em>, že pošta byla doručena) jakož i pošta, která bude jako kopie doručena do složky Nevyžádaná pošta. \r\n<br>\"Naučit jako spam a smazat\" naučí zprávu jako spam přes Bayesian theorem a současně vypočítá fuzzy hashes pro odmítnutí podobných zpráv v budoucnosti. \r\n<br> Prosím, berte na vědomí, že naučení více zpráv může být - záleží na vašem systému - časově náročné . <br> Položky na černé listině jsou z karantény vyloučeny.",
|
"qinfo": "Karanténa uloží do databáze odmítnutou poštu (odesílatel se <em>nedozví</em>, že pošta byla doručena) jakož i poštu, jež se jako kopie doručuje do složky Nevyžádaná pošta.\n <br>\"Naučit jako spam a smazat\" předá zprávu systému k naučení bayesiánskou analýzou jako spam a současně stanoví fuzzy hashe pro odmítání podobných zpráv v budoucnosti.\n <br> Vezměte na vědomí, že učení více zpráv může být podle výkonnosti systému zabrat více času. <br> Položky na seznamu zákazů jsou z karantény vyloučeny.",
|
||||||
"qitem": "Položka v karanténě",
|
"qitem": "Položka v karanténě",
|
||||||
"quarantine": "Karanténa",
|
"quarantine": "Karanténa",
|
||||||
"quick_actions": "Akce",
|
"quick_actions": "Akce",
|
||||||
@@ -1005,7 +1105,8 @@
|
|||||||
"hold_mail_legend": "Podrží vybrané e-maily. (Zabrání dalším pokusům o doručení)",
|
"hold_mail_legend": "Podrží vybrané e-maily. (Zabrání dalším pokusům o doručení)",
|
||||||
"show_message": "Zobrazit zprávu",
|
"show_message": "Zobrazit zprávu",
|
||||||
"unhold_mail": "Uvolnit",
|
"unhold_mail": "Uvolnit",
|
||||||
"unhold_mail_legend": "Uvolnit vybrané e-maily k doručení. (Pouze v případě předchozího podržení)"
|
"unhold_mail_legend": "Uvolnit vybrané e-maily k doručení. (Pouze v případě předchozího podržení)",
|
||||||
|
"unban": "odblokovat"
|
||||||
},
|
},
|
||||||
"ratelimit": {
|
"ratelimit": {
|
||||||
"disabled": "Vypnuto",
|
"disabled": "Vypnuto",
|
||||||
@@ -1066,7 +1167,7 @@
|
|||||||
"logged_in_as": "Přihlášen jako %s",
|
"logged_in_as": "Přihlášen jako %s",
|
||||||
"mailbox_added": "Mailová schránka %s přidána",
|
"mailbox_added": "Mailová schránka %s přidána",
|
||||||
"mailbox_modified": "Změny mailové schránky %s uloženy",
|
"mailbox_modified": "Změny mailové schránky %s uloženy",
|
||||||
"mailbox_removed": "Mailová schránka %s odebrána",
|
"mailbox_removed": "Mailová schránka %s odstraněna",
|
||||||
"nginx_reloaded": "Nginx reload byl úspěšný",
|
"nginx_reloaded": "Nginx reload byl úspěšný",
|
||||||
"object_modified": "Změny objektu %s uloženy",
|
"object_modified": "Změny objektu %s uloženy",
|
||||||
"password_policy_saved": "Politika hesel byla úspěšně uložena",
|
"password_policy_saved": "Politika hesel byla úspěšně uložena",
|
||||||
@@ -1100,7 +1201,14 @@
|
|||||||
"verified_yotp_login": "Yubico OTP přihlášení ověřeno",
|
"verified_yotp_login": "Yubico OTP přihlášení ověřeno",
|
||||||
"cors_headers_edited": "Nastavení CORS byla uložena",
|
"cors_headers_edited": "Nastavení CORS byla uložena",
|
||||||
"domain_footer_modified": "Změny patičky domény %s byly uloženy",
|
"domain_footer_modified": "Změny patičky domény %s byly uloženy",
|
||||||
"recovery_email_sent": "E-mail k obnovení byl odeslán na adresu %s"
|
"recovery_email_sent": "E-mail k obnovení byl odeslán na adresu %s",
|
||||||
|
"custom_login_modified": "Úpravy přihlašování úspěšně uloženy",
|
||||||
|
"domain_add_dkim_available": "Klíč DKIM už existoval",
|
||||||
|
"f2b_banlist_refreshed": "Seznam zákazů úspěšně obnoven.",
|
||||||
|
"iam_test_connection": "Spojení úspěšně navázano",
|
||||||
|
"ip_check_opt_in_modified": "Kontrola IP adresy úspěšně uložena",
|
||||||
|
"mailbox_renamed": "Schránka přejmenována z %s na %s",
|
||||||
|
"password_changed_success": "Heslo úspěšně změněno"
|
||||||
},
|
},
|
||||||
"tfa": {
|
"tfa": {
|
||||||
"api_register": "%s používá Yubico Cloud API. Prosím získejte API klíč pro své Yubico <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">ZDE</a>",
|
"api_register": "%s používá Yubico Cloud API. Prosím získejte API klíč pro své Yubico <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">ZDE</a>",
|
||||||
@@ -1116,7 +1224,7 @@
|
|||||||
"none": "Deaktivovat",
|
"none": "Deaktivovat",
|
||||||
"reload_retry": "- (znovu načtěte stránku, opakuje-li se chyba)",
|
"reload_retry": "- (znovu načtěte stránku, opakuje-li se chyba)",
|
||||||
"scan_qr_code": "Prosím načtěte následující kód svou aplikací na ověření nebo zadejte kód ručně.",
|
"scan_qr_code": "Prosím načtěte následující kód svou aplikací na ověření nebo zadejte kód ručně.",
|
||||||
"select": "Prosím vyberte...",
|
"select": "Vyberte prosím",
|
||||||
"set_tfa": "Nastavení způsobu dvoufaktorového ověření",
|
"set_tfa": "Nastavení způsobu dvoufaktorového ověření",
|
||||||
"start_webauthn_validation": "Zahájit inicializaci",
|
"start_webauthn_validation": "Zahájit inicializaci",
|
||||||
"tfa": "Dvoufaktorové ověření (TFA)",
|
"tfa": "Dvoufaktorové ověření (TFA)",
|
||||||
@@ -1125,7 +1233,10 @@
|
|||||||
"webauthn": "WebAuthn ověření",
|
"webauthn": "WebAuthn ověření",
|
||||||
"waiting_usb_auth": "<i>Čeká se na USB zařízení...</i><br><br>Prosím stiskněte tlačítko na svém WebAuthn USB zařízení.",
|
"waiting_usb_auth": "<i>Čeká se na USB zařízení...</i><br><br>Prosím stiskněte tlačítko na svém WebAuthn USB zařízení.",
|
||||||
"waiting_usb_register": "<i>Čeká se na USB zařízení...</i><br><br>Prosím zadejte své heslo výše a potvrďte WebAuthn registraci stiskem tlačítka na svém WebAuthn USB zařízení.",
|
"waiting_usb_register": "<i>Čeká se na USB zařízení...</i><br><br>Prosím zadejte své heslo výše a potvrďte WebAuthn registraci stiskem tlačítka na svém WebAuthn USB zařízení.",
|
||||||
"yubi_otp": "Yubico OTP ověření"
|
"yubi_otp": "Yubico OTP ověření",
|
||||||
|
"u2f_deprecated": "Zdá se, že váš klíč byl registrován zastaralou metodou U2F. Dojde k deaktivaci dvoufaktorové autentifikace a smazání klíče.",
|
||||||
|
"authenticators": "Autentifikátory",
|
||||||
|
"u2f_deprecated_important": "Registrujte svůj klíč novou metodou WebAuthn ve správě správců."
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"action": "Akce",
|
"action": "Akce",
|
||||||
@@ -1141,7 +1252,7 @@
|
|||||||
"alias_time_left": "Zbývající čas",
|
"alias_time_left": "Zbývající čas",
|
||||||
"alias_valid_until": "Platný do",
|
"alias_valid_until": "Platný do",
|
||||||
"aliases_also_send_as": "Smí odesílat také jako uživatel",
|
"aliases_also_send_as": "Smí odesílat také jako uživatel",
|
||||||
"aliases_send_as_all": "Nekontrolovat přístup odesílatele pro následující doménu(y) a jejich aliasy domény:",
|
"aliases_send_as_all": "Nekontrolovat přístup odesílatele pro následující doménu(y) a jejich aliasy",
|
||||||
"allowed_protocols": "Povolené protokoly",
|
"allowed_protocols": "Povolené protokoly",
|
||||||
"app_hint": "Hesla aplikací jsou alternativní heslo pro přihlášení k IMAP, SMTP, CalDAV, CardDAV a EAS. Uživatelské jméno zůstává stejné.<br>SOGo však nelze s heslem aplikace použít.",
|
"app_hint": "Hesla aplikací jsou alternativní heslo pro přihlášení k IMAP, SMTP, CalDAV, CardDAV a EAS. Uživatelské jméno zůstává stejné.<br>SOGo však nelze s heslem aplikace použít.",
|
||||||
"app_name": "Název aplikace",
|
"app_name": "Název aplikace",
|
||||||
@@ -1162,8 +1273,8 @@
|
|||||||
"description": "Popis",
|
"description": "Popis",
|
||||||
"delete_ays": "Potvrďte odstranění.",
|
"delete_ays": "Potvrďte odstranění.",
|
||||||
"direct_aliases": "Přímé aliasy",
|
"direct_aliases": "Přímé aliasy",
|
||||||
"direct_aliases_desc": "Na přímé aliasy se uplatňuje filtr spamu a nastavení pravidel TLS",
|
"direct_aliases_desc": "Na přímé aliasy se uplatňuje filtr spamu a nastavení pravidel TLS.",
|
||||||
"direct_protocol_access": "Tento uživatel mailové schránky má <b>přímý externí přístup</b> k následujícím protokolům a aplikacím. Toto nastavení je řízeno správcem. Pro udělení přístupu k jednotlivým protokolům a aplikacím lze vytvořit hesla aplikací.<br>Tlačítko \"Webmailu\" zajišťuje jednotné přihlášení k SOGo a je vždy k dispozici.",
|
"direct_protocol_access": "Tento uživatel mailové schránky má <b>přímý externí přístup</b> k následujícím protokolům a aplikacím. Toto nastavení je řízeno správcem. Pro udělení přístupu k jednotlivým protokolům a aplikacím lze vytvořit hesla aplikací.<br>Tlačítko \"Webmail\" zajišťuje jednotné přihlášení k SOGo a je vždy k dispozici.",
|
||||||
"eas_reset": "Smazat mezipaměť zařízení ActiveSync",
|
"eas_reset": "Smazat mezipaměť zařízení ActiveSync",
|
||||||
"eas_reset_help": "Obnovení mezipaměti zařízení pomůže zpravidla obnovit poškozený profil služby ActiveSync.<br><b>Upozornění:</b> Všechna data budou opětovně stažena!",
|
"eas_reset_help": "Obnovení mezipaměti zařízení pomůže zpravidla obnovit poškozený profil služby ActiveSync.<br><b>Upozornění:</b> Všechna data budou opětovně stažena!",
|
||||||
"eas_reset_now": "Smazat",
|
"eas_reset_now": "Smazat",
|
||||||
@@ -1204,7 +1315,7 @@
|
|||||||
"no_last_login": "Žádný záznam o přihlášení",
|
"no_last_login": "Žádný záznam o přihlášení",
|
||||||
"no_record": "Žádný záznam",
|
"no_record": "Žádný záznam",
|
||||||
"open_logs": "Otevřít záznam",
|
"open_logs": "Otevřít záznam",
|
||||||
"open_webmail_sso": "Webmailu",
|
"open_webmail_sso": "Webmail",
|
||||||
"password": "Heslo",
|
"password": "Heslo",
|
||||||
"password_now": "Současné heslo (pro potvrzení změny)",
|
"password_now": "Současné heslo (pro potvrzení změny)",
|
||||||
"password_repeat": "Heslo (znovu)",
|
"password_repeat": "Heslo (znovu)",
|
||||||
@@ -1236,13 +1347,13 @@
|
|||||||
"sogo_profile_reset": "Resetovat profil SOGo",
|
"sogo_profile_reset": "Resetovat profil SOGo",
|
||||||
"sogo_profile_reset_help": "Tato volba odstraní uživatelský profil SOGo a <b>nenávratně vymaže všechna data</b>.",
|
"sogo_profile_reset_help": "Tato volba odstraní uživatelský profil SOGo a <b>nenávratně vymaže všechna data</b>.",
|
||||||
"sogo_profile_reset_now": "Resetovat profil",
|
"sogo_profile_reset_now": "Resetovat profil",
|
||||||
"spam_aliases": "Dočasné e-mailové aliasy",
|
"spam_aliases": "Spam aliasy",
|
||||||
"spam_score_reset": "Obnovit výchozí nastavení serveru",
|
"spam_score_reset": "Obnovit výchozí nastavení serveru",
|
||||||
"spamfilter": "Filtr spamu",
|
"spamfilter": "Filtr spamu",
|
||||||
"spamfilter_behavior": "Hodnocení",
|
"spamfilter_behavior": "Hodnocení",
|
||||||
"spamfilter_bl": "Seznam zakázaných adres (blacklist)",
|
"spamfilter_bl": "Seznam zákazů",
|
||||||
"spamfilter_bl_desc": "Zakázané emailové adresy <b>budou vždy klasifikovány jako spam a odmítnuty</b>. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou mailovou schránkou), s výjimkou doménových košů a samotné mailové schránky.",
|
"spamfilter_bl_desc": "Zakázané emailové adresy budou <b>vždy</b> klasifikovány jako spam a odmítnuty. Odmítnutá pošta <b>se neukládá</b> do karantény. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou poštovní schránkou), s výjimkou doménových košů a samotné poštovní schránky.",
|
||||||
"spamfilter_default_score": "Výchozí hodnoty:",
|
"spamfilter_default_score": "Výchozí hodnoty",
|
||||||
"spamfilter_green": "Zelená: tato zpráva není spam",
|
"spamfilter_green": "Zelená: tato zpráva není spam",
|
||||||
"spamfilter_hint": "První hodnota představuje \"nízké spam skóre\" a druhá \"vysoké spam skóre\".",
|
"spamfilter_hint": "První hodnota představuje \"nízké spam skóre\" a druhá \"vysoké spam skóre\".",
|
||||||
"spamfilter_red": "Červená: Tato zpráva je spam a server ji odmítne",
|
"spamfilter_red": "Červená: Tato zpráva je spam a server ji odmítne",
|
||||||
@@ -1252,7 +1363,7 @@
|
|||||||
"spamfilter_table_empty": "Žádná data k zobrazení",
|
"spamfilter_table_empty": "Žádná data k zobrazení",
|
||||||
"spamfilter_table_remove": "smazat",
|
"spamfilter_table_remove": "smazat",
|
||||||
"spamfilter_table_rule": "Pravidlo",
|
"spamfilter_table_rule": "Pravidlo",
|
||||||
"spamfilter_wl": "Seznam povolených adres (whitelist)",
|
"spamfilter_wl": "Seznam povolení",
|
||||||
"spamfilter_wl_desc": "Povolené emailové adresy <b>nebudou nikdy klasifikovány jako spam</b>. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou mailovou schránkou), s výjimkou doménových košů a samotné mailové schránky.",
|
"spamfilter_wl_desc": "Povolené emailové adresy <b>nebudou nikdy klasifikovány jako spam</b>. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou mailovou schránkou), s výjimkou doménových košů a samotné mailové schránky.",
|
||||||
"spamfilter_yellow": "Žlutá: tato zpráva může být spam, bude označena jako spam a přesunuta do složky nevyžádané pošty",
|
"spamfilter_yellow": "Žlutá: tato zpráva může být spam, bude označena jako spam a přesunuta do složky nevyžádané pošty",
|
||||||
"status": "Stav",
|
"status": "Stav",
|
||||||
@@ -1274,7 +1385,7 @@
|
|||||||
"tag_in_subject": "V předmětu",
|
"tag_in_subject": "V předmětu",
|
||||||
"text": "Text",
|
"text": "Text",
|
||||||
"title": "Předmět",
|
"title": "Předmět",
|
||||||
"tls_enforce_in": "Vynutit TLS pro příchozí poštu ",
|
"tls_enforce_in": "Vynutit TLS pro příchozí poštu",
|
||||||
"tls_enforce_out": "Vynutit TLS pro odchozí poštu",
|
"tls_enforce_out": "Vynutit TLS pro odchozí poštu",
|
||||||
"tls_policy": "Politika šifrování",
|
"tls_policy": "Politika šifrování",
|
||||||
"tls_policy_warning": "<strong>Varování:</strong> Pokud se rozhodnete vynutit šifrovaný přenos pošty, může dojít ke ztrátě e-mailů.<br>Zprávy, které nesplňují tuto politiku, budou mailovým systémem odmítnuty.<br>Tato volba ovlivňuje primární e-mailovou adresu (přihlašovací jméno), všechny adresy odvozené z doménových aliasů i aliasy, jež mají tuto mailovou schránku jako cíl.",
|
"tls_policy_warning": "<strong>Varování:</strong> Pokud se rozhodnete vynutit šifrovaný přenos pošty, může dojít ke ztrátě e-mailů.<br>Zprávy, které nesplňují tuto politiku, budou mailovým systémem odmítnuty.<br>Tato volba ovlivňuje primární e-mailovou adresu (přihlašovací jméno), všechny adresy odvozené z doménových aliasů i aliasy, jež mají tuto mailovou schránku jako cíl.",
|
||||||
@@ -1290,7 +1401,16 @@
|
|||||||
"years": "let",
|
"years": "let",
|
||||||
"pushover_sound": "Zvukové upozornění",
|
"pushover_sound": "Zvukové upozornění",
|
||||||
"password_reset_info": "Pokud není zadán e-mail pro obnovení hesla, nelze tuto funkci použít.",
|
"password_reset_info": "Pokud není zadán e-mail pro obnovení hesla, nelze tuto funkci použít.",
|
||||||
"pw_recovery_email": "E-mail pro obnovení hesla"
|
"pw_recovery_email": "E-mail pro obnovení hesla",
|
||||||
|
"tfa_info": "Dvoufaktorová autentizace vám pomáhá chránit svůj účet. Je-li zapnuta, musíte si vytvořit aplikační hesla pro aplikace, jež dvoufaktorovou autentizaci nepodporují (např. poštovní klienti).",
|
||||||
|
"attribute": "Atribut",
|
||||||
|
"authentication": "Autentifikace",
|
||||||
|
"overview": "Přehled",
|
||||||
|
"protocols": "Protokoly",
|
||||||
|
"value": "Hodnota",
|
||||||
|
"expire_never": "Nikdy nevyprší",
|
||||||
|
"forever": "Navždy",
|
||||||
|
"spam_aliases_info": "Spam alias je dočasná adresa, již lze použít k ochraně skutečných adres. <br>Případně lze nastavit také dobu platnosti, po níž je alias automaticky deaktivován, čímž se řeší případy zneužitých či odcizených adres."
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"cannot_delete_self": "Nelze smazat právě přihlášeného uživatele",
|
"cannot_delete_self": "Nelze smazat právě přihlášeného uživatele",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user