mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-02-17 14:53:56 +00:00
Compare commits
308 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
885ba2510e | ||
|
|
0428f5c9bd | ||
|
|
a70c23065c | ||
|
|
caaa4a414d | ||
|
|
c3d841340c | ||
|
|
b8cd00111f | ||
|
|
81cda80651 | ||
|
|
c1d4f04c22 | ||
|
|
82276cd1ca | ||
|
|
56ea4302ed | ||
|
|
c06112b26e | ||
|
|
aa5a4f0998 | ||
|
|
bf4f471cfd | ||
|
|
978bff9dbc | ||
|
|
869d9af7dd | ||
|
|
af10499ecb | ||
|
|
a1a4d8ff98 | ||
|
|
95d61e8aa2 | ||
|
|
ec8dd1a54f | ||
|
|
382ee34d0e | ||
|
|
0999c9e9ab | ||
|
|
c485968e7f | ||
|
|
e727620bd3 | ||
|
|
71fa3ecebc | ||
|
|
70101d1187 | ||
|
|
c060c205d3 | ||
|
|
038b2efb75 | ||
|
|
1fe4cd03e9 | ||
|
|
12e02e67ff | ||
|
|
b6f57dfb78 | ||
|
|
3ebf2c2d2d | ||
|
|
1bac6f1ee7 | ||
|
|
67e7acd6bd | ||
|
|
910ce573d6 | ||
|
|
689336b3e1 | ||
|
|
01cf72cdef | ||
|
|
4cdb97c699 | ||
|
|
1bd795a9c6 | ||
|
|
39f29e6c30 | ||
|
|
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 | ||
|
|
95e0608749 | ||
|
|
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 | ||
|
|
8408b82e9c | ||
|
|
cd3b1ab828 | ||
|
|
ceebc56e62 | ||
|
|
70190e5230 |
69
.github/ISSUE_TEMPLATE/Bug_report.yml
vendored
69
.github/ISSUE_TEMPLATE/Bug_report.yml
vendored
@@ -11,22 +11,35 @@ body:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: I've found a bug and checked that ...
|
||||
description: Prior to placing the issue, please check following:** *(fill out each checkbox with an `X` once done)*
|
||||
label: Checklist prior issue creation
|
||||
description: Prior to creating the issue...
|
||||
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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- type: textarea
|
||||
attributes:
|
||||
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.
|
||||
render: plain text
|
||||
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.)
|
||||
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:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -36,45 +49,36 @@ body:
|
||||
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."
|
||||
render: plain text
|
||||
placeholder: |-
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## 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
|
||||
attributes:
|
||||
label: "Which branch are you using?"
|
||||
description: "#### `git rev-parse --abbrev-ref HEAD`"
|
||||
description: "#### Run: `git rev-parse --abbrev-ref HEAD`"
|
||||
multiple: false
|
||||
options:
|
||||
- master
|
||||
- master (stable)
|
||||
- staging
|
||||
- nightly
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: "Which architecture are you using?"
|
||||
description: "#### `uname -m`"
|
||||
description: "#### Run: `uname -m`"
|
||||
multiple: false
|
||||
options:
|
||||
- x86
|
||||
- x86_64
|
||||
- ARM64 (aarch64)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Operating System:"
|
||||
description: "#### Run: `lsb_release -ds`"
|
||||
placeholder: "e.g. Ubuntu 22.04 LTS"
|
||||
validations:
|
||||
required: true
|
||||
@@ -93,43 +97,44 @@ body:
|
||||
- type: input
|
||||
attributes:
|
||||
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:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Docker version:"
|
||||
description: "#### `docker version`"
|
||||
description: "#### Run: `docker version`"
|
||||
placeholder: "20.10.21"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
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"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "mailcow version:"
|
||||
description: "#### ```git describe --tags `git rev-list --tags --max-count=1` ```"
|
||||
placeholder: "2022-08"
|
||||
description: "#### Run: ```git describe --tags `git rev-list --tags --max-count=1` ```"
|
||||
placeholder: "2022-08x"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Reverse proxy:"
|
||||
placeholder: "e.g. Nginx/Traefik"
|
||||
placeholder: "e.g. nginx/Traefik, or none"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
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
|
||||
validations:
|
||||
required: true
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Logs of iptables -L -vn:"
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
||||
uses: actions/stale@v9.1.0
|
||||
uses: actions/stale@v10.1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||
days-before-stale: 60
|
||||
|
||||
2
.github/workflows/image_builds.yml
vendored
2
.github/workflows/image_builds.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
- "watchdog-mailcow"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Docker
|
||||
run: |
|
||||
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
||||
|
||||
4
.github/workflows/pr_to_nightly.yml
vendored
4
.github/workflows/pr_to_nightly.yml
vendored
@@ -8,11 +8,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run the Action
|
||||
uses: devops-infra/action-pull-request@v0.6.0
|
||||
uses: devops-infra/action-pull-request@v1.0.2
|
||||
with:
|
||||
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||
|
||||
2
.github/workflows/rebuild_backup_image.yml
vendored
2
.github/workflows/rebuild_backup_image.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
@@ -15,14 +15,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Generate postscreen_access.cidr
|
||||
run: |
|
||||
bash helper-scripts/update_postscreen_whitelist.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
|
||||
commit-message: update postscreen_access.cidr
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -75,3 +75,4 @@ refresh_images.sh
|
||||
update_diffs/
|
||||
create_cold_standby.sh
|
||||
!data/conf/nginx/mailcow_auth.conf
|
||||
data/conf/postfix/postfix-tlspol
|
||||
@@ -1,11 +1,11 @@
|
||||
# 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!
|
||||
|
||||
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
|
||||
|
||||
@@ -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.*
|
||||
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!
|
||||
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
|
||||
**_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:
|
||||
|
||||
### 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
|
||||
|
||||
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).
|
||||
|
||||
230
_modules/scripts/core.sh
Normal file
230
_modules/scripts/core.sh
Normal file
@@ -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
|
||||
}
|
||||
239
_modules/scripts/ipv6_controller.sh
Normal file
239
_modules/scripts/ipv6_controller.sh
Normal file
@@ -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"
|
||||
}
|
||||
96
_modules/scripts/migrate_options.sh
Normal file
96
_modules/scripts/migrate_options.sh
Normal file
@@ -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
|
||||
}
|
||||
300
_modules/scripts/new_options.sh
Normal file
300
_modules/scripts/new_options.sh
Normal file
@@ -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
|
||||
if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then
|
||||
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
|
||||
else
|
||||
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
|
||||
# Fetch certs for autoconfig and autodiscover subdomains
|
||||
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
|
||||
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig' 'mta-sts')
|
||||
fi
|
||||
|
||||
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
|
||||
@@ -258,6 +246,25 @@ while true; do
|
||||
done
|
||||
VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}")
|
||||
done
|
||||
|
||||
# Fetch alias domains where target domain has MTA-STS enabled
|
||||
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
|
||||
SQL_ALIAS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT ad.alias_domain FROM alias_domain ad INNER JOIN mta_sts m ON ad.target_domain = m.domain WHERE ad.active = 1 AND m.active = 1" -Bs)
|
||||
if [[ $? -eq 0 ]]; then
|
||||
while read alias_domain; do
|
||||
if [[ -z "${alias_domain}" ]]; then
|
||||
# ignore empty lines
|
||||
continue
|
||||
fi
|
||||
# Only add mta-sts subdomain for alias domains
|
||||
if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then
|
||||
if check_domain "mta-sts.${alias_domain}"; then
|
||||
VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}")
|
||||
fi
|
||||
fi
|
||||
done <<< "${SQL_ALIAS_DOMAINS}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if check_domain ${MAILCOW_HOSTNAME}; then
|
||||
@@ -299,7 +306,7 @@ while true; do
|
||||
VALIDATED_CERTIFICATES+=("${CERT_NAME}")
|
||||
|
||||
# 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="$?"
|
||||
if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully
|
||||
CERT_AMOUNT_CHANGED=1
|
||||
|
||||
@@ -93,8 +93,8 @@ until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do
|
||||
sleep 2
|
||||
done
|
||||
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/"
|
||||
ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} \
|
||||
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} \
|
||||
--account-key ${ACME_BASE}/acme/account.pem \
|
||||
--disable-check \
|
||||
--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
|
||||
echo "Cleaning up tmp files..."
|
||||
rm -rf /var/lib/clamav/clamav-*.tmp
|
||||
rm -rf /var/lib/clamav/tmp.*
|
||||
|
||||
# Prepare whitelist
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ FROM alpine:3.21
|
||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||
ARG GOSU_VERSION=1.16
|
||||
ARG GOSU_VERSION=1.19
|
||||
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
@@ -204,16 +204,17 @@ EOF
|
||||
# Create random master Password for SOGo SSO
|
||||
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
|
||||
# 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
|
||||
# Autogenerated by mailcow
|
||||
passdb {
|
||||
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
|
||||
|
||||
# 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
|
||||
# Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated
|
||||
cat <<'EOF' > /usr/local/bin/quota_notify.py
|
||||
|
||||
@@ -132,8 +132,8 @@ while ($row = $sth->fetchrow_arrayref()) {
|
||||
"--tmpdir", "/tmp",
|
||||
"--nofoldersizes",
|
||||
"--addheader",
|
||||
($timeout1 gt "0" ? () : ('--timeout1', $timeout1)),
|
||||
($timeout2 gt "0" ? () : ('--timeout2', $timeout2)),
|
||||
($timeout1 le "0" ? () : ('--timeout1', $timeout1)),
|
||||
($timeout2 le "0" ? () : ('--timeout2', $timeout2)),
|
||||
($exclude eq "" ? () : ("--exclude", $exclude)),
|
||||
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
|
||||
($maxage eq "0" ? () : ('--maxage', $maxage)),
|
||||
|
||||
@@ -8,7 +8,8 @@ from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import COMMASPACE, formatdate
|
||||
import jinja2
|
||||
from jinja2 import Template
|
||||
from jinja2 import TemplateError
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
import json
|
||||
import redis
|
||||
import time
|
||||
@@ -75,22 +76,27 @@ try:
|
||||
|
||||
def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
|
||||
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))
|
||||
if len(meta_query) == 0:
|
||||
return
|
||||
msg_count = len(meta_query)
|
||||
env = SandboxedEnvironment()
|
||||
if r.get('Q_HTML'):
|
||||
try:
|
||||
template = Template(r.get('Q_HTML'))
|
||||
except:
|
||||
print("Error: Cannot parse quarantine template, falling back to default template.")
|
||||
with open('/templates/quarantine.tpl') as file_:
|
||||
template = Template(file_.read())
|
||||
try:
|
||||
template = env.from_string(r.get('Q_HTML'))
|
||||
except Exception:
|
||||
print("Error: Cannot parse quarantine template, falling back to default template.")
|
||||
with open('/templates/quarantine.tpl') as file_:
|
||||
template = env.from_string(file_.read())
|
||||
else:
|
||||
with open('/templates/quarantine.tpl') as file_:
|
||||
template = Template(file_.read())
|
||||
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
|
||||
with open('/templates/quarantine.tpl') as file_:
|
||||
template = env.from_string(file_.read())
|
||||
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)
|
||||
count = 0
|
||||
while count < 15:
|
||||
|
||||
@@ -6,7 +6,7 @@ from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import COMMASPACE, formatdate
|
||||
import jinja2
|
||||
from jinja2 import Template
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
import redis
|
||||
import time
|
||||
import json
|
||||
@@ -33,16 +33,24 @@ while True:
|
||||
|
||||
if r.get('QW_HTML'):
|
||||
try:
|
||||
template = Template(r.get('QW_HTML'))
|
||||
except:
|
||||
print("Error: Cannot parse quarantine template, falling back to default template.")
|
||||
env = SandboxedEnvironment()
|
||||
template = env.from_string(r.get('QW_HTML'))
|
||||
except Exception:
|
||||
print("Error: Cannot parse quota template, falling back to default template.")
|
||||
with open('/templates/quota.tpl') as file_:
|
||||
template = Template(file_.read())
|
||||
env = SandboxedEnvironment()
|
||||
template = env.from_string(file_.read())
|
||||
else:
|
||||
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)
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
backend=iptables
|
||||
backend=nftables
|
||||
|
||||
nft list table ip filter &>/dev/null
|
||||
nftables_found=$?
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
DEBUG = False
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
@@ -20,10 +22,13 @@ from modules.Logger import Logger
|
||||
from modules.IPTables import IPTables
|
||||
from modules.NFTables import NFTables
|
||||
|
||||
def logdebug(msg):
|
||||
if DEBUG:
|
||||
logger.logInfo("DEBUG: %s" % msg)
|
||||
|
||||
# globals
|
||||
# Globals
|
||||
WHITELIST = []
|
||||
BLACKLIST= []
|
||||
BLACKLIST = []
|
||||
bans = {}
|
||||
quit_now = False
|
||||
exit_code = 0
|
||||
@@ -33,12 +38,10 @@ r = None
|
||||
pubsub = None
|
||||
clear_before_quit = False
|
||||
|
||||
|
||||
def refreshF2boptions():
|
||||
global f2boptions
|
||||
global quit_now
|
||||
global exit_code
|
||||
|
||||
f2boptions = {}
|
||||
|
||||
if not r.get('F2B_OPTIONS'):
|
||||
@@ -52,8 +55,9 @@ def refreshF2boptions():
|
||||
else:
|
||||
try:
|
||||
f2boptions = json.loads(r.get('F2B_OPTIONS'))
|
||||
except ValueError:
|
||||
logger.logCrit('Error loading F2B options: F2B_OPTIONS is not json')
|
||||
except ValueError as e:
|
||||
logger.logCrit(
|
||||
'Error loading F2B options: F2B_OPTIONS is not json. Exception: %s' % e)
|
||||
quit_now = True
|
||||
exit_code = 2
|
||||
|
||||
@@ -61,15 +65,15 @@ def refreshF2boptions():
|
||||
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
|
||||
|
||||
def verifyF2boptions(f2boptions):
|
||||
verifyF2boption(f2boptions,'ban_time', 1800)
|
||||
verifyF2boption(f2boptions,'max_ban_time', 10000)
|
||||
verifyF2boption(f2boptions,'ban_time_increment', True)
|
||||
verifyF2boption(f2boptions,'max_attempts', 10)
|
||||
verifyF2boption(f2boptions,'retry_window', 600)
|
||||
verifyF2boption(f2boptions,'netban_ipv4', 32)
|
||||
verifyF2boption(f2boptions,'netban_ipv6', 128)
|
||||
verifyF2boption(f2boptions,'banlist_id', str(uuid.uuid4()))
|
||||
verifyF2boption(f2boptions,'manage_external', 0)
|
||||
verifyF2boption(f2boptions, 'ban_time', 1800)
|
||||
verifyF2boption(f2boptions, 'max_ban_time', 10000)
|
||||
verifyF2boption(f2boptions, 'ban_time_increment', True)
|
||||
verifyF2boption(f2boptions, 'max_attempts', 10)
|
||||
verifyF2boption(f2boptions, 'retry_window', 600)
|
||||
verifyF2boption(f2boptions, 'netban_ipv4', 32)
|
||||
verifyF2boption(f2boptions, 'netban_ipv6', 128)
|
||||
verifyF2boption(f2boptions, 'banlist_id', str(uuid.uuid4()))
|
||||
verifyF2boption(f2boptions, 'manage_external', 0)
|
||||
|
||||
def verifyF2boption(f2boptions, f2boption, 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):
|
||||
global f2boptions
|
||||
global lock
|
||||
|
||||
logdebug("ban() called with address=%s" % address)
|
||||
refreshF2boptions()
|
||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||
RETRY_WINDOW = int(f2boptions['retry_window'])
|
||||
@@ -119,31 +123,43 @@ def ban(address):
|
||||
NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6'])
|
||||
|
||||
ip = get_ip(address)
|
||||
if not ip: return
|
||||
if not ip:
|
||||
logdebug("No valid IP -- skipping ban()")
|
||||
return
|
||||
address = str(ip)
|
||||
self_network = ipaddress.ip_network(address)
|
||||
|
||||
with lock:
|
||||
temp_whitelist = set(WHITELIST)
|
||||
if temp_whitelist:
|
||||
for wl_key in temp_whitelist:
|
||||
wl_net = ipaddress.ip_network(wl_key, False)
|
||||
if wl_net.overlaps(self_network):
|
||||
logger.logInfo('Address %s is whitelisted by rule %s' % (self_network, wl_net))
|
||||
return
|
||||
logdebug("Checking if %s overlaps with any WHITELIST entries" % self_network)
|
||||
if temp_whitelist:
|
||||
for wl_key in temp_whitelist:
|
||||
wl_net = ipaddress.ip_network(wl_key, False)
|
||||
logdebug("Checking overlap between %s and %s" % (self_network, wl_net))
|
||||
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)
|
||||
logdebug("Ban net: %s" % net)
|
||||
|
||||
if not net in bans:
|
||||
bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0}
|
||||
logdebug("Initing new ban counter for %s" % net)
|
||||
|
||||
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:
|
||||
bans[net]['attempts'] = 0
|
||||
logdebug("Ban counter for %s reset as window expired" % net)
|
||||
|
||||
bans[net]['attempts'] += 1
|
||||
bans[net]['last_attempt'] = current_attempt
|
||||
logdebug("%s attempts now %d" % (net, bans[net]['attempts']))
|
||||
|
||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||
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 ))
|
||||
if type(ip) is ipaddress.IPv4Address and int(f2boptions['manage_external']) != 1:
|
||||
with lock:
|
||||
logdebug("Calling tables.banIPv4(%s)" % net)
|
||||
tables.banIPv4(net)
|
||||
elif int(f2boptions['manage_external']) != 1:
|
||||
with lock:
|
||||
logdebug("Calling tables.banIPv6(%s)" % 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)
|
||||
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):
|
||||
global lock
|
||||
|
||||
logdebug("Calling unban() with net=%s" % net)
|
||||
if not net in bans:
|
||||
logger.logInfo('%s is not banned, skipping unban and deleting from queue (if any)' % net)
|
||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||
return
|
||||
|
||||
logger.logInfo(
|
||||
'%s is not banned, skipping unban and deleting from queue (if any)' % net)
|
||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||
return
|
||||
logger.logInfo('Unbanning %s' % net)
|
||||
if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network:
|
||||
with lock:
|
||||
logdebug("Calling tables.unbanIPv4(%s)" % net)
|
||||
tables.unbanIPv4(net)
|
||||
else:
|
||||
with lock:
|
||||
logdebug("Calling tables.unbanIPv6(%s)" % net)
|
||||
tables.unbanIPv6(net)
|
||||
|
||||
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
|
||||
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
|
||||
if net in bans:
|
||||
logdebug("Unban for %s, setting attempts=0, ban_counter+=1" % net)
|
||||
bans[net]['attempts'] = 0
|
||||
bans[net]['ban_counter'] += 1
|
||||
|
||||
@@ -204,17 +227,19 @@ def permBan(net, unban=False):
|
||||
|
||||
if is_unbanned:
|
||||
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:
|
||||
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():
|
||||
global lock
|
||||
logger.logInfo('Clearing all bans')
|
||||
for net in bans.copy():
|
||||
logdebug("Unbanning net: %s" % net)
|
||||
unban(net)
|
||||
with lock:
|
||||
logdebug("Clearing IPv4/IPv6 table")
|
||||
tables.clearIPv4Table()
|
||||
tables.clearIPv6Table()
|
||||
try:
|
||||
@@ -275,21 +300,35 @@ def snat6(snat_target):
|
||||
|
||||
def autopurge():
|
||||
global f2boptions
|
||||
|
||||
logdebug("autopurge thread started")
|
||||
while not quit_now:
|
||||
logdebug("autopurge tick")
|
||||
time.sleep(10)
|
||||
refreshF2boptions()
|
||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
||||
logdebug("QUEUE_UNBAN: %s" % QUEUE_UNBAN)
|
||||
if QUEUE_UNBAN:
|
||||
for net in QUEUE_UNBAN:
|
||||
logdebug("Autopurge: unbanning queued net: %s" % net)
|
||||
unban(str(net))
|
||||
for net in bans.copy():
|
||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||
NET_BAN_TIME = calcNetBanTime(bans[net]['ban_counter'])
|
||||
TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt']
|
||||
if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME:
|
||||
unban(net)
|
||||
# Only check expiry for actively banned IPs:
|
||||
active_bans = r.hgetall('F2B_ACTIVE_BANS')
|
||||
now = time.time()
|
||||
for net_str, expire_str in active_bans.items():
|
||||
logdebug("Checking ban expiry for (actively banned): %s" % net_str)
|
||||
# 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():
|
||||
global lock
|
||||
@@ -359,7 +398,7 @@ def whitelistUpdate():
|
||||
with lock:
|
||||
if Counter(new_whitelist) != Counter(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))
|
||||
|
||||
def blacklistUpdate():
|
||||
@@ -375,7 +414,7 @@ def blacklistUpdate():
|
||||
addban = set(new_blacklist).difference(BLACKLIST)
|
||||
delban = set(BLACKLIST).difference(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:
|
||||
for net in addban:
|
||||
permBan(net=net)
|
||||
@@ -386,42 +425,43 @@ def blacklistUpdate():
|
||||
|
||||
def sigterm_quit(signum, frame):
|
||||
global clear_before_quit
|
||||
logdebug("SIGTERM received, setting clear_before_quit to True and exiting")
|
||||
clear_before_quit = True
|
||||
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:
|
||||
clear()
|
||||
if pubsub is not None:
|
||||
pubsub.unsubscribe()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# init Logger
|
||||
logger = Logger()
|
||||
|
||||
# init backend
|
||||
backend = sys.argv[1]
|
||||
logdebug("Backend: %s" % backend)
|
||||
if backend == "nftables":
|
||||
logger.logInfo('Using NFTables backend')
|
||||
tables = NFTables(chain_name, logger)
|
||||
else:
|
||||
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)
|
||||
|
||||
# In case a previous session was killed without cleanup
|
||||
clear()
|
||||
|
||||
# Reinit MAILCOW chain
|
||||
# Is called before threads start, no locking
|
||||
logger.logInfo("Initializing mailcow netfilter chain")
|
||||
tables.initChainIPv4()
|
||||
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")
|
||||
else:
|
||||
logger.logInfo(f"Setting {chain_name} isolation")
|
||||
@@ -432,23 +472,28 @@ if __name__ == '__main__':
|
||||
try:
|
||||
redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '')
|
||||
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):
|
||||
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:
|
||||
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()
|
||||
pubsub = r.pubsub()
|
||||
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)
|
||||
else:
|
||||
break
|
||||
logger.set_redis(r)
|
||||
logdebug("Redis connection established, setting up F2B keys")
|
||||
|
||||
# rename fail2ban to netfilter
|
||||
if r.exists('F2B_LOG'):
|
||||
logdebug("Renaming F2B_LOG to NETFILTER_LOG")
|
||||
r.rename('F2B_LOG', 'NETFILTER_LOG')
|
||||
# clear bans in redis
|
||||
r.delete('F2B_ACTIVE_BANS')
|
||||
r.delete('F2B_PERM_BANS')
|
||||
|
||||
@@ -463,7 +508,7 @@ if __name__ == '__main__':
|
||||
snat_ip = os.getenv('SNAT_TO_SOURCE')
|
||||
snat_ipo = ipaddress.ip_address(snat_ip)
|
||||
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.start()
|
||||
except ValueError:
|
||||
@@ -499,4 +544,5 @@ if __name__ == '__main__':
|
||||
while not quit_now:
|
||||
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 json
|
||||
import datetime
|
||||
|
||||
class Logger:
|
||||
def __init__(self):
|
||||
@@ -8,17 +9,28 @@ class Logger:
|
||||
def set_redis(self, 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):
|
||||
tolog = {}
|
||||
tolog['time'] = int(round(time.time()))
|
||||
tolog['priority'] = priority
|
||||
tolog['message'] = message
|
||||
print(message)
|
||||
# build redis-friendly dict
|
||||
tolog = {
|
||||
'time': int(round(time.time())), # keep raw timestamp for Redis
|
||||
'priority': priority,
|
||||
'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:
|
||||
try:
|
||||
self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False))
|
||||
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):
|
||||
self.log('warn', message)
|
||||
@@ -27,4 +39,4 @@ class Logger:
|
||||
self.log('crit', 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'])};"
|
||||
listen_plain_config = f"listen {template_vars['HTTP_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_ssl_config += f"\nlisten [::]:{template_vars['HTTPS_PORT']} ssl;"
|
||||
listen_ssl_config += "\nhttp2 on;"
|
||||
@@ -58,7 +58,7 @@ def prepare_template_vars():
|
||||
'SOGOHOST': os.getenv("SOGOHOST", ipv4_network + ".248"),
|
||||
'RSPAMDHOST': os.getenv("RSPAMDHOST", "rspamd-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"),
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,13 @@ import time
|
||||
import magic
|
||||
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
|
||||
olefy_listen_addr_string = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1,::1')
|
||||
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")
|
||||
failed = False
|
||||
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 = b'[ { "error": "Unhandled error - too short olevba response" } ]'
|
||||
failed = True
|
||||
|
||||
@@ -3,15 +3,15 @@ FROM php:8.2-fpm-alpine3.21
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
# 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.28
|
||||
# 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.1
|
||||
# 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>.*)$
|
||||
ARG MEMCACHED_PECL_VERSION=3.2.0
|
||||
ARG MEMCACHED_PECL_VERSION=3.4.0
|
||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG REDIS_PECL_VERSION=6.1.0
|
||||
ARG REDIS_PECL_VERSION=6.3.0
|
||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG COMPOSER_VERSION=2.8.6
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ DELIMITER //
|
||||
CREATE EVENT clean_spamalias
|
||||
ON SCHEDULE EVERY 1 DAY DO
|
||||
BEGIN
|
||||
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP();
|
||||
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0;
|
||||
END;
|
||||
//
|
||||
DELIMITER ;
|
||||
|
||||
50
data/Dockerfiles/postfix-tlspol/Dockerfile
Normal file
50
data/Dockerfiles/postfix-tlspol/Dockerfile
Normal file
@@ -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"]
|
||||
7
data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh
Executable file
7
data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh
Executable file
@@ -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
data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh
Executable file
52
data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh
Executable file
@@ -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
data/Dockerfiles/postfix-tlspol/stop-supervisor.sh
Executable file
8
data/Dockerfiles/postfix-tlspol/stop-supervisor.sh
Executable file
@@ -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
|
||||
25
data/Dockerfiles/postfix-tlspol/supervisord.conf
Normal file
25
data/Dockerfiles/postfix-tlspol/supervisord.conf
Normal file
@@ -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
|
||||
45
data/Dockerfiles/postfix-tlspol/syslog-ng-redis_slave.conf
Normal file
45
data/Dockerfiles/postfix-tlspol/syslog-ng-redis_slave.conf
Normal file
@@ -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);
|
||||
};
|
||||
45
data/Dockerfiles/postfix-tlspol/syslog-ng.conf
Normal file
45
data/Dockerfiles/postfix-tlspol/syslog-ng.conf
Normal file
@@ -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
|
||||
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV LC_ALL C
|
||||
ENV LC_ALL=C
|
||||
|
||||
RUN dpkg-divert --local --rename --add /sbin/initctl \
|
||||
&& ln -sf /bin/true /sbin/initctl \
|
||||
|
||||
@@ -329,14 +329,17 @@ query = SELECT goto FROM alias
|
||||
SELECT id FROM alias
|
||||
WHERE address='%s'
|
||||
AND (active='1' OR active='2')
|
||||
AND sender_allowed='1'
|
||||
), (
|
||||
SELECT id FROM alias
|
||||
WHERE address='@%d'
|
||||
AND (active='1' OR active='2')
|
||||
AND sender_allowed='1'
|
||||
)
|
||||
)
|
||||
)
|
||||
AND active='1'
|
||||
AND sender_allowed='1'
|
||||
AND (domain IN
|
||||
(SELECT domain FROM domain
|
||||
WHERE domain='%d'
|
||||
@@ -390,7 +393,7 @@ hosts = unix:/var/run/mysqld/mysqld.sock
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT goto FROM spamalias
|
||||
WHERE address='%s'
|
||||
AND validity >= UNIX_TIMESTAMP()
|
||||
AND (validity >= UNIX_TIMESTAMP() OR permanent != 0)
|
||||
EOF
|
||||
|
||||
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>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG RSPAMD_VER=rspamd_3.11.1-1~ab0b44951
|
||||
ARG CODENAME=bookworm
|
||||
ARG RSPAMD_VER=rspamd_3.14.2-82~90302bc
|
||||
ARG CODENAME=trixie
|
||||
ENV LC_ALL=C
|
||||
|
||||
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 \
|
||||
netcat-traditional \
|
||||
wget \
|
||||
redis-tools \
|
||||
procps \
|
||||
redis-tools \
|
||||
procps \
|
||||
nano \
|
||||
lua-cjson \
|
||||
&& 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
|
||||
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
|
||||
ln -s /usr/lib/$(uname -m)-linux-gnu/liblua5.1-cjson.so.0.0.0 /usr/lib/rspamd/cjson.so
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG DEBIAN_VERSION=bookworm
|
||||
ARG SOGO_DEBIAN_REPOSITORY=https://packagingv2.sogo.nu/sogo-nightly-debian/
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||
ARG GOSU_VERSION=1.17
|
||||
ARG GOSU_VERSION=1.19
|
||||
ENV LC_ALL=C
|
||||
|
||||
# Prerequisites
|
||||
|
||||
@@ -24,6 +24,10 @@ while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
|
||||
done
|
||||
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
|
||||
RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ RUN apk add --update \
|
||||
fcgi \
|
||||
openssl \
|
||||
nagios-plugins-mysql \
|
||||
nagios-plugins-dns \
|
||||
nagios-plugins-disk \
|
||||
bind-tools \
|
||||
redis \
|
||||
@@ -32,9 +31,11 @@ RUN apk add --update \
|
||||
tzdata \
|
||||
whois \
|
||||
&& 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 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"]
|
||||
|
||||
39
data/Dockerfiles/watchdog/check_dns.sh
Executable file
39
data/Dockerfiles/watchdog/check_dns.sh
Executable file
@@ -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
|
||||
|
||||
if [ "${DEV_MODE}" != "n" ]; then
|
||||
echo -e "\e[31mEnabled Debug Mode\e[0m"
|
||||
set -x
|
||||
fi
|
||||
|
||||
trap "exit" INT TERM
|
||||
trap "kill 0" EXIT
|
||||
|
||||
@@ -297,7 +302,7 @@ unbound_checks() {
|
||||
touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow
|
||||
host_ip=$(get_container_ip unbound-mailcow)
|
||||
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')
|
||||
if [[ -z ${DNSSEC} ]]; then
|
||||
echo "DNSSEC failure" 2>> /tmp/unbound-mailcow 1>&2
|
||||
@@ -445,6 +450,31 @@ postfix_checks() {
|
||||
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() {
|
||||
err_count=0
|
||||
diff_c=0
|
||||
@@ -922,6 +952,18 @@ PID=$!
|
||||
echo "Spawned mailq_checks with PID ${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
|
||||
if ! dovecot_checks; then
|
||||
@@ -994,6 +1036,7 @@ PID=$!
|
||||
echo "Spawned cert_checks with PID ${PID}"
|
||||
BACKGROUND_TASKS+=(${PID})
|
||||
|
||||
if [[ "${SKIP_OLEFY}" =~ ^([nN][oO]|[nN])+$ ]]; then
|
||||
(
|
||||
while true; do
|
||||
if ! olefy_checks; then
|
||||
@@ -1005,6 +1048,7 @@ done
|
||||
PID=$!
|
||||
echo "Spawned olefy_checks with PID ${PID}"
|
||||
BACKGROUND_TASKS+=(${PID})
|
||||
fi
|
||||
|
||||
(
|
||||
while true; do
|
||||
|
||||
@@ -79,22 +79,31 @@ if ($isSOGoRequest) {
|
||||
}
|
||||
}
|
||||
if ($result === false){
|
||||
$result = apppass_login($post['username'], $post['password'], array($post['service'] => true), array(
|
||||
// If it's a SOGo Request, don't check for protocol access
|
||||
if ($isSOGoRequest) {
|
||||
$service = 'SOGO';
|
||||
$post['service'] = 'NONE';
|
||||
} else {
|
||||
$service = $post['service'];
|
||||
}
|
||||
|
||||
$result = apppass_login($post['username'], $post['password'], array(
|
||||
'service' => $post['service'],
|
||||
'is_internal' => true,
|
||||
'remote_addr' => $post['real_rip']
|
||||
));
|
||||
if ($result) {
|
||||
error_log('MAILCOWAUTH: App auth for user ' . $post['username']);
|
||||
set_sasl_log($post['username'], $post['real_rip'], $post['service']);
|
||||
error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $service . " from IP " . $post['real_rip']);
|
||||
set_sasl_log($post['username'], $post['real_rip'], $service);
|
||||
}
|
||||
}
|
||||
if ($result === false){
|
||||
// Init Identity Provider
|
||||
$iam_provider = identity_provider('init');
|
||||
$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']);
|
||||
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']);
|
||||
}
|
||||
}
|
||||
@@ -103,7 +112,7 @@ if ($result) {
|
||||
http_response_code(200); // OK
|
||||
$return['success'] = true;
|
||||
} 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
|
||||
}
|
||||
|
||||
|
||||
@@ -29,13 +29,23 @@ function auth_password_verify(request, password)
|
||||
insecure = true
|
||||
}
|
||||
|
||||
if c ~= 200 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_INTERNAL_FAILURE, "Upstream error"
|
||||
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error"
|
||||
end
|
||||
|
||||
local api_response = json.decode(table.concat(res))
|
||||
if api_response.success == true then
|
||||
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, ""
|
||||
end
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ events {
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
server_tokens off;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
@@ -48,13 +49,21 @@ http {
|
||||
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; }
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
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 / {
|
||||
return 301 https://$host$uri$is_args$args;
|
||||
}
|
||||
@@ -70,7 +79,7 @@ http {
|
||||
{%endif%}
|
||||
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
||||
|
||||
{% if not DISABLE_IPv6 %}
|
||||
{% if ENABLE_IPV6 %}
|
||||
{% if not HTTP_REDIRECT %}
|
||||
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
||||
{%endif%}
|
||||
@@ -82,7 +91,7 @@ http {
|
||||
ssl_certificate /etc/ssl/mail/cert.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;
|
||||
}
|
||||
@@ -97,7 +106,7 @@ http {
|
||||
{%endif%}
|
||||
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
||||
|
||||
{% if not DISABLE_IPv6 %}
|
||||
{% if ENABLE_IPV6 %}
|
||||
{% if not HTTP_REDIRECT %}
|
||||
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
||||
{%endif%}
|
||||
@@ -118,7 +127,7 @@ http {
|
||||
# rspamd dynmaps:
|
||||
server {
|
||||
listen 8081;
|
||||
{% if not DISABLE_IPv6 %}
|
||||
{% if ENABLE_IPV6 %}
|
||||
listen [::]:8081;
|
||||
{%endif%}
|
||||
index index.php index.html;
|
||||
@@ -191,7 +200,7 @@ http {
|
||||
{%endif%}
|
||||
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
|
||||
|
||||
{% if not DISABLE_IPv6 %}
|
||||
{% if ENABLE_IPV6 %}
|
||||
{% if not HTTP_REDIRECT %}
|
||||
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
|
||||
{%endif%}
|
||||
|
||||
@@ -14,7 +14,6 @@ ssl_session_tickets off;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=15768000;";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
add_header X-Download-Options noopen;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
@@ -76,6 +75,14 @@ location ^~ /.well-known/acme-challenge/ {
|
||||
allow all;
|
||||
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/carddav$ /SOGo/dav/ permanent;
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
; NOTE: Restart phpfpm on ANY manual changes to PHP files!
|
||||
|
||||
; opcache
|
||||
opcache.enable=1
|
||||
opcache.enable_cli=1
|
||||
opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=10000
|
||||
opcache.memory_consumption=128
|
||||
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_security_options =
|
||||
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
|
||||
mail_name = Postcow
|
||||
# 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 Tue Apr 1 00:20:51 UTC 2025
|
||||
# Whitelist generated by Postwhite v3.4 on Sun Feb 1 00:29:33 UTC 2026
|
||||
# https://github.com/stevejenkins/postwhite/
|
||||
# 2067 total rules
|
||||
# 2102 total rules
|
||||
2a00:1450:4000::/36 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::/49 permit
|
||||
2a01:111:f403:c000::/51 permit
|
||||
2a01:111:f403:d000::/53 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:3001:200::/56 permit
|
||||
2a01:b747:3002:200::/56 permit
|
||||
@@ -16,28 +29,42 @@
|
||||
2a01:b747:3005:200::/56 permit
|
||||
2a01:b747:3006:200::/56 permit
|
||||
2a02:a60:0:5::/64 permit
|
||||
2a0f:f640::/56 permit
|
||||
2c0f:fb50:4000::/36 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.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.94.40.108 permit
|
||||
3.121.107.214 permit
|
||||
3.129.120.190 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.25.194.0/23 permit
|
||||
8.25.196.0/23 permit
|
||||
8.39.54.0/23 permit
|
||||
8.39.54.250/31 permit
|
||||
8.40.222.0/23 permit
|
||||
8.40.222.250/31 permit
|
||||
8.36.116.0/24 permit
|
||||
8.39.144.0/24 permit
|
||||
12.130.86.238 permit
|
||||
13.107.246.59 permit
|
||||
13.107.213.51 permit
|
||||
13.107.246.51 permit
|
||||
13.108.16.0/20 permit
|
||||
13.110.208.0/21 permit
|
||||
13.110.209.0/24 permit
|
||||
13.110.216.0/22 permit
|
||||
13.110.224.0/20 permit
|
||||
13.111.0.0/16 permit
|
||||
13.111.191.0/24 permit
|
||||
13.216.7.111 permit
|
||||
13.216.54.180 permit
|
||||
15.200.21.50 permit
|
||||
15.200.44.248 permit
|
||||
15.200.201.185 permit
|
||||
@@ -50,16 +77,21 @@
|
||||
18.97.1.184/29 permit
|
||||
18.97.2.64/26 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.158.153.154 permit
|
||||
18.194.95.56 permit
|
||||
18.197.217.180 permit
|
||||
18.198.96.88 permit
|
||||
18.199.210.3 permit
|
||||
18.207.52.234 permit
|
||||
18.208.124.128/25 permit
|
||||
18.216.232.154 permit
|
||||
18.235.27.253 permit
|
||||
18.236.40.242 permit
|
||||
18.236.56.161 permit
|
||||
20.51.6.32/30 permit
|
||||
20.51.98.61 permit
|
||||
20.52.52.2 permit
|
||||
20.52.128.133 permit
|
||||
20.59.80.4/30 permit
|
||||
@@ -89,6 +121,7 @@
|
||||
23.253.183.147 permit
|
||||
23.253.183.148 permit
|
||||
23.253.183.150 permit
|
||||
24.110.64.0/18 permit
|
||||
27.123.204.128/30 permit
|
||||
27.123.204.132/31 permit
|
||||
27.123.204.148/30 permit
|
||||
@@ -101,8 +134,8 @@
|
||||
27.123.206.56/29 permit
|
||||
27.123.206.76/30 permit
|
||||
27.123.206.80/28 permit
|
||||
31.25.48.222 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
|
||||
@@ -121,17 +154,30 @@
|
||||
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.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.215.104.144 permit
|
||||
34.218.116.3 permit
|
||||
34.218.115.239 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.162.73.231 permit
|
||||
35.167.93.243 permit
|
||||
35.174.145.124 permit
|
||||
35.176.132.251 permit
|
||||
35.190.247.0/24 permit
|
||||
35.191.0.0/16 permit
|
||||
35.205.92.9 permit
|
||||
35.228.216.85 permit
|
||||
35.242.169.159 permit
|
||||
37.188.97.188 permit
|
||||
37.218.248.47 permit
|
||||
@@ -146,11 +192,19 @@
|
||||
40.233.83.78 permit
|
||||
40.233.88.28 permit
|
||||
44.206.138.57 permit
|
||||
44.210.169.44 permit
|
||||
44.217.45.156 permit
|
||||
44.236.56.93 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
|
||||
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.228.36.37 permit
|
||||
46.228.36.38/31 permit
|
||||
@@ -201,6 +255,7 @@
|
||||
46.243.88.177 permit
|
||||
46.243.95.179 permit
|
||||
46.243.95.180 permit
|
||||
50.16.246.183 permit
|
||||
50.18.45.249 permit
|
||||
50.18.121.236 permit
|
||||
50.18.121.248 permit
|
||||
@@ -214,14 +269,23 @@
|
||||
50.56.130.220 permit
|
||||
50.56.130.221 permit
|
||||
50.56.130.222 permit
|
||||
50.112.246.219 permit
|
||||
52.1.14.157 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.28.47 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.37.142.146 permit
|
||||
52.42.203.116 permit
|
||||
52.50.24.208 permit
|
||||
52.57.120.243 permit
|
||||
52.58.216.183 permit
|
||||
52.59.143.3 permit
|
||||
52.60.41.5 permit
|
||||
@@ -231,14 +295,6 @@
|
||||
52.94.124.0/28 permit
|
||||
52.95.48.152/29 permit
|
||||
52.95.49.88/29 permit
|
||||
52.96.91.34 permit
|
||||
52.96.111.82 permit
|
||||
52.96.214.50 permit
|
||||
52.96.222.194 permit
|
||||
52.96.222.226 permit
|
||||
52.96.223.2 permit
|
||||
52.96.228.130 permit
|
||||
52.96.229.242 permit
|
||||
52.100.0.0/15 permit
|
||||
52.102.0.0/16 permit
|
||||
52.103.0.0/17 permit
|
||||
@@ -263,24 +319,23 @@
|
||||
54.174.63.0/24 permit
|
||||
54.186.193.102 permit
|
||||
54.191.223.56 permit
|
||||
54.211.126.101 permit
|
||||
54.213.20.246 permit
|
||||
54.214.39.184 permit
|
||||
54.240.0.0/18 permit
|
||||
54.240.64.0/19 permit
|
||||
54.240.96.0/19 permit
|
||||
54.240.64.0/18 permit
|
||||
54.241.16.209 permit
|
||||
54.244.54.130 permit
|
||||
54.244.242.0/24 permit
|
||||
54.255.61.23 permit
|
||||
56.124.6.228 permit
|
||||
57.103.64.0/18 permit
|
||||
57.129.93.249 permit
|
||||
62.13.128.0/24 permit
|
||||
62.13.129.128/25 permit
|
||||
62.13.136.0/21 permit
|
||||
62.13.144.0/21 permit
|
||||
62.13.152.0/21 permit
|
||||
62.17.146.128/26 permit
|
||||
62.179.121.0/24 permit
|
||||
62.201.172.0/27 permit
|
||||
62.201.172.32/27 permit
|
||||
62.253.227.114 permit
|
||||
@@ -288,6 +343,9 @@
|
||||
63.128.21.0/24 permit
|
||||
63.143.57.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.20.241.45 permit
|
||||
64.69.212.0/24 permit
|
||||
@@ -300,6 +358,7 @@
|
||||
64.127.115.252 permit
|
||||
64.132.88.0/23 permit
|
||||
64.132.92.0/24 permit
|
||||
64.181.194.190 permit
|
||||
64.207.219.7 permit
|
||||
64.207.219.8 permit
|
||||
64.207.219.9 permit
|
||||
@@ -309,9 +368,6 @@
|
||||
64.207.219.13 permit
|
||||
64.207.219.14 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.72 permit
|
||||
64.207.219.73 permit
|
||||
@@ -321,9 +377,6 @@
|
||||
64.207.219.77 permit
|
||||
64.207.219.78 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.136 permit
|
||||
64.207.219.137 permit
|
||||
@@ -335,35 +388,14 @@
|
||||
64.207.219.143 permit
|
||||
64.233.160.0/19 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.190.0/24 permit
|
||||
65.54.241.0/24 permit
|
||||
65.55.29.77 permit
|
||||
65.55.33.64/28 permit
|
||||
65.55.34.0/24 permit
|
||||
65.55.42.224/28 permit
|
||||
65.55.52.224/27 permit
|
||||
65.55.78.128/25 permit
|
||||
65.55.81.48/28 permit
|
||||
65.55.90.0/24 permit
|
||||
65.55.94.0/25 permit
|
||||
65.55.111.0/24 permit
|
||||
65.55.113.64/26 permit
|
||||
65.55.116.0/25 permit
|
||||
65.55.126.0/25 permit
|
||||
65.55.174.0/25 permit
|
||||
65.55.178.128/27 permit
|
||||
65.55.234.192/26 permit
|
||||
65.110.161.77 permit
|
||||
65.123.29.213 permit
|
||||
65.123.29.220 permit
|
||||
65.154.166.0/24 permit
|
||||
65.212.180.36 permit
|
||||
66.102.0.0/20 permit
|
||||
66.119.150.192/26 permit
|
||||
66.162.193.226/31 permit
|
||||
66.163.184.0/24 permit
|
||||
66.163.185.0/24 permit
|
||||
66.163.186.0/24 permit
|
||||
@@ -477,7 +509,6 @@
|
||||
69.169.224.0/20 permit
|
||||
69.171.232.0/24 permit
|
||||
69.171.244.0/23 permit
|
||||
70.37.151.128/25 permit
|
||||
70.42.149.35 permit
|
||||
72.3.185.0/24 permit
|
||||
72.14.192.0/18 permit
|
||||
@@ -569,13 +600,11 @@
|
||||
74.86.241.250/31 permit
|
||||
74.112.67.243 permit
|
||||
74.125.0.0/16 permit
|
||||
74.202.227.40 permit
|
||||
74.208.4.200 permit
|
||||
74.208.4.201 permit
|
||||
74.208.4.220 permit
|
||||
74.208.4.221 permit
|
||||
74.209.250.0/24 permit
|
||||
75.2.70.75 permit
|
||||
76.223.128.0/19 permit
|
||||
76.223.176.0/20 permit
|
||||
77.238.176.0/24 permit
|
||||
@@ -598,6 +627,11 @@
|
||||
77.238.189.142 permit
|
||||
77.238.189.146/31 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
|
||||
82.165.159.2 permit
|
||||
82.165.159.3 permit
|
||||
@@ -613,10 +647,17 @@
|
||||
82.165.159.45 permit
|
||||
82.165.159.130 permit
|
||||
82.165.159.131 permit
|
||||
84.116.6.0/23 permit
|
||||
84.116.36.0/24 permit
|
||||
84.116.50.0/23 permit
|
||||
85.9.206.169 permit
|
||||
85.9.210.45 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
|
||||
87.238.80.0/21 permit
|
||||
87.248.103.12 permit
|
||||
@@ -656,12 +697,11 @@
|
||||
87.248.117.205 permit
|
||||
87.253.232.0/21 permit
|
||||
89.22.108.0/24 permit
|
||||
91.198.2.0/24 permit
|
||||
91.211.240.0/22 permit
|
||||
94.169.2.0/23 permit
|
||||
94.236.119.0/26 permit
|
||||
94.245.112.0/27 permit
|
||||
94.245.112.10/31 permit
|
||||
95.131.104.0/21 permit
|
||||
95.217.114.154 permit
|
||||
96.43.144.0/20 permit
|
||||
96.43.144.64/28 permit
|
||||
96.43.144.64/31 permit
|
||||
@@ -1152,7 +1192,6 @@
|
||||
98.139.245.208/30 permit
|
||||
98.139.245.212/31 permit
|
||||
99.78.197.208/28 permit
|
||||
99.83.190.102 permit
|
||||
103.9.96.0/22 permit
|
||||
103.28.42.0/24 permit
|
||||
103.151.192.0/23 permit
|
||||
@@ -1161,9 +1200,6 @@
|
||||
104.43.243.237 permit
|
||||
104.44.112.128/25 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.122.0/23 permit
|
||||
106.10.144.64/27 permit
|
||||
@@ -1289,6 +1325,7 @@
|
||||
106.50.16.0/28 permit
|
||||
107.20.18.111 permit
|
||||
107.20.210.250 permit
|
||||
107.22.191.150 permit
|
||||
108.174.0.0/24 permit
|
||||
108.174.0.215 permit
|
||||
108.174.3.0/24 permit
|
||||
@@ -1297,15 +1334,9 @@
|
||||
108.174.6.215 permit
|
||||
108.175.18.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
|
||||
109.224.244.0/24 permit
|
||||
109.237.142.0/24 permit
|
||||
111.221.23.128/25 permit
|
||||
111.221.26.0/27 permit
|
||||
111.221.66.0/25 permit
|
||||
111.221.69.128/25 permit
|
||||
111.221.112.0/21 permit
|
||||
112.19.199.64/29 permit
|
||||
112.19.242.64/29 permit
|
||||
116.214.12.47 permit
|
||||
@@ -1323,9 +1354,6 @@
|
||||
117.120.16.0/21 permit
|
||||
119.42.242.52/31 permit
|
||||
119.42.242.156 permit
|
||||
121.244.91.48 permit
|
||||
121.244.91.52 permit
|
||||
122.15.156.182 permit
|
||||
123.126.78.64/29 permit
|
||||
124.108.96.24/31 permit
|
||||
124.108.96.28/31 permit
|
||||
@@ -1353,6 +1381,7 @@
|
||||
128.245.248.0/21 permit
|
||||
129.41.77.70 permit
|
||||
129.41.169.249 permit
|
||||
129.77.16.0/20 permit
|
||||
129.80.5.164 permit
|
||||
129.80.64.36 permit
|
||||
129.80.67.121 permit
|
||||
@@ -1369,12 +1398,12 @@
|
||||
129.153.194.228 permit
|
||||
129.154.255.129 permit
|
||||
129.158.56.255 permit
|
||||
129.158.62.153 permit
|
||||
129.159.22.159 permit
|
||||
129.159.87.137 permit
|
||||
129.213.195.191 permit
|
||||
130.61.9.72 permit
|
||||
130.162.39.83 permit
|
||||
130.211.0.0/22 permit
|
||||
130.248.172.0/24 permit
|
||||
130.248.173.0/24 permit
|
||||
131.253.30.0/24 permit
|
||||
@@ -1383,26 +1412,15 @@
|
||||
132.226.26.225 permit
|
||||
132.226.49.32 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.113.0/26 permit
|
||||
134.170.141.64/26 permit
|
||||
134.170.143.0/24 permit
|
||||
134.170.174.0/24 permit
|
||||
135.84.80.0/24 permit
|
||||
135.84.81.0/24 permit
|
||||
135.84.82.0/24 permit
|
||||
135.84.83.0/24 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.135.0/24 permit
|
||||
136.147.176.0/20 permit
|
||||
@@ -1417,9 +1435,10 @@
|
||||
139.138.46.219 permit
|
||||
139.138.57.55 permit
|
||||
139.138.58.119 permit
|
||||
139.167.79.86 permit
|
||||
139.180.17.0/24 permit
|
||||
140.238.148.191 permit
|
||||
141.148.55.217 permit
|
||||
141.148.91.244 permit
|
||||
141.148.159.229 permit
|
||||
141.193.32.0/23 permit
|
||||
141.193.184.32/27 permit
|
||||
@@ -1452,6 +1471,7 @@
|
||||
146.20.215.0/24 permit
|
||||
146.20.215.182 permit
|
||||
146.88.28.0/24 permit
|
||||
146.148.116.76 permit
|
||||
147.154.32.0/25 permit
|
||||
147.243.1.47 permit
|
||||
147.243.1.48 permit
|
||||
@@ -1461,9 +1481,10 @@
|
||||
148.105.0.0/16 permit
|
||||
148.105.8.0/21 permit
|
||||
149.72.0.0/16 permit
|
||||
149.72.223.204 permit
|
||||
149.72.234.184 permit
|
||||
149.72.248.236 permit
|
||||
149.97.173.180 permit
|
||||
150.136.21.199 permit
|
||||
150.230.98.160 permit
|
||||
151.145.38.14 permit
|
||||
152.67.105.195 permit
|
||||
@@ -1473,20 +1494,7 @@
|
||||
155.248.220.138 permit
|
||||
155.248.234.149 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.11.0/25 permit
|
||||
157.55.49.0/25 permit
|
||||
157.55.61.0/24 permit
|
||||
157.55.157.128/25 permit
|
||||
157.55.225.0/25 permit
|
||||
157.56.24.0/25 permit
|
||||
157.56.120.128/26 permit
|
||||
157.56.232.0/21 permit
|
||||
157.56.240.0/20 permit
|
||||
157.56.248.0/21 permit
|
||||
157.58.30.128/25 permit
|
||||
157.58.196.96/29 permit
|
||||
157.58.249.3 permit
|
||||
@@ -1517,6 +1525,7 @@
|
||||
159.183.0.0/16 permit
|
||||
159.183.68.71 permit
|
||||
159.183.79.38 permit
|
||||
159.183.129.172 permit
|
||||
160.1.62.192 permit
|
||||
161.38.192.0/20 permit
|
||||
161.38.204.0/22 permit
|
||||
@@ -1533,11 +1542,14 @@
|
||||
163.114.132.120 permit
|
||||
163.114.134.16 permit
|
||||
163.114.135.16 permit
|
||||
163.116.128.0/17 permit
|
||||
163.192.116.87 permit
|
||||
163.192.125.176 permit
|
||||
163.192.196.146 permit
|
||||
163.192.204.161 permit
|
||||
164.152.23.32 permit
|
||||
164.152.25.241 permit
|
||||
164.177.132.168/30 permit
|
||||
165.173.128.0/24 permit
|
||||
165.173.180.250/31 permit
|
||||
165.173.182.250/31 permit
|
||||
166.78.68.0/22 permit
|
||||
166.78.68.221 permit
|
||||
166.78.69.169 permit
|
||||
@@ -1562,28 +1574,17 @@
|
||||
168.138.5.36 permit
|
||||
168.138.73.51 permit
|
||||
168.138.77.31 permit
|
||||
168.138.237.153 permit
|
||||
168.245.0.0/17 permit
|
||||
168.245.12.252 permit
|
||||
168.245.46.9 permit
|
||||
168.245.127.231 permit
|
||||
169.148.129.0/24 permit
|
||||
169.148.131.0/24 permit
|
||||
169.148.142.10 permit
|
||||
169.148.144.0/25 permit
|
||||
169.148.144.10 permit
|
||||
169.148.146.0/23 permit
|
||||
170.9.232.254 permit
|
||||
170.10.128.0/24 permit
|
||||
170.10.129.0/24 permit
|
||||
170.10.132.56/29 permit
|
||||
170.10.132.64/29 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.224/27 permit
|
||||
173.0.94.244/30 permit
|
||||
@@ -1611,9 +1612,14 @@
|
||||
182.50.78.64/28 permit
|
||||
183.240.219.64/29 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.28.196.0/22 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.227 permit
|
||||
185.80.95.31 permit
|
||||
@@ -1621,6 +1627,8 @@
|
||||
185.138.56.128/25 permit
|
||||
185.189.236.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.239.148 permit
|
||||
185.250.239.168 permit
|
||||
@@ -1673,6 +1681,7 @@
|
||||
188.125.85.234/31 permit
|
||||
188.125.85.236/31 permit
|
||||
188.125.85.238 permit
|
||||
188.165.51.139 permit
|
||||
188.172.128.0/20 permit
|
||||
192.0.64.0/18 permit
|
||||
192.18.139.154 permit
|
||||
@@ -1695,10 +1704,31 @@
|
||||
193.109.254.0/23 permit
|
||||
193.122.128.100 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.25.134.16/28 permit
|
||||
194.25.134.80/28 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.113.24.0/22 permit
|
||||
194.113.42.0/26 permit
|
||||
194.154.193.192/27 permit
|
||||
195.4.92.0/23 permit
|
||||
195.54.172.0/23 permit
|
||||
@@ -1712,6 +1742,7 @@
|
||||
198.61.254.21 permit
|
||||
198.61.254.231 permit
|
||||
198.178.234.57 permit
|
||||
198.202.211.1 permit
|
||||
198.244.48.0/20 permit
|
||||
198.244.56.107 permit
|
||||
198.244.56.108 permit
|
||||
@@ -1733,15 +1764,7 @@
|
||||
199.16.156.0/22 permit
|
||||
199.33.145.1 permit
|
||||
199.33.145.32 permit
|
||||
199.34.22.36 permit
|
||||
199.59.148.0/22 permit
|
||||
199.67.80.2 permit
|
||||
199.67.80.20 permit
|
||||
199.67.82.2 permit
|
||||
199.67.82.20 permit
|
||||
199.67.84.0/24 permit
|
||||
199.67.86.0/24 permit
|
||||
199.67.88.0/24 permit
|
||||
199.101.161.130 permit
|
||||
199.101.162.0/25 permit
|
||||
199.122.120.0/21 permit
|
||||
@@ -1794,15 +1817,14 @@
|
||||
204.14.232.64/28 permit
|
||||
204.14.234.64/28 permit
|
||||
204.75.142.0/24 permit
|
||||
204.79.197.212 permit
|
||||
204.92.114.187 permit
|
||||
204.92.114.203 permit
|
||||
204.92.114.204/31 permit
|
||||
204.141.32.0/23 permit
|
||||
204.141.42.0/23 permit
|
||||
204.216.164.202 permit
|
||||
204.220.160.0/21 permit
|
||||
204.220.168.0/21 permit
|
||||
204.220.176.0/20 permit
|
||||
204.220.181.105 permit
|
||||
204.232.168.0/24 permit
|
||||
205.139.110.0/24 permit
|
||||
205.201.128.0/20 permit
|
||||
@@ -1820,24 +1842,13 @@
|
||||
206.165.246.80/29 permit
|
||||
206.191.224.0/19 permit
|
||||
206.246.157.1 permit
|
||||
207.46.4.128/25 permit
|
||||
207.46.22.35 permit
|
||||
207.46.50.72 permit
|
||||
207.46.50.82 permit
|
||||
207.46.50.192/26 permit
|
||||
207.46.50.224 permit
|
||||
207.46.52.71 permit
|
||||
207.46.52.79 permit
|
||||
207.46.58.128/25 permit
|
||||
207.46.116.128/29 permit
|
||||
207.46.117.0/24 permit
|
||||
207.46.132.128/27 permit
|
||||
207.46.198.0/25 permit
|
||||
207.46.200.0/27 permit
|
||||
207.67.38.0/24 permit
|
||||
207.67.98.192/27 permit
|
||||
207.68.176.0/26 permit
|
||||
207.68.176.96/27 permit
|
||||
207.97.204.96/29 permit
|
||||
207.126.144.0/20 permit
|
||||
207.171.160.0/19 permit
|
||||
@@ -1877,8 +1888,6 @@
|
||||
208.71.42.212/31 permit
|
||||
208.71.42.214 permit
|
||||
208.72.249.240/29 permit
|
||||
208.74.204.5 permit
|
||||
208.74.204.9 permit
|
||||
208.75.120.0/22 permit
|
||||
208.76.62.0/24 permit
|
||||
208.76.63.0/24 permit
|
||||
@@ -1942,6 +1951,8 @@
|
||||
212.227.15.4 permit
|
||||
212.227.15.5 permit
|
||||
212.227.15.6 permit
|
||||
212.227.15.7 permit
|
||||
212.227.15.8 permit
|
||||
212.227.15.14 permit
|
||||
212.227.15.15 permit
|
||||
212.227.15.18 permit
|
||||
@@ -1958,30 +1969,44 @@
|
||||
212.227.15.53 permit
|
||||
212.227.15.54 permit
|
||||
212.227.15.55 permit
|
||||
212.227.17.1 permit
|
||||
212.227.17.2 permit
|
||||
212.227.17.7 permit
|
||||
212.227.17.11 permit
|
||||
212.227.17.12 permit
|
||||
212.227.17.16 permit
|
||||
212.227.17.17 permit
|
||||
212.227.17.18 permit
|
||||
212.227.17.19 permit
|
||||
212.227.17.20 permit
|
||||
212.227.17.21 permit
|
||||
212.227.17.22 permit
|
||||
212.227.17.26 permit
|
||||
212.227.17.27 permit
|
||||
212.227.17.28 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.225 permit
|
||||
212.227.126.226 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.145 permit
|
||||
213.199.138.181 permit
|
||||
213.199.138.191 permit
|
||||
213.199.161.128/27 permit
|
||||
213.199.177.0/26 permit
|
||||
216.17.150.242 permit
|
||||
216.17.150.251 permit
|
||||
216.24.224.0/20 permit
|
||||
216.27.86.152/31 permit
|
||||
216.39.60.154/31 permit
|
||||
216.39.60.156/30 permit
|
||||
216.39.60.160/30 permit
|
||||
@@ -2005,7 +2030,6 @@
|
||||
216.39.62.60/31 permit
|
||||
216.39.62.136/29 permit
|
||||
216.39.62.144/31 permit
|
||||
216.58.192.0/19 permit
|
||||
216.66.217.240/29 permit
|
||||
216.71.138.33 permit
|
||||
216.71.152.207 permit
|
||||
@@ -2019,6 +2043,8 @@
|
||||
216.99.5.68 permit
|
||||
216.109.114.32/27 permit
|
||||
216.109.114.64/29 permit
|
||||
216.113.162.65 permit
|
||||
216.113.163.65 permit
|
||||
216.128.126.97 permit
|
||||
216.136.162.65 permit
|
||||
216.136.162.120/29 permit
|
||||
@@ -2046,15 +2072,22 @@
|
||||
2001:0868:0100:0600::/64 permit
|
||||
2001:4860:4000::/36 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
|
||||
2603:1010:3:3::5b permit
|
||||
2603:1020:201:10::10f permit
|
||||
2603:1030:20e:3::23c permit
|
||||
2603:1030:b:3::152 permit
|
||||
2603:1030:c02:8::14 permit
|
||||
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
|
||||
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
|
||||
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
|
||||
2607:f8b0:4000::/36 permit
|
||||
2620:109:c003:104::/64 permit
|
||||
2620:109:c003:104::215 permit
|
||||
@@ -2067,5 +2100,8 @@
|
||||
2620:10d:c09c:400::8:1 permit
|
||||
2620:119:50c0:207::/64 permit
|
||||
2620:119:50c0:207::215 permit
|
||||
2620:1ec:46::51 permit
|
||||
2620:1ec:bdf::51 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
|
||||
/.+\.icu$/i
|
||||
/.+\.id$/i
|
||||
/.+\.info$/i
|
||||
/.+\.in.net$/i
|
||||
/.+\.ir$/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);
|
||||
$goto_branch_array = explode(',', $goto_branch);
|
||||
} 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']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
|
||||
@@ -56,7 +56,7 @@ function normalize_email($email) {
|
||||
$email = explode('@', $email);
|
||||
$email[0] = str_replace('.', '', $email[0]);
|
||||
$email = implode('@', $email);
|
||||
}
|
||||
}
|
||||
$gm_alt = "@googlemail.com";
|
||||
if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
|
||||
$email = explode('@', $email);
|
||||
@@ -114,7 +114,7 @@ function ucl_rcpts($object, $type) {
|
||||
$rcpt[] = str_replace('/', '\/', $row['address']);
|
||||
}
|
||||
// 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`
|
||||
WHERE `mailbox`.`username` = :object");
|
||||
$stmt->execute(array(
|
||||
@@ -184,7 +184,7 @@ while ($row = array_shift($rows)) {
|
||||
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
|
||||
<?php
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
||||
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
||||
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
|
||||
AND `object`= :object");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
@@ -468,4 +468,36 @@ while ($row = array_shift($rows)) {
|
||||
<?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
|
||||
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;
|
||||
}
|
||||
# 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'], '@')
|
||||
if #rcpt_split == 2 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
|
||||
end
|
||||
end
|
||||
@@ -146,8 +146,171 @@ rspamd_config:register_symbol({
|
||||
return false
|
||||
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)
|
||||
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 minbits = 32
|
||||
@@ -155,10 +318,18 @@ rspamd_config:register_symbol({
|
||||
maxbits = 32
|
||||
minbits = 8
|
||||
end
|
||||
|
||||
-- Add all CIDR notations with variants
|
||||
for i=maxbits,minbits,-1 do
|
||||
local nip = ip:apply_mask(i):to_string() .. "/" .. i
|
||||
table.insert(ip_check_table, nip)
|
||||
local masked_ip = ip:apply_mask(i)
|
||||
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
|
||||
|
||||
local function keep_spam_cb(err, data)
|
||||
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)
|
||||
@@ -166,12 +337,15 @@ rspamd_config:register_symbol({
|
||||
else
|
||||
for k,v in pairs(data) do
|
||||
if (v and v ~= userdata and v == '1') then
|
||||
rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result")
|
||||
task:set_pre_result('accept', 'ip matched with forward hosts')
|
||||
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', 'keep_spam')
|
||||
task:set_flag('no_stat')
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(ip_check_table, 1, 'KEEP_SPAM')
|
||||
local redis_ret_user = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
@@ -210,6 +384,7 @@ rspamd_config:register_symbol({
|
||||
rspamd_config:register_symbol({
|
||||
name = 'TAG_MOO',
|
||||
type = 'postfilter',
|
||||
flags = 'ignore_passthrough',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
@@ -218,9 +393,6 @@ rspamd_config:register_symbol({
|
||||
local rcpts = task:get_recipients('smtp')
|
||||
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 moo_tag_header = task:get_header('X-Moo-Tag', false)
|
||||
if moo_tag_header then
|
||||
@@ -231,101 +403,149 @@ rspamd_config:register_symbol({
|
||||
return true
|
||||
end
|
||||
|
||||
if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then
|
||||
local tag = tagged_rcpt[1].options[1]
|
||||
rspamd_logger.infox("found tag: %s", tag)
|
||||
local action = task:get_metric_action('default')
|
||||
rspamd_logger.infox("metric action now: %s", action)
|
||||
-- Check if we have exactly one recipient
|
||||
if not (rcpts and #rcpts == 1) then
|
||||
rspamd_logger.infox("TAG_MOO: not exactly one rcpt (%s), removing moo tag", rcpts and #rcpts or 0)
|
||||
remove_moo_tag()
|
||||
return
|
||||
end
|
||||
|
||||
if action ~= 'no action' and action ~= 'greylist' then
|
||||
rspamd_logger.infox("skipping tag handler for action: %s", action)
|
||||
remove_moo_tag()
|
||||
return true
|
||||
local rcpt_addr = rcpts[1]['addr']
|
||||
local rcpt_user = rcpts[1]['user']
|
||||
local rcpt_domain = rcpts[1]['domain']
|
||||
|
||||
-- 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
|
||||
|
||||
local function http_callback(err_message, code, body, headers)
|
||||
if body ~= nil and body ~= "" then
|
||||
rspamd_logger.infox(rspamd_config, "expanding rcpt to \"%s\"", body)
|
||||
if not tag then
|
||||
rspamd_logger.infox("TAG_MOO: no tag found in recipient %s, removing moo tag", rcpt_addr)
|
||||
remove_moo_tag()
|
||||
return
|
||||
end
|
||||
|
||||
local function tag_callback_subject(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
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)
|
||||
-- Optional: Check if domain is a mailcow domain
|
||||
-- When KEEP_SPAM is active, RCPT_MAILCOW_DOMAIN might not be set
|
||||
-- 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)
|
||||
if err or type(data) ~= 'string' then
|
||||
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 action = task:get_metric_action('default')
|
||||
rspamd_logger.infox("TAG_MOO: metric action: %s", action)
|
||||
|
||||
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
body, -- hash key
|
||||
false, -- is write
|
||||
tag_callback_subfolder, --callback
|
||||
'HGET', -- command
|
||||
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
||||
)
|
||||
if not redis_ret_subfolder then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
||||
-- Check if we have a pre-result (e.g., from KEEP_SPAM or POSTMASTER_HANDLER)
|
||||
local allow_processing = false
|
||||
|
||||
if task.has_pre_result then
|
||||
local has_pre, pre_action = task:has_pre_result()
|
||||
if has_pre then
|
||||
rspamd_logger.infox("TAG_MOO: pre-result detected: %s", tostring(pre_action))
|
||||
if pre_action == 'accept' then
|
||||
allow_processing = true
|
||||
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()
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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, "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")
|
||||
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
body, -- hash key
|
||||
false, -- is write
|
||||
tag_callback_subfolder, --callback
|
||||
'HGET', -- command
|
||||
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
||||
)
|
||||
if not redis_ret_subfolder then
|
||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt")
|
||||
remove_moo_tag()
|
||||
else
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url='http://nginx:8081/aliasexp.php',
|
||||
body='',
|
||||
callback=http_callback,
|
||||
headers={Rcpt=rcpt['addr']},
|
||||
})
|
||||
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
|
||||
|
||||
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
|
||||
else
|
||||
rspamd_logger.infox("TAG_MOO: invalid rcpt format")
|
||||
remove_moo_tag()
|
||||
end
|
||||
end,
|
||||
@@ -335,6 +555,7 @@ rspamd_config:register_symbol({
|
||||
rspamd_config:register_symbol({
|
||||
name = 'BCC',
|
||||
type = 'postfilter',
|
||||
flags = 'ignore_passthrough',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local rspamd_http = require "rspamd_http"
|
||||
@@ -363,11 +584,13 @@ rspamd_config:register_symbol({
|
||||
local email_content = tostring(task:get_content())
|
||||
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
||||
-- 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({
|
||||
task = task,
|
||||
host = os.getenv("IPV4_NETWORK") .. '.253',
|
||||
port = 591,
|
||||
from = task:get_from(stp)[1].addr,
|
||||
from = from_addr,
|
||||
recipients = bcc_dest,
|
||||
helo = 'bcc',
|
||||
timeout = 20,
|
||||
@@ -397,27 +620,41 @@ rspamd_config:register_symbol({
|
||||
end
|
||||
|
||||
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)
|
||||
if err_message == nil and code == 201 and body ~= nil then
|
||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
||||
send_mail(task, body)
|
||||
end
|
||||
rspamd_logger.infox("BCC: sending BCC to %s for rcpt match", body)
|
||||
send_mail(task, body)
|
||||
end
|
||||
end
|
||||
|
||||
local function from_callback(err_message, code, body, headers)
|
||||
if err_message == nil and code == 201 and body ~= nil then
|
||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
||||
send_mail(task, body)
|
||||
end
|
||||
rspamd_logger.infox("BCC: sending BCC to %s for from match", body)
|
||||
send_mail(task, body)
|
||||
end
|
||||
end
|
||||
|
||||
if rcpt_table then
|
||||
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({
|
||||
task=task,
|
||||
url='http://nginx:8081/bcc.php',
|
||||
@@ -430,7 +667,7 @@ rspamd_config:register_symbol({
|
||||
|
||||
if from_table then
|
||||
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({
|
||||
task=task,
|
||||
url='http://nginx:8081/bcc.php',
|
||||
@@ -441,7 +678,7 @@ rspamd_config:register_symbol({
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
-- Don't return true to avoid symbol being logged
|
||||
end,
|
||||
priority = 20
|
||||
})
|
||||
@@ -454,12 +691,18 @@ rspamd_config:register_symbol({
|
||||
local redis_params = rspamd_parse_redis_server('dyn_rl')
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local envfrom = task:get_from(1)
|
||||
local envrcpt = task:get_recipients(1) or {}
|
||||
local uname = task:get_user()
|
||||
if not envfrom or not uname then
|
||||
return false
|
||||
end
|
||||
|
||||
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 function redis_cb_user(err, data)
|
||||
@@ -544,13 +787,13 @@ rspamd_config:register_symbol({
|
||||
-- determine newline type
|
||||
local function newline(task)
|
||||
local t = task:get_newlines_type()
|
||||
|
||||
|
||||
if t == 'cr' then
|
||||
return '\r'
|
||||
elseif t == 'lf' then
|
||||
return '\n'
|
||||
end
|
||||
|
||||
|
||||
return '\r\n'
|
||||
end
|
||||
-- retrieve footer
|
||||
@@ -558,7 +801,7 @@ rspamd_config:register_symbol({
|
||||
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)
|
||||
else
|
||||
|
||||
|
||||
-- parse json string
|
||||
local footer = cjson.decode(data)
|
||||
if not footer then
|
||||
@@ -607,26 +850,30 @@ rspamd_config:register_symbol({
|
||||
if footer.plain and footer.plain ~= "" then
|
||||
footer.plain = lua_util.jinja_template(footer.plain, replacements, true)
|
||||
end
|
||||
|
||||
|
||||
-- add footer
|
||||
local out = {}
|
||||
local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
|
||||
|
||||
|
||||
local seen_cte
|
||||
local newline_s = newline(task)
|
||||
|
||||
|
||||
local function rewrite_ct_cb(name, hdr)
|
||||
if rewrite.need_rewrite_ct then
|
||||
if name:lower() == 'content-type' then
|
||||
local nct = string.format('%s: %s/%s; charset=utf-8',
|
||||
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
|
||||
-- include boundary if present
|
||||
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
|
||||
-- update Content-Type header
|
||||
-- update Content-Type header (include boundary if present)
|
||||
task:set_milter_reply({
|
||||
remove_headers = {['Content-Type'] = 0},
|
||||
})
|
||||
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
|
||||
elseif name:lower() == 'content-transfer-encoding' then
|
||||
@@ -645,16 +892,16 @@ rspamd_config:register_symbol({
|
||||
end
|
||||
out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
|
||||
end
|
||||
|
||||
|
||||
task:headers_foreach(rewrite_ct_cb, {full = true})
|
||||
|
||||
|
||||
if not seen_cte and rewrite.need_rewrite_ct then
|
||||
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
|
||||
end
|
||||
|
||||
|
||||
-- End of headers
|
||||
out[#out + 1] = newline_s
|
||||
|
||||
|
||||
if rewrite.out then
|
||||
for _,o in ipairs(rewrite.out) do
|
||||
out[#out + 1] = o
|
||||
@@ -698,4 +945,4 @@ rspamd_config:register_symbol({
|
||||
return true
|
||||
end,
|
||||
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);
|
||||
$goto_branch_array = explode(',', $goto_branch);
|
||||
} 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']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
@@ -236,6 +236,9 @@ foreach ($rcpt_final_mailboxes as $rcpt_final) {
|
||||
':action' => $action,
|
||||
':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 (
|
||||
SELECT `id`
|
||||
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);
|
||||
$goto_branch_array = explode(',', $goto_branch);
|
||||
} 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']));
|
||||
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
|
||||
if ($goto_branch) {
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
SOGoFoldersSendEMailNotifications = 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
|
||||
SOGoDisableOrganizerEventCheck = YES;
|
||||
|
||||
@@ -83,6 +86,12 @@
|
||||
SOGoMaximumFailedLoginInterval = 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;
|
||||
GCSChannelExpireAge = 60;
|
||||
|
||||
@@ -91,7 +100,7 @@
|
||||
//SoDebugBaseURL = YES;
|
||||
//ImapDebugEnabled = YES;
|
||||
//SOGoEASDebugEnabled = YES;
|
||||
SOGoEASSearchInBody = YES; // Experimental. Enabled since 2023-10
|
||||
SOGoEASSearchInBody = YES;
|
||||
//LDAPDebugEnabled = YES;
|
||||
//PGDebugEnabled = 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';
|
||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||
$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')) {
|
||||
@@ -33,6 +34,7 @@ $vmail_df = explode(',', (string)json_decode(docker('post', 'dovecot-mailcow', '
|
||||
// containers
|
||||
$containers_info = (array) docker('info');
|
||||
if ($clamd_status === false) unset($containers_info['clamd-mailcow']);
|
||||
if ($olefy_status === false) unset($containers_info['olefy-mailcow']);
|
||||
ksort($containers_info);
|
||||
$containers = array();
|
||||
foreach ($containers_info as $container => $container_info) {
|
||||
@@ -77,6 +79,7 @@ $template_data = [
|
||||
'gal' => @$_SESSION['gal'],
|
||||
'license_guid' => license('guid'),
|
||||
'clamd_status' => $clamd_status,
|
||||
'olefy_status' => $olefy_status,
|
||||
'containers' => $containers,
|
||||
'ip_check' => customize('get', 'ip_check'),
|
||||
'lang_admin' => json_encode($lang['admin']),
|
||||
|
||||
@@ -22,7 +22,8 @@ $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
|
||||
|
||||
$template = 'admin_index.twig';
|
||||
$template_data = [
|
||||
'login_delay' => @$_SESSION['ldelay']
|
||||
'login_delay' => @$_SESSION['ldelay'],
|
||||
'custom_login' => customize('get', 'custom_login'),
|
||||
];
|
||||
|
||||
$js_minifier->add('/web/js/site/index.js');
|
||||
|
||||
@@ -125,6 +125,7 @@ $template_data = [
|
||||
'logo_specs' => customize('get', 'main_logo_specs'),
|
||||
'logo_dark_specs' => customize('get', 'main_logo_dark_specs'),
|
||||
'ip_check' => customize('get', 'ip_check'),
|
||||
'custom_login' => customize('get', 'custom_login'),
|
||||
'password_complexity' => password_complexity('get'),
|
||||
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
|
||||
'cors_settings' => $cors_settings,
|
||||
|
||||
@@ -5847,6 +5847,7 @@ paths:
|
||||
client_id: "mailcow_client"
|
||||
client_secret: "*"
|
||||
redirect_url: "https://mail.mailcow.tld"
|
||||
redirect_url_extra: ["https://extramail.mailcow.tld"]
|
||||
version: "26.1.3"
|
||||
default_template: "Default"
|
||||
mappers:
|
||||
@@ -5900,6 +5901,9 @@ paths:
|
||||
redirect_url:
|
||||
description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc.
|
||||
type: string
|
||||
redirect_url_extra:
|
||||
description: Additional redirect URLs that OIDC Provider can use after authentication if valid.
|
||||
type: array
|
||||
version:
|
||||
description: Specifies the Keycloak version. Required if `authsource` is keycloak.
|
||||
type: string
|
||||
@@ -5990,6 +5994,7 @@ paths:
|
||||
client_id: "mailcow_client"
|
||||
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
|
||||
redirect_url: "https://mail.mailcow.tld"
|
||||
redirect_url_extra: ["https://extramail.mailcow.tld"]
|
||||
version: "26.1.3"
|
||||
default_template: "Default"
|
||||
mappers: ["small_mbox", "medium_mbox"]
|
||||
@@ -6034,6 +6039,7 @@ paths:
|
||||
client_id: "mailcow_client"
|
||||
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
|
||||
redirect_url: "https://mail.mailcow.tld"
|
||||
redirect_url_extra: ["https://extramail.mailcow.tld"]
|
||||
client_scopes: "openid profile email mailcow_template"
|
||||
default_template: "Default"
|
||||
mappers: ["small_mbox", "medium_mbox"]
|
||||
|
||||
@@ -29,8 +29,8 @@ header('Content-Type: application/xml');
|
||||
<clientConfig version="1.1">
|
||||
<emailProvider id="<?=$mailcow_hostname; ?>">
|
||||
<domain>%EMAILDOMAIN%</domain>
|
||||
<displayName>A mailcow mail server</displayName>
|
||||
<displayShortName>mail server</displayShortName>
|
||||
<displayName><?=$autodiscover_config['displayName']; ?></displayName>
|
||||
<displayShortName><?=$autodiscover_config['displayShortName']; ?></displayShortName>
|
||||
|
||||
<incomingServer type="imap">
|
||||
<hostname><?=$autodiscover_config['imap']['server']; ?></hostname>
|
||||
@@ -85,7 +85,7 @@ if (count($records) == 0 || $records[0]['target'] != '') { ?>
|
||||
<authentication>password-cleartext</authentication>
|
||||
</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 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>
|
||||
|
||||
@@ -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.auth.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;
|
||||
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
|
||||
|
||||
@@ -77,7 +79,7 @@ if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$login_role = check_login($login_user, $login_pass, array('eas' => TRUE));
|
||||
$login_role = check_login($login_user, $login_pass, array('service' => 'EAS'));
|
||||
|
||||
if ($login_role === "user") {
|
||||
header("Content-Type: application/xml");
|
||||
|
||||
@@ -22,6 +22,7 @@ $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
|
||||
$template = 'domainadmin_index.twig';
|
||||
$template_data = [
|
||||
'login_delay' => @$_SESSION['ldelay'],
|
||||
'custom_login' => customize('get', 'custom_login'),
|
||||
];
|
||||
|
||||
$js_minifier->add('/web/js/site/index.js');
|
||||
|
||||
@@ -48,6 +48,12 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
$rl = ratelimit('get', 'domain', $domain);
|
||||
$rlyhosts = relayhost('get');
|
||||
$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_data = [
|
||||
'acl' => $_SESSION['acl'],
|
||||
@@ -58,6 +64,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'dkim' => dkim('details', $domain),
|
||||
'domain_details' => $result,
|
||||
'domain_footer' => $domain_footer,
|
||||
'mta_sts' => $mta_sts,
|
||||
'mailboxes' => mailbox('get', 'mailboxes', $_GET["domain"]),
|
||||
'aliases' => mailbox('get', 'aliases', $_GET["domain"], 'address'),
|
||||
'alias_domains' => mailbox('get', 'alias_domains', $_GET["domain"])
|
||||
@@ -125,6 +132,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'mailbox' => $mailbox,
|
||||
'rl' => $rl,
|
||||
'pushover_data' => $pushover_data,
|
||||
'get_tagging_options' => mailbox('get', 'delimiter_action', $mailbox),
|
||||
'quarantine_notification' => $quarantine_notification,
|
||||
'quarantine_category' => $quarantine_category,
|
||||
'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
|
||||
$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>';
|
||||
$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();
|
||||
|
||||
@@ -128,6 +129,36 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
);
|
||||
}
|
||||
|
||||
// Check if domain is an alias domain and get target domain's MTA-STS
|
||||
$alias_domain_details = mailbox('get', 'alias_domain_details', $domain);
|
||||
$mta_sts_domain = $domain;
|
||||
|
||||
if ($alias_domain_details !== false && !empty($alias_domain_details['target_domain'])) {
|
||||
// This is an alias domain, check target domain for MTA-STS
|
||||
$mta_sts_domain = $alias_domain_details['target_domain'];
|
||||
}
|
||||
|
||||
$mta_sts = mailbox('get', 'mta_sts', $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(
|
||||
$domain,
|
||||
'TXT',
|
||||
@@ -341,15 +372,25 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
}
|
||||
|
||||
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' &&
|
||||
stripos($current['txt'], 'v=dmarc') === 0 &&
|
||||
$record[2] == $dmarc_link) {
|
||||
stripos($current['txt'], 'v=dmarc') === 0 &&
|
||||
$record[2] == $dmarc_link) {
|
||||
$current['txt'] = str_replace(' ', '', $current['txt']);
|
||||
$state = $current[$data_field[$current['type']]] . state_optional;
|
||||
}
|
||||
elseif ($current['type'] == 'TXT' &&
|
||||
stripos($current['txt'], 'v=spf') === 0 &&
|
||||
$record[2] == $spf_link) {
|
||||
stripos($current['txt'], 'v=spf') === 0 &&
|
||||
$record[2] == $spf_link) {
|
||||
$state = state_nomatch;
|
||||
$rslt = get_spf_allowed_hosts($record[0], true);
|
||||
if (in_array($ip, $rslt) && in_array(expand_ipv6($ip6), $rslt)) {
|
||||
@@ -358,8 +399,8 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
$state .= '<br />' . $current[$data_field[$current['type']]] . state_optional;
|
||||
}
|
||||
elseif ($current['type'] == 'TXT' &&
|
||||
stripos($current['txt'], 'v=dkim') === 0 &&
|
||||
stripos($record[2], 'v=dkim') === 0) {
|
||||
stripos($current['txt'], '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', $record[2], $dkim_matches_good);
|
||||
if ($dkim_matches_current[1] == $dkim_matches_good[1]) {
|
||||
@@ -367,7 +408,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
}
|
||||
}
|
||||
elseif ($current['type'] != 'TXT' &&
|
||||
isset($data_field[$current['type']]) && $state != state_good) {
|
||||
isset($data_field[$current['type']]) && $state != state_good) {
|
||||
$state = state_nomatch;
|
||||
if ($current[$data_field[$current['type']]] == $record[2]) {
|
||||
$state = state_good;
|
||||
|
||||
@@ -26,23 +26,25 @@ if (is_array($alertbox_log_parser)) {
|
||||
|
||||
// map tfa details for twig
|
||||
$pending_tfa_authmechs = [];
|
||||
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'])
|
||||
&& isset($pending_tfa_authmechs['yubi_otp'])) {
|
||||
$pending_tfa_authmechs['yubi_otp'] = true;
|
||||
}
|
||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||
&& !isset($pending_tfa_authmechs['yubi_otp'])
|
||||
&& isset($pending_tfa_authmechs['totp'])) {
|
||||
$pending_tfa_authmechs['totp'] = true;
|
||||
}
|
||||
if (isset($pending_tfa_authmechs['u2f'])) {
|
||||
$pending_tfa_authmechs['u2f'] = true;
|
||||
if (array_key_exists('pending_tfa_methods', $_SESSION)) {
|
||||
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'])
|
||||
&& isset($pending_tfa_authmechs['yubi_otp'])) {
|
||||
$pending_tfa_authmechs['yubi_otp'] = true;
|
||||
}
|
||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||
&& !isset($pending_tfa_authmechs['yubi_otp'])
|
||||
&& isset($pending_tfa_authmechs['totp'])) {
|
||||
$pending_tfa_authmechs['totp'] = true;
|
||||
}
|
||||
if (isset($pending_tfa_authmechs['u2f'])) {
|
||||
$pending_tfa_authmechs['u2f'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// globals
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
function app_passwd($_action, $_data = null) {
|
||||
global $pdo;
|
||||
global $lang;
|
||||
global $pdo;
|
||||
global $lang;
|
||||
$_data_log = $_data;
|
||||
!isset($_data_log['app_passwd']) ?: $_data_log['app_passwd'] = '*';
|
||||
!isset($_data_log['app_passwd2']) ?: $_data_log['app_passwd2'] = '*';
|
||||
@@ -43,20 +43,7 @@ function app_passwd($_action, $_data = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_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'
|
||||
);
|
||||
if (password_check($password, $password2) !== true) {
|
||||
return false;
|
||||
}
|
||||
$password_hashed = hash_password($password);
|
||||
@@ -88,15 +75,15 @@ function app_passwd($_action, $_data = null) {
|
||||
'log' => array(__FUNCTION__, $_action, $_data_log),
|
||||
'msg' => 'app_passwd_added'
|
||||
);
|
||||
break;
|
||||
break;
|
||||
case 'edit':
|
||||
$ids = (array)$_data['id'];
|
||||
foreach ($ids as $id) {
|
||||
$is_now = app_passwd('details', $id);
|
||||
if (!empty($is_now)) {
|
||||
$app_name = (!empty($_data['app_name'])) ? $_data['app_name'] : $is_now['name'];
|
||||
$password = (!empty($_data['password'])) ? $_data['password'] : null;
|
||||
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
|
||||
$password = (!empty($_data['app_passwd'])) ? $_data['app_passwd'] : null;
|
||||
$password2 = (!empty($_data['app_passwd2'])) ? $_data['app_passwd2'] : null;
|
||||
if (isset($_data['protocols'])) {
|
||||
$protocols = (array)$_data['protocols'];
|
||||
$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));
|
||||
if (!empty($password) && !empty($password2)) {
|
||||
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
|
||||
$_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'
|
||||
);
|
||||
if (password_check($password, $password2) !== true) {
|
||||
continue;
|
||||
}
|
||||
$password_hashed = hash_password($password);
|
||||
@@ -182,7 +156,7 @@ function app_passwd($_action, $_data = null) {
|
||||
'msg' => array('object_modified', htmlspecialchars(implode(', ', $ids)))
|
||||
);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case 'delete':
|
||||
$ids = (array)$_data['id'];
|
||||
foreach ($ids as $id) {
|
||||
@@ -213,19 +187,17 @@ function app_passwd($_action, $_data = null) {
|
||||
'msg' => array('app_passwd_removed', htmlspecialchars($id))
|
||||
);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
case 'get':
|
||||
$app_passwds = array();
|
||||
$stmt = $pdo->prepare("SELECT `id`, `name` FROM `app_passwd` WHERE `mailbox` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$app_passwds = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return $app_passwds;
|
||||
break;
|
||||
break;
|
||||
case 'details':
|
||||
$app_passwd_data = array();
|
||||
$stmt = $pdo->prepare("SELECT *
|
||||
FROM `app_passwd`
|
||||
WHERE `id` = :id");
|
||||
$stmt = $pdo->prepare("SELECT * FROM `app_passwd` WHERE `id` = :id");
|
||||
$stmt->execute(array(':id' => $_data));
|
||||
$app_passwd_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
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']));
|
||||
return $app_passwd_data;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
|
||||
function check_login($user, $pass, $extra = null) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
|
||||
$is_internal = $extra['is_internal'];
|
||||
$role = $extra['role'];
|
||||
$extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service'];
|
||||
|
||||
// Try validate admin
|
||||
if (!isset($role) || $role == "admin") {
|
||||
@@ -25,34 +26,20 @@ function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
|
||||
|
||||
// Try validate app password
|
||||
if (!isset($role) || $role == "app") {
|
||||
$result = apppass_login($user, $pass, $app_passwd_data);
|
||||
$result = apppass_login($user, $pass, $extra);
|
||||
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);
|
||||
set_sasl_log($user, $real_rip, $extra['service'], $pass);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Try validate user
|
||||
if (!isset($role) || $role == "user") {
|
||||
$result = user_login($user, $pass);
|
||||
$result = user_login($user, $pass, $extra);
|
||||
if ($result !== false) {
|
||||
if ($app_passwd_data['eas'] === true) {
|
||||
$service = 'EAS';
|
||||
} elseif ($app_passwd_data['dav'] === true) {
|
||||
$service = 'DAV';
|
||||
} else {
|
||||
$service = 'MAILCOWUI';
|
||||
}
|
||||
$real_rip = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
|
||||
set_sasl_log($user, $real_rip, $service);
|
||||
set_sasl_log($user, $real_rip, $extra['service']);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -193,6 +180,7 @@ function user_login($user, $pass, $extra = null){
|
||||
global $iam_settings;
|
||||
|
||||
$is_internal = $extra['is_internal'];
|
||||
$extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service'];
|
||||
|
||||
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
|
||||
if (!$is_internal){
|
||||
@@ -235,6 +223,14 @@ function user_login($user, $pass, $extra = null){
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!empty($row)) {
|
||||
// check if user has access to service (imap, smtp, pop3, sieve, dav, eas) if service is set
|
||||
$row['attributes'] = json_decode($row['attributes'], true);
|
||||
if ($extra['service'] != 'NONE') {
|
||||
$key = strtolower($extra['service']) . "_access";
|
||||
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -242,6 +238,14 @@ function user_login($user, $pass, $extra = null){
|
||||
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 ($extra['service'] != 'NONE') {
|
||||
$key = strtolower($extra['service']) . "_access";
|
||||
if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
switch ($row['authsource']) {
|
||||
case 'keycloak':
|
||||
// user authsource is keycloak, try using via rest flow
|
||||
@@ -351,6 +355,11 @@ function user_login($user, $pass, $extra = null){
|
||||
}
|
||||
// verify password
|
||||
if (verify_hash($row['password'], $pass) !== false) {
|
||||
|
||||
if (intval($row['attributes']['force_pw_update']) == 1) {
|
||||
$_SESSION['pending_pw_update'] = true;
|
||||
}
|
||||
|
||||
// check for tfa authenticators
|
||||
$authenticators = get_tfa($user);
|
||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
||||
@@ -386,7 +395,7 @@ function user_login($user, $pass, $extra = null){
|
||||
|
||||
return false;
|
||||
}
|
||||
function apppass_login($user, $pass, $app_passwd_data, $extra = null){
|
||||
function apppass_login($user, $pass, $extra = null){
|
||||
global $pdo;
|
||||
|
||||
$is_internal = $extra['is_internal'];
|
||||
@@ -402,20 +411,8 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){
|
||||
return false;
|
||||
}
|
||||
|
||||
$protocol = false;
|
||||
if ($app_passwd_data['eas']){
|
||||
$protocol = 'eas';
|
||||
} else if ($app_passwd_data['dav']){
|
||||
$protocol = 'dav';
|
||||
} else if ($app_passwd_data['smtp']){
|
||||
$protocol = 'smtp';
|
||||
} else if ($app_passwd_data['imap']){
|
||||
$protocol = 'imap';
|
||||
} else if ($app_passwd_data['sieve']){
|
||||
$protocol = 'sieve';
|
||||
} else if ($app_passwd_data['pop3']){
|
||||
$protocol = 'pop3';
|
||||
} else if (!$is_internal) {
|
||||
$extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service'];
|
||||
if (!$is_internal && $extra['service'] == 'NONE') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -436,7 +433,7 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
if ($protocol && $row[$protocol . '_access'] != '1'){
|
||||
if ($extra['service'] != 'NONE' && $row[strtolower($extra['service']) . '_access'] != '1'){
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -471,6 +468,9 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!$iam_provider) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get access_token for service account of mailcow client
|
||||
$admin_token = identity_provider("get-keycloak-admin-token");
|
||||
@@ -540,6 +540,17 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
|
||||
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
|
||||
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
|
||||
if (!empty($iam_settings['default_template'])) {
|
||||
@@ -653,10 +664,21 @@ function ldap_mbox_login($user, $pass, $extra = null){
|
||||
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
|
||||
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
|
||||
if (!empty($iam_settings['default_tempalte'])) {
|
||||
$mbox_template = $iam_settings['default_tempalte'];
|
||||
if (!empty($iam_settings['default_template'])) {
|
||||
$mbox_template = $iam_settings['default_template'];
|
||||
} else {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
|
||||
@@ -204,6 +204,35 @@ function customize($_action, $_item, $_data = null) {
|
||||
'msg' => 'ip_check_opt_in_modified'
|
||||
);
|
||||
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;
|
||||
case 'delete':
|
||||
@@ -264,7 +293,7 @@ function customize($_action, $_item, $_data = null) {
|
||||
}
|
||||
|
||||
if (empty($app_links)){
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
// convert from old style
|
||||
@@ -296,8 +325,10 @@ function customize($_action, $_item, $_data = null) {
|
||||
break;
|
||||
case 'ui_texts':
|
||||
try {
|
||||
$data['title_name'] = ($title_name = $redis->get('TITLE_NAME')) ? $title_name : 'mailcow UI';
|
||||
$data['main_name'] = ($main_name = $redis->get('MAIN_NAME')) ? $main_name : 'mailcow UI';
|
||||
$mailcow_hostname = strtolower(getenv("MAILCOW_HOSTNAME"));
|
||||
|
||||
$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['help_text'] = ($help_text = $redis->get('HELP_TEXT')) ? $help_text : false;
|
||||
if (!empty($redis->get('UI_IMPRESS'))) {
|
||||
@@ -357,6 +388,20 @@ function customize($_action, $_item, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -205,6 +205,42 @@ function password_complexity($_action, $_data = null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function password_generate(){
|
||||
$password_complexity = password_complexity('get');
|
||||
$min_length = max(16, intval($password_complexity['length']));
|
||||
|
||||
$lowercase = range('a', 'z');
|
||||
$uppercase = range('A', 'Z');
|
||||
$digits = range(0, 9);
|
||||
$special_chars = str_split('!@#$%^&*()?=');
|
||||
|
||||
$password = [
|
||||
$lowercase[random_int(0, count($lowercase) - 1)],
|
||||
$uppercase[random_int(0, count($uppercase) - 1)],
|
||||
$digits[random_int(0, count($digits) - 1)],
|
||||
$special_chars[random_int(0, count($special_chars) - 1)],
|
||||
];
|
||||
|
||||
$all = array_merge($lowercase, $uppercase, $digits, $special_chars);
|
||||
|
||||
while (count($password) < $min_length) {
|
||||
$password[] = $all[random_int(0, count($all) - 1)];
|
||||
}
|
||||
|
||||
// Cryptographically secure shuffle using Fisher-Yates algorithm
|
||||
$count = count($password);
|
||||
for ($i = $count - 1; $i > 0; $i--) {
|
||||
$j = random_int(0, $i);
|
||||
$temp = $password[$i];
|
||||
$password[$i] = $password[$j];
|
||||
$password[$j] = $temp;
|
||||
}
|
||||
|
||||
return implode('', $password);
|
||||
|
||||
}
|
||||
|
||||
function password_check($password1, $password2) {
|
||||
$password_complexity = password_complexity('get');
|
||||
|
||||
@@ -814,6 +850,32 @@ function verify_hash($hash, $password) {
|
||||
$hash = $components[4];
|
||||
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":
|
||||
return hash_equals(hash('md4', $password), $hash);
|
||||
|
||||
@@ -1001,11 +1063,12 @@ function edit_user_account($_data) {
|
||||
':password_hashed' => $password_hashed,
|
||||
':username' => $username
|
||||
));
|
||||
$_SESSION['pending_pw_update'] = false;
|
||||
|
||||
update_sogo_static_view();
|
||||
}
|
||||
// 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" ) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -1015,6 +1078,21 @@ function edit_user_account($_data) {
|
||||
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;
|
||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
|
||||
WHERE `username` = :username AND authsource = 'mailcow'");
|
||||
@@ -1106,11 +1184,21 @@ function user_get_alias_details($username) {
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
function is_valid_domain_name($domain_name) {
|
||||
function is_valid_domain_name($domain_name, $options = array()) {
|
||||
if (empty($domain_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert domain name to ASCII for validation
|
||||
$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)
|
||||
&& preg_match("/^.{1,253}$/", $domain_name)
|
||||
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
|
||||
@@ -2210,7 +2298,7 @@ function cors($action, $data = null) {
|
||||
$cors_settings['allowed_origins'] = $allowed_origins[0];
|
||||
if (in_array('*', $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'];
|
||||
}
|
||||
// always allow OPTIONS for preflight request
|
||||
@@ -2286,12 +2374,14 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach($rows as $row){
|
||||
switch ($row["key"]) {
|
||||
case "redirect_url_extra":
|
||||
case "mappers":
|
||||
case "templates":
|
||||
$settings[$row["key"]] = json_decode($row["value"]);
|
||||
break;
|
||||
case "use_ssl":
|
||||
case "use_tls":
|
||||
case "login_provisioning":
|
||||
case "ignore_ssl_errors":
|
||||
$settings[$row["key"]] = boolval($row["value"]);
|
||||
break;
|
||||
@@ -2300,6 +2390,10 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
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
|
||||
if ($settings["authsource"] == "generic-oidc" && empty($settings["client_scopes"])){
|
||||
$settings["client_scopes"] = "openid profile email mailcow_template";
|
||||
@@ -2364,7 +2458,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
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']) {
|
||||
case "keycloak":
|
||||
$_data['server_url'] = (!empty($_data['server_url'])) ? rtrim($_data['server_url'], '/') : null;
|
||||
@@ -2373,14 +2468,14 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
$_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'] = $_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;
|
||||
case "generic-oidc":
|
||||
$_data['authorize_url'] = (!empty($_data['authorize_url'])) ? $_data['authorize_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['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;
|
||||
case "ldap":
|
||||
$_data['host'] = (!empty($_data['host'])) ? str_replace(" ", "", $_data['host']) : "";
|
||||
@@ -2394,7 +2489,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['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15;
|
||||
$_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;
|
||||
}
|
||||
|
||||
@@ -2418,6 +2513,18 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
}
|
||||
$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
|
||||
if (isset($_data['default_template'])) {
|
||||
$_data['default_template'] = (empty($_data['default_template'])) ? "" : $_data['default_template'];
|
||||
@@ -2752,6 +2859,16 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
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['default_template'])) {
|
||||
$mbox_template = $iam_settings['default_template'];
|
||||
@@ -2851,7 +2968,19 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
|
||||
case "get-redirect":
|
||||
if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc')
|
||||
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();
|
||||
return $authUrl;
|
||||
break;
|
||||
|
||||
@@ -49,6 +49,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
// Default to 1 yr
|
||||
$_data["validity"] = 8760;
|
||||
}
|
||||
if (isset($_data["permanent"]) && filter_var($_data["permanent"], FILTER_VALIDATE_BOOL)) {
|
||||
$permanent = 1;
|
||||
}
|
||||
else {
|
||||
$permanent = 0;
|
||||
}
|
||||
$domain = $_data['domain'];
|
||||
$description = $_data['description'];
|
||||
$valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain'];
|
||||
@@ -65,13 +71,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
return false;
|
||||
}
|
||||
$validity = strtotime("+" . $_data["validity"] . " hour");
|
||||
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`) VALUES
|
||||
(:address, :description, :goto, :validity)");
|
||||
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`, `permanent`) VALUES
|
||||
(:address, :description, :goto, :validity, :permanent)");
|
||||
$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,
|
||||
':description' => $description,
|
||||
':goto' => $username,
|
||||
':validity' => $validity
|
||||
':validity' => $validity,
|
||||
':permanent' => $permanent
|
||||
));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
@@ -684,15 +691,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
return true;
|
||||
break;
|
||||
case 'alias':
|
||||
$addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address']));
|
||||
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
|
||||
$active = intval($_data['active']);
|
||||
$sogo_visible = intval($_data['sogo_visible']);
|
||||
$goto_null = intval($_data['goto_null']);
|
||||
$goto_spam = intval($_data['goto_spam']);
|
||||
$goto_ham = intval($_data['goto_ham']);
|
||||
$addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address']));
|
||||
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
|
||||
$internal = intval($_data['internal']);
|
||||
$active = intval($_data['active']);
|
||||
$sender_allowed = intval($_data['sender_allowed']);
|
||||
$sogo_visible = intval($_data['sogo_visible']);
|
||||
$goto_null = intval($_data['goto_null']);
|
||||
$goto_spam = intval($_data['goto_spam']);
|
||||
$goto_ham = intval($_data['goto_ham']);
|
||||
$private_comment = $_data['private_comment'];
|
||||
$public_comment = $_data['public_comment'];
|
||||
$public_comment = $_data['public_comment'];
|
||||
if (strlen($private_comment) > 160 | strlen($public_comment) > 160){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -842,8 +851,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `active`)
|
||||
VALUES (: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`, `sender_allowed`, `active`)
|
||||
VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :internal, :sender_allowed, :active)");
|
||||
if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
|
||||
$stmt->execute(array(
|
||||
':address' => '@'.$domain,
|
||||
@@ -853,6 +862,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':goto' => $goto,
|
||||
':domain' => $domain,
|
||||
':sogo_visible' => $sogo_visible,
|
||||
':internal' => $internal,
|
||||
':sender_allowed' => $sender_allowed,
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
@@ -864,6 +875,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':goto' => $goto,
|
||||
':domain' => $domain,
|
||||
':sogo_visible' => $sogo_visible,
|
||||
':internal' => $internal,
|
||||
':sender_allowed' => $sender_allowed,
|
||||
':active' => $active
|
||||
));
|
||||
}
|
||||
@@ -1065,6 +1078,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
||||
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||
$_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
|
||||
$_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
|
||||
}
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['active']);
|
||||
$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']);
|
||||
@@ -1075,6 +1090,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
||||
$smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
||||
$sieve_access = (isset($_data['sieve_access'])) ? intval($_data['sieve_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
||||
$eas_access = (isset($_data['eas_access'])) ? intval($_data['eas_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']);
|
||||
$dav_access = (isset($_data['dav_access'])) ? intval($_data['dav_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']);
|
||||
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0;
|
||||
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
|
||||
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
|
||||
@@ -1093,6 +1110,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
'pop3_access' => strval($pop3_access),
|
||||
'smtp_access' => strval($smtp_access),
|
||||
'sieve_access' => strval($sieve_access),
|
||||
'eas_access' => strval($eas_access),
|
||||
'dav_access' => strval($dav_access),
|
||||
'relayhost' => strval($relayhost),
|
||||
'passwd_update' => time(),
|
||||
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
|
||||
@@ -1223,6 +1242,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(
|
||||
':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
|
||||
foreach($tags as $index => $tag){
|
||||
if (empty($tag)) continue;
|
||||
@@ -1392,6 +1419,80 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
|
||||
return mailbox('add', 'mailbox', $mailbox_attributes);
|
||||
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':
|
||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
$description = $_data['description'];
|
||||
@@ -1613,6 +1714,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr = array();
|
||||
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
|
||||
$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_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";
|
||||
@@ -1628,12 +1730,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
||||
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||
$attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
|
||||
$attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
|
||||
}
|
||||
else {
|
||||
$attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
|
||||
$attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
||||
$attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
||||
$attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
||||
$attr['eas_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']);
|
||||
$attr['dav_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']);
|
||||
}
|
||||
if (isset($_data['acl'])) {
|
||||
$_data['acl'] = (array)$_data['acl'];
|
||||
@@ -2017,15 +2123,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (empty($_data['validity'])) {
|
||||
if (empty($_data['validity']) && empty($_data['permanent'])) {
|
||||
continue;
|
||||
}
|
||||
$validity = round((int)time() + ($_data['validity'] * 3600));
|
||||
$stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE
|
||||
if (isset($_data['permanent']) && filter_var($_data['permanent'], FILTER_VALIDATE_BOOL)) {
|
||||
$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");
|
||||
$stmt->execute(array(
|
||||
':address' => $address,
|
||||
':validity' => $validity
|
||||
':validity' => $validity,
|
||||
':permanent' => $permanent
|
||||
));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
@@ -2398,7 +2512,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
foreach ($ids as $id) {
|
||||
$is_now = mailbox('get', 'alias_details', $id);
|
||||
if (!empty($is_now)) {
|
||||
$internal = (isset($_data['internal'])) ? intval($_data['internal']) : $is_now['internal'];
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||
$sender_allowed = (isset($_data['sender_allowed'])) ? intval($_data['sender_allowed']) : $is_now['sender_allowed'];
|
||||
$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_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0;
|
||||
@@ -2583,6 +2699,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`domain` = :domain,
|
||||
`goto` = :goto,
|
||||
`sogo_visible`= :sogo_visible,
|
||||
`internal`= :internal,
|
||||
`sender_allowed`= :sender_allowed,
|
||||
`active`= :active
|
||||
WHERE `id` = :id");
|
||||
$stmt->execute(array(
|
||||
@@ -2592,6 +2710,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':domain' => $domain,
|
||||
':goto' => $goto,
|
||||
':sogo_visible' => $sogo_visible,
|
||||
':internal' => $internal,
|
||||
':sender_allowed' => $sender_allowed,
|
||||
':active' => $active,
|
||||
':id' => $is_now['id']
|
||||
));
|
||||
@@ -2939,6 +3059,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
||||
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||
$_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
|
||||
$_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
|
||||
}
|
||||
if (!empty($is_now)) {
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||
@@ -2948,6 +3070,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
(int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
|
||||
(int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
|
||||
(int)$sieve_access = (isset($_data['sieve_access']) && hasACLAccess("protocol_access")) ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
|
||||
(int)$eas_access = (isset($_data['eas_access']) && hasACLAccess("protocol_access")) ? intval($_data['eas_access']) : intval($is_now['attributes']['eas_access']);
|
||||
(int)$dav_access = (isset($_data['dav_access']) && hasACLAccess("protocol_access")) ? intval($_data['dav_access']) : intval($is_now['attributes']['dav_access']);
|
||||
(int)$relayhost = (isset($_data['relayhost']) && hasACLAccess("mailbox_relayhost")) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
|
||||
(int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
|
||||
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
|
||||
@@ -3081,9 +3205,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
}
|
||||
if (isset($_data['sender_acl'])) {
|
||||
// Get sender_acl items set by admin
|
||||
$current_sender_acls = mailbox('get', 'sender_acl_handles', $username);
|
||||
$sender_acl_admin = array_merge(
|
||||
mailbox('get', 'sender_acl_handles', $username)['sender_acl_domains']['ro'],
|
||||
mailbox('get', 'sender_acl_handles', $username)['sender_acl_addresses']['ro']
|
||||
$current_sender_acls['sender_acl_domains']['ro'],
|
||||
$current_sender_acls['sender_acl_addresses']['ro']
|
||||
);
|
||||
// Get sender_acl items from POST array
|
||||
// Set sender_acl_domain_admin to empty array if sender_acl contains "default" to trigger a reset
|
||||
@@ -3171,16 +3296,25 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(
|
||||
':username' => $username
|
||||
));
|
||||
$fixed_sender_aliases = mailbox('get', 'sender_acl_handles', $username)['fixed_sender_aliases'];
|
||||
$sender_acl_handles = mailbox('get', 'sender_acl_handles', $username);
|
||||
$fixed_sender_aliases_allowed = $sender_acl_handles['fixed_sender_aliases_allowed'];
|
||||
$fixed_sender_aliases_blocked = $sender_acl_handles['fixed_sender_aliases_blocked'];
|
||||
|
||||
foreach ($sender_acl_merged as $sender_acl) {
|
||||
$domain = ltrim($sender_acl, '@');
|
||||
if (is_valid_domain_name($domain)) {
|
||||
$sender_acl = '@' . $domain;
|
||||
}
|
||||
// Don't add if allowed by alias
|
||||
if (in_array($sender_acl, $fixed_sender_aliases)) {
|
||||
|
||||
// Always add to sender_acl table to create explicit permission
|
||||
// Skip only if it's in allowed list (would be redundant)
|
||||
// But DO add if it's in blocked list (creates override)
|
||||
if (in_array($sender_acl, $fixed_sender_aliases_allowed)) {
|
||||
// Skip: already allowed by sender_allowed=1, no need for sender_acl entry
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add to sender_acl (either override for blocked aliases, or grant for selectable ones)
|
||||
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`)
|
||||
VALUES (:sender_acl, :username)");
|
||||
$stmt->execute(array(
|
||||
@@ -3231,6 +3365,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),
|
||||
`attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.eas_access', :eas_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.dav_access', :dav_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email),
|
||||
`attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash)
|
||||
WHERE `username` = :username");
|
||||
@@ -3245,6 +3381,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':pop3_access' => $pop3_access,
|
||||
':sieve_access' => $sieve_access,
|
||||
':smtp_access' => $smtp_access,
|
||||
':eas_access' => $eas_access,
|
||||
':dav_access' => $dav_access,
|
||||
':recovery_email' => $pw_recovery_email,
|
||||
':relayhost' => $relayhost,
|
||||
':username' => $username,
|
||||
@@ -3259,6 +3397,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
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
|
||||
foreach($tags as $index => $tag){
|
||||
if (empty($tag)) continue;
|
||||
@@ -3604,6 +3749,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr = array();
|
||||
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
|
||||
$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_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'];
|
||||
@@ -3619,6 +3765,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
|
||||
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||
$attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0;
|
||||
$attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0;
|
||||
}
|
||||
else {
|
||||
foreach ($is_now as $key => $value){
|
||||
@@ -3724,6 +3872,125 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
|
||||
return true;
|
||||
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':
|
||||
if (!is_array($_data['name'])) {
|
||||
$names = array();
|
||||
@@ -3929,13 +4196,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$data['sender_acl_addresses']['rw'] = array();
|
||||
$data['sender_acl_addresses']['selectable'] = array();
|
||||
$data['fixed_sender_aliases'] = array();
|
||||
$data['fixed_sender_aliases_allowed'] = array();
|
||||
$data['fixed_sender_aliases_blocked'] = array();
|
||||
$data['external_sender_aliases'] = array();
|
||||
// Fixed addresses
|
||||
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'");
|
||||
// Fixed addresses - split by sender_allowed status
|
||||
$stmt = $pdo->prepare("SELECT `address`, `sender_allowed` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'");
|
||||
$stmt->execute(array(':goto' => '(^|,)'.preg_quote($_data, '/').'($|,)'));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
// Keep old array for backward compatibility
|
||||
$data['fixed_sender_aliases'][] = $row['address'];
|
||||
// Split into allowed/blocked for proper display
|
||||
if ($row['sender_allowed'] == '1') {
|
||||
$data['fixed_sender_aliases_allowed'][] = $row['address'];
|
||||
} else {
|
||||
$data['fixed_sender_aliases_blocked'][] = $row['address'];
|
||||
}
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias_domain_alias` FROM `mailbox`, `alias_domain`
|
||||
WHERE `alias_domain`.`target_domain` = `mailbox`.`domain`
|
||||
@@ -4368,10 +4644,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`description`,
|
||||
`validity`,
|
||||
`created`,
|
||||
`modified`
|
||||
`modified`,
|
||||
`permanent`
|
||||
FROM `spamalias`
|
||||
WHERE `goto` = :username
|
||||
AND `validity` >= :unixnow");
|
||||
AND (`validity` >= :unixnow
|
||||
OR `permanent` != 0)");
|
||||
$stmt->execute(array(':username' => $_data, ':unixnow' => time()));
|
||||
$tladata = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return $tladata;
|
||||
@@ -4490,8 +4768,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`address`,
|
||||
`public_comment`,
|
||||
`private_comment`,
|
||||
`internal`,
|
||||
`active`,
|
||||
`sogo_visible`,
|
||||
`sender_allowed`,
|
||||
`created`,
|
||||
`modified`
|
||||
FROM `alias`
|
||||
@@ -4520,10 +4800,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$aliasdata['goto'] = $row['goto'];
|
||||
$aliasdata['address'] = $row['address'];
|
||||
(!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_int'] = $row['active'];
|
||||
$aliasdata['sogo_visible'] = $row['sogo_visible'];
|
||||
$aliasdata['sogo_visible_int'] = $row['sogo_visible'];
|
||||
$aliasdata['sender_allowed'] = $row['sender_allowed'];
|
||||
$aliasdata['created'] = $row['created'];
|
||||
$aliasdata['modified'] = $row['modified'];
|
||||
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) {
|
||||
@@ -4944,7 +5226,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->execute(array(':domain' => $row['domain'], ':username' => $_data));
|
||||
$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()));
|
||||
$SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];
|
||||
@@ -5012,6 +5294,20 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
return $rows;
|
||||
}
|
||||
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':
|
||||
$resourcedata = array();
|
||||
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
|
||||
@@ -5397,6 +5693,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt->execute(array(
|
||||
':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 `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);");
|
||||
try {
|
||||
|
||||
@@ -22,7 +22,7 @@ function quarantine($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
$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 rcpt IN (SELECT username FROM mailbox)');
|
||||
$stmt->execute(array(':hash' => $hash));
|
||||
@@ -65,7 +65,7 @@ function quarantine($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
$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 `username` IN (SELECT `username` FROM `mailbox`)');
|
||||
$stmt->execute(array(':hash' => $hash));
|
||||
@@ -169,7 +169,7 @@ function quarantine($_action, $_data = null) {
|
||||
}
|
||||
}
|
||||
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(
|
||||
array('220', 'HELO quarantine' . chr(10)),
|
||||
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
||||
@@ -464,7 +464,7 @@ function quarantine($_action, $_data = null) {
|
||||
}
|
||||
}
|
||||
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(
|
||||
array('220', 'HELO quarantine' . chr(10)),
|
||||
array('250', 'MAIL FROM: ' . $sender . chr(10)),
|
||||
@@ -833,7 +833,7 @@ function quarantine($_action, $_data = null) {
|
||||
)));
|
||||
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));
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
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 = [
|
||||
'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'),
|
||||
|
||||
@@ -4,7 +4,7 @@ function init_db_schema()
|
||||
try {
|
||||
global $pdo;
|
||||
|
||||
$db_version = "27012025_1555";
|
||||
$db_version = "28012026_1000";
|
||||
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
@@ -184,6 +184,8 @@ function init_db_schema()
|
||||
"private_comment" => "TEXT",
|
||||
"public_comment" => "TEXT",
|
||||
"sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"internal" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"sender_allowed" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||
),
|
||||
"keys" => array(
|
||||
@@ -345,10 +347,14 @@ function init_db_schema()
|
||||
"notified" => "TINYINT(1) NOT NULL DEFAULT '0'",
|
||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||
"user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'",
|
||||
"qhash" => "VARCHAR(64)",
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("id")
|
||||
),
|
||||
"key" => array(
|
||||
"qhash" => array("qhash")
|
||||
)
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
@@ -471,6 +477,23 @@ function init_db_schema()
|
||||
),
|
||||
"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(
|
||||
"cols" => array(
|
||||
"username" => "VARCHAR(255) NOT NULL",
|
||||
@@ -532,7 +555,8 @@ function init_db_schema()
|
||||
"description" => "TEXT NOT NULL",
|
||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
|
||||
"validity" => "INT(11)"
|
||||
"validity" => "INT(11)",
|
||||
"permanent" => "TINYINT(1) NOT NULL DEFAULT '0'"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
@@ -1315,6 +1339,14 @@ function init_db_schema()
|
||||
$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
|
||||
$pdo->query("UPDATE `imapsync` SET `custom_params` = ''
|
||||
WHERE `custom_params` LIKE '%pipemess%'
|
||||
@@ -1363,6 +1395,8 @@ function init_db_schema()
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.imap_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.imap_access') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.pop3_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.pop3_access') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.smtp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.smtp_access') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.eas_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.eas_access') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.dav_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.dav_access') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_VALUE(`attributes`, '$.mailbox_format') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_VALUE(`attributes`, '$.quarantine_notification') IS NULL;");
|
||||
$pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', \"reject\") WHERE JSON_VALUE(`attributes`, '$.quarantine_category') IS NULL;");
|
||||
@@ -1482,6 +1516,10 @@ function init_db_schema()
|
||||
'msg' => 'db_init_complete'
|
||||
);
|
||||
}
|
||||
|
||||
// fill quarantine.qhash
|
||||
$pdo->query("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE ISNULL(`qhash`)");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
if (php_sapi_name() == "cli") {
|
||||
echo "DB initialization failed: " . print_r($e, true) . PHP_EOL;
|
||||
|
||||
@@ -121,7 +121,7 @@ class mailcowPdo extends OAuth2\Storage\Pdo {
|
||||
$this->config['user_table'] = 'mailbox';
|
||||
}
|
||||
public function checkUserCredentials($username, $password) {
|
||||
if (check_login($username, $password) == 'user') {
|
||||
if (check_login($username, $password, array("role" => "user", "service" => "NONE")) == 'user') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
// Start session
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_name($SESSION_NAME);
|
||||
ini_set("session.cookie_httponly", 1);
|
||||
ini_set("session.cookie_samesite", $SESSION_SAMESITE_POLICY);
|
||||
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ if (isset($_GET["cancel_tfa_login"])) {
|
||||
|
||||
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||
$login_user = strtolower(trim($_POST["login_user"]));
|
||||
$as = check_login($login_user, $_POST["pass_user"], false, array("role" => "admin"));
|
||||
$as = check_login($login_user, $_POST["pass_user"], array("role" => "admin", "service" => "MAILCOWUI"));
|
||||
|
||||
if ($as == "admin") {
|
||||
session_regenerate_id(true);
|
||||
|
||||
@@ -55,7 +55,7 @@ if (isset($_GET["cancel_tfa_login"])) {
|
||||
|
||||
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||
$login_user = strtolower(trim($_POST["login_user"]));
|
||||
$as = check_login($login_user, $_POST["pass_user"], false, array("role" => "domain_admin"));
|
||||
$as = check_login($login_user, $_POST["pass_user"], array("role" => "domain_admin", "service" => "MAILCOWUI"));
|
||||
|
||||
if ($as == "domainadmin") {
|
||||
session_regenerate_id(true);
|
||||
|
||||
@@ -76,8 +76,11 @@ if (isset($_POST["verify_tfa_login"])) {
|
||||
|
||||
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
|
||||
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
|
||||
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
|
||||
header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}");
|
||||
if (intval($user_details['attributes']['sogo_access']) == 1 &&
|
||||
intval($user_details['attributes']['force_pw_update']) != 1 &&
|
||||
getenv('SKIP_SOGO') != "y" &&
|
||||
!$is_dual) {
|
||||
header("Location: /SOGo/so/");
|
||||
die();
|
||||
} else {
|
||||
header("Location: /user");
|
||||
@@ -116,7 +119,7 @@ if (isset($_GET["cancel_tfa_login"])) {
|
||||
|
||||
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||
$login_user = strtolower(trim($_POST["login_user"]));
|
||||
$as = check_login($login_user, $_POST["pass_user"], false, array("role" => "user"));
|
||||
$as = check_login($login_user, $_POST["pass_user"], array("role" => "user", "service" => "MAILCOWUI"));
|
||||
|
||||
if ($as == "user") {
|
||||
set_user_loggedin_session($login_user);
|
||||
@@ -139,8 +142,11 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||
|
||||
$user_details = mailbox("get", "mailbox_details", $login_user);
|
||||
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
|
||||
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
|
||||
header("Location: /SOGo/so/{$login_user}");
|
||||
if (intval($user_details['attributes']['sogo_access']) == 1 &&
|
||||
intval($user_details['attributes']['force_pw_update']) != 1 &&
|
||||
getenv('SKIP_SOGO') != "y" &&
|
||||
!$is_dual) {
|
||||
header("Location: /SOGo/so/");
|
||||
die();
|
||||
} else {
|
||||
header("Location: /user");
|
||||
|
||||
@@ -33,6 +33,8 @@ if ($https_port === FALSE) {
|
||||
//$https_port = 1234;
|
||||
// Other settings =>
|
||||
$autodiscover_config = array(
|
||||
'displayName' => 'A mailcow mail server',
|
||||
'displayShortName' => 'mail server',
|
||||
// General autodiscover service type: "activesync" or "imap"
|
||||
// emClient uses autodiscover, but does not support ActiveSync. mailcow excludes emClient from ActiveSync.
|
||||
// With SOGo disabled, the type will always fallback to imap. CalDAV and CardDAV will be excluded, too.
|
||||
@@ -83,8 +85,9 @@ $DEFAULT_LANG = 'en-gb';
|
||||
// https://en.wikipedia.org/wiki/IETF_language_tag
|
||||
$AVAILABLE_LANGUAGES = array(
|
||||
// 'ca-es' => 'Català (Catalan)',
|
||||
'bg-bg' => 'Български (Bulgarian)',
|
||||
'cs-cz' => 'Čeština (Czech)',
|
||||
'da-dk' => 'Danish (Dansk)',
|
||||
'da-dk' => 'Dansk (Danish)',
|
||||
'de-de' => 'Deutsch (German)',
|
||||
'en-gb' => 'English',
|
||||
'es-es' => 'Español (Spanish)',
|
||||
@@ -109,6 +112,7 @@ $AVAILABLE_LANGUAGES = array(
|
||||
'sv-se' => 'Svenska (Swedish)',
|
||||
'tr-tr' => 'Türkçe (Turkish)',
|
||||
'uk-ua' => 'Українська (Ukrainian)',
|
||||
'vi-vn' => 'Tiếng Việt (Vietnamese)',
|
||||
'zh-cn' => '简体中文 (Simplified Chinese)',
|
||||
'zh-tw' => '繁體中文 (Traditional Chinese)',
|
||||
);
|
||||
@@ -152,6 +156,13 @@ $LOG_PAGINATION_SIZE = 50;
|
||||
// Session lifetime in seconds
|
||||
$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
|
||||
$OTP_LABEL = "mailcow UI";
|
||||
|
||||
@@ -185,6 +196,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)
|
||||
$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)
|
||||
$MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'] = 'hourly';
|
||||
|
||||
@@ -200,6 +217,12 @@ $MAILBOX_DEFAULT_ATTRIBUTES['smtp_access'] = true;
|
||||
// Mailbox has sieve access by default
|
||||
$MAILBOX_DEFAULT_ATTRIBUTES['sieve_access'] = true;
|
||||
|
||||
// Mailbox has ActiveSync/EAS access by default
|
||||
$MAILBOX_DEFAULT_ATTRIBUTES['eas_access'] = true;
|
||||
|
||||
// Mailbox has CalDAV/CardDAV (DAV) access by default
|
||||
$MAILBOX_DEFAULT_ATTRIBUTES['dav_access'] = true;
|
||||
|
||||
// Mailbox receives notifications about...
|
||||
// "add_header" - mail that was put into the Junk folder
|
||||
// "reject" - mail that was rejected
|
||||
@@ -237,12 +260,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
|
||||
$RSPAMD_MAPS = array(
|
||||
'regex' => array(
|
||||
'Header-From: Blacklist' => 'global_mime_from_blacklist.map',
|
||||
'Header-From: Whitelist' => 'global_mime_from_whitelist.map',
|
||||
'Envelope Sender Blacklist' => 'global_smtp_from_blacklist.map',
|
||||
'Envelope Sender Whitelist' => 'global_smtp_from_whitelist.map',
|
||||
'Recipient Blacklist' => 'global_rcpt_blacklist.map',
|
||||
'Recipient Whitelist' => 'global_rcpt_whitelist.map',
|
||||
'Header-From: Denylist' => 'global_mime_from_blacklist.map',
|
||||
'Header-From: Allowlist' => 'global_mime_from_whitelist.map',
|
||||
'Envelope Sender Denylist' => 'global_smtp_from_blacklist.map',
|
||||
'Envelope Sender Allowlist' => 'global_smtp_from_whitelist.map',
|
||||
'Recipient Denylist' => 'global_rcpt_blacklist.map',
|
||||
'Recipient Allowlist' => 'global_rcpt_whitelist.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 DE (only fired in combination with fishy TLDs)' => 'bad_words_de.map',
|
||||
@@ -256,57 +279,57 @@ $RSPAMD_MAPS = array(
|
||||
|
||||
$IMAPSYNC_OPTIONS = array(
|
||||
'whitelist' => array(
|
||||
'abort',
|
||||
'authmd51',
|
||||
'authmd52',
|
||||
'abort',
|
||||
'authmd51',
|
||||
'authmd52',
|
||||
'authmech1',
|
||||
'authmech2',
|
||||
'authuser1',
|
||||
'authuser2',
|
||||
'debug',
|
||||
'debugcontent',
|
||||
'debugcrossduplicates',
|
||||
'debugflags',
|
||||
'debugfolders',
|
||||
'debugimap',
|
||||
'debugimap1',
|
||||
'debugimap2',
|
||||
'debugmemory',
|
||||
'debugssl',
|
||||
'authuser1',
|
||||
'authuser2',
|
||||
'debug',
|
||||
'debugcontent',
|
||||
'debugcrossduplicates',
|
||||
'debugflags',
|
||||
'debugfolders',
|
||||
'debugimap',
|
||||
'debugimap1',
|
||||
'debugimap2',
|
||||
'debugmemory',
|
||||
'debugssl',
|
||||
'delete1emptyfolders',
|
||||
'delete2folders',
|
||||
'disarmreadreceipts',
|
||||
'delete2folders',
|
||||
'disarmreadreceipts',
|
||||
'domain1',
|
||||
'domain2',
|
||||
'domino1',
|
||||
'domino2',
|
||||
'domino1',
|
||||
'domino2',
|
||||
'dry',
|
||||
'errorsmax',
|
||||
'exchange1',
|
||||
'exchange2',
|
||||
'exchange1',
|
||||
'exchange2',
|
||||
'exitwhenover',
|
||||
'expunge1',
|
||||
'f1f2',
|
||||
'filterbuggyflags',
|
||||
'f1f2',
|
||||
'filterbuggyflags',
|
||||
'folder',
|
||||
'folderfirst',
|
||||
'folderlast',
|
||||
'folderrec',
|
||||
'gmail1',
|
||||
'gmail2',
|
||||
'idatefromheader',
|
||||
'gmail1',
|
||||
'gmail2',
|
||||
'idatefromheader',
|
||||
'include',
|
||||
'inet4',
|
||||
'inet6',
|
||||
'justconnect',
|
||||
'justfolders',
|
||||
'justfoldersizes',
|
||||
'justlogin',
|
||||
'keepalive1',
|
||||
'keepalive2',
|
||||
'justconnect',
|
||||
'justfolders',
|
||||
'justfoldersizes',
|
||||
'justlogin',
|
||||
'keepalive1',
|
||||
'keepalive2',
|
||||
'log',
|
||||
'logdir',
|
||||
'logfile',
|
||||
'logfile',
|
||||
'maxbytesafter',
|
||||
'maxlinelength',
|
||||
'maxmessagespersecond',
|
||||
@@ -314,62 +337,62 @@ $IMAPSYNC_OPTIONS = array(
|
||||
'maxsleep',
|
||||
'minage',
|
||||
'minsize',
|
||||
'noabletosearch',
|
||||
'noabletosearch1',
|
||||
'noabletosearch2',
|
||||
'noexpunge1',
|
||||
'noexpunge2',
|
||||
'noabletosearch',
|
||||
'noabletosearch1',
|
||||
'noabletosearch2',
|
||||
'noexpunge1',
|
||||
'noexpunge2',
|
||||
'nofoldersizesatend',
|
||||
'noid',
|
||||
'nolog',
|
||||
'nomixfolders',
|
||||
'noresyncflags',
|
||||
'nossl1',
|
||||
'nossl2',
|
||||
'nosyncacls',
|
||||
'notls1',
|
||||
'notls2',
|
||||
'nouidexpunge2',
|
||||
'nousecache',
|
||||
'noid',
|
||||
'nolog',
|
||||
'nomixfolders',
|
||||
'noresyncflags',
|
||||
'nossl1',
|
||||
'nossl2',
|
||||
'nosyncacls',
|
||||
'notls1',
|
||||
'notls2',
|
||||
'nouidexpunge2',
|
||||
'nousecache',
|
||||
'oauthaccesstoken1',
|
||||
'oauthaccesstoken2',
|
||||
'oauthdirect1',
|
||||
'oauthdirect2',
|
||||
'office1',
|
||||
'office2',
|
||||
'pidfile',
|
||||
'pidfilelocking',
|
||||
'office1',
|
||||
'office2',
|
||||
'pidfile',
|
||||
'pidfilelocking',
|
||||
'prefix1',
|
||||
'prefix2',
|
||||
'proxyauth1',
|
||||
'proxyauth2',
|
||||
'resyncflags',
|
||||
'resynclabels',
|
||||
'search',
|
||||
'proxyauth1',
|
||||
'proxyauth2',
|
||||
'resyncflags',
|
||||
'resynclabels',
|
||||
'search',
|
||||
'search1',
|
||||
'search2',
|
||||
'search2',
|
||||
'sep1',
|
||||
'sep2',
|
||||
'showpasswords',
|
||||
'skipemptyfolders',
|
||||
'ssl2',
|
||||
'ssl2',
|
||||
'sslargs1',
|
||||
'sslargs2',
|
||||
'sslargs2',
|
||||
'subfolder1',
|
||||
'subscribe',
|
||||
'subscribe',
|
||||
'subscribed',
|
||||
'syncacls',
|
||||
'syncduplicates',
|
||||
'syncinternaldates',
|
||||
'synclabels',
|
||||
'tests',
|
||||
'testslive',
|
||||
'testslive6',
|
||||
'tls2',
|
||||
'truncmess',
|
||||
'usecache',
|
||||
'useheader',
|
||||
'useuid'
|
||||
'synclabels',
|
||||
'tests',
|
||||
'testslive',
|
||||
'testslive6',
|
||||
'tls2',
|
||||
'truncmess',
|
||||
'usecache',
|
||||
'useheader',
|
||||
'useuid'
|
||||
),
|
||||
'blacklist' => array(
|
||||
'skipmess',
|
||||
|
||||
@@ -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') {
|
||||
$user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']);
|
||||
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false;
|
||||
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) {
|
||||
header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}");
|
||||
if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") {
|
||||
header("Location: /SOGo/so/");
|
||||
} else {
|
||||
header("Location: /user");
|
||||
}
|
||||
@@ -27,22 +27,30 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
|
||||
exit();
|
||||
}
|
||||
|
||||
$host = strtolower($_SERVER['HTTP_HOST'] ?? '');
|
||||
if (str_starts_with($host, 'autodiscover.') || str_starts_with($host, 'autoconfig.')) {
|
||||
http_response_code(404);
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
|
||||
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
|
||||
$_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
|
||||
|
||||
$has_iam_sso = false;
|
||||
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_data = [
|
||||
'oauth2_request' => @$_SESSION['oauth2_request'],
|
||||
'is_mobileconfig' => str_contains($_SESSION['index_query_string'], 'mobileconfig'),
|
||||
'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');
|
||||
|
||||
@@ -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'}});
|
||||
}
|
||||
|
||||
$(".generate_password").click(async function( event ) {
|
||||
try {
|
||||
$(".generate_password").click(async function( event ) {
|
||||
try {
|
||||
var password_policy = await window.fetch("/api/v1/get/passwordpolicy", { method:'GET', cache:'no-cache' });
|
||||
var password_policy = await password_policy.json();
|
||||
random_passwd_length = password_policy.length;
|
||||
@@ -48,7 +48,11 @@ $(document).ready(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
|
||||
function shake(div,interval,distance,times) {
|
||||
@@ -125,7 +129,7 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
// responsive tabs, scroll to opened tab
|
||||
$(document).on("shown.bs.collapse shown.bs.tab", function (e) {
|
||||
var target = $(e.target);
|
||||
@@ -409,4 +413,4 @@ function copyToClipboard(id) {
|
||||
// only works with https connections
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
mailcow_alert_box(lang.copy_to_clipboard, "success");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,10 +51,19 @@ jQuery(function($){
|
||||
$('.submit_rspamd_regex').attr({"disabled": true});
|
||||
});
|
||||
$("#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();
|
||||
$("#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); });
|
||||
|
||||
$(".refresh_table").on('click', function(e) {
|
||||
@@ -558,7 +567,7 @@ jQuery(function($){
|
||||
} else if (table == 'oauth2clientstable') {
|
||||
$.each(data, function (i, item) {
|
||||
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>' +
|
||||
'</div>';
|
||||
item.scope = "profile";
|
||||
@@ -573,7 +582,7 @@ jQuery(function($){
|
||||
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="#" 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>';
|
||||
});
|
||||
} else if (table == 'adminstable') {
|
||||
@@ -655,7 +664,7 @@ jQuery(function($){
|
||||
$(this).html('<i class="bi bi-arrow-repeat icon-spin"></i> ');
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'inc/ajax/relay_check.php',
|
||||
url: '/inc/ajax/relay_check.php',
|
||||
dataType: 'text',
|
||||
data: $('#test_relayhost_form').serialize(),
|
||||
complete: function (data) {
|
||||
@@ -715,7 +724,6 @@ jQuery(function($){
|
||||
$('.app_hide').off('change');
|
||||
$('.app_hide').on('change', function (e) {
|
||||
var value = $(this).is(':checked') ? '1' : '0';
|
||||
console.log(value)
|
||||
$(this).parent().children(':first-child').val(value);
|
||||
})
|
||||
}
|
||||
@@ -789,6 +797,18 @@ jQuery(function($){
|
||||
$('.iam_ldap_rolemap_del').click(async function(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
|
||||
$('#iam_provider').on('change', function(){
|
||||
// toggle password fields
|
||||
@@ -833,4 +853,22 @@ jQuery(function($){
|
||||
if ($(elem).parent().parent().parent().parent().children().length > 1)
|
||||
$(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) {
|
||||
return response.json();
|
||||
}).then(function(data) {
|
||||
console.log(data);
|
||||
|
||||
// display host ips
|
||||
if (data.ipv4)
|
||||
$("#host_ipv4").text(data.ipv4);
|
||||
@@ -1007,7 +1005,7 @@ jQuery(function($){
|
||||
"data-order": cellData.sortBy,
|
||||
"data-sort": cellData.sortBy
|
||||
});
|
||||
},
|
||||
},
|
||||
render: function (data) {
|
||||
return data.value;
|
||||
}
|
||||
@@ -1032,7 +1030,7 @@ jQuery(function($){
|
||||
"data-order": cellData.sortBy,
|
||||
"data-sort": cellData.sortBy
|
||||
});
|
||||
},
|
||||
},
|
||||
render: function (data) {
|
||||
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) {
|
||||
return response.json();
|
||||
}).then(function(data) {
|
||||
console.log(data);
|
||||
|
||||
if (data){
|
||||
// display table data
|
||||
$("#host_date").text(data.system_time);
|
||||
@@ -1399,8 +1395,6 @@ function update_container_stats(timeout=5){
|
||||
var diskIOCtx = Chart.getChart(container + "_DiskIOChart");
|
||||
var netIOCtx = Chart.getChart(container + "_NetIOChart");
|
||||
|
||||
console.log(container);
|
||||
console.log(data);
|
||||
prev_stats = null;
|
||||
if (data.length >= 2){
|
||||
prev_stats = data[data.length -2];
|
||||
|
||||
@@ -66,7 +66,6 @@ $(document).ready(function() {
|
||||
// load tags
|
||||
if ($('#tags').length){
|
||||
var tagsEl = $('#tags').parent().find('.tag-values')[0];
|
||||
console.log($(tagsEl).val())
|
||||
var tags = JSON.parse($(tagsEl).val());
|
||||
$(tagsEl).val("");
|
||||
|
||||
|
||||
@@ -269,6 +269,24 @@ $(document).ready(function() {
|
||||
function setMailboxTemplateData(template){
|
||||
$("#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"){
|
||||
$('#quarantine_notification_never').prop('checked', true);
|
||||
$('#quarantine_notification_hourly').prop('checked', false);
|
||||
@@ -334,6 +352,12 @@ $(document).ready(function() {
|
||||
if (template.sieve_access == 1){
|
||||
protocol_access.push("sieve");
|
||||
}
|
||||
if (template.eas_access == 1){
|
||||
protocol_access.push("eas");
|
||||
}
|
||||
if (template.dav_access == 1){
|
||||
protocol_access.push("dav");
|
||||
}
|
||||
$('#protocol_access').selectpicker('val', protocol_access);
|
||||
|
||||
var acl = [];
|
||||
@@ -915,6 +939,8 @@ jQuery(function($){
|
||||
item.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
item.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
item.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
item.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
item.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
|
||||
if (item.attributes.quarantine_notification === 'never') {
|
||||
item.quarantine_notification = lang.never;
|
||||
} else if (item.attributes.quarantine_notification === 'hourly') {
|
||||
@@ -1078,6 +1104,18 @@ jQuery(function($){
|
||||
defaultContent: '',
|
||||
className: 'none'
|
||||
},
|
||||
{
|
||||
title: 'EAS',
|
||||
data: 'eas_access',
|
||||
defaultContent: '',
|
||||
className: 'none'
|
||||
},
|
||||
{
|
||||
title: 'DAV',
|
||||
data: 'dav_access',
|
||||
defaultContent: '',
|
||||
className: 'none'
|
||||
},
|
||||
{
|
||||
title: lang.quarantine_notification,
|
||||
data: 'quarantine_notification',
|
||||
@@ -1191,6 +1229,8 @@ jQuery(function($){
|
||||
item.attributes.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.imap_access == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.smtp_access == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sieve_access == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.eas_access = '<i class="text-' + (item.attributes.eas_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.eas_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.eas_access == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.dav_access = '<i class="text-' + (item.attributes.dav_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.dav_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.dav_access == 1 ? '1' : '0') + '</span></i>';
|
||||
item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>';
|
||||
if (item.attributes.quarantine_notification === 'never') {
|
||||
item.attributes.quarantine_notification = lang.never;
|
||||
@@ -1299,6 +1339,16 @@ jQuery(function($){
|
||||
data: 'attributes.sieve_access',
|
||||
defaultContent: '',
|
||||
},
|
||||
{
|
||||
title: 'EAS',
|
||||
data: 'attributes.eas_access',
|
||||
defaultContent: '',
|
||||
},
|
||||
{
|
||||
title: 'DAV',
|
||||
data: 'attributes.dav_access',
|
||||
defaultContent: '',
|
||||
},
|
||||
{
|
||||
title: 'SOGO',
|
||||
data: 'attributes.sogo_access',
|
||||
@@ -1931,11 +1981,6 @@ jQuery(function($){
|
||||
defaultContent: '',
|
||||
responsivePriority: 5,
|
||||
},
|
||||
{
|
||||
title: lang.bcc_destinations,
|
||||
data: 'bcc_dest',
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang.sogo_visible,
|
||||
data: 'sogo_visible',
|
||||
@@ -1954,6 +1999,15 @@ jQuery(function($){
|
||||
data: 'private_comment',
|
||||
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,
|
||||
data: 'active',
|
||||
|
||||
@@ -97,18 +97,17 @@ jQuery(function($){
|
||||
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 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 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 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.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_data = real_rip + ip_location + app_password;
|
||||
|
||||
$(".last-sasl-login").append(`
|
||||
<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="fw-bold">` + real_rip + `</div>
|
||||
<small class="fst-italic mt-2">` + service + ` ` + local_datetime + `</small>
|
||||
<div class="fw-bold">` + ip_location + real_rip + `</div>
|
||||
<small class="fst-italic mt-2">` + service + ` ` + local_datetime + `</small>` + app_password + `
|
||||
</div>
|
||||
<span>` + ip_location + `</span>
|
||||
</li>
|
||||
`);
|
||||
})
|
||||
@@ -169,7 +168,6 @@ jQuery(function($){
|
||||
type: "GET",
|
||||
url: "/api/v1/get/time_limited_aliases",
|
||||
dataSrc: function(data){
|
||||
console.log(data);
|
||||
$.each(data, function (i, item) {
|
||||
if (acl_data.spam_alias === 1) {
|
||||
item.action = '<div class="btn-group">' +
|
||||
@@ -177,6 +175,10 @@ jQuery(function($){
|
||||
'</div>';
|
||||
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.validity = {
|
||||
value: item.validity,
|
||||
permanent: item.permanent
|
||||
};
|
||||
}
|
||||
else {
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
|
||||
@@ -220,9 +222,21 @@ jQuery(function($){
|
||||
title: lang.alias_valid_until,
|
||||
data: 'validity',
|
||||
defaultContent: '',
|
||||
createdCell: function(td, cellData) {
|
||||
createSortableDate(td, cellData)
|
||||
}
|
||||
render: function (data, type) {
|
||||
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,
|
||||
@@ -262,7 +276,6 @@ jQuery(function($){
|
||||
type: "GET",
|
||||
url: '/api/v1/get/syncjobs/' + encodeURIComponent(mailcow_cc_username) + '/no_log',
|
||||
dataSrc: function(data){
|
||||
console.log(data);
|
||||
$.each(data, function (i, item) {
|
||||
item.user1 = escapeHtml(item.user1);
|
||||
item.log = '<a href="#syncjobLogModal" data-bs-toggle="modal" data-syncjob-id="' + item.id + '">' + lang.open_logs + '</a>'
|
||||
@@ -418,7 +431,6 @@ jQuery(function($){
|
||||
type: "GET",
|
||||
url: '/api/v1/get/app-passwd/all',
|
||||
dataSrc: function(data){
|
||||
console.log(data);
|
||||
$.each(data, function (i, item) {
|
||||
item.name = escapeHtml(item.name)
|
||||
item.protocols = []
|
||||
@@ -514,7 +526,6 @@ jQuery(function($){
|
||||
type: "GET",
|
||||
url: '/api/v1/get/policy_wl_mailbox',
|
||||
dataSrc: function(data){
|
||||
console.log(data);
|
||||
$.each(data, function (i, item) {
|
||||
if (validateEmail(item.object)) {
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="policy_wl_mailbox" name="multi_select" value="' + item.prefid + '" />';
|
||||
@@ -585,7 +596,6 @@ jQuery(function($){
|
||||
type: "GET",
|
||||
url: '/api/v1/get/policy_bl_mailbox',
|
||||
dataSrc: function(data){
|
||||
console.log(data);
|
||||
$.each(data, function (i, item) {
|
||||
if (validateEmail(item.object)) {
|
||||
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":
|
||||
process_add_return(app_passwd('add', $attr));
|
||||
break;
|
||||
case "mta-sts":
|
||||
process_add_return(mailbox('add', 'mta_sts', $attr));
|
||||
break;
|
||||
// return no route found if no case is matched
|
||||
default:
|
||||
http_response_code(404);
|
||||
@@ -1004,9 +1007,9 @@ if (isset($_GET['query'])) {
|
||||
['db' => 'last_pw_change', 'dt' => 5, 'dummy' => true, 'order_subquery' => "JSON_EXTRACT(attributes, '$.passwd_update')"],
|
||||
['db' => 'in_use', 'dt' => 6, 'dummy' => true, 'order_subquery' => "(SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`) / `m`.`quota`"],
|
||||
['db' => 'name', 'dt' => 7],
|
||||
['db' => 'messages', 'dt' => 18, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"],
|
||||
['db' => 'tags', 'dt' => 20, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']],
|
||||
['db' => 'active', 'dt' => 21],
|
||||
['db' => 'messages', 'dt' => 20, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"],
|
||||
['db' => 'tags', 'dt' => 23, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']],
|
||||
['db' => 'active', 'dt' => 24],
|
||||
];
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php';
|
||||
@@ -1976,6 +1979,9 @@ if (isset($_GET['query'])) {
|
||||
case "ip_check":
|
||||
process_edit_return(customize('edit', 'ip_check', $attr));
|
||||
break;
|
||||
case "custom_login":
|
||||
process_edit_return(customize('edit', 'custom_login', $attr));
|
||||
break;
|
||||
case "self":
|
||||
if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
|
||||
process_edit_return(domain_admin('edit', $attr));
|
||||
@@ -1986,6 +1992,7 @@ if (isset($_GET['query'])) {
|
||||
break;
|
||||
case "cors":
|
||||
process_edit_return(cors('edit', $attr));
|
||||
break;
|
||||
case "identity-provider":
|
||||
process_edit_return(identity_provider('edit', $attr));
|
||||
break;
|
||||
@@ -1998,6 +2005,9 @@ if (isset($_GET['query'])) {
|
||||
case "reset-password-notification":
|
||||
process_edit_return(reset_password('edit_notification', $attr));
|
||||
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
|
||||
default:
|
||||
http_response_code(404);
|
||||
|
||||
1391
data/web/lang/lang.bg-bg.json
Normal file
1391
data/web/lang/lang.bg-bg.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,16 @@
|
||||
"spam_alias": "Àlies temporals",
|
||||
"spam_score": "Puntuació de correu brossa",
|
||||
"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": {
|
||||
"activate_filter_warn": "All other filters will be deactivated, when active is checked.",
|
||||
@@ -73,7 +82,25 @@
|
||||
"validate": "Validar",
|
||||
"validation_success": "Validated successfully",
|
||||
"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": {
|
||||
"access": "Accés",
|
||||
@@ -557,4 +584,4 @@
|
||||
"week": "Setmana",
|
||||
"weeks": "Setmanes"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"acl": {
|
||||
"alias_domains": "Doménové aliasy",
|
||||
"app_passwds": "Hesla aplikací",
|
||||
"app_passwds": "Správa hesel aplikací",
|
||||
"bcc_maps": "BCC mapy",
|
||||
"delimiter_action": "Zacházení s označkovanou poštou",
|
||||
"domain_desc": "Změnit popis domény",
|
||||
@@ -24,7 +24,7 @@
|
||||
"sogo_access": "Správa přístupu do SOGo",
|
||||
"sogo_profile_reset": "Resetování profilu SOGo",
|
||||
"spam_alias": "Dočasné aliasy",
|
||||
"spam_policy": "Blacklist/Whitelist",
|
||||
"spam_policy": "Denylist/Allowlist",
|
||||
"spam_score": "Skóre spamu",
|
||||
"syncjobs": "Synchronizační úlohy",
|
||||
"tls_policy": "Pravidla TLS",
|
||||
@@ -82,12 +82,12 @@
|
||||
"password": "Heslo",
|
||||
"password_repeat": "Potvrzení nového hesla (opakujte)",
|
||||
"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ář",
|
||||
"public_comment": "Veřejný komentář",
|
||||
"quota_mb": "Kvóta (MiB)",
|
||||
"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_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ě.",
|
||||
@@ -109,7 +109,10 @@
|
||||
"validate": "Ověřit",
|
||||
"validation_success": "Úspěšně ověřeno",
|
||||
"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ů.",
|
||||
"sender_allowed": "Povolit jako alias pro odesílání"
|
||||
},
|
||||
"admin": {
|
||||
"access": "Přístupy",
|
||||
@@ -147,7 +150,7 @@
|
||||
"arrival_time": "Čas zařazení do fronty (čas na serveru)",
|
||||
"authed_user": "Přihlášený uživatel",
|
||||
"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",
|
||||
"logo_normal_label": "Normální",
|
||||
"logo_dark_label": "Inverzní pro tmavý režim",
|
||||
@@ -181,16 +184,16 @@
|
||||
"empty": "Žádné výsledky",
|
||||
"excludes": "Vyloučit tyto příjemce",
|
||||
"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_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_netban_ipv4": "Rozsah IPv4 podsítě k zablokování (8-32)",
|
||||
"f2b_netban_ipv6": "Rozsah IPv6 podsítě k zablokování (8-128)",
|
||||
"f2b_parameters": "Parametry automatického firewallu",
|
||||
"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_whitelist": "Sítě/hostitelé na whitelistu",
|
||||
"f2b_whitelist": "Sítě či hostitelé na seznamu povolení",
|
||||
"filter_table": "Tabulka filtrů",
|
||||
"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ů).",
|
||||
@@ -256,7 +259,7 @@
|
||||
"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_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_sender": "Odesílatel upozornění",
|
||||
"quarantine_notification_subject": "Předmět upozornění",
|
||||
@@ -264,7 +267,7 @@
|
||||
"quarantine_release_format": "Formát propuštěných položek",
|
||||
"quarantine_release_format_att": "Jako příloha",
|
||||
"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_sender": "Odesílatel upozornění",
|
||||
"quota_notification_subject": "Předmět upozornění",
|
||||
@@ -283,7 +286,7 @@
|
||||
"relay_rcpt": "\"Komu:\" adresa",
|
||||
"relay_run": "Provést test",
|
||||
"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_row": "Smazat řádek",
|
||||
"reset_default": "Obnovit výchozí nastavení",
|
||||
@@ -299,11 +302,11 @@
|
||||
"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_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_agree": "Budu opatrný!",
|
||||
"rspamd_global_filters_info": "Mapa globálních filtrů obsahuje jiné globální black- a whitelisty.",
|
||||
"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_info": "Mapa globálních filtrů obsahuje různé seznamy povolených a zakázaných serverů.",
|
||||
"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",
|
||||
"sal_level": "Úroveň 'Moo'",
|
||||
"save": "Uložit změny",
|
||||
@@ -324,8 +327,8 @@
|
||||
"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_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",
|
||||
"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\"",
|
||||
"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>\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_header_announcement": "Oznámení",
|
||||
"ui_header_announcement_active": "Nastavit jako aktivní",
|
||||
@@ -344,17 +347,72 @@
|
||||
"validate_license_now": "Ověřit GUID na licenčním serveru",
|
||||
"verify": "Ověřit",
|
||||
"yes": "✓",
|
||||
"f2b_ban_time_increment": "Délka banu je prodlužována s každým dalším banem",
|
||||
"f2b_max_ban_time": "Maximální délka banu (s)",
|
||||
"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 bloku (s)",
|
||||
"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_settings": "Nastavení obnovení hesla",
|
||||
"password_settings": "Nastavení hesel",
|
||||
"password_reset_tmpl_html": "HTML š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",
|
||||
"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": {
|
||||
"access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",
|
||||
@@ -408,7 +466,7 @@
|
||||
"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_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",
|
||||
"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ý",
|
||||
@@ -486,7 +544,20 @@
|
||||
"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.",
|
||||
"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": {
|
||||
"emptyTable": "Tabulka neobsahuje žádná data",
|
||||
@@ -548,7 +619,8 @@
|
||||
"update_failed": "Nepodařilo se zkontrolovat aktualizace",
|
||||
"wip": "Nedokončená vývojová verze",
|
||||
"memory": "Paměť",
|
||||
"container_disabled": "Kontejner je zastaven nebo zakázán"
|
||||
"container_disabled": "Kontejner je zastaven nebo zakázán",
|
||||
"cores": "jádra"
|
||||
},
|
||||
"diagnostics": {
|
||||
"cname_from_a": "Hodnota odvozena z A/AAAA záznamu. Lze použít, pokud záznam ukazuje na správný zdroj.",
|
||||
@@ -569,7 +641,7 @@
|
||||
"alias": "Upravit alias",
|
||||
"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ě.",
|
||||
"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_passwd": "Heslo aplikace",
|
||||
"app_passwd_protocols": "Povolené protokoly pro hesla aplikací",
|
||||
@@ -607,7 +679,7 @@
|
||||
"inactive": "Neaktivní",
|
||||
"kind": "Druh",
|
||||
"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_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.",
|
||||
@@ -641,7 +713,7 @@
|
||||
"ratelimit": "Omezení přenosu",
|
||||
"redirect_uri": "URL přesměrování/odvolání",
|
||||
"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_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ě.",
|
||||
@@ -662,11 +734,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).",
|
||||
"spam_alias": "Vytvořit nebo změnit dočasné aliasy",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"timeout1": "Časový limit pro připojení ke vzdálenému serveru",
|
||||
"timeout2": "Časový limit pro připojení k lokálnímu serveru",
|
||||
@@ -690,7 +762,28 @@
|
||||
"custom_attributes": "Vlastní atributy",
|
||||
"footer_exclude": "Vyloučit ze zápatí",
|
||||
"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).",
|
||||
"sender_allowed": "Povolit jako alias pro odesílání",
|
||||
"sender_allowed_info": "Není-li povoleno, lze alias použít jen pro příjem pošty. Lze také obejít v Sender ACL a nastavit konkrétní schránky, jež tento alias mohou používat"
|
||||
},
|
||||
"fido2": {
|
||||
"confirm": "Potvrdit",
|
||||
@@ -711,7 +804,7 @@
|
||||
"cancel": "Zrušit",
|
||||
"confirm_delete": "Potvdit smazání",
|
||||
"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_nok": "Nalezeno! Toto je potenciálně nebezpečné heslo!",
|
||||
"hibp_ok": "Nebyla nalezena žádná shoda.",
|
||||
@@ -720,12 +813,12 @@
|
||||
"restart_container": "Restartovat kontejner",
|
||||
"restart_container_info": "<b>Důležité:</b> Šetrný restart může chvíli trvat, prosím čekejte...",
|
||||
"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": {
|
||||
"administration": "Hlavní nastavení",
|
||||
"apps": "Aplikace",
|
||||
"debug": "Systémové informace",
|
||||
"debug": "Informace",
|
||||
"email": "E-Mail",
|
||||
"mailcow_system": "Systém",
|
||||
"mailcow_config": "Nastavení",
|
||||
@@ -753,7 +846,15 @@
|
||||
"new_password": "Nové heslo",
|
||||
"new_password_confirm": "Ověření nového hesla",
|
||||
"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": {
|
||||
"action": "Akce",
|
||||
@@ -785,7 +886,7 @@
|
||||
"bcc": "BCC",
|
||||
"bcc_destination": "Cíl kopie",
|
||||
"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_map": "Skrytá kopie",
|
||||
"bcc_map_type": "Typ skryté kopie",
|
||||
@@ -840,7 +941,7 @@
|
||||
"last_run_reset": "Znovu naplánovat",
|
||||
"mailbox": "Mailová schránka",
|
||||
"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_templates": "Šablony schránek",
|
||||
"mailbox_quota": "Max. velikost schránky",
|
||||
@@ -860,7 +961,7 @@
|
||||
"private_comment": "Soukromý komentář",
|
||||
"public_comment": "Veřejný komentář",
|
||||
"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",
|
||||
"quarantine_category": "Kategorie oznámení karantény",
|
||||
"quarantine_notification": "Upozornění z karantény",
|
||||
@@ -869,7 +970,7 @@
|
||||
"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_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_info": "Původní příjemce musí být platná emailová adresa nebo název domény.",
|
||||
"recipient_maps": "Mapy příjemců",
|
||||
@@ -888,7 +989,7 @@
|
||||
"sieve_preset_5": "Automatický odpovídač (dovolená)",
|
||||
"sieve_preset_6": "Odmítnout zprávu s odpovědí",
|
||||
"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>.",
|
||||
"sogo_visible": "Alias dostupný v SOGo",
|
||||
"sogo_visible_n": "Skrýt alias v SOGo",
|
||||
@@ -928,7 +1029,9 @@
|
||||
"waiting": "Čekání",
|
||||
"weekly": "Každý týden",
|
||||
"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": {
|
||||
"access_denied": "K udělení přístupu se přihlašte jako vlastník mailové schránky.",
|
||||
@@ -936,7 +1039,7 @@
|
||||
"deny": "Zamítnout",
|
||||
"permit": "Ověřit aplikaci",
|
||||
"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í"
|
||||
},
|
||||
"quarantine": {
|
||||
@@ -960,7 +1063,7 @@
|
||||
"notified": "Oznámeno",
|
||||
"qhandler_success": "Požadavek úspěšně přijat. Můžete nyní zavřít okno.",
|
||||
"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ě",
|
||||
"quarantine": "Karanténa",
|
||||
"quick_actions": "Akce",
|
||||
@@ -1005,7 +1108,8 @@
|
||||
"hold_mail_legend": "Podrží vybrané e-maily. (Zabrání dalším pokusům o doručení)",
|
||||
"show_message": "Zobrazit zprávu",
|
||||
"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": {
|
||||
"disabled": "Vypnuto",
|
||||
@@ -1066,7 +1170,7 @@
|
||||
"logged_in_as": "Přihlášen jako %s",
|
||||
"mailbox_added": "Mailová schránka %s přidána",
|
||||
"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ý",
|
||||
"object_modified": "Změny objektu %s uloženy",
|
||||
"password_policy_saved": "Politika hesel byla úspěšně uložena",
|
||||
@@ -1100,7 +1204,14 @@
|
||||
"verified_yotp_login": "Yubico OTP přihlášení ověřeno",
|
||||
"cors_headers_edited": "Nastavení CORS byla uložena",
|
||||
"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": {
|
||||
"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 +1227,7 @@
|
||||
"none": "Deaktivovat",
|
||||
"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ě.",
|
||||
"select": "Prosím vyberte...",
|
||||
"select": "Vyberte prosím",
|
||||
"set_tfa": "Nastavení způsobu dvoufaktorového ověření",
|
||||
"start_webauthn_validation": "Zahájit inicializaci",
|
||||
"tfa": "Dvoufaktorové ověření (TFA)",
|
||||
@@ -1125,7 +1236,10 @@
|
||||
"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_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": {
|
||||
"action": "Akce",
|
||||
@@ -1141,7 +1255,7 @@
|
||||
"alias_time_left": "Zbývající čas",
|
||||
"alias_valid_until": "Platný do",
|
||||
"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",
|
||||
"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",
|
||||
@@ -1162,8 +1276,8 @@
|
||||
"description": "Popis",
|
||||
"delete_ays": "Potvrďte odstranění.",
|
||||
"direct_aliases": "Přímé aliasy",
|
||||
"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_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 \"Webmail\" zajišťuje jednotné přihlášení k SOGo a je vždy k dispozici.",
|
||||
"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_now": "Smazat",
|
||||
@@ -1204,7 +1318,7 @@
|
||||
"no_last_login": "Žádný záznam o přihlášení",
|
||||
"no_record": "Žádný záznam",
|
||||
"open_logs": "Otevřít záznam",
|
||||
"open_webmail_sso": "Webmailu",
|
||||
"open_webmail_sso": "Webmail",
|
||||
"password": "Heslo",
|
||||
"password_now": "Současné heslo (pro potvrzení změny)",
|
||||
"password_repeat": "Heslo (znovu)",
|
||||
@@ -1236,13 +1350,13 @@
|
||||
"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_now": "Resetovat profil",
|
||||
"spam_aliases": "Dočasné e-mailové aliasy",
|
||||
"spam_aliases": "Spam aliasy",
|
||||
"spam_score_reset": "Obnovit výchozí nastavení serveru",
|
||||
"spamfilter": "Filtr spamu",
|
||||
"spamfilter_behavior": "Hodnocení",
|
||||
"spamfilter_bl": "Seznam zakázaných adres (blacklist)",
|
||||
"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_default_score": "Výchozí hodnoty:",
|
||||
"spamfilter_bl": "Seznam zákazů",
|
||||
"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_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_red": "Červená: Tato zpráva je spam a server ji odmítne",
|
||||
@@ -1252,7 +1366,7 @@
|
||||
"spamfilter_table_empty": "Žádná data k zobrazení",
|
||||
"spamfilter_table_remove": "smazat",
|
||||
"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_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",
|
||||
@@ -1274,7 +1388,7 @@
|
||||
"tag_in_subject": "V předmětu",
|
||||
"text": "Text",
|
||||
"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_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.",
|
||||
@@ -1290,7 +1404,16 @@
|
||||
"years": "let",
|
||||
"pushover_sound": "Zvukové upozornění",
|
||||
"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": {
|
||||
"cannot_delete_self": "Nelze smazat právě přihlášeného uživatele",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"sogo_access": "Verwalten des SOGo-Zugriffsrechts erlauben",
|
||||
"sogo_profile_reset": "SOGo-Profil zurücksetzen",
|
||||
"spam_alias": "Temporäre E-Mail-Aliasse",
|
||||
"spam_policy": "Blacklist/Whitelist",
|
||||
"spam_policy": "Deny/Allowlist",
|
||||
"spam_score": "Spam-Bewertung",
|
||||
"syncjobs": "Sync Jobs",
|
||||
"tls_policy": "Verschlüsselungsrichtlinie",
|
||||
@@ -71,6 +71,9 @@
|
||||
"goto_spam": "Nachrichten als <span class=\"text-danger\"><b>Spam</b></span> lernen",
|
||||
"hostname": "Host",
|
||||
"inactive": "Inaktiv",
|
||||
"internal": "Intern",
|
||||
"internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.",
|
||||
"sender_allowed": "Als dieser Alias senden erlauben",
|
||||
"kind": "Art",
|
||||
"mailbox_quota_def": "Standard-Quota einer Mailbox",
|
||||
"mailbox_quota_m": "Max. Speicherplatz pro Mailbox (MiB)",
|
||||
@@ -134,6 +137,7 @@
|
||||
"admin_domains": "Domain-Zuweisungen",
|
||||
"admins": "Administratoren",
|
||||
"admins_ldap": "LDAP-Administratoren",
|
||||
"admin_quicklink": "Quicklink zur Admin-Loginseite ausblenden",
|
||||
"advanced_settings": "Erweiterte Einstellungen",
|
||||
"api_allow_from": "IP-Adressen oder Netzwerke (CIDR Notation) für Zugriff auf API",
|
||||
"api_info": "Die API befindet sich noch in Entwicklung, die Dokumentation kann unter <a href=\"/api\">/api</a> abgerufen werden.",
|
||||
@@ -146,7 +150,7 @@
|
||||
"arrival_time": "Ankunftszeit (Serverzeit)",
|
||||
"authed_user": "Auth. Benutzer",
|
||||
"ays": "Soll der Vorgang wirklich ausgeführt werden?",
|
||||
"ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Bannzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Blacklist-Einträge.",
|
||||
"ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Bannzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Allowlist-Einträge.",
|
||||
"change_logo": "Logo ändern",
|
||||
"configuration": "Konfiguration",
|
||||
"convert_html_to_text": "Konvertiere HTML zu reinem Text",
|
||||
@@ -155,6 +159,7 @@
|
||||
"credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.",
|
||||
"customer_id": "Kunde",
|
||||
"customize": "UI-Anpassung",
|
||||
"login_page": "Login-Seite",
|
||||
"destination": "Ziel",
|
||||
"dkim_add_key": "ARC/DKIM-Key hinzufügen",
|
||||
"dkim_domains_selector": "Selector",
|
||||
@@ -173,6 +178,7 @@
|
||||
"domain": "Domain",
|
||||
"domain_admin": "Administrator hinzufügen",
|
||||
"domain_admins": "Domain-Administratoren",
|
||||
"domainadmin_quicklink": "Quicklink zur Domainadmin-Loginseite ausblenden",
|
||||
"domain_s": "Domain(s)",
|
||||
"duplicate": "Duplizieren",
|
||||
"duplicate_dkim": "DKIM duplizieren",
|
||||
@@ -181,9 +187,9 @@
|
||||
"excludes": "Diese Empfänger ausschließen",
|
||||
"f2b_ban_time": "Bannzeit in Sekunden",
|
||||
"f2b_ban_time_increment": "Bannzeit erhöht sich mit jedem Bann",
|
||||
"f2b_blacklist": "Blacklist für Netzwerke und Hosts",
|
||||
"f2b_blacklist": "Denyliste für Netzwerke und Hosts",
|
||||
"f2b_filter": "Regex-Filter",
|
||||
"f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
|
||||
"f2b_list_info": "Ein Host oder Netzwerk auf der Denyliste wird immer eine Allowlist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
|
||||
"f2b_manage_external": "Fail2Ban extern verwalten",
|
||||
"f2b_manage_external_info": "Fail2ban wird die Banlist weiterhin pflegen, jedoch werden keine aktiven Regeln zum blockieren gesetzt. Die unten generierte Banlist, kann verwendet werden, um den Datenverkehr extern zu blockieren.",
|
||||
"f2b_max_attempts": "Max. Versuche",
|
||||
@@ -193,8 +199,10 @@
|
||||
"f2b_parameters": "Fail2ban-Parameter",
|
||||
"f2b_regex_info": "Berücksichtigte Logs: SOGo, Postfix, Dovecot, PHP-FPM.",
|
||||
"f2b_retry_window": "Wiederholungen im Zeitraum von (s)",
|
||||
"f2b_whitelist": "Whitelist für Netzwerke und Hosts",
|
||||
"f2b_whitelist": "Allowliste für Netzwerke und Hosts",
|
||||
"filter_table": "Tabelle filtern",
|
||||
"force_sso_text": "Wenn ein externer OIDC-Provider konfiguriert ist, blendet diese Option die mailcow-Loginform aus und zeigt nur den Single-Sign-On-Button an.",
|
||||
"force_sso": "mailcow-Login deaktivieren und nur Single Sign-On anzeigen",
|
||||
"forwarding_hosts": "Weiterleitungs-Hosts",
|
||||
"forwarding_hosts_add_hint": "Sie können entweder IPv4-/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.",
|
||||
"forwarding_hosts_hint": "Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die übliche Verwendung für diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.",
|
||||
@@ -222,6 +230,7 @@
|
||||
"iam_host": "Host",
|
||||
"iam_host_info": "Gib einen oder mehrere LDAP-Hosts ein, getrennt durch Kommas.",
|
||||
"iam_import_users": "Importiere Benutzer",
|
||||
"iam_login_provisioning": "Benutzer beim Login erstellen",
|
||||
"iam_mapping": "Attribut Mapping",
|
||||
"iam_bindpass": "Bind Passwort",
|
||||
"iam_periodic_full_sync": "Vollsynchronisation",
|
||||
@@ -266,6 +275,7 @@
|
||||
"message": "Nachricht",
|
||||
"message_size": "Nachrichtengröße",
|
||||
"nexthop": "Next Hop",
|
||||
"needs_restart": "benötigt Neustart",
|
||||
"no": "✕",
|
||||
"no_active_bans": "Keine aktiven Banns",
|
||||
"no_new_rows": "Keine weiteren Zeilen vorhanden",
|
||||
@@ -308,6 +318,7 @@
|
||||
"quarantine_release_format_att": "Als Anhang",
|
||||
"quarantine_release_format_raw": "Unverändertes Original",
|
||||
"quarantine_retention_size": "Rückhaltungen pro Mailbox:<br><small>0 bedeutet <b>inaktiv</b>.</small>",
|
||||
"quicklink_text": "Quicklinks zu anderen Login-Seiten unter der Loginform ein- oder ausblenden",
|
||||
"quota_notification_html": "Benachrichtigungs-E-Mail Inhalt:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>",
|
||||
"quota_notification_sender": "Benachrichtigungs-E-Mail Absender",
|
||||
"quota_notification_subject": "Benachrichtigungs-E-Mail Betreff",
|
||||
@@ -347,8 +358,8 @@
|
||||
"rspamd_com_settings": "Ein Name wird automatisch generiert. Beispielinhalte zur Einsicht stehen nachstehend bereit. Siehe auch <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd docs</a>",
|
||||
"rspamd_global_filters": "Globale Filter-Maps",
|
||||
"rspamd_global_filters_agree": "Ich werde vorsichtig sein!",
|
||||
"rspamd_global_filters_info": "Globale Filter-Maps steuern globales White- und Blacklisting dieses Servers.",
|
||||
"rspamd_global_filters_regex": "Die akzeptierte Form für Einträge sind <b>ausschließlich</b> Regular Expressions.\r\n Trotz rudimentärer Überprüfung der Map, kann es zu fehlerhaften Einträgen kommen, die Rspamd im schlechtesten Fall mit unvorhersehbarer Funktionalität bestraft.<br>\r\n Das korrekte Format lautet \"/pattern/options\" (Beispiel: <code>/.+@domain\\.tld/i</code>).<br>\r\n Der Name der Map beschreibt die jeweilige Funktion.<br>\r\n Rspamd versucht die Maps umgehend aufzulösen. Bei Problemen sollte <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">Rspamd manuell neugestartet werden</a>.<br>Elemente auf Blacklists sind von der Quarantäne ausgeschlossen.",
|
||||
"rspamd_global_filters_info": "Globale Filter-Maps steuern globales Allow- und Denylisting dieses Servers.",
|
||||
"rspamd_global_filters_regex": "Die akzeptierte Form für Einträge sind <b>ausschließlich</b> Regular Expressions.\r\n Trotz rudimentärer Überprüfung der Map, kann es zu fehlerhaften Einträgen kommen, die Rspamd im schlechtesten Fall mit unvorhersehbarer Funktionalität bestraft.<br>\r\n Das korrekte Format lautet \"/pattern/options\" (Beispiel: <code>/.+@domain\\.tld/i</code>).<br>\r\n Der Name der Map beschreibt die jeweilige Funktion.<br>\r\n Rspamd versucht die Maps umgehend aufzulösen. Bei Problemen sollte <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">Rspamd manuell neugestartet werden</a>.<br>Elemente auf Denylisten sind von der Quarantäne ausgeschlossen.",
|
||||
"rspamd_settings_map": "Rspamd-Settings-Map",
|
||||
"sal_level": "Moo-Level",
|
||||
"save": "Änderungen speichern",
|
||||
@@ -387,6 +398,7 @@
|
||||
"unchanged_if_empty": "Unverändert, wenn leer",
|
||||
"upload": "Hochladen",
|
||||
"username": "Benutzername",
|
||||
"user_quicklink": "Quicklink zur Benutzer-Loginseite ausblenden",
|
||||
"validate_license_now": "GUID erneut verifizieren",
|
||||
"verify": "Verifizieren",
|
||||
"yes": "✓",
|
||||
@@ -398,7 +410,9 @@
|
||||
"allowed_methods": "Access-Control-Allow-Methods",
|
||||
"allowed_origins": "Access-Control-Allow-Origin",
|
||||
"logo_dark_label": "Invertiert für den Darkmode",
|
||||
"logo_normal_label": "Normal"
|
||||
"logo_normal_label": "Normal",
|
||||
"user_link": "Nutzer-Link",
|
||||
"filter": "Filter"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
|
||||
@@ -472,10 +486,13 @@
|
||||
"mailboxes_in_use": "Maximale Anzahl an Mailboxen muss größer oder gleich %d sein",
|
||||
"malformed_username": "Benutzername hat ein falsches Format",
|
||||
"map_content_empty": "Inhalt darf nicht leer sein",
|
||||
"max_age_invalid": "Maximales Alter %s ist ungültig",
|
||||
"max_alias_exceeded": "Anzahl an Alias-Adressen überschritten",
|
||||
"max_mailbox_exceeded": "Anzahl an Mailboxen überschritten (%d von %d)",
|
||||
"max_quota_in_use": "Mailbox-Speicherplatzlimit muss größer oder gleich %d MiB sein",
|
||||
"maxquota_empty": "Max. Speicherplatz pro Mailbox darf nicht 0 sein.",
|
||||
"mode_invalid": "Modus %s ist ungültig",
|
||||
"mx_invalid": "MX-Eintrag %s ist ungültig",
|
||||
"mysql_error": "MySQL-Fehler: %s",
|
||||
"network_host_invalid": "Netzwerk oder Host ungültig: %s",
|
||||
"next_hop_interferes": "%s verhindert das Hinzufügen von Next Hop %s",
|
||||
@@ -535,10 +552,12 @@
|
||||
"username_invalid": "Benutzername %s kann nicht verwendet werden",
|
||||
"validity_missing": "Bitte geben Sie eine Gültigkeitsdauer an",
|
||||
"value_missing": "Bitte alle Felder ausfüllen",
|
||||
"version_invalid": "Version %s ist ungültig",
|
||||
"yotp_verification_failed": "Yubico OTP-Verifizierung fehlgeschlagen: %s",
|
||||
"template_exists": "Vorlage %s existiert bereits",
|
||||
"template_id_invalid": "Vorlagen-ID %s ungültig",
|
||||
"template_name_invalid": "Name der Vorlage ungültig"
|
||||
"template_name_invalid": "Name der Vorlage ungültig",
|
||||
"required_data_missing": "Die benötigten Daten: %s fehlen"
|
||||
},
|
||||
"datatables": {
|
||||
"collapse_all": "Alle Einklappen",
|
||||
@@ -674,6 +693,10 @@
|
||||
"grant_types": "Grant-types",
|
||||
"hostname": "Servername",
|
||||
"inactive": "Inaktiv",
|
||||
"internal": "Intern",
|
||||
"internal_info": "Interne Aliasse sind nur von der eigenen Domäne oder Alias-Domänen erreichbar.",
|
||||
"sender_allowed": "Als dieser Alias senden erlauben",
|
||||
"sender_allowed_info": "Wenn deaktiviert, kann dieser Alias nur E-Mails empfangen. Verwenden Sie Sender-ACL, um bestimmten Postfächern die Berechtigung zum Senden zu erteilen.",
|
||||
"kind": "Art",
|
||||
"last_modified": "Zuletzt geändert",
|
||||
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*\\.google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
|
||||
@@ -692,6 +715,17 @@
|
||||
"maxbytespersecond": "Max. Übertragungsrate in Bytes/s (0 für unlimitiert)",
|
||||
"mbox_rl_info": "Dieses Limit wird auf den SASL Loginnamen angewendet und betrifft daher alle Absenderadressen, die der eingeloggte Benutzer verwendet. Bei Mailbox Ratelimit überwiegt ein Domain-weites Ratelimit.",
|
||||
"mins_interval": "Intervall (min)",
|
||||
"mta_sts": "MTA-STS",
|
||||
"mta_sts_info": "<a href='https://de.wikipedia.org/wiki/STARTTLS#MTA-STS' target='_blank'>MTA-STS</a> ist ein Standard, der den E-Mail-Versand zwischen Mailservern zwingt, TLS mit gültigen Zertifikaten zu verwenden. <br>Er wird verwendet, wenn <a target='_blank' href='https://de.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> aufgrund fehlender oder nicht unterstützter DNSSEC nicht möglich ist.<br><b>Hinweis</b>: Wenn die empfangende Domain DANE mit DNSSEC unterstützt, wird DANE <b>immer</b> bevorzugt – MTA-STS fungiert nur als Fallback.",
|
||||
"mta_sts_version": "Version",
|
||||
"mta_sts_version_info": "Definiert die Version des MTA-STS-Standards – derzeit ist nur <code>STSv1</code> gültig.",
|
||||
"mta_sts_mode": "Modus",
|
||||
"mta_sts_mode_info": "Es gibt drei Modi zur Auswahl:<ul><li><em>testing</em> – Die Richtlinie wird nur überwacht, Verstöße haben keine Auswirkungen.</li><li><em>enforce</em> – Die Richtlinie wird strikt durchgesetzt, Verbindungen ohne gültiges TLS werden abgelehnt.</li><li><em>none</em> – Die Richtlinie wird veröffentlicht, aber nicht angewendet.</li></ul>",
|
||||
"mta_sts_max_age": "Maximales Alter",
|
||||
"mta_sts_max_age_info": "Zeit in Sekunden, die empfangende Mailserver diese Richtlinie zwischenspeichern dürfen, bevor sie erneut abgerufen wird.",
|
||||
"mta_sts_mx": "MX-Server",
|
||||
"mta_sts_mx_info": "Erlaubt das Senden nur an explizit aufgeführte Mailserver-Hostnamen; der sendende MTA überprüft, ob der DNS-MX-Hostname mit der Richtlinienliste übereinstimmt, und erlaubt die Zustellung nur mit einem gültigen TLS-Zertifikat (schützt vor MITM).",
|
||||
"mta_sts_mx_notice": "Es können mehrere MX-Server angegeben werden (durch Kommas getrennt).",
|
||||
"multiple_bookings": "Mehrfaches Buchen",
|
||||
"nexthop": "Next Hop",
|
||||
"none_inherit": "Keine Auswahl / Erben",
|
||||
@@ -738,7 +772,7 @@
|
||||
"sogo_visible_info": "Diese Option hat lediglich Einfluss auf Objekte, die in SOGo darstellbar sind (geteilte oder nicht-geteilte Alias-Adressen mit dem Ziel mindestens einer lokalen Mailbox).",
|
||||
"spam_alias": "Anpassen temporärer Alias-Adressen",
|
||||
"spam_filter": "Spamfilter",
|
||||
"spam_policy": "Hinzufügen und Entfernen von Einträgen in White- und Blacklists",
|
||||
"spam_policy": "Hinzufügen und Entfernen von Einträgen in Allow- und Denylisten",
|
||||
"spam_score": "Einen benutzerdefiniterten Spam-Score festlegen",
|
||||
"subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>",
|
||||
"syncjob": "Sync-Job bearbeiten",
|
||||
@@ -806,9 +840,13 @@
|
||||
"forgot_password": "> Passwort vergessen?",
|
||||
"invalid_pass_reset_token": "Der Rücksetz-Token für das Passwort ist ungültig oder abgelaufen.<br>Bitte fordern Sie einen neuen Link zur Passwortwiederherstellung an.",
|
||||
"login": "Anmelden",
|
||||
"login_user": "Benutzer Anmelden",
|
||||
"login_dadmin": "Domain-Administrator Anmelden",
|
||||
"login_admin": "Administrator Anmelden",
|
||||
"login_linkstext": "Nicht der richtige Login?",
|
||||
"login_usertext": "Als Benutzer anmelden",
|
||||
"login_domainadmintext": "Als Domainadmin anmelden",
|
||||
"login_admintext": "Als Admin anmelden",
|
||||
"login_user": "Anmeldung als Benutzer",
|
||||
"login_dadmin": "Anmeldung als Domain-Administrator",
|
||||
"login_admin": "Anmeldung als Administrator",
|
||||
"mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.",
|
||||
"new_password": "Neues Passwort",
|
||||
"new_password_confirm": "Neues Passwort bestätigen",
|
||||
@@ -816,7 +854,8 @@
|
||||
"password": "Passwort",
|
||||
"reset_password": "Passwort zurücksetzen",
|
||||
"request_reset_password": "Passwortänderung anfordern",
|
||||
"username": "Benutzername"
|
||||
"username": "Benutzername",
|
||||
"email": "E-Mail-Adresse"
|
||||
},
|
||||
"mailbox": {
|
||||
"action": "Aktion",
|
||||
@@ -837,7 +876,7 @@
|
||||
"add_tls_policy_map": "TLS-Richtlinieneintrag hinzufügen",
|
||||
"address_rewriting": "Adressumschreibung",
|
||||
"alias": "Alias",
|
||||
"alias_domain_alias_hint": "Alias-Adressen werden <b>nicht</b> automatisch auch auf Domain-Alias Adressen angewendet. Eine Alias-Adresse <code>mein-alias@domain</code> bildet demnach <b>nicht</b> die Adresse <code>mein-alias@alias-domain</code> ab.<br>E-Mail-Weiterleitungen an externe Postfächer sollten über Sieve (SOGo Weiterleitung oder im Reiter \"Filter\") angelegt werden. Der Button \"Alias über Alias-Domains expandieren\" erstellt fehlende Alias-Adressen in Alias-Domains.",
|
||||
"alias_domain_alias_hint": "Alias-Adressen werden <b>nicht</b> automatisch auch auf Domain-Alias Adressen angewendet. Eine Alias-Adresse <code>mein-alias@domain</code> bildet demnach <b>nicht</b> die Adresse <code>mein-alias@alias-domain</code> ab.<br>E-Mail-Weiterleitungen an externe Postfächer sollten über Sieve (SOGo Weiterleitung oder im Reiter Filter) angelegt werden. Der Button Alias über Alias-Domains expandieren erstellt fehlende Alias-Adressen in Alias-Domains.",
|
||||
"alias_domain_backupmx": "Alias-Domain für Relay-Domain inaktiv",
|
||||
"aliases": "Aliasse",
|
||||
"allow_from_smtp": "Nur folgende IPs für <b>SMTP</b> erlauben",
|
||||
@@ -891,6 +930,7 @@
|
||||
"in_use": "Prozentualer Gebrauch",
|
||||
"inactive": "Inaktiv",
|
||||
"insert_preset": "Beispiel \"%s\" laden",
|
||||
"internal": "Intern",
|
||||
"kind": "Art",
|
||||
"last_mail_login": "Letzter Mail-Login",
|
||||
"last_modified": "Zuletzt geändert",
|
||||
@@ -928,7 +968,7 @@
|
||||
"recipient_map_new": "Neuer Empfänger",
|
||||
"recipient_map_new_info": "Der neue Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.",
|
||||
"recipient_map_old": "Original-Empfänger",
|
||||
"recipient_map_old_info": "Der originale Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.",
|
||||
"recipient_map_old_info": "Der originäre Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.",
|
||||
"recipient_maps": "Empfängerumschreibungen",
|
||||
"relay_all": "Alle Empfänger-Adressen relayen",
|
||||
"relay_unknown": "Unbekannte Mailboxen relayen",
|
||||
@@ -950,7 +990,7 @@
|
||||
"sogo_visible": "Alias Sichtbarkeit in SOGo",
|
||||
"sogo_visible_n": "Alias in SOGo verbergen",
|
||||
"sogo_visible_y": "Alias in SOGo anzeigen",
|
||||
"spam_aliases": "Temp. Alias",
|
||||
"spam_aliases": "Spam-Alias",
|
||||
"stats": "Statistik",
|
||||
"status": "Status",
|
||||
"sync_jobs": "Synchronisationen",
|
||||
@@ -991,7 +1031,8 @@
|
||||
"syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung",
|
||||
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem",
|
||||
"syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit",
|
||||
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen"
|
||||
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen",
|
||||
"iam": "Identitätsanbieter"
|
||||
},
|
||||
"oauth2": {
|
||||
"access_denied": "Bitte als Mailbox-Nutzer einloggen, um den Zugriff via OAuth2 zu erlauben.",
|
||||
@@ -1023,7 +1064,7 @@
|
||||
"notified": "Benachrichtigt",
|
||||
"qhandler_success": "Aktion wurde an das System übergeben. Sie dürfen dieses Fenster nun schließen.",
|
||||
"qid": "Rspamd QID",
|
||||
"qinfo": "Das Quarantänesystem speichert abgelehnte Nachrichten in der Datenbank (dem Sender wird <em>nicht</em> signalisiert, dass seine E-Mail zugestellt wurde) als auch diese, die als Kopie in den Junk-Ordner der jeweiligen Mailbox zugestellt wurden.\r\n <br>\"Als Spam lernen und löschen\" lernt Nachrichten nach bayesscher Statistik als Spam und erstellt Fuzzy Hashes ausgehend von der jeweiligen Nachricht, um ähnliche Inhalte zukünftig zu unterbinden.\r\n <br>Der Prozess des Lernens kann abhängig vom System zeitintensiv sein.<br>Auf Blacklists vorkommende Elemente sind von der Quarantäne ausgeschlossen.",
|
||||
"qinfo": "Das Quarantänesystem speichert abgelehnte Nachrichten in der Datenbank (dem Sender wird <em>nicht</em> signalisiert, dass seine E-Mail zugestellt wurde) als auch diese, die als Kopie in den Junk-Ordner der jeweiligen Mailbox zugestellt wurden.\r\n <br>\"Als Spam lernen und löschen\" lernt Nachrichten nach bayesscher Statistik als Spam und erstellt Fuzzy Hashes ausgehend von der jeweiligen Nachricht, um ähnliche Inhalte zukünftig zu unterbinden.\r\n <br>Der Prozess des Lernens kann abhängig vom System zeitintensiv sein.<br>Auf Denylisten vorkommende Elemente sind von der Quarantäne ausgeschlossen.",
|
||||
"qitem": "Quarantäneeintrag",
|
||||
"quarantine": "Quarantäne",
|
||||
"quick_actions": "Aktionen",
|
||||
@@ -1062,7 +1103,7 @@
|
||||
"legend": "Funktionen der Mailqueue Aktionen:",
|
||||
"ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?",
|
||||
"deliver_mail": "Ausliefern",
|
||||
"deliver_mail_legend": "Versucht eine erneute Zustellung der ausgwählten Mails.",
|
||||
"deliver_mail_legend": "Versucht eine erneute Zustellung der ausgewählten Mails.",
|
||||
"hold_mail": "Zurückhalten",
|
||||
"hold_mail_legend": "Hält die ausgewählten Mails zurück. (Verhindert weitere Zustellversuche)",
|
||||
"queue_manager": "Queue Manager",
|
||||
@@ -1094,6 +1135,7 @@
|
||||
"bcc_edited": "BCC-Map-Eintrag %s wurde geändert",
|
||||
"bcc_saved": "BCC- Map-Eintrag wurde gespeichert",
|
||||
"cors_headers_edited": "CORS Einstellungen wurden erfolgreich gespeichert",
|
||||
"custom_login_modified": "Login Anpassung wurde erfolgreich gespeichert",
|
||||
"db_init_complete": "Datenbankinitialisierung abgeschlossen",
|
||||
"delete_filter": "Filter-ID %s wurde gelöscht",
|
||||
"delete_filters": "Filter gelöscht: %s",
|
||||
@@ -1231,7 +1273,7 @@
|
||||
"delete_ays": "Soll der Löschvorgang wirklich ausgeführt werden?",
|
||||
"direct_aliases": "Direkte Alias-Adressen",
|
||||
"direct_aliases_desc": "Nur direkte Alias-Adressen werden für benutzerdefinierte Einstellungen berücksichtigt.",
|
||||
"direct_protocol_access": "Der Hauptbenutzer hat <b>direkten, externen Zugriff</b> auf folgende Protokolle und Anwendungen. Diese Einstellung wird vom Administrator gesteuert. App-Passwörter können verwendet werden, um individuelle Zugänge für Protokolle und Anwendungen zu erstellen.<br>Der Button \"Webmail\" kann unabhängig der Einstellung immer verwendet werden.",
|
||||
"direct_protocol_access": "Der Hauptbenutzer hat <b>direkten, externen Zugriff</b> auf folgende Protokolle und Anwendungen. Diese Einstellung wird vom Administrator gesteuert. App-Passwörter können verwendet werden, um individuelle Zugänge für Protokolle und Anwendungen zu erstellen.<br>Der Button Webmail kann unabhängig der Einstellung immer verwendet werden.",
|
||||
"eas_reset": "ActiveSync-Geräte-Cache zurücksetzen",
|
||||
"eas_reset_help": "In vielen Fällen kann ein ActiveSync-Profil durch das Zurücksetzen des Caches repariert werden.<br><b>Vorsicht:</b> Alle Elemente werden erneut heruntergeladen!",
|
||||
"eas_reset_now": "Jetzt zurücksetzen",
|
||||
@@ -1242,7 +1284,9 @@
|
||||
"encryption": "Verschlüsselung",
|
||||
"excludes": "Ausschlüsse",
|
||||
"expire_in": "Ungültig in",
|
||||
"expire_never": "Niemals ungültig",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"forever": "Für immer",
|
||||
"force_pw_update": "Das Passwort für diesen Benutzer <b>muss</b> geändert werden, damit die Zugriffssperre auf die Groupware-Komponenten wieder freigeschaltet wird.",
|
||||
"from": "von",
|
||||
"generate": "generieren",
|
||||
@@ -1307,12 +1351,13 @@
|
||||
"sogo_profile_reset": "SOGo-Profil zurücksetzen",
|
||||
"sogo_profile_reset_help": "Das Profil wird inklusive <b>aller</b> Kalender- und Kontaktdaten <b>unwiederbringlich gelöscht</b>.",
|
||||
"sogo_profile_reset_now": "Profil jetzt zurücksetzen",
|
||||
"spam_aliases": "Temporäre E-Mail-Aliasse",
|
||||
"spam_aliases": "Spam E-Mail-Aliasse",
|
||||
"spam_aliases_info": "Ein Spam-Alias ist eine temporäre E-Mailadresse, die benutzt werden kann, um eine echte E-Mail Adressen zu schützen. <br>Optional kann eine Ablaufzeit gesetzt werden, sodass der Alias nach dem definierten Zeitraum automatisch deaktiviert wird, was missbrauchte oder geleakte Adressen effektiv entsorgt.",
|
||||
"spam_score_reset": "Auf Server-Standard zurücksetzen",
|
||||
"spamfilter": "Spamfilter",
|
||||
"spamfilter_behavior": "Bewertung",
|
||||
"spamfilter_bl": "Blacklist",
|
||||
"spamfilter_bl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>immer</b> als Spam erfasst und abgelehnt werden. Die Quarantäne-Funktion ist für diese Nachrichten deaktiviert. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte nicht-\"Catch All\" Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
|
||||
"spamfilter_bl": "Denyliste",
|
||||
"spamfilter_bl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>immer</b> als Spam erfasst und abgelehnt werden. Die Quarantäne-Funktion ist für diese Nachrichten <b>deaktiviert</b>. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte Nicht-„Catch-All“-Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
|
||||
"spamfilter_default_score": "Standardwert",
|
||||
"spamfilter_green": "Grün: Die Nachricht ist kein Spam",
|
||||
"spamfilter_hint": "Der erste Wert beschreibt den \"low spam score\", der zweite Wert den \"high spam score\".",
|
||||
@@ -1323,8 +1368,8 @@
|
||||
"spamfilter_table_empty": "Keine Einträge vorhanden",
|
||||
"spamfilter_table_remove": "Entfernen",
|
||||
"spamfilter_table_rule": "Regel",
|
||||
"spamfilter_wl": "Whitelist",
|
||||
"spamfilter_wl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>nicht</b> erfasst werden sollen. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte nicht-\"Catch All\" Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
|
||||
"spamfilter_wl": "Allowliste",
|
||||
"spamfilter_wl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>nicht</b> erfasst werden sollen. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte Nicht-„Catch-All“-Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
|
||||
"spamfilter_yellow": "Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben",
|
||||
"status": "Status",
|
||||
"sync_jobs": "Sync Jobs",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"sogo_access": "Allow management of SOGo access",
|
||||
"sogo_profile_reset": "Reset SOGo profile",
|
||||
"spam_alias": "Temporary aliases",
|
||||
"spam_policy": "Blacklist/Whitelist",
|
||||
"spam_policy": "Denylist/Allowlist",
|
||||
"spam_score": "Spam score",
|
||||
"syncjobs": "Sync jobs",
|
||||
"tls_policy": "TLS policy",
|
||||
@@ -71,6 +71,9 @@
|
||||
"goto_spam": "Learn as <span class=\"text-danger\"><b>spam</b></span>",
|
||||
"hostname": "Host",
|
||||
"inactive": "Inactive",
|
||||
"internal": "Internal",
|
||||
"internal_info": "Internal aliases are only accessible from the own domain or alias domains.",
|
||||
"sender_allowed": "Allow to send as this alias",
|
||||
"kind": "Kind",
|
||||
"mailbox_quota_def": "Default mailbox quota",
|
||||
"mailbox_quota_m": "Max. quota per mailbox (MiB)",
|
||||
@@ -134,6 +137,7 @@
|
||||
"admin_domains": "Domain assignments",
|
||||
"admins": "Administrators",
|
||||
"admins_ldap": "LDAP Administrators",
|
||||
"admin_quicklink": "Hide Quicklink to Admin Login Page",
|
||||
"advanced_settings": "Advanced settings",
|
||||
"allowed_methods": "Access-Control-Allow-Methods",
|
||||
"allowed_origins": "Access-Control-Allow-Origin",
|
||||
@@ -150,7 +154,7 @@
|
||||
"arrival_time": "Arrival time (server time)",
|
||||
"authed_user": "Auth. user",
|
||||
"ays": "Are you sure you want to proceed?",
|
||||
"ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by blacklisting.",
|
||||
"ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by denylisting.",
|
||||
"change_logo": "Change logo",
|
||||
"logo_normal_label": "Normal",
|
||||
"logo_dark_label": "Inverted for dark mode",
|
||||
@@ -161,6 +165,7 @@
|
||||
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
|
||||
"customer_id": "Customer ID",
|
||||
"customize": "Customize",
|
||||
"login_page": "Login Page",
|
||||
"destination": "Destination",
|
||||
"dkim_add_key": "Add ARC/DKIM key",
|
||||
"dkim_domains_selector": "Selector",
|
||||
@@ -179,6 +184,7 @@
|
||||
"domain": "Domain",
|
||||
"domain_admin": "Domain administrator",
|
||||
"domain_admins": "Domain administrators",
|
||||
"domainadmin_quicklink": "Hide Quicklink to Domainadmin Login Page",
|
||||
"domain_s": "Domain/s",
|
||||
"duplicate": "Duplicate",
|
||||
"duplicate_dkim": "Duplicate DKIM record",
|
||||
@@ -187,9 +193,9 @@
|
||||
"excludes": "Excludes these recipients",
|
||||
"f2b_ban_time": "Ban time (s)",
|
||||
"f2b_ban_time_increment": "Ban time is incremented with each ban",
|
||||
"f2b_blacklist": "Blacklisted networks/hosts",
|
||||
"f2b_blacklist": "Denylisted networks/hosts",
|
||||
"f2b_filter": "Regex filters",
|
||||
"f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
|
||||
"f2b_list_info": "A denylisted host or network will always outweigh a allowlist entity. <b>List updates will take a few seconds to be applied.</b>",
|
||||
"f2b_manage_external": "Manage Fail2Ban externally",
|
||||
"f2b_manage_external_info": "Fail2ban will still maintain the banlist, but it will not actively set rules to block traffic. Use the generated banlist below to externally block the traffic.",
|
||||
"f2b_max_attempts": "Max. attempts",
|
||||
@@ -199,9 +205,11 @@
|
||||
"f2b_parameters": "Fail2ban parameters",
|
||||
"f2b_regex_info": "Logs taken into consideration: SOGo, Postfix, Dovecot, PHP-FPM.",
|
||||
"f2b_retry_window": "Retry window (s) for max. attempts",
|
||||
"f2b_whitelist": "Whitelisted networks/hosts",
|
||||
"f2b_whitelist": "Allowlisted networks/hosts",
|
||||
"filter": "Filter",
|
||||
"filter_table": "Filter table",
|
||||
"force_sso_text": "If an external OIDC provider is configured, this option hides the default mailcow login forms and only shows the Single Sign-On button",
|
||||
"force_sso": "Disable mailcow Login and show only Single Sign-On",
|
||||
"forwarding_hosts": "Forwarding Hosts",
|
||||
"forwarding_hosts_add_hint": "You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).",
|
||||
"forwarding_hosts_hint": "Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.",
|
||||
@@ -229,6 +237,7 @@
|
||||
"iam_host": "Host",
|
||||
"iam_host_info": "Enter one or more LDAP hosts, separated by commas.",
|
||||
"iam_import_users": "Import Users",
|
||||
"iam_login_provisioning": "Auto-create users on login",
|
||||
"iam_mapping": "Attribute Mapping",
|
||||
"iam_bindpass": "Bind Password",
|
||||
"iam_periodic_full_sync": "Periodic Full Sync",
|
||||
@@ -273,6 +282,7 @@
|
||||
"message": "Message",
|
||||
"message_size": "Message size",
|
||||
"nexthop": "Next hop",
|
||||
"needs_restart": "needs restart",
|
||||
"no": "✕",
|
||||
"no_active_bans": "No active bans",
|
||||
"no_new_rows": "No further rows available",
|
||||
@@ -317,6 +327,7 @@
|
||||
"quarantine_release_format_att": "As attachment",
|
||||
"quarantine_release_format_raw": "Unmodified original",
|
||||
"quarantine_retention_size": "Retentions per mailbox:<br><small>0 indicates <b>inactive</b>.</small>",
|
||||
"quicklink_text": "Show or hide quick links to other login pages under the login form",
|
||||
"quota_notification_html": "Notification email template:<br><small>Leave empty to restore default template.</small>",
|
||||
"quota_notification_sender": "Notification email sender",
|
||||
"quota_notification_subject": "Notification email subject",
|
||||
@@ -357,8 +368,8 @@
|
||||
"rspamd_com_settings": "A setting name will be auto-generated, please see the example presets below. For more details see <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd docs</a>",
|
||||
"rspamd_global_filters": "Global filter maps",
|
||||
"rspamd_global_filters_agree": "I will be careful!",
|
||||
"rspamd_global_filters_info": "Global filter maps contain different kind of global black and whitelists.",
|
||||
"rspamd_global_filters_regex": "Their names explain their purpose. All content must contain valid regular expression in the format of \"/pattern/options\" (e.g. <code>/.+@domain\\.tld/i</code>).<br>\r\n Although rudimentary checks are being executed on each line of regex, Rspamds functionality can be broken, if it fails to read the syntax correctly.<br>\r\n Rspamd will try to read the map content when changed. If you experience problems, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restart Rspamd</a> to enforce a map reload.<br>Blacklisted elements are excluded from quarantine.",
|
||||
"rspamd_global_filters_info": "Global filter maps contain different kind of global deny and allowlists.",
|
||||
"rspamd_global_filters_regex": "Their names explain their purpose. All content must contain valid regular expression in the format of \"/pattern/options\" (e.g. <code>/.+@domain\\.tld/i</code>).<br>\r\n Although rudimentary checks are being executed on each line of regex, Rspamds functionality can be broken, if it fails to read the syntax correctly.<br>\r\n Rspamd will try to read the map content when changed. If you experience problems, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restart Rspamd</a> to enforce a map reload.<br>Denylisted elements are excluded from quarantine.",
|
||||
"rspamd_settings_map": "Rspamd settings map",
|
||||
"sal_level": "Moo level",
|
||||
"save": "Save changes",
|
||||
@@ -398,6 +409,7 @@
|
||||
"upload": "Upload",
|
||||
"username": "Username",
|
||||
"user_link": "User-Link",
|
||||
"user_quicklink": "Hide Quicklink to User Login Page",
|
||||
"validate_license_now": "Validate GUID against license server",
|
||||
"verify": "Verify",
|
||||
"yes": "✓"
|
||||
@@ -474,10 +486,13 @@
|
||||
"mailboxes_in_use": "Max. mailboxes must be greater or equal to %d",
|
||||
"malformed_username": "Malformed username",
|
||||
"map_content_empty": "Map content cannot be empty",
|
||||
"max_age_invalid": "Max age %s is invalid",
|
||||
"max_alias_exceeded": "Max. aliases exceeded",
|
||||
"max_mailbox_exceeded": "Max. mailboxes exceeded (%d of %d)",
|
||||
"max_quota_in_use": "Mailbox quota must be greater or equal to %d MiB",
|
||||
"maxquota_empty": "Max. quota per mailbox must not be 0.",
|
||||
"mode_invalid": "Mode %s is invalid",
|
||||
"mx_invalid": "MX record %s is invalid",
|
||||
"mysql_error": "MySQL error: %s",
|
||||
"network_host_invalid": "Invalid network or host: %s",
|
||||
"next_hop_interferes": "%s interferes with nexthop %s",
|
||||
@@ -541,6 +556,7 @@
|
||||
"username_invalid": "Username %s cannot be used",
|
||||
"validity_missing": "Please assign a period of validity",
|
||||
"value_missing": "Please provide all values",
|
||||
"version_invalid": "Version %s is invalid",
|
||||
"yotp_verification_failed": "Yubico OTP verification failed: %s"
|
||||
},
|
||||
"datatables": {
|
||||
@@ -677,6 +693,10 @@
|
||||
"grant_types": "Grant types",
|
||||
"hostname": "Hostname",
|
||||
"inactive": "Inactive",
|
||||
"internal": "Internal",
|
||||
"internal_info": "Internal aliases are only accessible from the own domain or alias domains.",
|
||||
"sender_allowed": "Allow to send as this alias",
|
||||
"sender_allowed_info": "If disabled, this alias can only receive mail. Use sender ACL to override and grant specific mailboxes permission to send.",
|
||||
"kind": "Kind",
|
||||
"last_modified": "Last modified",
|
||||
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*\\.google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
|
||||
@@ -695,6 +715,17 @@
|
||||
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
|
||||
"mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit.",
|
||||
"mins_interval": "Interval (min)",
|
||||
"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> is a standard that enforces email delivery between mail servers to use TLS with valid certificates. <br>It is used when <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> is not possible due to missing or unsupported DNSSEC.<br><b>Note</b>: If the receiving domain supports DANE with DNSSEC, DANE is <b>always</b> preferred – MTA-STS only acts as a fallback.",
|
||||
"mta_sts_version": "Version",
|
||||
"mta_sts_version_info": "Defines the version of the MTA-STS standard – currently only <code>STSv1</code> is valid.",
|
||||
"mta_sts_mode": "Mode",
|
||||
"mta_sts_mode_info": "There are three modes to choose from:<ul><li><em>testing</em> – policy is only monitored, violations have no impact.</li><li><em>enforce</em> – policy is strictly enforced, connections without valid TLS are rejected.</li><li><em>none</em> – policy is published but not applied.</li></ul>",
|
||||
"mta_sts_max_age": "Max age",
|
||||
"mta_sts_max_age_info": "Time in seconds that receiving mail servers may cache this policy until refetching.",
|
||||
"mta_sts_mx": "MX server",
|
||||
"mta_sts_mx_info": "Allows sending only to explicitly listed mail server hostnames; the sending MTA checks if the DNS MX hostname matches the policy list, and only allows delivery with a valid TLS certificate (guards against MITM).",
|
||||
"mta_sts_mx_notice": "Multiple MX servers can be specified (separated by commas).",
|
||||
"multiple_bookings": "Multiple bookings",
|
||||
"none_inherit": "None / Inherit",
|
||||
"nexthop": "Next hop",
|
||||
@@ -742,7 +773,7 @@
|
||||
"sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.",
|
||||
"spam_alias": "Create or change time limited alias addresses",
|
||||
"spam_filter": "Spam filter",
|
||||
"spam_policy": "Add or remove items to white-/blacklist",
|
||||
"spam_policy": "Add or remove items to allow-/denylist",
|
||||
"spam_score": "Set a custom spam score",
|
||||
"subfolder2": "Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>",
|
||||
"syncjob": "Edit sync job",
|
||||
@@ -809,6 +840,10 @@
|
||||
"forgot_password": "> Forgot Password?",
|
||||
"invalid_pass_reset_token": "The reset password token is invalid or has expired.<br>Please request a new password reset link.",
|
||||
"login": "Login",
|
||||
"login_linkstext": "Not the correct login?",
|
||||
"login_usertext": "Log in as user",
|
||||
"login_domainadmintext": "Log in as domain admin",
|
||||
"login_admintext": "Log in as admin",
|
||||
"login_user": "User Login",
|
||||
"login_dadmin": "Domain-Administrator Login",
|
||||
"login_admin": "Administrator Login",
|
||||
@@ -819,7 +854,8 @@
|
||||
"password": "Password",
|
||||
"reset_password": "Reset Password",
|
||||
"request_reset_password": "Request password change",
|
||||
"username": "Username"
|
||||
"username": "Username",
|
||||
"email": "Email address"
|
||||
},
|
||||
"mailbox": {
|
||||
"action": "Action",
|
||||
@@ -899,6 +935,7 @@
|
||||
"in_use": "In use (%)",
|
||||
"inactive": "Inactive",
|
||||
"insert_preset": "Insert example preset \"%s\"",
|
||||
"internal": "Internal",
|
||||
"kind": "Kind",
|
||||
"last_mail_login": "Last mail login",
|
||||
"last_modified": "Last modified",
|
||||
@@ -1027,7 +1064,7 @@
|
||||
"notified": "Notified",
|
||||
"qhandler_success": "Request successfully sent to the system. You can now close the window.",
|
||||
"qid": "Rspamd QID",
|
||||
"qinfo": "The quarantine system will save rejected mail to the database (the sender will <em>not</em> be given the impression of a delivered mail) as well as mail, that is delivered as copy into the Junk folder of a mailbox.\r\n <br>\"Learn as spam and delete\" will learn a message as spam via Bayesian theorem and also calculate fuzzy hashes to deny similar messages in the future.\r\n <br>Please be aware that learning multiple messages can be - depending on your system - time consuming.<br>Blacklisted elements are excluded from the quarantine.",
|
||||
"qinfo": "The quarantine system will save rejected mail to the database (the sender will <em>not</em> be given the impression of a delivered mail) as well as mail, that is delivered as copy into the Junk folder of a mailbox.\r\n <br>\"Learn as spam and delete\" will learn a message as spam via Bayesian theorem and also calculate fuzzy hashes to deny similar messages in the future.\r\n <br>Please be aware that learning multiple messages can be - depending on your system - time consuming.<br>Denylisted elements are excluded from the quarantine.",
|
||||
"qitem": "Quarantine item",
|
||||
"quarantine": "Quarantine",
|
||||
"quick_actions": "Actions",
|
||||
@@ -1105,6 +1142,7 @@
|
||||
"bcc_edited": "BCC map entry %s edited",
|
||||
"bcc_saved": "BCC map entry saved",
|
||||
"cors_headers_edited": "CORS settings have been saved",
|
||||
"custom_login_modified": "Login customisation was saved successfully",
|
||||
"db_init_complete": "Database initialization completed",
|
||||
"delete_filter": "Deleted filters ID %s",
|
||||
"delete_filters": "Deleted filters: %s",
|
||||
@@ -1253,7 +1291,9 @@
|
||||
"encryption": "Encryption",
|
||||
"excludes": "Excludes",
|
||||
"expire_in": "Expire in",
|
||||
"expire_never": "Never Expire",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"forever": "Forever",
|
||||
"force_pw_update": "You <b>must</b> set a new password to be able to access groupware related services.",
|
||||
"from": "from",
|
||||
"generate": "generate",
|
||||
@@ -1320,12 +1360,13 @@
|
||||
"sogo_profile_reset": "Reset SOGo profile",
|
||||
"sogo_profile_reset_help": "This will destroy a user's SOGo profile and <b>delete all contact and calendar data irretrievable</b>.",
|
||||
"sogo_profile_reset_now": "Reset profile now",
|
||||
"spam_aliases": "Temporary email aliases",
|
||||
"spam_aliases": "Spam email aliases",
|
||||
"spam_aliases_info": "A spam alias is a temporary email address that can be used to protect real email addresses. <br>Optionally, an expiration time can be set so that the alias is automatically deactivated after the defined period, effectively disposing of abused or leaked addresses.",
|
||||
"spam_score_reset": "Reset to server default",
|
||||
"spamfilter": "Spam filter",
|
||||
"spamfilter_behavior": "Rating",
|
||||
"spamfilter_bl": "Blacklist",
|
||||
"spamfilter_bl_desc": "Blacklisted email addresses to <b>always</b> classify as spam and reject. Rejected mail will <b>not</b> be copied to quarantine. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
|
||||
"spamfilter_bl": "Denylist",
|
||||
"spamfilter_bl_desc": "Denylisted email addresses to <b>always</b> classify as spam and reject. Rejected mail will <b>not</b> be copied to quarantine. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
|
||||
"spamfilter_default_score": "Default values",
|
||||
"spamfilter_green": "Green: this message is not spam",
|
||||
"spamfilter_hint": "The first value describes the \"low spam score\", the second represents the \"high spam score\".",
|
||||
@@ -1336,8 +1377,8 @@
|
||||
"spamfilter_table_empty": "No data to display",
|
||||
"spamfilter_table_remove": "remove",
|
||||
"spamfilter_table_rule": "Rule",
|
||||
"spamfilter_wl": "Whitelist",
|
||||
"spamfilter_wl_desc": "Whitelisted email addresses are programmed to <b>never</b> classify as spam. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
|
||||
"spamfilter_wl": "Allowlist",
|
||||
"spamfilter_wl_desc": "Allowlisted email addresses are programmed to <b>never</b> classify as spam. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
|
||||
"spamfilter_yellow": "Yellow: this message may be spam, will be tagged as spam and moved to your junk folder",
|
||||
"status": "Status",
|
||||
"sync_jobs": "Sync jobs",
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
"quarantine": "Acciones de cuarentena",
|
||||
"quarantine_attachments": "Archivos ajuntos en cuarentena",
|
||||
"quarantine_notification": "Notificaciones de cuarentena",
|
||||
"ratelimit": "Rate limit",
|
||||
"ratelimit": "Límite de peticiones",
|
||||
"recipient_maps": "Rutas del destinatario",
|
||||
"sogo_profile_reset": "Resetear perfil SOGo",
|
||||
"spam_alias": "Aliases temporales",
|
||||
"spam_policy": "Lista blanca/negra",
|
||||
"spam_policy": "Lista de bloqueo/desbloqueo",
|
||||
"spam_score": "Puntuación de spam",
|
||||
"syncjobs": "Trabajos de sincronización",
|
||||
"tls_policy": "Póliza de TLS",
|
||||
@@ -25,8 +25,10 @@
|
||||
"quarantine_category": "Cambiar categoría de las notificaciones de cuarentena",
|
||||
"domain_relayhost": "Cambiar relayhost por un dominio",
|
||||
"extend_sender_acl": "Permitir extender la ACL del remitente por direcciones externas",
|
||||
"pw_reset": "Permitir el reset de la contraseña del usario mailcow",
|
||||
"sogo_access": "Permitir la gestión del acceso a SOGo"
|
||||
"pw_reset": "Permitir el restablecimiento de la contraseña del usuario mailcow",
|
||||
"sogo_access": "Permitir la gestión del acceso a SOGo",
|
||||
"mailbox_relayhost": "Cambiar el host de reenvío para un buzón",
|
||||
"smtp_ip_access": "Cambiar hosts permitidos para SMTP"
|
||||
},
|
||||
"add": {
|
||||
"activate_filter_warn": "Todos los demás filtros se desactivarán cuando este filtro se active.",
|
||||
@@ -34,7 +36,7 @@
|
||||
"add": "Agregar",
|
||||
"add_domain_only": "Agregar dominio solamente",
|
||||
"add_domain_restart": "Agregar dominio y reiniciar SOGo",
|
||||
"alias_address": "Dirección(es) alias:",
|
||||
"alias_address": "Dirección(es) alias",
|
||||
"alias_address_info": "<small>Dirección(es) de correo completa(s) ó @dominio.com, para atrapar todos los mensajes para un dominio (separado por coma). <b>Dominios que existan en mailcow solamente</b>.</small>",
|
||||
"alias_domain": "Dominio alias",
|
||||
"alias_domain_info": "<small>Nombres de dominio válidos solamente (separado por coma).</small>",
|
||||
@@ -45,13 +47,13 @@
|
||||
"delete1": "Eliminar de la fuente cuando se complete",
|
||||
"delete2": "Eliminar mensajes en el destino que no están en la fuente",
|
||||
"delete2duplicates": "Eliminar duplicados en el destino",
|
||||
"description": "Descripción:",
|
||||
"description": "Descripción",
|
||||
"destination": "Destino",
|
||||
"domain": "Dominio",
|
||||
"domain_quota_m": "Cuota total del dominio (MiB):",
|
||||
"domain_quota_m": "Cuota total del dominio (MiB)",
|
||||
"enc_method": "Método de cifrado",
|
||||
"exclude": "Excluir objectos (regex)",
|
||||
"full_name": "Nombre completo:",
|
||||
"full_name": "Nombre completo",
|
||||
"gal": "Lista global de direcciones (GAL)",
|
||||
"gal_info": "El GAL contiene todos los objetos de un dominio y no puede ser editado por ningún usuario. Falta información de disponibilidad en SOGo, si está desactivada. <b>Reinicia SOGo para aplicar los cambios.</b>",
|
||||
"generate": "Generar",
|
||||
@@ -61,20 +63,20 @@
|
||||
"hostname": "Host",
|
||||
"kind": "Tipo",
|
||||
"mailbox_quota_def": "Cuota de buzón predeterminada",
|
||||
"mailbox_quota_m": "Máx. cuota por buzón (MiB):",
|
||||
"mailbox_username": "Nombre de usuario (parte izquierda de una dirección de correo):",
|
||||
"max_aliases": "Máx. alias posibles:",
|
||||
"max_mailboxes": "Máx. buzones posibles:",
|
||||
"mailbox_quota_m": "Máx. cuota por buzón (MiB)",
|
||||
"mailbox_username": "Nombre de usuario (parte izquierda de una dirección de correo)",
|
||||
"max_aliases": "Máx. alias posibles",
|
||||
"max_mailboxes": "Máx. buzones posibles",
|
||||
"mins_interval": "Intervalo de sondeo (minutos)",
|
||||
"multiple_bookings": "Múltiples reservas",
|
||||
"nexthop": "Siguiente destino",
|
||||
"password": "Constraseña:",
|
||||
"password_repeat": "Confirmación de contraseña (repetir):",
|
||||
"password": "Contraseña",
|
||||
"password_repeat": "Confirmación de contraseña (repetir)",
|
||||
"port": "Puerto",
|
||||
"post_domain_add": "<b>Nota:</b> Necesitarás reiniciar el contenedor del servicio SOGo despues de agregar un nuevo dominio",
|
||||
"quota_mb": "Cuota (MiB):",
|
||||
"post_domain_add": "Es necesario reiniciar el contenedor del servicio SOGo, \"sogo-mailcow\", tras agregar un nuevo dominio.<br><br>Además, la configuración DNS de los dominios debería ser comprobada. En cuanto la configuración DNS se apruebe, reinicie \"acme-mailcow\" para generar automáticamente certificados para su nuevo dominio (autoconfig.<dominio>, autodiscover.<dominio>).<br>Este paso es opcional y se reintentará cada 24 horas.",
|
||||
"quota_mb": "Cuota (MiB)",
|
||||
"relay_all": "Retransmitir todos los destinatarios",
|
||||
"relay_all_info": "<small>Si eliges <b>no</b> retransmitir a todos los destinatarios, necesitas agregar un buzón \"ciego\" por cada destinatario que debe ser retransmitido.</small>",
|
||||
"relay_all_info": "↪ Si se elige <b>no</b> retransmitir todos los destinatarios, será necesario agregar un buzón \"ciego\" por cada destinatario que deba ser retransmitido.",
|
||||
"relay_domain": "Retransmitir este dominio",
|
||||
"select": "Por favor selecciona...",
|
||||
"select_domain": "Por favor elige un dominio primero",
|
||||
@@ -83,7 +85,7 @@
|
||||
"skipcrossduplicates": "Omitir mensajes duplicados en carpetas (orden de llegada)",
|
||||
"subscribeall": "Suscribirse a todas las carpetas",
|
||||
"syncjob": "Añadir trabajo de sincronización",
|
||||
"syncjob_hint": "Ten en cuenta que las contraseñas deben guardarse en texto sin cifrado",
|
||||
"syncjob_hint": "Tenga en cuenta que las contraseñas deben guardarse en texto plano sin cifrar",
|
||||
"target_address": "Direcciones destino:",
|
||||
"target_address_info": "<small>Dirección(es) de correo completa(s) (separado por coma).</small>",
|
||||
"target_domain": "Dominio destino:",
|
||||
@@ -100,7 +102,13 @@
|
||||
"comment_info": "Los comentarios privados no son visibles al usuario, mientras que los comentarios públicos aparecerán sobre la información general del usuario",
|
||||
"dry": "Simular la sincronización",
|
||||
"private_comment": "Comentario privado",
|
||||
"app_passwd_protocols": "Protocolos autorizados para la contraseña de la aplicación"
|
||||
"app_passwd_protocols": "Protocolos autorizados para la contraseña de la aplicación",
|
||||
"relay_transport_info": "<div class=\"label label-info\">Información</div> Puede definir mapas de transporte para un destino personalizado para este dominio. En caso de no definirlo, se realizará una búsqueda MX.",
|
||||
"bcc_dest_format": "El destino del CCO debe ser una única dirección de correo electrónico válida.<br>Si necesita enviar una copia a varias direcciones, cree un alias y utilícelo aquí.",
|
||||
"domain_matches_hostname": "El dominio %s coincide con el nombre de host",
|
||||
"relay_unknown_only": "Reenviar sólo los buzones no existentes. Los buzones existentes se entregarán localmente.",
|
||||
"relayhost_wrapped_tls_info": "Por favor, no utilice puertos con TLS (habitualmente, el puerto 465). <br>Utilice cualquier puerto no cifrado y emita STARTTLS. Se puede crear una política para imponer TLS en \"TLS policy maps\".",
|
||||
"tags": "Etiquetas"
|
||||
},
|
||||
"admin": {
|
||||
"access": "Acceso",
|
||||
@@ -129,7 +137,7 @@
|
||||
"app_name": "Nombre de la app",
|
||||
"apps_name": "Nombre \"mailcow Apps\"",
|
||||
"arrival_time": "Tiempo de llegada (hora del servidor)",
|
||||
"ban_list_info": "Lista de IPs bloqueadas: <b>red (tiempo de prohibición restante) - [acciones]</b>.<br />Las IPs en cola para ser desbloqueadas se eliminarán de la lista de bloqueos en unos pocos segundos.<br />Las etiquetas rojas indican bloqueos permanentes mediante la inclusión en la lista negra.",
|
||||
"ban_list_info": "Lista de direcciones IP bloqueadas: <b>red (tiempo de prohibición restante) - [acciones]</b>.<br />Las direcciones IP en cola para ser desbloqueadas se eliminarán de la lista de bloqueos en unos segundos.<br />Las etiquetas rojas indican bloqueos permanentes por inclusión en la lista de bloqueo.",
|
||||
"change_logo": "Cambiar logo",
|
||||
"configuration": "Configuración",
|
||||
"credentials_transport_warning": "<b>Advertencia</b>: al agregar una nueva entrada de ruta de transporte se actualizarán las credenciales para todas las entradas con una columna de \"siguiente destino\" coincidente.",
|
||||
@@ -157,15 +165,15 @@
|
||||
"excludes": "Excluye a estos destinatarios",
|
||||
"f2b_ban_time": "Tiempo de restricción (s)",
|
||||
"f2b_ban_time_increment": "Tiempo de restricción se incrementa con cada restricción",
|
||||
"f2b_blacklist": "Redes y hosts en lista negra",
|
||||
"f2b_list_info": "Un host o red en lista negra siempre superará a una entidad de la lista blanca. <b>Las actualizaciones de la lista tardarán unos segundos en aplicarse.</b>",
|
||||
"f2b_blacklist": "Redes y hosts en lista de bloqueo",
|
||||
"f2b_list_info": "Un host o red en lista de bloqueo siempre tendrá prioridad sobre una entidad de la lista de desbloqueo. <b>Las actualizaciones de la lista tardarán unos segundos en aplicarse.</b>",
|
||||
"f2b_max_attempts": "Max num. de intentos",
|
||||
"f2b_max_ban_time": "Max tiempo de restricción (s)",
|
||||
"f2b_netban_ipv4": "Tamaño de subred IPv4 para aplicar la restricción (8-32)",
|
||||
"f2b_netban_ipv6": "Tamaño de subred IPv6 para aplicar la restricción (8-128)",
|
||||
"f2b_parameters": "Parametros Fail2ban",
|
||||
"f2b_retry_window": "Ventana de tiempo entre reintentos",
|
||||
"f2b_whitelist": "Redes y hosts en lista blanca",
|
||||
"f2b_whitelist": "Redes y hosts en lista de desbloqueo",
|
||||
"filter_table": "Filtrar tabla",
|
||||
"forwarding_hosts": "Hosts de reenvío",
|
||||
"forwarding_hosts_add_hint": "Se puede especificar direcciones IPv4 / IPv6, redes en notación CIDR, nombres de host (que se resolverán en direcciones IP) o dominios (que se resolverán en direcciones IP consultando registros SPF o, en su defecto, registros MX)",
|
||||
@@ -253,7 +261,146 @@
|
||||
"unban_pending": "Desbloqueo pendiente",
|
||||
"unchanged_if_empty": "Si no hay cambios déjalo en blanco",
|
||||
"upload": "Cargar",
|
||||
"username": "Nombre de usuario"
|
||||
"username": "Nombre de usuario",
|
||||
"force_sso_text": "Si se configura un proveedor OIDC externo, esta opción oculta los formularios por defecto de inicio de sesión y muestra solamente el botón de inicio de sesión único",
|
||||
"admin_quicklink": "Ocultar enlace rápido a página de inicio de sesión para administradores",
|
||||
"iam_default_template_description": "Si no se asigna una plantilla a un usuario, se utilizará la plantilla por defecto para crear el buzón, pero no para actualizarlo.",
|
||||
"reset_password_vars": "<code>{{link}}</code> El enlace generado para el restablecimiento de contraseña<br><code>{{username}}</code> El buzón del usuario que ha solicitado el restablecimiento de contraseña<br><code>{{username2}}</code> La dirección del buzón de recuperación de contraseña<br><code>{{date}}</code> La fecha en que se realizó la solicitud de restablecimiento de contraseña<br><code>{{token_lifetime}}</code> El periodo de vigencia del token en minutos<br><code>{{hostname}}</code> El servidor Mailcow",
|
||||
"api_info": "La API es un trabajo en curso. La documentación se puede encontrar en <a href=\"/api\">/api</a>",
|
||||
"iam_description": "Configurar un proveedor de autenticación externo<br>Los buzones de usuario se crearán automáticamente la primera vez que se inicie sesión, siempre que se hayan configurado las equivalencias de atributos",
|
||||
"ui_header_announcement_help": "El anuncio será visible para todos los usuarios conectados y también en la pantalla de inicio de sesión.",
|
||||
"html": "HTML",
|
||||
"oauth2_redirect_uri": "URI de redirección",
|
||||
"quarantine_bcc": "Remitir una copia de todas las notificaciones (CCO) a este destinatario: <br><small>Dejar en blanco para desactivar. <b>Correo sin firmar y sin comprobar. Debe entregarse solo internamente.</b></small>",
|
||||
"quarantine_redirect": "<b>Redirigir todas las notificaciones</b> a este destinatario:<br><small>Dejar en blanco para desactivar. <b>Correo sin firmar y sin comprobar. Debe entregarse sólo internamente.</b></small>",
|
||||
"iam_authorize_url": "Endpoint de autorización",
|
||||
"sal_level": "Nivel de Moo",
|
||||
"ui_footer": "Pie de página (se permite HTML)",
|
||||
"is_mx_based": "Basado en MX",
|
||||
"password_reset_tmpl_text": "Plantilla de texto",
|
||||
"password_length": "Longitud de la contraseña",
|
||||
"quicklink_text": "Mostrar u ocultar enlaces rápidos a otras páginas de inicio bajo el formulario de inicio de sesión",
|
||||
"password_policy_lowerupper": "Debe contener caracteres en minúsculas y mayúsculas",
|
||||
"rspamd_global_filters_regex": "Sus nombres indican su propósito. Todo el contenido debe constar de expresiones regulares válidas con el formato \"/patrón/opciones\" (por ejemplo, <code>/.+@domain\\.tld/i</code>).<br>\n Si bien se llevan a cabo comprobaciones básicas de cada expresión regular, la funcionalidad de Rspamd puede verse inutilizada, si no consigue interpretar correctamente la sintaxis utilizada.<br>\n Rspamd intentará leer el contenido del mapa cuando éste se modifique. En caso de de problemas, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">reinicie Rspamd</a> para forzar una recarga del mapa.<br>Los elementos incluidos en listas de bloqueo se excluyen de la cuarentena.",
|
||||
"iam_use_ssl_info": "Si se habilita SSL y el puerto se establece en el 389, se cambiará automáticamente al 636.",
|
||||
"iam_login_provisioning": "Crear usuarios automáticamente al iniciar sesión",
|
||||
"iam_periodic_full_sync": "Sincronización completa periódica",
|
||||
"iam_port": "Puerto",
|
||||
"iam_realm": "Ámbito",
|
||||
"iam_redirect_url": "URL de redirección",
|
||||
"iam_server_url": "URL del servidor",
|
||||
"iam_sso": "Inicio de sesión único (SSO)",
|
||||
"iam_sync_interval": "Intervalo de sincronización/importación (minutos)",
|
||||
"iam_test_connection": "Comprobar conexión",
|
||||
"iam_token_url": "Endpoint del token",
|
||||
"iam_username_field": "Campo de nombre de usuario",
|
||||
"iam_use_ssl": "Utilizar SSL",
|
||||
"iam_use_tls": "Utilizar STARTTLS",
|
||||
"iam_userinfo_url": "Endopint de información de usuario",
|
||||
"iam_use_tls_info": "Si se habilita TLS, se debe utilizar el puerto por defecto del servidor LDAP (389). No se permiten puertos SSL.",
|
||||
"iam_version": "Versión",
|
||||
"ignore_ssl_error": "Ignorar errores de SSL",
|
||||
"ip_check": "Comprobación IP",
|
||||
"ip_check_disabled": "La comprobación de IP está desactivada. Puede activarla en<br> <strong>Sistema > Configuración > Opciones > Personalizar</strong>.",
|
||||
"ip_check_opt_in": "Aceptar utilizar el servicio de terceros <strong>ipv4.mailcow.email</strong> y <strong>ipv6.mailcow.email</strong> para resolver direcciones IP externas.",
|
||||
"last_applied": "Aplicado por última vez",
|
||||
"license_info": "No es obligatorio contar con una licencia, pero ayuda a continuar el desarrollo.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Indique aquí su GUID</a> o <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">adquiera servicios de soporte para su instalación de Mailcow.</a>",
|
||||
"login_time": "Hora de inicio de sesión",
|
||||
"lookup_mx": "Destino es una expresión regular con la que contrastar el nombre MX (<code>.*\\.google\\.com</code> para dirigir todo el tráfico dirigido a un MX que termina en google.com a través de este salto)",
|
||||
"message": "Mensaje",
|
||||
"no": "✕",
|
||||
"optional": "opcional",
|
||||
"app_hide": "Ocultar para inicio de sesión",
|
||||
"convert_html_to_text": "Convertir HTML a texto plano",
|
||||
"cors_settings": "Configuración de CORS",
|
||||
"customer_id": "ID de cliente",
|
||||
"dkim_overwrite_key": "Sobrescribir la clave DKIM existente",
|
||||
"domain_admin": "Administrador de dominio",
|
||||
"f2b_manage_external": "Gestionar Fail2Ban de manera externa",
|
||||
"f2b_manage_external_info": "Fail2Ban conservará la lista de bloqueo, pero no establecerá activamente reglas para bloquear el tráfico. Utilizar la lista de bloqueo siguiente para bloquear externamente el tráfico.",
|
||||
"filter": "Filtrar",
|
||||
"admins": "Administradores",
|
||||
"admins_ldap": "Administradores de LDAP",
|
||||
"advanced_settings": "Configuración avanzada",
|
||||
"allowed_methods": "Access-Control-Allow-Methods",
|
||||
"allowed_origins": "Access-Control-Allow-Origin",
|
||||
"api_read_only": "Acceso de sólo lectura",
|
||||
"api_read_write": "Acceso de lectura y escritura",
|
||||
"api_skip_ip_check": "Omitir la comprobación de la IP para la API",
|
||||
"authed_user": "Usuario autentificado",
|
||||
"ays": "¿Está seguro de querer continuar?",
|
||||
"logo_normal_label": "Normal",
|
||||
"logo_dark_label": "Invertido para modo oscuro",
|
||||
"copy_to_clipboard": "¡Texto copiado al portapapeles!",
|
||||
"login_page": "Inicio de sesión",
|
||||
"domainadmin_quicklink": "Ocultar enlace rápido a página de inicio de sesión para administradores de dominios",
|
||||
"domain_s": "Dominio(s)",
|
||||
"f2b_filter": "Filtros regex",
|
||||
"f2b_regex_info": "Registros tomados en consideración: SOGo, Postfix, Dovecot, PHP-FPM.",
|
||||
"force_sso": "Deshabilitar el inicio de sesión de Mailcow y mostrar solamente el inicio de sesión único",
|
||||
"guid": "GUID - ID de instancia único",
|
||||
"guid_and_license": "GUID y licencia",
|
||||
"hash_remove_info": "Al eliminar un hash de límite de velocidad (si todavía existe) se reiniciará su contador por completo.<br> Cada hash se indica con un color individual.",
|
||||
"iam": "Proveedor de identidad",
|
||||
"iam_attribute_field": "Campo de atributo",
|
||||
"iam_auth_flow": "Flujo de autenticación",
|
||||
"iam_basedn": "DN de base",
|
||||
"iam_client_id": "ID de cliente",
|
||||
"iam_client_secret": "Secreto de cliente",
|
||||
"iam_client_scopes": "Ámbitos de cliente",
|
||||
"iam_default_template": "Plantilla por defecto",
|
||||
"iam_host": "Host",
|
||||
"iam_host_info": "Introduzca uno o más hosts de LDAP, separados por comas.",
|
||||
"iam_import_users": "Importar usuarios",
|
||||
"iam_mapping": "Equivalencias de atributos",
|
||||
"needs_restart": "necesita reinicio",
|
||||
"oauth2_apps": "Aplicaciones OAuth2",
|
||||
"oauth2_add_client": "Añadir cliente OAuth2",
|
||||
"oauth2_renew_secret": "Generar nuevo secreto de cliente",
|
||||
"oauth2_revoke_tokens": "Revocar todos los tokens de cliente",
|
||||
"options": "Opciones",
|
||||
"password_policy": "Política de contraseñas",
|
||||
"password_policy_chars": "Debe contener al menos un caracter alfabético",
|
||||
"password_policy_length": "La longitud mínima de la contraseña es %d",
|
||||
"password_policy_numbers": "Debe contener al menos un número",
|
||||
"password_policy_special_chars": "Debe contener caracteres especiales",
|
||||
"password_reset_info": "Si no se indica una dirección para la recuperación de contraseñas, esta función no puede utilizarse.",
|
||||
"password_reset_settings": "Configuración de recuperación de contraseña",
|
||||
"password_reset_tmpl_html": "Plantilla HTML",
|
||||
"password_settings": "Configuración de contraseña",
|
||||
"priority": "Prioridad",
|
||||
"quarantine_max_score": "Descartar notificación si la puntuación de spam de un mensaje de correo es mayor que este valor:<br><small>Por defecto 9999.0</small>",
|
||||
"queue_unban": "desbloquear",
|
||||
"regex_maps": "Mapas regex",
|
||||
"relay_rcpt": "Dirección \"Para:\"",
|
||||
"reset_limit": "Eliminar hash",
|
||||
"restore_template": "Dejar en blanco para restablecer la plantilla por defecto",
|
||||
"rsetting_no_selection": "Seleccione una regla",
|
||||
"rsettings_preset_3": "Permitir solamente remitentes específicos para un buzón (utilizar únicamente como buzón interno)",
|
||||
"rsettings_preset_4": "Deshabilitar Rspamd para un dominio",
|
||||
"rspamd_global_filters": "Mapas de filtrado globales",
|
||||
"rspamd_global_filters_agree": "¡Tendré cuidado!",
|
||||
"rspamd_global_filters_info": "Los mapas de filtrado globales contienen distintos tipos de listos de bloqueo y desbloqueo.",
|
||||
"service": "Servicio",
|
||||
"service_id": "ID de servicio",
|
||||
"success": "Éxito",
|
||||
"task": "Tarea",
|
||||
"time": "Tiempo",
|
||||
"title": "Título",
|
||||
"transport_dest_format": "Regex o sintaxis: ejemplo.org, .ejemplo.org, *, buzon@ejemplo.org (se pueden introducir varios valores separados por comas)",
|
||||
"transport_test_rcpt_info": "• Utilizar null@hosted.mailcow.de para comprobar la retransmisión a un destino externo.",
|
||||
"ui_header_announcement": "Anuncios",
|
||||
"ui_header_announcement_content": "Texto (se permite HTML)",
|
||||
"ui_header_announcement_select": "Seleccionar tipo de anuncio",
|
||||
"ui_header_announcement_type": "Tipo",
|
||||
"ui_header_announcement_type_danger": "Muy importante",
|
||||
"ui_header_announcement_type_info": "Información",
|
||||
"ui_header_announcement_type_warning": "Importante",
|
||||
"user_link": "Enlace de usuario",
|
||||
"user_quicklink": "Ocultar enlace rápido a página de inicio de sesión de usuario",
|
||||
"validate_license_now": "Validar el GUID contra el servidor de licencias",
|
||||
"verify": "Verificar",
|
||||
"yes": "✓"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Acceso denegado o datos del formulario inválidos",
|
||||
@@ -341,7 +488,60 @@
|
||||
"username_invalid": "Nombre de usuario no se puede utilizar",
|
||||
"validity_missing": "Por favor asigna un periodo de validez",
|
||||
"value_missing": "Por favor proporcione todos los valores",
|
||||
"yotp_verification_failed": "Verificación Yubico OTP fallida: %s"
|
||||
"yotp_verification_failed": "Verificación Yubico OTP fallida: %s",
|
||||
"last_key": "La última clave no se puede eliminar, en su lugar desactive la autenticación de doble factor.",
|
||||
"img_dimensions_exceeded": "La imagen excede el tamaño máximo permitido",
|
||||
"authsource_in_use": "El proveedor de identidades no se puede cambiar al estar en uso por uno o más usuario(s).",
|
||||
"app_name_empty": "El nombre de la aplicación no puede quedar vacío",
|
||||
"recovery_email_failed": "No se ha podido enviar un correo de recuperación. Contacte con su administrador.",
|
||||
"tls_policy_map_dest_invalid": "Destino de política no válido",
|
||||
"cors_invalid_method": "Allow-Method especificado no válido",
|
||||
"dkim_domain_or_sel_exists": "Ya existe una clave DKIM para \"%s\" y no será sobrescrita",
|
||||
"webauthn_publickey_failed": "No se ha almacenado ninguna clave pública para el autenticador seleccionado",
|
||||
"invalid_reset_token": "Token de restablecimiento no válido",
|
||||
"password_reset_na": "El restablecimiento de contraseña no está disponible en estos momentos. Contacte con su administrador.",
|
||||
"generic_server_error": "Se ha producido un error inesperado en el servidor. Contacte con su administrador.",
|
||||
"reset_f2b_regex": "El filtro Regex no se ha podido restablecer a tiempo, inténtelo de nuevo o espere unos segundos y vuelva a cargar la página web.",
|
||||
"extra_acl_invalid": "Dirección de remitente externo \"%s\" no válida",
|
||||
"extra_acl_invalid_domain": "El remitente externo \"%s\" utiliza un dominio no válido",
|
||||
"max_alias_exceeded": "Se ha excedido el número máximo de alias",
|
||||
"app_passwd_id_invalid": "Contraseña de aplicación con ID %s no válida",
|
||||
"comment_too_long": "Comentario demasiado largo, máximo de 160 caracteres permitidos",
|
||||
"cors_invalid_origin": "Allow-Origin especificado no válido",
|
||||
"demo_mode_enabled": "Modo demo activado",
|
||||
"description_invalid": "Descripción de recurso para %s no válida",
|
||||
"extended_sender_acl_denied": "no se encuentra ACL para establecer direcciones de remitente externo",
|
||||
"fido2_verification_failed": "Verificación FIDO2 fallida: %s",
|
||||
"file_open_error": "El archivo no se puede abrir para escritura",
|
||||
"global_filter_write_error": "No se ha podido escribir el archivo de filtro: %s",
|
||||
"global_map_invalid": "Mapa global con ID %s no válido",
|
||||
"global_map_write_error": "No se ha podido guardar el mapa global con ID %s: %s",
|
||||
"ham_learn_error": "Error de aprendizaje de correo deseado: %s",
|
||||
"iam_test_connection": "Conexión fallida",
|
||||
"imagick_exception": "Error: Excepción en Imagick al leer la imagen",
|
||||
"img_invalid": "No se ha podido validar el archivo de imagen",
|
||||
"img_size_exceeded": "La imagen excede el tamaño máximo de archivo",
|
||||
"img_tmp_missing": "No se ha podido validar el archivo de imagen: archivo temporal no encontrado",
|
||||
"invalid_filter_type": "Tipo de filtro no válido",
|
||||
"invalid_mime_type": "Tipo MIME no válido",
|
||||
"maxquota_empty": "La cuota máxima por buzón no debe ser cero.",
|
||||
"nginx_reload_failed": "Recarga de Nginx fallida: %s",
|
||||
"password_reset_invalid_user": "Buzón no encontrado o dirección de correo para recuperación no establecida",
|
||||
"pushover_credentials_missing": "Falta el token y/o la clave de Pushover",
|
||||
"pushover_key": "La clave de Pushover tiene un formato incorrecto",
|
||||
"pushover_token": "El token de Pushover tiene un formato incorrecto",
|
||||
"required_data_missing": "Datos necesarios %s no proporcionados",
|
||||
"reset_token_limit_exceeded": "Se ha superado el límite de tokens de restablecimiento. Inténtelo más tarde.",
|
||||
"resource_invalid": "Nombre de recurso %s no válido",
|
||||
"targetd_relay_domain": "El dominio de destino %s es un dominio de retransmisión",
|
||||
"template_exists": "La plantilla %s ya existe",
|
||||
"template_id_invalid": "Plantilla con ID %s no válida",
|
||||
"template_name_invalid": "Nombre de plantilla no válido",
|
||||
"temp_error": "Error transitorio",
|
||||
"tfa_token_invalid": "Token de autenticación de doble factor no válido",
|
||||
"to_invalid": "El destinatario no puede quedar en blanco",
|
||||
"webauthn_authenticator_failed": "No se ha localizado el autenticador seleccionado",
|
||||
"webauthn_username_failed": "El autenticador seleccionado pertenece a otra cuenta"
|
||||
},
|
||||
"debug": {
|
||||
"containers_info": "Información de los contenedores",
|
||||
@@ -357,7 +557,29 @@
|
||||
"started_at": "Iniciado el",
|
||||
"uptime": "Uptime",
|
||||
"static_logs": "Logs estáticos",
|
||||
"system_containers": "Sistema y Contenedores"
|
||||
"system_containers": "Sistema y Contenedores",
|
||||
"show_ip": "Mostrar IP pública",
|
||||
"wip": "Actualmente incompleto",
|
||||
"current_time": "Hora del sistema",
|
||||
"service": "Servicio",
|
||||
"timezone": "Huso horario",
|
||||
"update_available": "Hay una actualización disponible",
|
||||
"update_failed": "No se han podido comprobar las actualizaciones",
|
||||
"architecture": "Arquitectura",
|
||||
"chart_this_server": "Gráfico (este servidor)",
|
||||
"container_running": "En ejecución",
|
||||
"container_disabled": "Contenedor detenido o desactivado",
|
||||
"container_stopped": "Detenido",
|
||||
"cores": "Núcleos",
|
||||
"error_show_ip": "No se han podido resolver las direcciones IP públicas",
|
||||
"history_all_servers": "Historial (todos los servidores)",
|
||||
"login_time": "Tiempo",
|
||||
"memory": "Memoria",
|
||||
"online_users": "Usuarios conectados",
|
||||
"started_on": "Iniciado",
|
||||
"success": "Éxito",
|
||||
"no_update_available": "El sistema está actualizado",
|
||||
"username": "Nombre de usuario"
|
||||
},
|
||||
"diagnostics": {
|
||||
"cname_from_a": "Valor derivado del registro A / AAAA. Esto es permitido siempre que el registro apunte al recurso correcto.",
|
||||
@@ -367,7 +589,8 @@
|
||||
"dns_records_name": "Nombre",
|
||||
"dns_records_status": "Información actual",
|
||||
"dns_records_type": "Tipo",
|
||||
"optional": "Este récord es opcional."
|
||||
"optional": "Este récord es opcional.",
|
||||
"dns_records_docs": "Consulte también <a target=\"_blank\" href=\"https://docs.mailcow.email/getstarted/prerequisite-dns\">la documentación</a>."
|
||||
},
|
||||
"edit": {
|
||||
"active": "Activo",
|
||||
@@ -435,13 +658,79 @@
|
||||
"title": "Editar objeto",
|
||||
"unchanged_if_empty": "Si no hay cambios dejalo en blanco",
|
||||
"username": "Nombre de usuario",
|
||||
"validate_save": "Validar y guardar"
|
||||
"validate_save": "Validar y guardar",
|
||||
"app_passwd_protocols": "Protocolos permitidos con contraseña de aplicación",
|
||||
"domain_footer_info": "Los pies de página de dominio se añaden a todos los mensajes salientes remitidos por una dirección de dicho dominio.<br> Están disponibles las siguientes variables para el pie de página:",
|
||||
"sender_acl_info": "Si el usuario del buzón A tiene permitido enviar como el buzón B, la dirección de remitente no se mostrará automáticamente como seleccionable en el campo \"De\" en SOGo.<br>\n El usuario del buzón B necesitará crear una delegación en SOGo para permitir al usuario A seleccionar su dirección como remitente. Para delegar un buzón en SOGo, utilice el menú (tres puntos) a la derecha del nombre del buzón en la esquina superior izquierda, en la vista de correo. Este comportamiento no se aplica a direcciones alias.",
|
||||
"sogo_access_info": "Tras iniciar sesión, el usuario será redirigido automáticamente a SOGo.",
|
||||
"comment_info": "Un comentario privado no es visible para el usuario, mientras que un comentario público se muestra como descripción emergente al pasar el ratón en la vista general del usuario",
|
||||
"quota_warning_bcc_info": "Los avisos se enviarán como copias separadas a los siguientes destinatarios. Se indicará en el asunto el usuario afectado entre paréntesis, como por ejemplo: <code>Aviso de cuota (usuario@ejemplo.com)</code>.",
|
||||
"sogo_access": "Redirección directa a SOGo",
|
||||
"sogo_visible_info": "Esta opción solamente afecta a objetos que puedan ser visualizados en SOGo (alias compartidos o no compartidos que apunten al menos a un buzón interno). Si se oculta, el alias no aparecerá como seleccionable en SOGo.",
|
||||
"extended_sender_acl_info": "Se aconseja importar una clave de dominio DKIM, si está disponible.<br>\n Recuerde añadir este servidor al registro SPF correspondiente.<br>\n Siempre que se añada un dominio o alias a este servidor, que se superponga con una dirección externa, se eliminará la dirección externa.<br>\n Utilice @dominio.tld para permitir enviar como *@dominio.tld.",
|
||||
"pushover_info": "La configuración de notificaciones push se aplicará a todos los mensajes limpios (no spam) entregados a <b>%s</b> incluyendo alias (compartidos, no compartidos, etiquetados).",
|
||||
"mbox_rl_info": "Este límite de peticiones se aplica al nombre de inicio de sesión SASL, coincide con cualquier dirección \"de\" que utilice el usuario conectado. Un límite de buzón tiene precedencia sobre un límite del dominio.",
|
||||
"admin": "Editar administrador",
|
||||
"none_inherit": "Ninguno / heredar",
|
||||
"sender_acl_disabled": "<span class=\"badge fs-6 bg-danger\">Comprobación de remitente desactivada</span>",
|
||||
"footer_exclude": "Excluir del pie de página",
|
||||
"acl": "ACL (permisos)",
|
||||
"advanced_settings": "Configuración avanzada",
|
||||
"allow_from_smtp": "Permitir únicamente a las siguientes direcciones IP utilizar <b>SMTP</b>",
|
||||
"allow_from_smtp_info": "Dejar en blanco para permitir cualquier remitente.<br>Direcciones y redes IPv4/IPv6.",
|
||||
"allowed_protocols": "Protocolos permitidos para acceso directo del usuario (no afecta a protocolos con contraseña de aplicación)",
|
||||
"app_name": "Nombre de aplicación",
|
||||
"app_passwd": "Contraseña de aplicación",
|
||||
"created_on": "Creado",
|
||||
"custom_attributes": "Atributos personalizados",
|
||||
"delete_ays": "Por favor, confirme el proceso de eliminación.",
|
||||
"disable_login": "Deshabilitar inicio de sesión (se aceptará el correo entrante)",
|
||||
"domain_footer": "Pie de página para todos los usuarios del dominio",
|
||||
"domain_footer_html": "Pie de página HTML",
|
||||
"domain_footer_skip_replies": "Descartar pie de página en correos de respuesta",
|
||||
"extended_sender_acl": "Direcciones de remisión externas",
|
||||
"generate": "generar",
|
||||
"lookup_mx": "El destino es una expresión regular con la que contrastar el nombre MX (<code>.*\\.google\\.com</code> para dirigir todo el correo enviado a un MX que termine en google.com a través de este salto)",
|
||||
"mailbox_relayhost_info": "Aplicable únicamente al buzón y sus alias directos, anula el host de retransmisión para el dominio",
|
||||
"mailbox_rename": "Renombrar buzón",
|
||||
"mailbox_rename_agree": "He creado una copia de seguridad.",
|
||||
"mailbox_rename_warning": "¡IMPORTANTE! Realice una copia de seguridad antes de renombrar el buzón.",
|
||||
"mailbox_rename_alias": "Crear alias automáticamente",
|
||||
"mailbox_rename_title": "Nuevo nombre de buzón local",
|
||||
"password_recovery_email": "Dirección de correo para recuperación de contraseña",
|
||||
"private_comment": "Comentario privado",
|
||||
"public_comment": "Comentario público",
|
||||
"pushover": "Pushover",
|
||||
"pushover_evaluate_x_prio": "Escalar correo de alta prioridad [<code>X-Priority: 1</code>]",
|
||||
"pushover_sender_array": "Tener en cuenta únicamente las siguientes direcciones de correo de remitente <small>(separados por comas)</small>",
|
||||
"pushover_text": "Texto de notificación",
|
||||
"pushover_title": "Título de notificación",
|
||||
"pushover_sound": "Sonido",
|
||||
"pushover_verify": "Verificar credenciales",
|
||||
"quota_warning_bcc": "CCO de aviso de cuota",
|
||||
"ratelimit": "Límite de peticiones",
|
||||
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Información</div> Puede definir mapas de transporte para destinatarios personalizados para este dominio. Si no se establece, se realizará una búsqueda MX.",
|
||||
"relay_unknown_only": "Reenviar solamente los buzones no existentes. Los buzones existentes se entregarán localmente.",
|
||||
"sogo_visible": "Alias visible en SOGo.",
|
||||
"spam_alias": "Crear o modificar alias temporales (con caducidad)",
|
||||
"spam_filter": "Filtro de spam",
|
||||
"spam_policy": "Añadir o eliminar elementos de la lista de bloqueo/desbloqueo",
|
||||
"spam_score": "Establecer una puntuación de spam personalizada"
|
||||
},
|
||||
"footer": {
|
||||
"hibp_nok": "¡Se encontró coincidencia - esta es una contraseña <b>no segura</b>, selecciona otra!",
|
||||
"hibp_ok": "No se encontraron coincidencias",
|
||||
"loading": "Espera por favor...",
|
||||
"restart_now": "Reiniciar ahora"
|
||||
"restart_now": "Reiniciar ahora",
|
||||
"restart_container": "Reiniciar contenedor",
|
||||
"restart_container_info": "<b>Importante:</b> Un reinicio limpio puede llevar un tiempo. Por favor, espere a que finalice.",
|
||||
"cancel": "Cancelar",
|
||||
"confirm_delete": "Confirmar eliminación",
|
||||
"delete_now": "Eliminar ahora",
|
||||
"delete_these_items": "Confirme sus cambios para el siguiente ID de objeto",
|
||||
"hibp_check": "Comprobar en haveibeenpwned.com",
|
||||
"nothing_selected": "Nada seleccionado",
|
||||
"restarting_container": "Reiniciando contenedor, puede llevar un tiempo"
|
||||
},
|
||||
"header": {
|
||||
"administration": "Administración",
|
||||
@@ -450,17 +739,38 @@
|
||||
"mailcow_config": "Configuración",
|
||||
"quarantine": "Cuarentena",
|
||||
"restart_sogo": "Reiniciar SOGo",
|
||||
"user_settings": "Configuraciones de usuario"
|
||||
"user_settings": "Configuraciones de usuario",
|
||||
"mailcow_system": "Sistema",
|
||||
"apps": "Aplicaciones",
|
||||
"restart_netfilter": "Reiniciar netfilter"
|
||||
},
|
||||
"info": {
|
||||
"awaiting_tfa_confirmation": "En espera de confirmación de TFA",
|
||||
"no_action": "No hay acción aplicable"
|
||||
"no_action": "No hay acción aplicable",
|
||||
"session_expires": "Su sesión expirará en unos 15 segundos"
|
||||
},
|
||||
"login": {
|
||||
"delayed": "El inicio de sesión ha sido retrasado %s segundos.",
|
||||
"login": "Inicio de sesión",
|
||||
"password": "Contraseña",
|
||||
"username": "Nombre de usuario"
|
||||
"username": "Nombre de usuario",
|
||||
"login_admin": "Inicio de sesión de administrador",
|
||||
"invalid_pass_reset_token": "El token de restablecimiento de contraseña no es válido o ha caducado.<br>Solicite un nuevo enlace de restablecimiento de contraseña.",
|
||||
"fido2_webauthn": "Inicio de sesión FIDO2/WebAuthn",
|
||||
"forgot_password": "> ¿Contraseña olvidada?",
|
||||
"mobileconfig_info": "Inicie sesión como usuario de buzón para descargar el perfil de conexión solicitado para dispositivos Apple.",
|
||||
"new_password": "Nueva contraseña",
|
||||
"back_to_mailcow": "Volver a mailcow",
|
||||
"login_linkstext": "¿Sesión incorrecta?",
|
||||
"login_usertext": "Iniciar sesión como usuario",
|
||||
"login_domainadmintext": "Iniciar sesión como administrador de dominio",
|
||||
"login_admintext": "Iniciar sesión como administrador",
|
||||
"login_user": "Inicio de sesión de usuario",
|
||||
"login_dadmin": "Inicio de sesión de administrador de dominio",
|
||||
"new_password_confirm": "Confirmar nueva contraseña",
|
||||
"other_logins": "o iniciar sesión con",
|
||||
"reset_password": "Restablecer contraseña",
|
||||
"request_reset_password": "Solicitar cambio de contraseña"
|
||||
},
|
||||
"mailbox": {
|
||||
"action": "Acción",
|
||||
@@ -572,7 +882,67 @@
|
||||
"toggle_all": "Selecionar todo",
|
||||
"username": "Nombre de usuario",
|
||||
"waiting": "Esperando",
|
||||
"weekly": "Cada semana"
|
||||
"weekly": "Cada semana",
|
||||
"sieve_preset_4": "Colocar en bandeja de entrada, omitir procesamiento posterior en filtros de sieve",
|
||||
"goto_spam": "Aprender como <b>correo no deseado</b>",
|
||||
"sieve_preset_header": "Vea los preajustes de ejemplo más abajo. Para más detalles, consulte <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Wikipedia</a>.",
|
||||
"syncjob_EXIT_AUTHENTICATION_FAILURE_USER1": "Nombre de usuario o contraseña incorrectos",
|
||||
"tls_policy_maps_enforced_tls": "Estas políticas tendrán precedencia también para aquellos usuarios de buzones en los que sea obligatoria una conexión TLS. Si no se indica ninguna política a continuación, dichos usuarios aplicarán los valores predeterminados que se especifiquen en <code>smtp_tls_mandatory_protocols</code> y <code>smtp_tls_mandatory_ciphers</code>.",
|
||||
"alias_domain_alias_hint": "Los alias <b>no</b> se aplican a dominios de alias automáticamente. Una dirección alias <code>mi-alias@dominio</code> <b>no cubre</b> la dirección <code>mi-alias@dominio-alias</code> (donde \"dominio-alias\" es un hipotético alias para el dominio \"dominio\").<br>Utilice un filtro sieve para redirigir el correo a un buzón externo (ver la pestaña \"Filtros\" o utilice SOGo -> Desvío). Utilice \"expandir alias a dominio de alias\" para agregar automáticamente los alias que falten.",
|
||||
"domain_templates": "Plantillas de dominio",
|
||||
"sieve_preset_1": "Desechar correo con tipos de archivo probablemente peligrosos",
|
||||
"created_on": "Creado",
|
||||
"disable_login": "No permitir iniciar sesión (se seguirá aceptando el correo entrante)",
|
||||
"mailbox": "Buzón",
|
||||
"mailbox_defaults": "Ajustes por defecto",
|
||||
"sogo_visible_y": "Mostrar alias en SOGo",
|
||||
"spam_aliases": "Alias temporal",
|
||||
"add_template": "Añadir plantilla",
|
||||
"all_domains": "Todos los dominios",
|
||||
"allow_from_smtp": "Permitir únicamente a estas direcciones IP utilizar <b>SMTP</b>",
|
||||
"allow_from_smtp_info": "Dejar vacío para permitir cualquier remitente.<br>Direcciones y redes IPv4/IPv6.",
|
||||
"allowed_protocols": "Protocolos permitidos",
|
||||
"goto_ham": "Aprender como <b>correo deseado</b>",
|
||||
"iam": "Proveedor de identidad",
|
||||
"insert_preset": "Insertar valor predeterminado de ejemplo \"%s\"",
|
||||
"last_mail_login": "Último acceso al correo",
|
||||
"last_pw_change": "Último cambio de contraseña",
|
||||
"mailbox_defaults_info": "Definir configuración por defecto para nuevos buzones.",
|
||||
"mailbox_templates": "Plantillas de buzón",
|
||||
"no": "✕",
|
||||
"open_logs": "Abrir registros",
|
||||
"owner": "Propietario",
|
||||
"private_comment": "Comentario privado",
|
||||
"public_comment": "Comentario público",
|
||||
"q_add_header": "al mover a carpeta de Spam",
|
||||
"q_all": " al mover a carpeta de Spam y al rechazar",
|
||||
"q_reject": "al rechazar",
|
||||
"quarantine_category": "Categoría de notificación de cuarentena",
|
||||
"recipient": "Destinatario",
|
||||
"relay_unknown": "Retransmitir buzones desconocidos",
|
||||
"sender": "Remitente",
|
||||
"sieve_preset_2": "Marcar siempre el correo de un remitente específico como leído",
|
||||
"sieve_preset_3": "Descartar silenciosamente, detener procesado de sieve",
|
||||
"sieve_preset_5": "Respuesta automática (vacaciones)",
|
||||
"sieve_preset_6": "Rechazar correo con respuesta",
|
||||
"sieve_preset_7": "Redireccionar y guardar/descartar",
|
||||
"sieve_preset_8": "Redirigir correo de un remitente específico, marcarlo como leído y clasificarlo en subcarpeta",
|
||||
"sogo_visible": "Alias visible en SOGo",
|
||||
"sogo_visible_n": "Ocultar alias en SOGo",
|
||||
"stats": "Estadísticas",
|
||||
"syncjob_check_log": "Comprobar registros",
|
||||
"syncjob_last_run_result": "Resultado de la última ejecución",
|
||||
"syncjob_EX_OK": "Éxito",
|
||||
"syncjob_EXIT_CONNECTION_FAILURE": "Problema de conexión",
|
||||
"syncjob_EXIT_TLS_FAILURE": "Problema con la conexión cifrada",
|
||||
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problema de autenticación",
|
||||
"syncjob_EXIT_OVERQUOTA": "El buzón de destino ha superado la cuota",
|
||||
"syncjob_EXIT_CONNECTION_FAILURE_HOST1": "No es posible conectar con el servidor remoto",
|
||||
"table_size": "Tamaño de la tabla",
|
||||
"table_size_show_n": "Mostrar %s elementos",
|
||||
"templates": "Plantillas",
|
||||
"template": "Plantilla",
|
||||
"yes": "✓"
|
||||
},
|
||||
"oauth2": {
|
||||
"access_denied": "Inicie sesión como propietario del buzón para otorgar acceso a través de OAuth2.",
|
||||
@@ -614,7 +984,13 @@
|
||||
"subj": "Asunto",
|
||||
"text_from_html_content": "Contenido (html convertido)",
|
||||
"text_plain_content": "Contenido (text/plain)",
|
||||
"toggle_all": "Seleccionar todos"
|
||||
"toggle_all": "Seleccionar todos",
|
||||
"confirm": "Confirmar",
|
||||
"deliver_inbox": "Entregar en bandeja de entrada",
|
||||
"download_eml": "Descargar (.eml)",
|
||||
"info": "Información",
|
||||
"junk_folder": "Carpeta de correo no deseado",
|
||||
"notified": "Notificado"
|
||||
},
|
||||
"queue": {
|
||||
"queue_manager": "Administrador de cola"
|
||||
@@ -708,6 +1084,7 @@
|
||||
"aliases_send_as_all": "No verificar permisos del remitente para los siguientes dominios (y sus aliases)",
|
||||
"change_password": "Cambiar contraseña",
|
||||
"create_syncjob": "Crear nuevo trabajo de sincronización",
|
||||
"created_on": "Creado",
|
||||
"daily": "Cada día",
|
||||
"day": "Día",
|
||||
"description": "Descripción",
|
||||
@@ -719,6 +1096,9 @@
|
||||
"edit": "Editar",
|
||||
"encryption": "Cifrado",
|
||||
"excludes": "Excluye",
|
||||
"expire_in": "Expirará en",
|
||||
"expire_never": "Nunca expirará",
|
||||
"forever": "Siempre",
|
||||
"hour": "Hora",
|
||||
"hourly": "Cada hora",
|
||||
"hours": "Horas",
|
||||
@@ -739,7 +1119,8 @@
|
||||
"shared_aliases": "Alias compartidos",
|
||||
"shared_aliases_desc": "Los alias compartidos no se ven afectados por la configuración específica del usuario, como el filtro de correo no deseado o la política de cifrado. Los filtros de spam correspondientes solo pueden ser realizados por un administrador como una política de dominio.",
|
||||
"sogo_profile_reset": "Resetear perfil SOGo",
|
||||
"spam_aliases": "Alias de email temporales",
|
||||
"spam_aliases": "Alias de email de spam",
|
||||
"spam_aliases_info": "Un alias de spam es una dirección de correo electrónico temporal que se puede usar para proteger direcciones de correo electrónico reales. <br>Opcionalmente, se puede establecer un tiempo de expiración para que el alias se desactive automáticamente después del período definido, eliminando efectivamente las direcciones abusadas o filtradas.",
|
||||
"spamfilter": "Filtro anti-spam",
|
||||
"spamfilter_behavior": "Clasificación",
|
||||
"spamfilter_bl": "Lista negra",
|
||||
@@ -773,11 +1154,55 @@
|
||||
"waiting": "Esperando",
|
||||
"week": "Semana",
|
||||
"weekly": "Cada semana",
|
||||
"weeks": "Semanas"
|
||||
"weeks": "Semanas",
|
||||
"with_app_password": "con contraseña de aplicación",
|
||||
"year": "año",
|
||||
"years": "años"
|
||||
},
|
||||
"warning": {
|
||||
"domain_added_sogo_failed": "Se agregó el dominio pero no se pudo reiniciar SOGo, revisa los logs del servidor.",
|
||||
"fuzzy_learn_error": "Error aprendiendo hash: %s",
|
||||
"ip_invalid": "IP inválida omitida: %s"
|
||||
"ip_invalid": "IP inválida omitida: %s",
|
||||
"cannot_delete_self": "No se puede eliminar el usuario conectado"
|
||||
},
|
||||
"datatables": {
|
||||
"collapse_all": "Contraer todo",
|
||||
"aria": {
|
||||
"sortAscending": ": activar para ordenar ascendentemente según la columna",
|
||||
"sortDescending": ": activar para ordenar descendentemente según la columna"
|
||||
},
|
||||
"infoEmpty": "Mostrando 0 a 0 de 0 apuntes",
|
||||
"paginate": {
|
||||
"last": "Última",
|
||||
"next": "Siguiente",
|
||||
"previous": "Anterior",
|
||||
"first": "Primero"
|
||||
},
|
||||
"processing": "Espere, por favor...",
|
||||
"decimal": ".",
|
||||
"emptyTable": "Sin datos disponibles en la tabla",
|
||||
"expand_all": "Ampliar todo",
|
||||
"info": "Mostrando apuntes _START_ a _END_ de _TOTAL_",
|
||||
"infoFiltered": "(filtrado a partir de _MAX_ entradas totales)",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "Mostrar entradas de _MENU_",
|
||||
"loadingRecords": "Cargando...",
|
||||
"search": "Buscar:",
|
||||
"zeroRecords": "No se han encontrado registros coincidentes"
|
||||
},
|
||||
"fido2": {
|
||||
"set_fido2": "Registrar dispositivo FIDO2",
|
||||
"set_fido2_touchid": "Registrar Touch ID en Apple M1",
|
||||
"set_fn": "Establecer nombre amistoso (fácil de recordar)",
|
||||
"confirm": "Confirmar",
|
||||
"fido2_auth": "Iniciar sesión con FIDO2",
|
||||
"fido2_success": "Dispositivo registrado con éxito",
|
||||
"fido2_validation_failed": "Validación fallida",
|
||||
"fn": "Nombre amistoso (fácil de recordar)",
|
||||
"known_ids": "ID conocidas",
|
||||
"none": "Deshabilitado",
|
||||
"register_status": "Estado de registro",
|
||||
"rename": "Renombrar",
|
||||
"start_fido2_validation": "Iniciar validación FIDO2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"quarantine_notification": "Modifier la notification de quarantaine",
|
||||
"quarantine_category": "Modifier la catégorie de la notification de quarantaine",
|
||||
"ratelimit": "Limite d'envoi",
|
||||
"recipient_maps": "Cartes destinataire",
|
||||
"recipient_maps": "Cartes des destinataires",
|
||||
"smtp_ip_access": "Changer les hôtes autorisés pour SMTP",
|
||||
"sogo_access": "Autoriser la gestion des accès à SOGo",
|
||||
"sogo_profile_reset": "Réinitialiser le profil SOGo",
|
||||
@@ -109,7 +109,9 @@
|
||||
"bcc_dest_format": "La destination Cci doit être une seule adresse de courriel valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.",
|
||||
"tags": "Etiquettes",
|
||||
"app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application",
|
||||
"dry": "Simuler la synchronisation"
|
||||
"dry": "Simuler la synchronisation",
|
||||
"internal": "Interne",
|
||||
"internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine."
|
||||
},
|
||||
"admin": {
|
||||
"access": "Accès",
|
||||
@@ -359,7 +361,57 @@
|
||||
"service": "Service",
|
||||
"success": "Succès",
|
||||
"cors_settings": "Paramètres CORS",
|
||||
"login_time": "Horodatage de connexion"
|
||||
"login_time": "Horodatage de connexion",
|
||||
"domainadmin_quicklink": "Masquer le lien rapide vers la page de connexion de l'administrateur du domaine",
|
||||
"force_sso_text": "Si un fournisseur OIDC externe est configuré, cette option masque les formulaires de connexion par défaut de mailcow et n'affiche que le bouton Singe Sign-On",
|
||||
"app_hide": "Masquer pour la connexion",
|
||||
"admin_quicklink": "Masquer le lien rapide vers la page de connexion de l'administrateur",
|
||||
"login_page": "Page de connexion",
|
||||
"force_sso": "Désactiver la connexion mailcow et n'afficher que Singe Sign-On",
|
||||
"iam_attribute_field": "Champ d'attribut",
|
||||
"iam_authorize_url": "Endpoint d'autorisation",
|
||||
"iam_auth_flow": "Flow d'authentification",
|
||||
"iam_auth_flow_info": "Outre le flux de code d'autorisation (flux standard dans Keycloak), qui est utilisé pour la connexion unique, mailcow prend également en charge le flux d'authentification avec des informations d'identification directes. Le flux Mailpassword tente de valider les informations d'identification de l'utilisateur en utilisant l'API REST de Keycloak Admin. mailcow récupère le mot de passe haché dans l'attribut <code>mailcow_password</code>, qui est mappé dans Keycloak.",
|
||||
"iam_basedn": "Base DN",
|
||||
"iam_client_id": "ID Client",
|
||||
"iam_client_secret": "Secret Client",
|
||||
"iam_use_ssl": "Utiliser SSL",
|
||||
"iam_redirect_url": "Url de redirection",
|
||||
"iam_use_ssl_info": "Si le protocole SSL est activé et que le port est défini sur 389, il sera automatiquement remplacé par 636.",
|
||||
"iam_use_tls_info": "Si vous activez TLS, vous devez utiliser le port par défaut de votre serveur LDAP (389). Les ports SSL ne peuvent pas être utilisés.",
|
||||
"iam_version": "Version",
|
||||
"ignore_ssl_error": "Ignorer les erreurs SSL",
|
||||
"iam_login_provisioning": "Créer automatiquement l'utilisateur à la connexion",
|
||||
"iam_mapping": "Mapping des attributs",
|
||||
"iam_bindpass": "Lier le mot de passe",
|
||||
"iam_periodic_full_sync": "Synchronisation complète périodique",
|
||||
"iam_port": "Port",
|
||||
"iam_realm": "Realm",
|
||||
"iam_rest_flow": "Flow Mailpassword",
|
||||
"iam_server_url": "URL du serveur",
|
||||
"iam_sso": "Single Sign-On",
|
||||
"iam_sync_interval": "Interval de Sync / Import (min)",
|
||||
"iam_test_connection": "Test de connexion",
|
||||
"iam_token_url": "Endpoint Token",
|
||||
"iam_userinfo_url": "Endpoint User info",
|
||||
"iam_username_field": "Champ du nom d'utilisateur",
|
||||
"iam_binddn": "Bind DN",
|
||||
"iam_use_tls": "Utiliser StartTLS",
|
||||
"quicklink_text": "Afficher ou masquer les liens rapides vers d'autres pages de connexion sous le formulaire de connexion",
|
||||
"task": "Tâche",
|
||||
"user_link": "Lien utilisateur",
|
||||
"user_quicklink": "Masquer le lien rapide vers la page de connexion de l'utilisateur",
|
||||
"iam_client_scopes": "Scopes Client",
|
||||
"iam_default_template": "Template par défaut",
|
||||
"iam_default_template_description": "Si aucun modèle n'est attribué à un utilisateur, le modèle par défaut sera utilisé pour créer la boîte aux lettres, mais pas pour la mettre à jour.",
|
||||
"iam_description": "Configurer un fournisseur externe pour l'authentification<br>Les boîtes aux lettres des utilisateurs seront automatiquement créées lors de leur première connexion, à condition qu'un mappage d'attributs ait été défini.",
|
||||
"iam_extra_permission": "Pour que les paramètres suivants fonctionnent, le client mailcow dans Keycloak a besoin d'un <code>Compte de service</code> et de l'autorisation de <code>voir les utilisateurs</code>.",
|
||||
"iam_host": "Hôte",
|
||||
"iam_host_info": "Saisissez un ou plusieurs hôtes LDAP, séparés par des virgules.",
|
||||
"iam_import_users": "Importer des utilisateurs",
|
||||
"filter": "Filtrer",
|
||||
"needs_restart": "nécessite un redémarrage",
|
||||
"iam": "Fournisseur d'identité"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Accès refusé ou données de formulaire non valides",
|
||||
@@ -393,7 +445,7 @@
|
||||
"global_filter_write_error": "Impossible d’écrire le fichier de filtre : %s",
|
||||
"global_map_invalid": "ID de carte globale %s non valide",
|
||||
"global_map_write_error": "Impossible d’écrire l’ID de la carte globale %s : %s",
|
||||
"goto_empty": "Une adresse alias doit contenir au moins une adresse 'goto'valide",
|
||||
"goto_empty": "Une adresse alias doit contenir au moins une adresse 'goto' valide",
|
||||
"goto_invalid": "Adresse Goto %s non valide",
|
||||
"ham_learn_error": "Erreur d'apprentissage Ham : %s",
|
||||
"imagick_exception": "Erreur : Exception Imagick lors de la lecture de l’image",
|
||||
@@ -496,7 +548,15 @@
|
||||
"password_reset_invalid_user": "Boîte mail introuvable ou aucune adresse de récupération n'a été définie",
|
||||
"password_reset_na": "La réinitialisation des mots de passe est actuellement indisponible. Veuillez contacter votre administrateur.",
|
||||
"reset_token_limit_exceeded": "Le nombre limite de jetons de réinitialisation a été dépassé. Veuillez réessayer plus tard.",
|
||||
"to_invalid": "Le destinataire ne doit pas être vide"
|
||||
"to_invalid": "Le destinataire ne doit pas être vide",
|
||||
"generic_server_error": "Une erreur de serveur inattendue s'est produite. Veuillez contacter votre administrateur.",
|
||||
"authsource_in_use": "Le fournisseur d'identité ne peut pas être modifié ou supprimé car il est actuellement utilisé par un ou plusieurs utilisateurs.",
|
||||
"iam_test_connection": "Échec de la connexion",
|
||||
"required_data_missing": "La donnée requise %s est manquante",
|
||||
"max_age_invalid": "L'âge maximum %s est invalide",
|
||||
"mode_invalid": "Le mode %s est invalide",
|
||||
"mx_invalid": "L'enregistrement MX %s est invalide",
|
||||
"version_invalid": "La version %s est invalide"
|
||||
},
|
||||
"debug": {
|
||||
"chart_this_server": "Graphique (ce serveur)",
|
||||
@@ -553,7 +613,7 @@
|
||||
"alias": "Éditer les alias",
|
||||
"allow_from_smtp": "Restreindre l'utilisation de <b>SMTP</b> à ces adresses IP",
|
||||
"allow_from_smtp_info": "Laissez vide pour autoriser tous les expéditeurs.<br>Adresses IPv4/IPv6 et réseaux.",
|
||||
"allowed_protocols": "Protocoles autorisés",
|
||||
"allowed_protocols": "Protocoles autorisés pour l'accès direct de l'utilisateur (n'affecte pas les protocoles de mot de passe de l'application)",
|
||||
"app_name": "Nom de l'application",
|
||||
"app_passwd": "Mot de passe de l'application",
|
||||
"automap": "Tenter de cibler automatiquement les dossiers (« Sent items », « Sent » => « Sent » etc.)",
|
||||
@@ -641,7 +701,7 @@
|
||||
"spam_score": "Définir un score spam personnalisé",
|
||||
"subfolder2": "Synchronisation dans le sous-dossier sur la destination<br><small>(vide = ne pas utiliser de sous-dossier)</small>",
|
||||
"syncjob": "Modifier la tâche de synchronisation",
|
||||
"target_address": "Adresse(s) Goto<small>(séparé(s) par des virgules)</small>",
|
||||
"target_address": "Adresse(s) Goto <small>(séparé(s) par des virgules)</small>",
|
||||
"target_domain": "Domaine cible",
|
||||
"timeout1": "Délai de connexion à l’hôte distant",
|
||||
"timeout2": "Délai de connexion à l’hôte local",
|
||||
@@ -672,7 +732,7 @@
|
||||
"none_inherit": "Aucun / Héritage",
|
||||
"quota_warning_bcc": "Avertissement sur les quotas BCC",
|
||||
"quota_warning_bcc_info": "Les avertissements seront envoyés en copies séparées aux destinataires suivants. Le sujet sera précédé du nom d'utilisateur correspondant entre parenthèses, par exemple : <code>Avertissement sur les quotas (user@example.com)</code>.",
|
||||
"sogo_access_info": "L'authentification unique à partir de l'interface de messagerie reste opérationnelle. Ce paramètre n'affecte pas l'accès à tous les autres services et ne supprime ni, ne modifie le profil SOGo existant d'un utilisateur.",
|
||||
"sogo_access_info": "Après s'être connecté, l'utilisateur est automatiquement redirigé vers SOGo.",
|
||||
"admin": "Modifier l'administrateur",
|
||||
"password_recovery_email": "Adresse email de récupération",
|
||||
"mailbox_rename_title": "Nouveau nom de la partie locale de la boîte de réception",
|
||||
@@ -680,9 +740,22 @@
|
||||
"mailbox_rename_agree": "J'ai fait une sauvegarde.",
|
||||
"mailbox_rename_warning": "IMPORTANT ! Faites une sauvegarde avant de renommer la boîte de réception.",
|
||||
"mailbox_rename_alias": "Créer un alias automatiquement",
|
||||
"sogo_access": "Autoriser la connexion directe à SOGo",
|
||||
"sogo_access": "Redirection directe vers SOGo",
|
||||
"pushover": "Pushover",
|
||||
"pushover_sound": "Son"
|
||||
"pushover_sound": "Son",
|
||||
"internal": "Interne",
|
||||
"internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine.",
|
||||
"mta_sts": "MTA-STS",
|
||||
"mta_sts_version": "Version",
|
||||
"mta_sts_version_info": "Défini la version du standard MTA-STS – actuellement seul <code>STSv1</code> est valide.",
|
||||
"mta_sts_mode": "Mode",
|
||||
"mta_sts_max_age": "Âge maximum",
|
||||
"mta_sts_mx": "Serveur MX",
|
||||
"mta_sts_mx_notice": "Plusieurs serveurs MX peuvent être spécifiés (séparés par des virgules).",
|
||||
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> est un standard qui oblige la délivrance des courriels entre les serveurs de courriels à utiliser TLS avec des certificats valides. <br>Il est utilisé quand <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> n'est pas possible à cause d'un manque ou d'un non support de DNSSEC.<br><b>Note</b> : Si le domaine du destinataire supporte DANE avec DNSSEC, DANE est <b>toujours</b> préféré – MTA-STS sert seulement en secours.",
|
||||
"mta_sts_mode_info": "Il y a trois modes parmi lesquels choisir :<ul><li><em>testing</em> – la politique est seulement surveillée, les violations n'ont pas d'impact.</li><li><em>enforce</em> – la politique est appliquée strictement, les connexions sans TLS valide sont rejetées.</li><li><em>none</em> – la politique est publiée mais non appliquée.</li></ul>",
|
||||
"mta_sts_max_age_info": "Durée en secondes pendant laquelle les serveurs de courriel peuvent mettre en cache cette politique avant de revérifier.",
|
||||
"mta_sts_mx_info": "Autoriser l'envoi uniquement aux noms d'hôtes des serveurs de courriels indiqués explicitement ; le MTA émetteur vérifie si le nom d'hôte DNS du MX correspond à la liste de la politique, et autorise la délivrance seulement avec un certificat TLS valide (protège contre le MITM)."
|
||||
},
|
||||
"footer": {
|
||||
"cancel": "Annuler",
|
||||
@@ -730,7 +803,15 @@
|
||||
"new_password": "Nouveau mot de passe",
|
||||
"new_password_confirm": "Confirmer le nouveau mot de passe",
|
||||
"reset_password": "Réinitialiser le mot de passe",
|
||||
"request_reset_password": "Demander le changement du mot de passe"
|
||||
"request_reset_password": "Demander le changement du mot de passe",
|
||||
"login_user": "Connexion Utilisateur",
|
||||
"login_dadmin": "Connexion Administrateur de domaine",
|
||||
"login_admin": "Connexion Administrateur",
|
||||
"login_linkstext": "L'identifiant n'est pas correct ?",
|
||||
"login_usertext": "Se connecter en tant qu'utilisateur",
|
||||
"login_domainadmintext": "Se connecter en tant qu'administrateur du domaine",
|
||||
"login_admintext": "Se connecter en tant qu'administrateur",
|
||||
"email": "Adresse de courriel"
|
||||
},
|
||||
"mailbox": {
|
||||
"action": "Action",
|
||||
@@ -836,8 +917,8 @@
|
||||
"recipient_map_new": "Nouveau destinataire",
|
||||
"recipient_map_new_info": "La destination de la carte du destinataire doit être une adresse de courriel valide ou un nom de domaine.",
|
||||
"recipient_map_old": "Destinataire original",
|
||||
"recipient_map_old_info": "Une carte de destination originale doit être une adresse de courriel valide ou un nom de domaine.",
|
||||
"recipient_maps": "Cartes des bénéficiaires",
|
||||
"recipient_map_old_info": "La destination originale des cartes des destinataires doit être une adresse de courriel valide ou un nom de domaine.",
|
||||
"recipient_maps": "Cartes des destinataires",
|
||||
"relay_all": "Relayer tous les destinataires",
|
||||
"remove": "Supprimer",
|
||||
"resources": "Ressources",
|
||||
@@ -905,7 +986,9 @@
|
||||
"template": "Modèle",
|
||||
"syncjob_check_log": "Vérifier le journal",
|
||||
"recipient": "Destinataire",
|
||||
"open_logs": "Afficher les journaux"
|
||||
"open_logs": "Afficher les journaux",
|
||||
"iam": "Fournisseur d'identité",
|
||||
"internal": "Interne"
|
||||
},
|
||||
"oauth2": {
|
||||
"access_denied": "Veuillez vous connecter en tant que propriétaire de la boîte de réception pour accorder l’accès via Oauth2.",
|
||||
@@ -1076,7 +1159,9 @@
|
||||
"recovery_email_sent": "Email de réinitialisation envoyé à %s",
|
||||
"mailbox_renamed": "La boîte de réception a été renommée de %s à %s",
|
||||
"template_modified": "Les modifications au modèle %s ont été enregistrées",
|
||||
"password_policy_saved": "La politique de mot de passe a été enregistrée avec succès"
|
||||
"password_policy_saved": "La politique de mot de passe a été enregistrée avec succès",
|
||||
"custom_login_modified": "La personnalisation de la connexion a été sauvegardée avec succès",
|
||||
"iam_test_connection": "Connexion réussie"
|
||||
},
|
||||
"tfa": {
|
||||
"api_register": "%s utilise l'API Yubico Cloud. Veuillez obtenir une clé API pour votre clé <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">ici</a>",
|
||||
@@ -1160,7 +1245,7 @@
|
||||
"email_and_dav": "Courriel, calendriers et contacts",
|
||||
"encryption": "Chiffrement",
|
||||
"excludes": "Exclus",
|
||||
"expire_in": "Expire dans",
|
||||
"expire_in": "Expirer dans",
|
||||
"force_pw_update": "Vous <b>devez</b> définir un nouveau mot de passe pour pouvoir accéder aux services liés aux logiciels de groupe.",
|
||||
"generate": "générer",
|
||||
"hour": "heure",
|
||||
@@ -1181,7 +1266,7 @@
|
||||
"no_last_login": "Aucune dernière information de connexion à l'interface",
|
||||
"no_record": "Pas d'enregistrement",
|
||||
"password": "Mot de passe",
|
||||
"password_now": "Mot de passe courant (confirmer les changements)",
|
||||
"password_now": "Mot de passe actuel (confirmer les changements)",
|
||||
"password_repeat": "Mot de passe (répéter)",
|
||||
"pushover_evaluate_x_prio": "Acheminement du courrier hautement prioritaire [<code>X-Priority: 1</code>]",
|
||||
"pushover_info": "Les paramètres de notification push s’appliqueront à tout le courrier propre (non spam) livré à <b>%s</b> y compris les alias (partagés, non partagés, étiquetés).",
|
||||
@@ -1256,8 +1341,8 @@
|
||||
"with_app_password": "avec le mot de passe de l'application",
|
||||
"apple_connection_profile_with_app_password": "Un nouveau mot de passe est généré et ajouté au profil, de sorte qu'aucun mot de passe ne doit être saisi lors de la configuration de votre appareil. Ne partagez pas le fichier car il vous donne un accès complet à votre boîte de réception.",
|
||||
"attribute": "Attribut",
|
||||
"direct_protocol_access": "Cet utilisateur de la boîte aux lettres dispose d'un <b>accès externe direct</b> aux protocoles et applications suivants. Votre administrateur contrôle ce paramètre. Il est possible de créer des mots de passe d'application pour accorder l'accès à des protocoles et des applications individuels.<br>Le bouton « Connexion au webmail » permet une connexion unique à SOGo et est toujours disponible.",
|
||||
"open_webmail_sso": "Connexion au webmail",
|
||||
"direct_protocol_access": "Cet utilisateur de la boîte aux lettres dispose d'un <b>accès externe direct</b> aux protocoles et applications suivants. Votre administrateur contrôle ce paramètre. Il est possible de créer des mots de passe d'application pour accorder l'accès à des protocoles et des applications individuels.<br>Le bouton « Connexion au Webmail » permet une connexion unique à SOGo et est toujours disponible.",
|
||||
"open_webmail_sso": "Connexion au Webmail",
|
||||
"recent_successful_connections": "Voir les connexions réussies",
|
||||
"syncjob_EXIT_TLS_FAILURE": "Problème de connexion chiffrée",
|
||||
"syncjob_EXIT_AUTHENTICATION_FAILURE": "Problème d'authentification",
|
||||
@@ -1286,7 +1371,14 @@
|
||||
"open_logs": "Afficher les journaux",
|
||||
"pushover_sound": "Son",
|
||||
"mailbox_general": "Général",
|
||||
"mailbox_settings": "Paramètres"
|
||||
"mailbox_settings": "Paramètres",
|
||||
"tfa_info": "L'authentification à deux facteurs permet de protéger votre compte. Si vous l'activez, vous aurez besoin de mots de passe d'application pour vous connecter à des applications ou des services qui ne prennent pas en charge l'authentification à deux facteurs (par exemple les clients e-mails).",
|
||||
"overview": "Vue d'ensemble",
|
||||
"expire_never": "Ne jamais expirer",
|
||||
"forever": "Pour toujours",
|
||||
"spam_aliases_info": "Un alias de spam est une adresse de courriel temporaire qui peut être utilisée pour protéger les véritables adresses de courriel. <br> De manière optionnelle, une durée d'expiration peut être définie afin que l'alias soit automatiquement désactivé après la période définie, éliminant ainsi les adresses étant abusées ou ayant fuité.",
|
||||
"authentication": "Authentification",
|
||||
"protocols": "Protocoles"
|
||||
},
|
||||
"warning": {
|
||||
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user