mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-02-19 21:30:03 +00:00
Compare commits
275 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a729d89bf | ||
|
|
74b4097ee0 | ||
|
|
e00d0d5f8d | ||
|
|
c5e399ebc2 | ||
|
|
cb9ca772b1 | ||
|
|
162f05ccda | ||
|
|
6c97c4f372 | ||
|
|
6d4fcacd83 | ||
|
|
1994f706c0 | ||
|
|
e34afd3fdd | ||
|
|
a6f71faf46 | ||
|
|
b26ccc2019 | ||
|
|
b1c1e403d2 | ||
|
|
8753ea2be6 | ||
|
|
9fee568082 | ||
|
|
294a406b91 | ||
|
|
8b933f1967 | ||
|
|
824a473fea | ||
|
|
7f790c5360 | ||
|
|
52431a3942 | ||
|
|
8017394e9d | ||
|
|
76194be7dd | ||
|
|
3b23afa0ff | ||
|
|
6e00d653ce | ||
|
|
b6c036496d | ||
|
|
5d7c9b20bc | ||
|
|
4b400eadb1 | ||
|
|
ab2abda8cc | ||
|
|
2fe21e9641 | ||
|
|
b7ed6982d8 | ||
|
|
fd927853cb | ||
|
|
c48f4f4ab8 | ||
|
|
a4c006828e | ||
|
|
b56291f62b | ||
|
|
0cdf7647c4 | ||
|
|
8fe1cc4961 | ||
|
|
bf050f17c4 | ||
|
|
edd85dea8d | ||
|
|
3bf90c1f73 | ||
|
|
292306b191 | ||
|
|
b3e0a66222 | ||
|
|
e994cf4d05 | ||
|
|
cc0dc2eae0 | ||
|
|
a001a0584f | ||
|
|
926af87cfb | ||
|
|
b0339372b5 | ||
|
|
e398cb91e9 | ||
|
|
6ee0303b0f | ||
|
|
68616c2d57 | ||
|
|
f8de520d29 | ||
|
|
10077ece31 | ||
|
|
c918726143 | ||
|
|
3885b07a99 | ||
|
|
fcf27d640d | ||
|
|
82fde23cc1 | ||
|
|
cbca306fc1 | ||
|
|
6a8986fe4f | ||
|
|
ff34eb12e2 | ||
|
|
fbecd60e56 | ||
|
|
c37bf0bb32 | ||
|
|
2208d7e6fb | ||
|
|
e426c3a7e7 | ||
|
|
03fccb28e9 | ||
|
|
8fbfd99dd6 | ||
|
|
7f7a869678 | ||
|
|
73257151c4 | ||
|
|
efb2572f0f | ||
|
|
66aa28b5de | ||
|
|
987a027339 | ||
|
|
eea81e21f6 | ||
|
|
a689109f44 | ||
|
|
58c0a46459 | ||
|
|
2dbe8bf4ca | ||
|
|
ef7ec06947 | ||
|
|
fc7ea7a247 | ||
|
|
9b478b3859 | ||
|
|
384e5a2e64 | ||
|
|
aadeeb0df3 | ||
|
|
f33d82ffc1 | ||
|
|
ffeeb179e1 | ||
|
|
8e2d3a6db5 | ||
|
|
70126e1f0c | ||
|
|
b9ae174a6a | ||
|
|
9715c57314 | ||
|
|
b9f8959d92 | ||
|
|
9c814cc182 | ||
|
|
cf6594220c | ||
|
|
2cf952eb36 | ||
|
|
6fc86dd7d3 | ||
|
|
bf13af9691 | ||
|
|
1af9c21a50 | ||
|
|
443941e687 | ||
|
|
527577b438 | ||
|
|
9daf2d80c0 | ||
|
|
38b0641742 | ||
|
|
f675af5bb0 | ||
|
|
533c4e7956 | ||
|
|
1b2c2c0037 | ||
|
|
97768494e1 | ||
|
|
4a052da289 | ||
|
|
18d7a55b15 | ||
|
|
9ca2fb7ccf | ||
|
|
b4e8355827 | ||
|
|
e0bde1c459 | ||
|
|
27c007ebd3 | ||
|
|
8f3ea09732 | ||
|
|
af626d98d3 | ||
|
|
34b0574e56 | ||
|
|
49d738809b | ||
|
|
2fa3a22eca | ||
|
|
dc5eb6f92e | ||
|
|
ba8902f0b1 | ||
|
|
11e9a77840 | ||
|
|
64cd7e74c5 | ||
|
|
cac65d081e | ||
|
|
e5ada994be | ||
|
|
6ba2459645 | ||
|
|
58f63aad08 | ||
|
|
8a8687a63c | ||
|
|
f7f93c360d | ||
|
|
c160e1f68e | ||
|
|
47c08ab8d2 | ||
|
|
cd83ffbaa2 | ||
|
|
e12981a821 | ||
|
|
47fd1bb894 | ||
|
|
20582b6353 | ||
|
|
c8ff5387c0 | ||
|
|
7cb138d515 | ||
|
|
3dd4c45fab | ||
|
|
549539bec9 | ||
|
|
e449cac464 | ||
|
|
62e458f39b | ||
|
|
b37caaf9e5 | ||
|
|
7660ca89ae | ||
|
|
36b5cccd18 | ||
|
|
9decfa9c31 | ||
|
|
3aee2b6cf5 | ||
|
|
17d797cee4 | ||
|
|
75550eeea3 | ||
|
|
0d09c86c12 | ||
|
|
2db8f482db | ||
|
|
00d4b32a1b | ||
|
|
8a82bab1f3 | ||
|
|
237a25e6b0 | ||
|
|
5dc836671d | ||
|
|
26be1cb602 | ||
|
|
dc7a48cbf9 | ||
|
|
52455be815 | ||
|
|
5c851f2935 | ||
|
|
bbbdcfb625 | ||
|
|
b054a57e16 | ||
|
|
fd73b3ad88 | ||
|
|
8c0637b556 | ||
|
|
914a8204d4 | ||
|
|
d92ffe8fc7 | ||
|
|
e0eb3a4f13 | ||
|
|
1fb0060a73 | ||
|
|
d7430bf516 | ||
|
|
35f039a119 | ||
|
|
79432a40d7 | ||
|
|
98cdb95bc0 | ||
|
|
02a55ce9db | ||
|
|
6f4720e1ea | ||
|
|
6a807b7799 | ||
|
|
8d4ef147d2 | ||
|
|
8ed6217d1c | ||
|
|
7dae4a976d | ||
|
|
3b83949ba3 | ||
|
|
d8baadb991 | ||
|
|
7d3f9fa407 | ||
|
|
705d144a85 | ||
|
|
ff05cff36c | ||
|
|
861fa7b145 | ||
|
|
d65a0bba44 | ||
|
|
dac1bd88dc | ||
|
|
288dbfa37c | ||
|
|
a0e55cb9b1 | ||
|
|
86ba019ca0 | ||
|
|
3cb9c2ece5 | ||
|
|
1787c53d98 | ||
|
|
8ae762a8c8 | ||
|
|
63426c3cd0 | ||
|
|
e184713c67 | ||
|
|
1926625297 | ||
|
|
63bb8e8cef | ||
|
|
583c5b48a0 | ||
|
|
d08ccbce78 | ||
|
|
5a9702771c | ||
|
|
eb91d9905b | ||
|
|
38cc85fa4c | ||
|
|
77e6ef218c | ||
|
|
464b6f2e93 | ||
|
|
20c90642f9 | ||
|
|
57e67ea8f7 | ||
|
|
c9e9628383 | ||
|
|
909f07939e | ||
|
|
a310493485 | ||
|
|
1e09df20b6 | ||
|
|
087481ac12 | ||
|
|
c941e802d4 | ||
|
|
39589bd441 | ||
|
|
2e57325dde | ||
|
|
2072301d89 | ||
|
|
b236fd3ac6 | ||
|
|
b968695e31 | ||
|
|
694f1d1623 | ||
|
|
93e4d58606 | ||
|
|
cc77caad67 | ||
|
|
f74573f5d0 | ||
|
|
deb6f0babc | ||
|
|
cb978136bd | ||
|
|
1159450cc4 | ||
|
|
a0613e4b10 | ||
|
|
68989f0a45 | ||
|
|
7da5e3697e | ||
|
|
6e7a0eb662 | ||
|
|
b25ac855ca | ||
|
|
3e02dcbb95 | ||
|
|
53be119e39 | ||
|
|
25bdc4c9ed | ||
|
|
9d4055fc4d | ||
|
|
d2edf359ac | ||
|
|
aa1d92dfbb | ||
|
|
b89d71e6e4 | ||
|
|
ed493f9c3a | ||
|
|
76f8a5b7de | ||
|
|
cb3bc207b9 | ||
|
|
b5db5dd0b4 | ||
|
|
90a7cff2c9 | ||
|
|
cc3adbe78c | ||
|
|
bd6a7210b7 | ||
|
|
905a202873 | ||
|
|
accedf0280 | ||
|
|
99d9a2eacd | ||
|
|
ac4f131fa8 | ||
|
|
7f6f7e0e9f | ||
|
|
43bb26f28c | ||
|
|
b29dc37991 | ||
|
|
cf9f02adbb | ||
|
|
6dc0bdbfa3 | ||
|
|
b5a1a18b04 | ||
|
|
b4eeb0ffae | ||
|
|
48549ead7f | ||
|
|
01b0ad0fd9 | ||
|
|
2b21501450 | ||
|
|
b491f6af9b | ||
|
|
942ef7c254 | ||
|
|
1ee3bb42f3 | ||
|
|
25007b1963 | ||
|
|
f442378377 | ||
|
|
333b7ebc0c | ||
|
|
5896766fc3 | ||
|
|
89540aec28 | ||
|
|
b960143045 | ||
|
|
6ab45cf668 | ||
|
|
fd206a7ef6 | ||
|
|
1c7347d38d | ||
|
|
7f58c422f2 | ||
|
|
0a0e2b5e93 | ||
|
|
de00c424f4 | ||
|
|
a249e2028d | ||
|
|
68036eeccf | ||
|
|
b4bb11320f | ||
|
|
c61938db23 | ||
|
|
c24543fea0 | ||
|
|
38497b04ac | ||
|
|
efab11720d | ||
|
|
40fdf99a55 | ||
|
|
efcca61f5a | ||
|
|
4dad0002cd | ||
|
|
d4dd1e37ce | ||
|
|
a8dfa95126 | ||
|
|
4f109c1a94 | ||
|
|
28cec99699 | ||
|
|
3e194c7906 |
10
.github/ISSUE_TEMPLATE/Bug_report.yml
vendored
10
.github/ISSUE_TEMPLATE/Bug_report.yml
vendored
@@ -62,6 +62,16 @@ body:
|
||||
- nightly
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: "Which architecture are you using?"
|
||||
description: "#### `uname -m`"
|
||||
multiple: false
|
||||
options:
|
||||
- x86
|
||||
- ARM64 (aarch64)
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: "Operating System:"
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/pr_to_nighty_template.yml
vendored
12
.github/ISSUE_TEMPLATE/pr_to_nighty_template.yml
vendored
@@ -1,13 +1,3 @@
|
||||
## :memo: Brief description
|
||||
<!-- Diff summary - START -->
|
||||
<!-- Diff summary - END -->
|
||||
|
||||
|
||||
## :computer: Commits
|
||||
<!-- Diff commits - START -->
|
||||
<!-- Diff commits - END -->
|
||||
|
||||
|
||||
## :file_folder: Modified files
|
||||
<!-- Diff files - START -->
|
||||
<!-- Diff files - END -->
|
||||
<!-- Diff files - END -->
|
||||
38
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
38
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<!-- _Please make sure to review and check all of these items, otherwise we might refuse your PR:_ -->
|
||||
|
||||
## Contribution Guidelines
|
||||
|
||||
* [ ] I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree them
|
||||
|
||||
<!-- _NOTE: this tickbox is needed to fullfil on order to get your PR reviewed._ -->
|
||||
|
||||
## What does this PR include?
|
||||
|
||||
### Short Description
|
||||
|
||||
<!-- Please write a short description, what your PR does here. -->
|
||||
|
||||
### Affected Containers
|
||||
|
||||
<!-- Please list all affected Docker containers here, which you commited changes to -->
|
||||
|
||||
<!--
|
||||
|
||||
Please list them like this:
|
||||
|
||||
- container1
|
||||
- container2
|
||||
- container3
|
||||
etc.
|
||||
|
||||
-->
|
||||
|
||||
## Did you run tests?
|
||||
|
||||
### What did you tested?
|
||||
|
||||
<!-- Please write shortly, what you've tested (which components etc.). -->
|
||||
|
||||
### What were the final results? (Awaited, got)
|
||||
|
||||
<!-- Please write shortly, what your final tests results were. What did you awaited? Was the outcome the awaited one? -->
|
||||
37
.github/workflows/check_if_support_labeled.yml
vendored
Normal file
37
.github/workflows/check_if_support_labeled.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Check if labeled support, if so send message and close issue
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
jobs:
|
||||
add-comment:
|
||||
if: github.event.label.name == 'support'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Add comment
|
||||
run: gh issue comment "$NUMBER" --body "$BODY"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SUPPORTISSUES_ACTION_PAT }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
BODY: |
|
||||
**THIS IS A AUTOMATED MESSAGE!**
|
||||
|
||||
It seems your issue is not a bug.
|
||||
Therefore we highly advise you to get support!
|
||||
|
||||
You can get support either by:
|
||||
- ordering a paid [support contract at Servercow](https://www.servercow.de/mailcow?lang=en#support/) (Directly from the developers) or
|
||||
- using the [community forum](https://community.mailcow.email) (**Based on volunteers! NO guaranteed answer**) or
|
||||
- using the [Telegram support channel](https://t.me/mailcow) (**Based on volunteers! NO guaranteed answer**)
|
||||
|
||||
This issue will be closed. If you think your reported issue is not a support case feel free to comment above and if so the issue will reopened.
|
||||
|
||||
- name: Close issue
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SUPPORTISSUES_ACTION_PAT }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
run: gh issue close "$NUMBER" -r "not planned"
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging
|
||||
steps:
|
||||
- name: Send message
|
||||
uses: thollander/actions-comment-pull-request@v2.4.3
|
||||
uses: thollander/actions-comment-pull-request@v2.5.0
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
|
||||
message: |
|
||||
|
||||
2
.github/workflows/rebuild_backup_image.yml
vendored
2
.github/workflows/rebuild_backup_image.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
bash helper-scripts/update_postscreen_whitelist.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
|
||||
commit-message: update postscreen_access.cidr
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@ data/conf/dovecot/acl_anyone
|
||||
data/conf/dovecot/dovecot-master.passwd
|
||||
data/conf/dovecot/dovecot-master.userdb
|
||||
data/conf/dovecot/extra.conf
|
||||
data/conf/dovecot/mail_replica.conf
|
||||
data/conf/dovecot/global_sieve_*
|
||||
data/conf/dovecot/last_login
|
||||
data/conf/dovecot/lua
|
||||
|
||||
@@ -1,33 +1,52 @@
|
||||
# Contribution Guidelines (Last modified on 18th December 2023)
|
||||
# Contribution Guidelines
|
||||
**_Last modified on 15th August 2024_**
|
||||
|
||||
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!
|
||||
|
||||
## Pull Requests (Last modified on 18th December 2023)
|
||||
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.
|
||||
|
||||
## Topics
|
||||
|
||||
- [Pull Requests](#pull-requests)
|
||||
- [Issue Reporting](#issue-reporting)
|
||||
- [Guidelines](#issue-reporting-guidelines)
|
||||
- [Issue Report Guide](#issue-report-guide)
|
||||
|
||||
## Pull Requests
|
||||
**_Last modified on 15th August 2024_**
|
||||
|
||||
However, please note the following regarding pull requests:
|
||||
|
||||
1. **ALWAYS** create your PR using the staging branch of your locally cloned mailcow instance, as the pull request will end up in said staging branch of mailcow once approved. Ideally, you should simply create a new branch for your pull request that is named after the type of your PR (e.g. `feat/` for function updates or `fix/` for bug fixes) and the actual content (e.g. `sogo-6.0.0` for an update from SOGo to version 6 or `html-escape` for a fix that includes escaping HTML in mailcow).
|
||||
2. Please **keep** this pull request branch **clean** and free of commits that have nothing to do with the changes you have made (e.g. commits from other users from other branches). *If you make changes to the `update.sh` script or other scripts that trigger a commit, there is usually a developer mode for clean working in this case.
|
||||
3. **Test your changes before you commit them as a pull request.** <ins>If possible</ins>, write a small **test log** or demonstrate the functionality with a **screenshot or GIF**. *We will of course also test your pull request ourselves, but proof from you will save us the question of whether you have tested your own changes yourself.*
|
||||
4. 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.*
|
||||
5. 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.
|
||||
6. 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!
|
||||
2. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english.
|
||||
3. Please **keep** this pull request branch **clean** and free of commits that have nothing to do with the changes you have made (e.g. commits from other users from other branches). *If you make changes to the `update.sh` script or other scripts that trigger a commit, there is usually a developer mode for clean working in this case.*
|
||||
4. **Test your changes before you commit them as a pull request.** <ins>If possible</ins>, write a small **test log** or demonstrate the functionality with a **screenshot or GIF**. *We will of course also test your pull request ourselves, but proof from you will save us the question of whether you have tested your own changes yourself.*
|
||||
5. **Please use** the pull request template we provide once creating a pull request. *HINT: During editing you encounter comments which looks like: `<!-- CONTENT -->`. These can be removed or kept, as they will not rendered later on GitHub! Please only create actual content without the said comments.*
|
||||
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!
|
||||
|
||||
---
|
||||
|
||||
## Issue Reporting (Last modified on 18th December 2023)
|
||||
## Issue Reporting
|
||||
**_Last modified on 15th August 2024_**
|
||||
|
||||
If you plan to report a issue within mailcow please read and understand the following rules:
|
||||
|
||||
### 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).
|
||||
2. **ONLY** report an error if you have the **necessary know-how (at least the basics)** for the administration of an e-mail server and the usage of Docker. mailcow is a complex and fully-fledged e-mail server including groupware components on a Docker basement and it requires a bit of technical know-how for debugging and operating.
|
||||
3. **ONLY** report bugs that are contained in the latest mailcow release series. *The definition of the latest release series includes the last major patch (e.g. 2023-12) and all minor patches (revisions) below it (e.g. 2023-12a, b, c etc.).* New issue reports published starting from January 1, 2024 must meet this criterion, as versions below the latest releases are no longer supported by us.
|
||||
4. When reporting a problem, please be as detailed as possible and include even the smallest changes to your mailcow installation. Simply fill out the corresponding bug report form in detail and accurately to minimize possible questions.
|
||||
5. **Before you open an issue/feature request**, please first check whether a similar request already exists in the mailcow tracker on GitHub. If so, please include yourself in this request.
|
||||
6. When you create a issue/feature request: Please note that the creation does <ins>**not guarantee an instant implementation or fix by the mailcow team or the community**</ins>.
|
||||
7. Please **ALWAYS** anonymize any sensitive information in your bug report or feature request before submitting it.
|
||||
3. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english.
|
||||
4. **ONLY** report bugs that are contained in the latest mailcow release series. *The definition of the latest release series includes the last major patch (e.g. 2023-12) and all minor patches (revisions) below it (e.g. 2023-12a, b, c etc.).* New issue reports published starting from January 1, 2024 must meet this criterion, as versions below the latest releases are no longer supported by us.
|
||||
5. When reporting a problem, please be as detailed as possible and include even the smallest changes to your mailcow installation. Simply fill out the corresponding bug report form in detail and accurately to minimize possible questions.
|
||||
6. **Before you open an issue/feature request**, please first check whether a similar request already exists in the mailcow tracker on GitHub. If so, please include yourself in this request.
|
||||
7. When you create a issue/feature request: Please note that the creation does <ins>**not guarantee an instant implementation or fix by the mailcow team or the community**</ins>.
|
||||
8. Please **ALWAYS** anonymize any sensitive information in your bug report or feature request before submitting it.
|
||||
|
||||
### Quick guide to reporting problems:
|
||||
### Issue Report Guide
|
||||
1. Read your logs; follow them to see what the reason for your problem is.
|
||||
2. Follow the leads given to you in your logfiles and start investigating.
|
||||
3. Restarting the troubled service or the whole stack to see if the problem persists.
|
||||
@@ -36,4 +55,4 @@ If you plan to report a issue within mailcow please read and understand the foll
|
||||
6. [Create an issue](https://github.com/mailcow/mailcow-dockerized/issues/new/choose) over at our GitHub repository if you think your problem might be a bug or a missing feature you badly need. But please make sure, that you include **all the logs** and a full description to your problem.
|
||||
7. Ask your questions in our community-driven [support channels](https://docs.mailcow.email/#community-support-and-chat).
|
||||
|
||||
## When creating an issue/feature request or a pull request, you will be asked to confirm these guidelines.
|
||||
## When creating an issue/feature request or a pull request, you will be asked to confirm these guidelines.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.20
|
||||
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
RUN apk upgrade --no-cache \
|
||||
&& apk add --update --no-cache \
|
||||
@@ -14,9 +15,7 @@ RUN apk upgrade --no-cache \
|
||||
tini \
|
||||
tzdata \
|
||||
python3 \
|
||||
py3-pip \
|
||||
&& pip3 install --upgrade pip \
|
||||
&& pip3 install acme-tiny
|
||||
acme-tiny --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/
|
||||
|
||||
COPY acme.sh /srv/acme.sh
|
||||
COPY functions.sh /srv/functions.sh
|
||||
|
||||
@@ -33,6 +33,10 @@ if [[ "${ONLY_MAILCOW_HOSTNAME}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
ONLY_MAILCOW_HOSTNAME=y
|
||||
fi
|
||||
|
||||
if [[ "${AUTODISCOVER_SAN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
AUTODISCOVER_SAN=y
|
||||
fi
|
||||
|
||||
# Request individual certificate for every domain
|
||||
if [[ "${ENABLE_SSL_SNI}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
ENABLE_SSL_SNI=y
|
||||
@@ -119,7 +123,7 @@ done
|
||||
log_f "Database OK"
|
||||
|
||||
log_f "Waiting for Nginx..."
|
||||
until $(curl --output /dev/null --silent --head --fail http://nginx:8081); do
|
||||
until $(curl --output /dev/null --silent --head --fail http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network:8081); do
|
||||
sleep 2
|
||||
done
|
||||
log_f "Nginx OK"
|
||||
@@ -133,7 +137,7 @@ log_f "Resolver OK"
|
||||
# Waiting for domain table
|
||||
log_f "Waiting for domain table..."
|
||||
while [[ -z ${DOMAIN_TABLE} ]]; do
|
||||
curl --silent http://nginx/ >/dev/null 2>&1
|
||||
curl --silent http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network/ >/dev/null 2>&1
|
||||
DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
|
||||
[[ -z ${DOMAIN_TABLE} ]] && sleep 10
|
||||
done
|
||||
@@ -211,7 +215,11 @@ while true; do
|
||||
ADDITIONAL_SAN_ARR+=($i)
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
|
||||
# Fetch certs for autoconfig and autodiscover subdomains
|
||||
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
|
||||
fi
|
||||
|
||||
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
|
||||
# Start IP detection
|
||||
|
||||
@@ -2,32 +2,32 @@
|
||||
|
||||
# Reading container IDs
|
||||
# Wrapping as array to ensure trimmed content when calling $NGINX etc.
|
||||
NGINX=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"nginx-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " "))
|
||||
DOVECOT=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"dovecot-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " "))
|
||||
POSTFIX=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " "))
|
||||
NGINX=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"nginx-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " "))
|
||||
DOVECOT=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"dovecot-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " "))
|
||||
POSTFIX=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " "))
|
||||
|
||||
reload_nginx(){
|
||||
echo "Reloading Nginx..."
|
||||
NGINX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi/containers/${NGINX}/exec -d '{"cmd":"reload", "task":"nginx"}' --silent -H 'Content-type: application/json' | jq -r .type)
|
||||
NGINX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${NGINX}/exec -d '{"cmd":"reload", "task":"nginx"}' --silent -H 'Content-type: application/json' | jq -r .type)
|
||||
[[ ${NGINX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Nginx, restarting container..."; restart_container ${NGINX} ; }
|
||||
}
|
||||
|
||||
reload_dovecot(){
|
||||
echo "Reloading Dovecot..."
|
||||
DOVECOT_RELOAD_RET=$(curl -X POST --insecure https://dockerapi/containers/${DOVECOT}/exec -d '{"cmd":"reload", "task":"dovecot"}' --silent -H 'Content-type: application/json' | jq -r .type)
|
||||
DOVECOT_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${DOVECOT}/exec -d '{"cmd":"reload", "task":"dovecot"}' --silent -H 'Content-type: application/json' | jq -r .type)
|
||||
[[ ${DOVECOT_RELOAD_RET} != 'success' ]] && { echo "Could not reload Dovecot, restarting container..."; restart_container ${DOVECOT} ; }
|
||||
}
|
||||
|
||||
reload_postfix(){
|
||||
echo "Reloading Postfix..."
|
||||
POSTFIX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi/containers/${POSTFIX}/exec -d '{"cmd":"reload", "task":"postfix"}' --silent -H 'Content-type: application/json' | jq -r .type)
|
||||
POSTFIX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/exec -d '{"cmd":"reload", "task":"postfix"}' --silent -H 'Content-type: application/json' | jq -r .type)
|
||||
[[ ${POSTFIX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Postfix, restarting container..."; restart_container ${POSTFIX} ; }
|
||||
}
|
||||
|
||||
restart_container(){
|
||||
for container in $*; do
|
||||
echo "Restarting ${container}..."
|
||||
C_REST_OUT=$(curl -X POST --insecure https://dockerapi/containers/${container}/restart --silent | jq -r '.msg')
|
||||
C_REST_OUT=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${container}/restart --silent | jq -r '.msg')
|
||||
echo "${C_REST_OUT}"
|
||||
done
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt update && apt install pigz
|
||||
@@ -1,12 +1,14 @@
|
||||
FROM clamav/clamav:1.0.3_base
|
||||
FROM alpine:3.20
|
||||
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
RUN apk upgrade --no-cache \
|
||||
&& apk add --update --no-cache \
|
||||
rsync \
|
||||
clamav \
|
||||
bind-tools \
|
||||
bash
|
||||
bash \
|
||||
tini
|
||||
|
||||
# init
|
||||
COPY clamd.sh /clamd.sh
|
||||
@@ -14,7 +16,9 @@ RUN chmod +x /sbin/tini
|
||||
|
||||
# healthcheck
|
||||
COPY healthcheck.sh /healthcheck.sh
|
||||
COPY clamdcheck.sh /usr/local/bin
|
||||
RUN chmod +x /healthcheck.sh
|
||||
RUN chmod +x /usr/local/bin/clamdcheck.sh
|
||||
HEALTHCHECK --start-period=6m CMD "/healthcheck.sh"
|
||||
|
||||
ENTRYPOINT []
|
||||
|
||||
14
data/Dockerfiles/clamd/clamdcheck.sh
Normal file
14
data/Dockerfiles/clamd/clamdcheck.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
if [ "${CLAMAV_NO_CLAMD:-}" != "false" ]; then
|
||||
if [ "$(echo "PING" | nc localhost 3310)" != "PONG" ]; then
|
||||
echo "ERROR: Unable to contact server"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Clamd is up"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,7 +1,8 @@
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.20
|
||||
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --update --no-cache python3 \
|
||||
@@ -9,12 +10,13 @@ RUN apk add --update --no-cache python3 \
|
||||
openssl \
|
||||
tzdata \
|
||||
py3-psutil \
|
||||
py3-redis \
|
||||
py3-async-timeout \
|
||||
&& pip3 install --upgrade pip \
|
||||
fastapi \
|
||||
uvicorn \
|
||||
aiodocker \
|
||||
docker \
|
||||
aioredis
|
||||
docker
|
||||
RUN mkdir /app/modules
|
||||
|
||||
COPY docker-entrypoint.sh /app/
|
||||
|
||||
@@ -5,16 +5,63 @@ import json
|
||||
import uuid
|
||||
import async_timeout
|
||||
import asyncio
|
||||
import aioredis
|
||||
import aiodocker
|
||||
import docker
|
||||
import logging
|
||||
from logging.config import dictConfig
|
||||
from fastapi import FastAPI, Response, Request
|
||||
from modules.DockerApi import DockerApi
|
||||
from redis import asyncio as aioredis
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
dockerapi = None
|
||||
app = FastAPI()
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
global dockerapi
|
||||
|
||||
# Initialize a custom logger
|
||||
logger = logging.getLogger("dockerapi")
|
||||
logger.setLevel(logging.INFO)
|
||||
# Configure the logger to output logs to the terminal
|
||||
handler = logging.StreamHandler()
|
||||
handler.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter("%(levelname)s: %(message)s")
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
logger.info("Init APP")
|
||||
|
||||
# Init redis client
|
||||
if os.environ['REDIS_SLAVEOF_IP'] != "":
|
||||
redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0")
|
||||
else:
|
||||
redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0")
|
||||
|
||||
# Init docker clients
|
||||
sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
|
||||
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
|
||||
|
||||
dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger)
|
||||
|
||||
logger.info("Subscribe to redis channel")
|
||||
# Subscribe to redis channel
|
||||
dockerapi.pubsub = redis.pubsub()
|
||||
await dockerapi.pubsub.subscribe("MC_CHANNEL")
|
||||
asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub))
|
||||
|
||||
|
||||
yield
|
||||
|
||||
# Close docker connections
|
||||
dockerapi.sync_docker_client.close()
|
||||
await dockerapi.async_docker_client.close()
|
||||
|
||||
# Close redis
|
||||
await dockerapi.pubsub.unsubscribe("MC_CHANNEL")
|
||||
await dockerapi.redis_client.close()
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
# Define Routes
|
||||
@app.get("/host/stats")
|
||||
@@ -144,53 +191,7 @@ async def post_container_update_stats(container_id : str):
|
||||
|
||||
stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats'))
|
||||
return Response(content=json.dumps(stats, indent=4), media_type="application/json")
|
||||
|
||||
# Events
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
global dockerapi
|
||||
|
||||
# Initialize a custom logger
|
||||
logger = logging.getLogger("dockerapi")
|
||||
logger.setLevel(logging.INFO)
|
||||
# Configure the logger to output logs to the terminal
|
||||
handler = logging.StreamHandler()
|
||||
handler.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter("%(levelname)s: %(message)s")
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
logger.info("Init APP")
|
||||
|
||||
# Init redis client
|
||||
if os.environ['REDIS_SLAVEOF_IP'] != "":
|
||||
redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0")
|
||||
else:
|
||||
redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0")
|
||||
|
||||
# Init docker clients
|
||||
sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
|
||||
async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')
|
||||
|
||||
dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger)
|
||||
|
||||
logger.info("Subscribe to redis channel")
|
||||
# Subscribe to redis channel
|
||||
dockerapi.pubsub = redis.pubsub()
|
||||
await dockerapi.pubsub.subscribe("MC_CHANNEL")
|
||||
asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub))
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
global dockerapi
|
||||
|
||||
# Close docker connections
|
||||
dockerapi.sync_docker_client.close()
|
||||
await dockerapi.async_docker_client.close()
|
||||
|
||||
# Close redis
|
||||
await dockerapi.pubsub.unsubscribe("MC_CHANNEL")
|
||||
await dockerapi.redis_client.close()
|
||||
|
||||
|
||||
# PubSub Handler
|
||||
async def handle_pubsub_messages(channel: aioredis.client.PubSub):
|
||||
|
||||
@@ -358,8 +358,8 @@ class DockerApi:
|
||||
for line in cmd_response.split("\n"):
|
||||
if '$2$' in line:
|
||||
hash = line.strip()
|
||||
hash_out = re.search('\$2\$.+$', hash).group(0)
|
||||
rspamd_passphrase_hash = re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
|
||||
hash_out = re.search(r'\$2\$.+$', hash).group(0)
|
||||
rspamd_passphrase_hash = re.sub(r'[^0-9a-zA-Z\$]+', '', hash_out.rstrip())
|
||||
rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc"
|
||||
cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename)
|
||||
cmd_response = self.exec_cmd_container(container, cmd, user="_rspamd")
|
||||
|
||||
@@ -1,119 +1,116 @@
|
||||
FROM debian:bullseye-slim
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
FROM alpine:3.20
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
# renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG DOVECOT=2.3.21
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
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
|
||||
ENV LC_ALL C
|
||||
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
# Add groups and users before installing Dovecot to not break compatibility
|
||||
RUN groupadd -g 5000 vmail \
|
||||
&& groupadd -g 401 dovecot \
|
||||
&& groupadd -g 402 dovenull \
|
||||
&& groupadd -g 999 sogo \
|
||||
&& usermod -a -G sogo nobody \
|
||||
&& useradd -g vmail -u 5000 vmail -d /var/vmail \
|
||||
&& useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \
|
||||
&& useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull \
|
||||
&& touch /etc/default/locale \
|
||||
&& apt-get update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
build-essential \
|
||||
apt-transport-https \
|
||||
RUN addgroup -g 5000 vmail \
|
||||
&& addgroup -g 401 dovecot \
|
||||
&& addgroup -g 402 dovenull \
|
||||
&& sed -i "s/999/99/" /etc/group \
|
||||
&& addgroup -g 999 sogo \
|
||||
&& addgroup nobody sogo \
|
||||
&& adduser -D -u 5000 -G vmail -h /var/vmail vmail \
|
||||
&& adduser -D -G dovecot -u 401 -h /dev/null -s /sbin/nologin dovecot \
|
||||
&& adduser -D -G dovenull -u 402 -h /dev/null -s /sbin/nologin dovenull \
|
||||
&& apk add --no-cache --update \
|
||||
bash \
|
||||
bind-tools \
|
||||
findutils \
|
||||
envsubst \
|
||||
ca-certificates \
|
||||
cpanminus \
|
||||
curl \
|
||||
dnsutils \
|
||||
dirmngr \
|
||||
gettext \
|
||||
gnupg2 \
|
||||
coreutils \
|
||||
jq \
|
||||
libauthen-ntlm-perl \
|
||||
libcgi-pm-perl \
|
||||
libcrypt-openssl-rsa-perl \
|
||||
libcrypt-ssleay-perl \
|
||||
libdata-uniqid-perl \
|
||||
libdbd-mysql-perl \
|
||||
libdbi-perl \
|
||||
libdigest-hmac-perl \
|
||||
libdist-checkconflicts-perl \
|
||||
libencode-imaputf7-perl \
|
||||
libfile-copy-recursive-perl \
|
||||
libfile-tail-perl \
|
||||
libhtml-parser-perl \
|
||||
libio-compress-perl \
|
||||
libio-socket-inet6-perl \
|
||||
libio-socket-ssl-perl \
|
||||
libio-tee-perl \
|
||||
libipc-run-perl \
|
||||
libjson-webtoken-perl \
|
||||
liblockfile-simple-perl \
|
||||
libmail-imapclient-perl \
|
||||
libmodule-implementation-perl \
|
||||
libmodule-scandeps-perl \
|
||||
libnet-ssleay-perl \
|
||||
libpackage-stash-perl \
|
||||
libpackage-stash-xs-perl \
|
||||
libpar-packer-perl \
|
||||
libparse-recdescent-perl \
|
||||
libproc-processtable-perl \
|
||||
libreadonly-perl \
|
||||
libregexp-common-perl \
|
||||
libssl-dev \
|
||||
libsys-meminfo-perl \
|
||||
libterm-readkey-perl \
|
||||
libtest-deep-perl \
|
||||
libtest-fatal-perl \
|
||||
libtest-mock-guard-perl \
|
||||
libtest-mockobject-perl \
|
||||
libtest-nowarnings-perl \
|
||||
libtest-pod-perl \
|
||||
libtest-requires-perl \
|
||||
libtest-simple-perl \
|
||||
libtest-warn-perl \
|
||||
libtry-tiny-perl \
|
||||
libunicode-string-perl \
|
||||
liburi-perl \
|
||||
libwww-perl \
|
||||
lua-sql-mysql \
|
||||
lua \
|
||||
lua-cjson \
|
||||
lua-socket \
|
||||
lua-sql-mysql \
|
||||
lua5.3-sql-mysql \
|
||||
icu-data-full \
|
||||
mariadb-connector-c \
|
||||
gcompat \
|
||||
mariadb-client \
|
||||
perl \
|
||||
perl-ntlm \
|
||||
perl-cgi \
|
||||
perl-crypt-openssl-rsa \
|
||||
perl-utils \
|
||||
perl-crypt-ssleay \
|
||||
perl-data-uniqid \
|
||||
perl-dbd-mysql \
|
||||
perl-dbi \
|
||||
perl-digest-hmac \
|
||||
perl-dist-checkconflicts \
|
||||
perl-encode-imaputf7 \
|
||||
perl-file-copy-recursive \
|
||||
perl-file-tail \
|
||||
perl-io-socket-inet6 \
|
||||
perl-io-gzip \
|
||||
perl-io-socket-ssl \
|
||||
perl-io-tee \
|
||||
perl-ipc-run \
|
||||
perl-json-webtoken \
|
||||
perl-mail-imapclient \
|
||||
perl-module-implementation \
|
||||
perl-module-scandeps \
|
||||
perl-net-ssleay \
|
||||
perl-package-stash \
|
||||
perl-package-stash-xs \
|
||||
perl-par-packer \
|
||||
perl-parse-recdescent \
|
||||
perl-lockfile-simple \
|
||||
libproc \
|
||||
perl-readonly \
|
||||
perl-regexp-common \
|
||||
perl-sys-meminfo \
|
||||
perl-term-readkey \
|
||||
perl-test-deep \
|
||||
perl-test-fatal \
|
||||
perl-test-mockobject \
|
||||
perl-test-mock-guard \
|
||||
perl-test-pod \
|
||||
perl-test-requires \
|
||||
perl-test-simple \
|
||||
perl-test-warn \
|
||||
perl-try-tiny \
|
||||
perl-unicode-string \
|
||||
perl-proc-processtable \
|
||||
perl-app-cpanminus \
|
||||
procps \
|
||||
python3-pip \
|
||||
redis-server \
|
||||
supervisor \
|
||||
python3 \
|
||||
py3-mysqlclient \
|
||||
py3-html2text \
|
||||
py3-jinja2 \
|
||||
py3-redis \
|
||||
redis \
|
||||
syslog-ng \
|
||||
syslog-ng-core \
|
||||
syslog-ng-mod-redis \
|
||||
syslog-ng-redis \
|
||||
syslog-ng-json \
|
||||
supervisor \
|
||||
tzdata \
|
||||
wget \
|
||||
&& dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
|
||||
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
|
||||
&& chmod +x /usr/local/bin/gosu \
|
||||
&& gosu nobody true \
|
||||
&& apt-key adv --fetch-keys https://repo.dovecot.org/DOVECOT-REPO-GPG \
|
||||
&& echo "deb https://repo.dovecot.org/ce-${DOVECOT}/debian/bullseye bullseye main" > /etc/apt/sources.list.d/dovecot.list \
|
||||
&& apt-get update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
dovecot-lua \
|
||||
dovecot-managesieved \
|
||||
dovecot-sieve \
|
||||
dovecot \
|
||||
dovecot-dev \
|
||||
dovecot-lmtpd \
|
||||
dovecot-lua \
|
||||
dovecot-ldap \
|
||||
dovecot-mysql \
|
||||
dovecot-core \
|
||||
dovecot-sql \
|
||||
dovecot-submissiond \
|
||||
dovecot-pigeonhole-plugin \
|
||||
dovecot-pop3d \
|
||||
dovecot-imapd \
|
||||
dovecot-solr \
|
||||
&& pip3 install mysql-connector-python html2text jinja2 redis \
|
||||
&& apt-get autoremove --purge -y \
|
||||
&& apt-get autoclean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm -rf /tmp/* /var/tmp/* /root/.cache/
|
||||
# imapsync dependencies
|
||||
RUN cpan Crypt::OpenSSL::PKCS12
|
||||
dovecot-fts-solr \
|
||||
dovecot-fts-flatcurve \
|
||||
&& arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \
|
||||
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$arch" \
|
||||
&& chmod +x /usr/local/bin/gosu \
|
||||
&& gosu nobody true
|
||||
|
||||
COPY trim_logs.sh /usr/local/bin/trim_logs.sh
|
||||
COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh
|
||||
@@ -133,6 +130,7 @@ COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
|
||||
COPY quarantine_notify.py /usr/local/bin/quarantine_notify.py
|
||||
COPY quota_notify.py /usr/local/bin/quota_notify.py
|
||||
COPY repl_health.sh /usr/local/bin/repl_health.sh
|
||||
COPY optimize-fts.sh /usr/local/bin/optimize-fts.sh
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
|
||||
|
||||
@@ -29,6 +29,7 @@ ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||
# Create missing directories
|
||||
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
|
||||
[[ ! -d /etc/dovecot/lua/ ]] && mkdir -p /etc/dovecot/lua/
|
||||
[[ ! -d /etc/dovecot/conf.d/ ]] && mkdir -p /etc/dovecot/conf.d/
|
||||
[[ ! -d /var/vmail/_garbage ]] && mkdir -p /var/vmail/_garbage
|
||||
[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve
|
||||
[[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo
|
||||
@@ -109,7 +110,14 @@ EOF
|
||||
|
||||
echo -n ${ACL_ANYONE} > /etc/dovecot/acl_anyone
|
||||
|
||||
if [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
if [[ "${FLATCURVE_EXPERIMENTAL}" =~ ^([yY][eE][sS]|[yY]) ]]; then
|
||||
echo -e "\e[33mActivating Flatcurve as FTS Backend...\e[0m"
|
||||
echo -e "\e[33mDepending on your previous setup a full reindex might be needed... \e[0m"
|
||||
echo -e "\e[34mVisit https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-fts/#fts-related-dovecot-commands to learn how to reindex\e[0m"
|
||||
echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins
|
||||
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins_imap
|
||||
echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_flatcurve notify listescape replication' > /etc/dovecot/mail_plugins_lmtp
|
||||
elif [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication' > /etc/dovecot/mail_plugins
|
||||
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify listescape replication mail_log' > /etc/dovecot/mail_plugins_imap
|
||||
echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl notify listescape replication' > /etc/dovecot/mail_plugins_lmtp
|
||||
@@ -239,6 +247,51 @@ function script_deinit()
|
||||
end
|
||||
EOF
|
||||
|
||||
# Temporarily set FTS depending on user choice inside mailcow.conf. Will be removed as soon as Solr is dropped
|
||||
if [[ "${FLATCURVE_EXPERIMENTAL}" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
||||
cat <<EOF > /etc/dovecot/conf.d/fts.conf
|
||||
# Autogenerated by mailcow
|
||||
plugin {
|
||||
fts_autoindex = yes
|
||||
fts_autoindex_exclude = \Junk
|
||||
fts_autoindex_exclude2 = \Trash
|
||||
fts = flatcurve
|
||||
|
||||
# Maximum term length can be set via the 'maxlen' argument (maxlen is
|
||||
# specified in bytes, not number of UTF-8 characters)
|
||||
fts_tokenizer_email_address = maxlen=100
|
||||
fts_tokenizer_generic = algorithm=simple maxlen=30
|
||||
|
||||
# These are not flatcurve settings, but required for Dovecot FTS. See
|
||||
# Dovecot FTS Configuration link above for further information.
|
||||
fts_languages = en es de
|
||||
fts_tokenizers = generic email-address
|
||||
|
||||
# OPTIONAL: Recommended default FTS core configuration
|
||||
fts_filters = normalizer-icu snowball stopwords
|
||||
fts_filters_en = lowercase snowball english-possessive stopwords
|
||||
}
|
||||
EOF
|
||||
elif [[ ! "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
||||
cat <<EOF > /etc/dovecot/conf.d/fts.conf
|
||||
# Autogenerated by mailcow
|
||||
plugin {
|
||||
fts = solr
|
||||
fts_autoindex = yes
|
||||
fts_autoindex_exclude = \Junk
|
||||
fts_autoindex_exclude2 = \Trash
|
||||
fts_solr = url=http://solr:8983/solr/dovecot-fts/
|
||||
|
||||
fts_tokenizers = generic email-address
|
||||
fts_tokenizer_generic = algorithm=simple
|
||||
|
||||
fts_filters = normalizer-icu snowball stopwords
|
||||
fts_filters_en = lowercase snowball english-possessive stopwords
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
|
||||
# Replace patterns in app-passdb.lua
|
||||
sed -i "s/__DBUSER__/${DBUSER}/g" /etc/dovecot/lua/passwd-verify.lua
|
||||
sed -i "s/__DBPASS__/${DBPASS}/g" /etc/dovecot/lua/passwd-verify.lua
|
||||
@@ -335,6 +388,14 @@ sys.exit()
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Set mail_replica for HA setups
|
||||
if [[ -n ${MAILCOW_REPLICA_IP} && -n ${DOVEADM_REPLICA_PORT} ]]; then
|
||||
cat <<EOF > /etc/dovecot/mail_replica.conf
|
||||
# Autogenerated by mailcow
|
||||
mail_replica = tcp:${MAILCOW_REPLICA_IP}:${DOVEADM_REPLICA_PORT}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# 401 is user dovecot
|
||||
if [[ ! -s /mail_crypt/ecprivkey.pem || ! -s /mail_crypt/ecpubkey.pem ]]; then
|
||||
openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem
|
||||
@@ -350,14 +411,6 @@ sievec /var/vmail/sieve/global_sieve_after.sieve
|
||||
sievec /usr/lib/dovecot/sieve/report-spam.sieve
|
||||
sievec /usr/lib/dovecot/sieve/report-ham.sieve
|
||||
|
||||
for file in /var/vmail/*/*/sieve/*.sieve ; do
|
||||
if [[ "$file" == "/var/vmail/*/*/sieve/*.sieve" ]]; then
|
||||
continue
|
||||
fi
|
||||
sievec "$file" "$(dirname "$file")/../.dovecot.svbin"
|
||||
chown vmail:vmail "$(dirname "$file")/../.dovecot.svbin"
|
||||
done
|
||||
|
||||
# Fix permissions
|
||||
chown root:root /etc/dovecot/sql/*.conf
|
||||
chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/lua/passwd-verify.lua
|
||||
@@ -378,7 +431,8 @@ chmod +x /usr/lib/dovecot/sieve/rspamd-pipe-ham \
|
||||
/usr/local/bin/maildir_gc.sh \
|
||||
/usr/local/sbin/stop-supervisor.sh \
|
||||
/usr/local/bin/quota_notify.py \
|
||||
/usr/local/bin/repl_health.sh
|
||||
/usr/local/bin/repl_health.sh \
|
||||
/usr/local/bin/optimize-fts.sh
|
||||
|
||||
# Prepare environment file for cronjobs
|
||||
printenv | sed 's/^\(.*\)$/export \1/g' > /source_env.sh
|
||||
@@ -432,4 +486,8 @@ done
|
||||
# May be related to something inside Docker, I seriously don't know
|
||||
touch /etc/dovecot/lua/passwd-verify.lua
|
||||
|
||||
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
|
||||
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
||||
7
data/Dockerfiles/dovecot/optimize-fts.sh
Normal file
7
data/Dockerfiles/dovecot/optimize-fts.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ && ! "${FLATCURVE_EXPERIMENTAL}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
exit 0
|
||||
else
|
||||
doveadm fts optimize -A
|
||||
fi
|
||||
@@ -3,11 +3,10 @@
|
||||
import smtplib
|
||||
import os
|
||||
import sys
|
||||
import mysql.connector
|
||||
import MySQLdb
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import COMMASPACE, formatdate
|
||||
import cgi
|
||||
import jinja2
|
||||
from jinja2 import Template
|
||||
import json
|
||||
@@ -50,7 +49,7 @@ try:
|
||||
def query_mysql(query, headers = True, update = False):
|
||||
while True:
|
||||
try:
|
||||
cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user=os.environ.get('DBUSER'), passwd=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci")
|
||||
cnx = MySQLdb.connect(user=os.environ.get('DBUSER'), password=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci")
|
||||
except Exception as ex:
|
||||
print('%s - trying again...' % (ex))
|
||||
time.sleep(3)
|
||||
|
||||
@@ -55,7 +55,7 @@ try:
|
||||
msg.attach(text_part)
|
||||
msg.attach(html_part)
|
||||
msg['To'] = username
|
||||
p = Popen(['/usr/lib/dovecot/dovecot-lda', '-d', username, '-o', '"plugin/quota=maildir:User quota:noenforcing"'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
|
||||
p = Popen(['/usr/libexec/dovecot/dovecot-lda', '-d', username, '-o', '"plugin/quota=maildir:User quota:noenforcing"'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
|
||||
p.communicate(input=bytes(msg.as_string(), 'utf-8'))
|
||||
|
||||
domain = username.split("@")[-1]
|
||||
|
||||
@@ -11,7 +11,7 @@ fi
|
||||
|
||||
# Is replication active?
|
||||
# grep on file is less expensive than doveconf
|
||||
if ! grep -qi mail_replica /etc/dovecot/dovecot.conf; then
|
||||
if [ -n ${MAILCOW_REPLICA_IP} ]; then
|
||||
${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
|
||||
exit
|
||||
fi
|
||||
|
||||
@@ -3,8 +3,8 @@ FILE=/tmp/mail$$
|
||||
cat > $FILE
|
||||
trap "/bin/rm -f $FILE" 0 1 2 3 13 15
|
||||
|
||||
cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzydel
|
||||
cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnham
|
||||
cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd
|
||||
cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzydel
|
||||
cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/learnham
|
||||
cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzyadd
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -3,8 +3,8 @@ FILE=/tmp/mail$$
|
||||
cat > $FILE
|
||||
trap "/bin/rm -f $FILE" 0 1 2 3 13 15
|
||||
|
||||
cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzydel
|
||||
cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnspam
|
||||
cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd
|
||||
cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzydel
|
||||
cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/learnspam
|
||||
cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzyadd
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -11,7 +11,7 @@ else
|
||||
fi
|
||||
|
||||
# Deploy
|
||||
curl --connect-timeout 15 --retry 10 --max-time 30 http://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"' | tr -dc '0-9').tar.gz --output /tmp/sa-rules-heinlein.tar.gz
|
||||
curl --connect-timeout 15 --retry 10 --max-time 30 https://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"' | tr -dc '0-9').tar.gz --output /tmp/sa-rules-heinlein.tar.gz
|
||||
if gzip -t /tmp/sa-rules-heinlein.tar.gz; then
|
||||
tar xfvz /tmp/sa-rules-heinlein.tar.gz -C /tmp/sa-rules-heinlein
|
||||
cat /tmp/sa-rules-heinlein/*cf > /etc/rspamd/custom/sa-rules
|
||||
@@ -21,11 +21,11 @@ sed -i -e 's/\([^\\]\)\$\([^\/]\)/\1\\$\2/g' /etc/rspamd/custom/sa-rules
|
||||
|
||||
if [[ "$(cat /etc/rspamd/custom/sa-rules | md5sum | cut -d' ' -f1)" != "${HASH_SA_RULES}" ]]; then
|
||||
CONTAINER_NAME=rspamd-mailcow
|
||||
CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | \
|
||||
CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | \
|
||||
jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | \
|
||||
jq -rc "select( .name | tostring | contains(\"${CONTAINER_NAME}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id")
|
||||
if [[ ! -z ${CONTAINER_ID} ]]; then
|
||||
curl --silent --insecure -XPOST --connect-timeout 15 --max-time 120 https://dockerapi/containers/${CONTAINER_ID}/restart
|
||||
curl --silent --insecure -XPOST --connect-timeout 15 --max-time 120 https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/restart
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@ autostart=true
|
||||
|
||||
[program:dovecot]
|
||||
command=/usr/sbin/dovecot -F
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=true
|
||||
|
||||
[eventlistener:processes]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@version: 3.28
|
||||
@version: 4.5
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
@@ -6,11 +6,12 @@ options {
|
||||
use_dns(no);
|
||||
use_fqdn(no);
|
||||
owner("root"); group("adm"); perm(0640);
|
||||
stats_freq(0);
|
||||
stats(freq(0));
|
||||
keep_timestamp(no);
|
||||
bad_hostname("^gconfd$");
|
||||
};
|
||||
source s_src {
|
||||
unix-stream("/dev/log");
|
||||
source s_dgram {
|
||||
unix-dgram("/dev/log");
|
||||
internal();
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
@@ -36,7 +37,7 @@ filter f_replica {
|
||||
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
|
||||
};
|
||||
log {
|
||||
source(s_src);
|
||||
source(s_dgram);
|
||||
filter(f_replica);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@version: 3.28
|
||||
@version: 4.5
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
@@ -6,11 +6,12 @@ options {
|
||||
use_dns(no);
|
||||
use_fqdn(no);
|
||||
owner("root"); group("adm"); perm(0640);
|
||||
stats_freq(0);
|
||||
stats(freq(0));
|
||||
keep_timestamp(no);
|
||||
bad_hostname("^gconfd$");
|
||||
};
|
||||
source s_src {
|
||||
unix-stream("/dev/log");
|
||||
source s_dgram {
|
||||
unix-dgram("/dev/log");
|
||||
internal();
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
@@ -36,7 +37,7 @@ filter f_replica {
|
||||
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
|
||||
};
|
||||
log {
|
||||
source(s_src);
|
||||
source(s_dgram);
|
||||
filter(f_replica);
|
||||
destination(d_stdout);
|
||||
filter(f_mail);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
FROM alpine:3.17
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
FROM alpine:3.20
|
||||
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
ENV XTABLES_LIBDIR /usr/lib/xtables
|
||||
ENV PYTHON_IPTABLES_XTABLES_VERSION 12
|
||||
ENV IPTABLES_LIBDIR /usr/lib
|
||||
@@ -14,6 +16,7 @@ RUN apk add --virtual .build-deps \
|
||||
openssl-dev \
|
||||
&& apk add -U python3 \
|
||||
iptables \
|
||||
iptables-dev \
|
||||
ip6tables \
|
||||
xtables-addons \
|
||||
nftables \
|
||||
|
||||
@@ -21,28 +21,6 @@ from modules.IPTables import IPTables
|
||||
from modules.NFTables import NFTables
|
||||
|
||||
|
||||
# connect to redis
|
||||
while True:
|
||||
try:
|
||||
redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '')
|
||||
redis_slaveof_port = os.getenv('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)
|
||||
else:
|
||||
r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0)
|
||||
r.ping()
|
||||
except Exception as ex:
|
||||
print('%s - trying again in 3 seconds' % (ex))
|
||||
time.sleep(3)
|
||||
else:
|
||||
break
|
||||
pubsub = r.pubsub()
|
||||
|
||||
# rename fail2ban to netfilter
|
||||
if r.exists('F2B_LOG'):
|
||||
r.rename('F2B_LOG', 'NETFILTER_LOG')
|
||||
|
||||
|
||||
# globals
|
||||
WHITELIST = []
|
||||
BLACKLIST= []
|
||||
@@ -50,18 +28,10 @@ bans = {}
|
||||
quit_now = False
|
||||
exit_code = 0
|
||||
lock = Lock()
|
||||
|
||||
|
||||
# init Logger
|
||||
logger = Logger(r)
|
||||
# init backend
|
||||
backend = sys.argv[1]
|
||||
if backend == "nftables":
|
||||
logger.logInfo('Using NFTables backend')
|
||||
tables = NFTables("MAILCOW", logger)
|
||||
else:
|
||||
logger.logInfo('Using IPTables backend')
|
||||
tables = IPTables("MAILCOW", logger)
|
||||
chain_name = "MAILCOW"
|
||||
r = None
|
||||
pubsub = None
|
||||
clear_before_quit = False
|
||||
|
||||
|
||||
def refreshF2boptions():
|
||||
@@ -110,16 +80,16 @@ def refreshF2bregex():
|
||||
global exit_code
|
||||
if not r.get('F2B_REGEX'):
|
||||
f2bregex = {}
|
||||
f2bregex[1] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
|
||||
f2bregex[2] = 'Rspamd UI: Invalid password by ([0-9a-f\.:]+)'
|
||||
f2bregex[3] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+'
|
||||
f2bregex[4] = 'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+'
|
||||
f2bregex[5] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+'
|
||||
f2bregex[6] = '-login: Disconnected.+ \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),'
|
||||
f2bregex[7] = '-login: Aborted login.+ \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
|
||||
f2bregex[8] = '-login: Aborted login.+ \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
|
||||
f2bregex[9] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
|
||||
f2bregex[10] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
|
||||
f2bregex[1] = r'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
|
||||
f2bregex[2] = r'Rspamd UI: Invalid password by ([0-9a-f\.:]+)'
|
||||
f2bregex[3] = r'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+'
|
||||
f2bregex[4] = r'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+'
|
||||
f2bregex[5] = r'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+'
|
||||
f2bregex[6] = r'-login: Disconnected.+ \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),'
|
||||
f2bregex[7] = r'-login: Aborted login.+ \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
|
||||
f2bregex[8] = r'-login: Aborted login.+ \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
|
||||
f2bregex[9] = r'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
|
||||
f2bregex[10] = r'([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
|
||||
r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False))
|
||||
else:
|
||||
try:
|
||||
@@ -144,8 +114,6 @@ def ban(address):
|
||||
global lock
|
||||
|
||||
refreshF2boptions()
|
||||
BAN_TIME = int(f2boptions['ban_time'])
|
||||
BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
|
||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||
RETRY_WINDOW = int(f2boptions['retry_window'])
|
||||
NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4'])
|
||||
@@ -180,7 +148,7 @@ def ban(address):
|
||||
|
||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||
cur_time = int(round(time.time()))
|
||||
NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter']
|
||||
NET_BAN_TIME = calcNetBanTime(bans[net]['ban_counter'])
|
||||
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:
|
||||
@@ -250,17 +218,21 @@ def clear():
|
||||
with lock:
|
||||
tables.clearIPv4Table()
|
||||
tables.clearIPv6Table()
|
||||
r.delete('F2B_ACTIVE_BANS')
|
||||
r.delete('F2B_PERM_BANS')
|
||||
pubsub.unsubscribe()
|
||||
try:
|
||||
if r is not None:
|
||||
r.delete('F2B_ACTIVE_BANS')
|
||||
r.delete('F2B_PERM_BANS')
|
||||
except Exception as ex:
|
||||
logger.logWarn('Error clearing redis keys F2B_ACTIVE_BANS and F2B_PERM_BANS: %s' % ex)
|
||||
|
||||
def watch():
|
||||
logger.logInfo('Watching Redis channel F2B_CHANNEL')
|
||||
pubsub.subscribe('F2B_CHANNEL')
|
||||
|
||||
global pubsub
|
||||
global quit_now
|
||||
global exit_code
|
||||
|
||||
logger.logInfo('Watching Redis channel F2B_CHANNEL')
|
||||
pubsub.subscribe('F2B_CHANNEL')
|
||||
|
||||
while not quit_now:
|
||||
try:
|
||||
for item in pubsub.listen():
|
||||
@@ -280,6 +252,7 @@ def watch():
|
||||
ban(addr)
|
||||
except Exception as ex:
|
||||
logger.logWarn('Error reading log line from pubsub: %s' % ex)
|
||||
pubsub = None
|
||||
quit_now = True
|
||||
exit_code = 2
|
||||
|
||||
@@ -302,12 +275,11 @@ def snat6(snat_target):
|
||||
tables.snat6(snat_target, os.getenv('IPV6_NETWORK', 'fd4d:6169:6c63:6f77::/64'))
|
||||
|
||||
def autopurge():
|
||||
global f2boptions
|
||||
|
||||
while not quit_now:
|
||||
time.sleep(10)
|
||||
refreshF2boptions()
|
||||
BAN_TIME = int(f2boptions['ban_time'])
|
||||
MAX_BAN_TIME = int(f2boptions['max_ban_time'])
|
||||
BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
|
||||
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
|
||||
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
|
||||
if QUEUE_UNBAN:
|
||||
@@ -315,9 +287,9 @@ def autopurge():
|
||||
unban(str(net))
|
||||
for net in bans.copy():
|
||||
if bans[net]['attempts'] >= MAX_ATTEMPTS:
|
||||
NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** bans[net]['ban_counter']
|
||||
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 or TIME_SINCE_LAST_ATTEMPT > MAX_BAN_TIME:
|
||||
if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME:
|
||||
unban(net)
|
||||
|
||||
def mailcowChainOrder():
|
||||
@@ -331,6 +303,16 @@ def mailcowChainOrder():
|
||||
if quit_now: return
|
||||
quit_now, exit_code = tables.checkIPv6ChainOrder()
|
||||
|
||||
def calcNetBanTime(ban_counter):
|
||||
global f2boptions
|
||||
|
||||
BAN_TIME = int(f2boptions['ban_time'])
|
||||
MAX_BAN_TIME = int(f2boptions['max_ban_time'])
|
||||
BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment'])
|
||||
NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** ban_counter
|
||||
NET_BAN_TIME = max([BAN_TIME, min([NET_BAN_TIME, MAX_BAN_TIME])])
|
||||
return NET_BAN_TIME
|
||||
|
||||
def isIpNetwork(address):
|
||||
try:
|
||||
ipaddress.ip_network(address, False)
|
||||
@@ -403,21 +385,76 @@ def blacklistUpdate():
|
||||
permBan(net=net, unban=True)
|
||||
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
|
||||
|
||||
def quit(signum, frame):
|
||||
global quit_now
|
||||
quit_now = True
|
||||
def sigterm_quit(signum, frame):
|
||||
global clear_before_quit
|
||||
clear_before_quit = True
|
||||
sys.exit(exit_code)
|
||||
|
||||
def berfore_quit():
|
||||
if clear_before_quit:
|
||||
clear()
|
||||
if pubsub is not None:
|
||||
pubsub.unsubscribe()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
refreshF2boptions()
|
||||
atexit.register(berfore_quit)
|
||||
signal.signal(signal.SIGTERM, sigterm_quit)
|
||||
|
||||
# init Logger
|
||||
logger = Logger()
|
||||
|
||||
# init backend
|
||||
backend = sys.argv[1]
|
||||
if backend == "nftables":
|
||||
logger.logInfo('Using NFTables backend')
|
||||
tables = NFTables(chain_name, logger)
|
||||
else:
|
||||
logger.logInfo('Using IPTables backend')
|
||||
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"):
|
||||
logger.logInfo(f"Skipping {chain_name} isolation")
|
||||
else:
|
||||
logger.logInfo(f"Setting {chain_name} isolation")
|
||||
tables.create_mailcow_isolation_rule("br-mailcow", [3306, 6379, 8983, 12345], os.getenv("MAILCOW_REPLICA_IP"))
|
||||
|
||||
# connect to redis
|
||||
while True:
|
||||
try:
|
||||
redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '')
|
||||
redis_slaveof_port = os.getenv('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)
|
||||
else:
|
||||
r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0)
|
||||
r.ping()
|
||||
pubsub = r.pubsub()
|
||||
except Exception as ex:
|
||||
print('%s - trying again in 3 seconds' % (ex))
|
||||
time.sleep(3)
|
||||
else:
|
||||
break
|
||||
logger.set_redis(r)
|
||||
|
||||
# rename fail2ban to netfilter
|
||||
if r.exists('F2B_LOG'):
|
||||
r.rename('F2B_LOG', 'NETFILTER_LOG')
|
||||
# clear bans in redis
|
||||
r.delete('F2B_ACTIVE_BANS')
|
||||
r.delete('F2B_PERM_BANS')
|
||||
|
||||
refreshF2boptions()
|
||||
|
||||
watch_thread = Thread(target=watch)
|
||||
watch_thread.daemon = True
|
||||
watch_thread.start()
|
||||
@@ -460,9 +497,6 @@ if __name__ == '__main__':
|
||||
whitelistupdate_thread.daemon = True
|
||||
whitelistupdate_thread.start()
|
||||
|
||||
signal.signal(signal.SIGTERM, quit)
|
||||
atexit.register(clear)
|
||||
|
||||
while not quit_now:
|
||||
time.sleep(0.5)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import iptc
|
||||
import time
|
||||
import os
|
||||
|
||||
class IPTables:
|
||||
def __init__(self, chain_name, logger):
|
||||
@@ -211,3 +212,41 @@ class IPTables:
|
||||
target = rule.create_target("SNAT")
|
||||
target.to_source = snat_target
|
||||
return rule
|
||||
|
||||
def create_mailcow_isolation_rule(self, _interface:str, _dports:list, _allow:str = ""):
|
||||
try:
|
||||
chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name)
|
||||
|
||||
# insert mailcow isolation rule
|
||||
rule = iptc.Rule()
|
||||
rule.in_interface = f'!{_interface}'
|
||||
rule.out_interface = _interface
|
||||
rule.protocol = 'tcp'
|
||||
rule.create_target("DROP")
|
||||
match = rule.create_match("multiport")
|
||||
match.dports = ','.join(map(str, _dports))
|
||||
|
||||
if rule in chain.rules:
|
||||
chain.delete_rule(rule)
|
||||
chain.insert_rule(rule, position=0)
|
||||
|
||||
# insert mailcow isolation exception rule
|
||||
if _allow != "":
|
||||
rule = iptc.Rule()
|
||||
rule.src = _allow
|
||||
rule.in_interface = f'!{_interface}'
|
||||
rule.out_interface = _interface
|
||||
rule.protocol = 'tcp'
|
||||
rule.create_target("ACCEPT")
|
||||
match = rule.create_match("multiport")
|
||||
match.dports = ','.join(map(str, _dports))
|
||||
|
||||
if rule in chain.rules:
|
||||
chain.delete_rule(rule)
|
||||
chain.insert_rule(rule, position=0)
|
||||
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.logCrit(f"Error adding {self.chain_name} isolation: {e}")
|
||||
return False
|
||||
@@ -2,7 +2,10 @@ import time
|
||||
import json
|
||||
|
||||
class Logger:
|
||||
def __init__(self, redis):
|
||||
def __init__(self):
|
||||
self.r = None
|
||||
|
||||
def set_redis(self, redis):
|
||||
self.r = redis
|
||||
|
||||
def log(self, priority, message):
|
||||
@@ -10,8 +13,12 @@ class Logger:
|
||||
tolog['time'] = int(round(time.time()))
|
||||
tolog['priority'] = priority
|
||||
tolog['message'] = message
|
||||
self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False))
|
||||
print(message)
|
||||
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))
|
||||
|
||||
def logWarn(self, message):
|
||||
self.log('warn', message)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import nftables
|
||||
import ipaddress
|
||||
import os
|
||||
|
||||
class NFTables:
|
||||
def __init__(self, chain_name, logger):
|
||||
@@ -40,6 +41,7 @@ class NFTables:
|
||||
exit_code = 2
|
||||
|
||||
if chain_position > 0:
|
||||
chain_position += 1
|
||||
self.logger.logCrit(f'MAILCOW target is in position {chain_position} in the {filter_table} {chain} table, restarting container to fix it...')
|
||||
err = True
|
||||
exit_code = 2
|
||||
@@ -266,6 +268,17 @@ class NFTables:
|
||||
|
||||
return self.nft_exec_dict(delete_command)
|
||||
|
||||
def delete_filter_rule(self, _family:str, _chain: str, _handle:str):
|
||||
delete_command = self.get_base_dict()
|
||||
_rule_opts = {'family': _family,
|
||||
'table': 'filter',
|
||||
'chain': _chain,
|
||||
'handle': _handle }
|
||||
_delete = {'delete': {'rule': _rule_opts} }
|
||||
delete_command["nftables"].append(_delete)
|
||||
|
||||
return self.nft_exec_dict(delete_command)
|
||||
|
||||
def snat_rule(self, _family: str, snat_target: str, source_address: str):
|
||||
chain_name = self.nft_chain_names[_family]['nat']['postrouting']
|
||||
|
||||
@@ -297,8 +310,8 @@ class NFTables:
|
||||
rule_handle = rule["handle"]
|
||||
break
|
||||
|
||||
dest_net = ipaddress.ip_network(source_address)
|
||||
target_net = ipaddress.ip_network(snat_target)
|
||||
dest_net = ipaddress.ip_network(source_address, strict=False)
|
||||
target_net = ipaddress.ip_network(snat_target, strict=False)
|
||||
|
||||
if rule_found:
|
||||
saddr_ip = rule["expr"][0]["match"]["right"]["prefix"]["addr"]
|
||||
@@ -309,9 +322,9 @@ class NFTables:
|
||||
|
||||
target_ip = rule["expr"][3]["snat"]["addr"]
|
||||
|
||||
saddr_net = ipaddress.ip_network(saddr_ip + '/' + str(saddr_len))
|
||||
daddr_net = ipaddress.ip_network(daddr_ip + '/' + str(daddr_len))
|
||||
current_target_net = ipaddress.ip_network(target_ip)
|
||||
saddr_net = ipaddress.ip_network(saddr_ip + '/' + str(saddr_len), strict=False)
|
||||
daddr_net = ipaddress.ip_network(daddr_ip + '/' + str(daddr_len), strict=False)
|
||||
current_target_net = ipaddress.ip_network(target_ip, strict=False)
|
||||
|
||||
match = all((
|
||||
dest_net == saddr_net,
|
||||
@@ -381,7 +394,7 @@ class NFTables:
|
||||
break
|
||||
return chain_handle
|
||||
|
||||
def get_rules_handle(self, _family: str, _table: str, chain_name: str):
|
||||
def get_rules_handle(self, _family: str, _table: str, chain_name: str, _comment_filter = "mailcow"):
|
||||
rule_handle = []
|
||||
# Command: 'nft list chain {family} {table} {chain_name}'
|
||||
_chain_opts = {'family': _family, 'table': _table, 'name': chain_name}
|
||||
@@ -397,7 +410,7 @@ class NFTables:
|
||||
|
||||
rule = _object["rule"]
|
||||
if rule["family"] == _family and rule["table"] == _table and rule["chain"] == chain_name:
|
||||
if rule.get("comment") and rule["comment"] == "mailcow":
|
||||
if rule.get("comment") and rule["comment"] == _comment_filter:
|
||||
rule_handle.append(rule["handle"])
|
||||
return rule_handle
|
||||
|
||||
@@ -405,7 +418,7 @@ class NFTables:
|
||||
json_command = self.get_base_dict()
|
||||
|
||||
expr_opt = []
|
||||
ipaddr_net = ipaddress.ip_network(ipaddr)
|
||||
ipaddr_net = ipaddress.ip_network(ipaddr, strict=False)
|
||||
right_dict = {'prefix': {'addr': str(ipaddr_net.network_address), 'len': int(ipaddr_net.prefixlen) } }
|
||||
|
||||
left_dict = {'payload': {'protocol': _family, 'field': 'saddr'} }
|
||||
@@ -439,6 +452,8 @@ class NFTables:
|
||||
continue
|
||||
|
||||
rule = _object["rule"]["expr"][0]["match"]
|
||||
if not "payload" in rule["left"]:
|
||||
continue
|
||||
left_opt = rule["left"]["payload"]
|
||||
if not left_opt["protocol"] == _family:
|
||||
continue
|
||||
@@ -454,7 +469,7 @@ class NFTables:
|
||||
current_rule_net = ipaddress.ip_network(current_rule_ip)
|
||||
|
||||
# ip to ban
|
||||
candidate_net = ipaddress.ip_network(ipaddr)
|
||||
candidate_net = ipaddress.ip_network(ipaddr, strict=False)
|
||||
|
||||
if current_rule_net == candidate_net:
|
||||
rule_handle = _object["rule"]["handle"]
|
||||
@@ -493,3 +508,152 @@ class NFTables:
|
||||
position+=1
|
||||
|
||||
return position if rule_found else False
|
||||
|
||||
def create_mailcow_isolation_rule(self, _interface:str, _dports:list, _allow:str = ""):
|
||||
family = "ip"
|
||||
table = "filter"
|
||||
comment_filter_drop = "mailcow isolation"
|
||||
comment_filter_allow = "mailcow isolation allow"
|
||||
json_command = self.get_base_dict()
|
||||
|
||||
# Delete old mailcow isolation rules
|
||||
handles = self.get_rules_handle(family, table, self.chain_name, comment_filter_drop)
|
||||
for handle in handles:
|
||||
self.delete_filter_rule(family, self.chain_name, handle)
|
||||
handles = self.get_rules_handle(family, table, self.chain_name, comment_filter_allow)
|
||||
for handle in handles:
|
||||
self.delete_filter_rule(family, self.chain_name, handle)
|
||||
|
||||
# insert mailcow isolation rule
|
||||
_match_dict_drop = [
|
||||
{
|
||||
"match": {
|
||||
"op": "!=",
|
||||
"left": {
|
||||
"meta": {
|
||||
"key": "iifname"
|
||||
}
|
||||
},
|
||||
"right": _interface
|
||||
}
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"op": "==",
|
||||
"left": {
|
||||
"meta": {
|
||||
"key": "oifname"
|
||||
}
|
||||
},
|
||||
"right": _interface
|
||||
}
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"op": "==",
|
||||
"left": {
|
||||
"payload": {
|
||||
"protocol": "tcp",
|
||||
"field": "dport"
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
"set": _dports
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"counter": {
|
||||
"packets": 0,
|
||||
"bytes": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"drop": None
|
||||
}
|
||||
]
|
||||
rule_drop = { "insert": { "rule": {
|
||||
"family": family,
|
||||
"table": table,
|
||||
"chain": self.chain_name,
|
||||
"comment": comment_filter_drop,
|
||||
"expr": _match_dict_drop
|
||||
}}}
|
||||
json_command["nftables"].append(rule_drop)
|
||||
|
||||
# insert mailcow isolation allow rule
|
||||
if _allow != "":
|
||||
_match_dict_allow = [
|
||||
{
|
||||
"match": {
|
||||
"op": "==",
|
||||
"left": {
|
||||
"payload": {
|
||||
"protocol": "ip",
|
||||
"field": "saddr"
|
||||
}
|
||||
},
|
||||
"right": _allow
|
||||
}
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"op": "!=",
|
||||
"left": {
|
||||
"meta": {
|
||||
"key": "iifname"
|
||||
}
|
||||
},
|
||||
"right": _interface
|
||||
}
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"op": "==",
|
||||
"left": {
|
||||
"meta": {
|
||||
"key": "oifname"
|
||||
}
|
||||
},
|
||||
"right": _interface
|
||||
}
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"op": "==",
|
||||
"left": {
|
||||
"payload": {
|
||||
"protocol": "tcp",
|
||||
"field": "dport"
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
"set": _dports
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"counter": {
|
||||
"packets": 0,
|
||||
"bytes": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"accept": None
|
||||
}
|
||||
]
|
||||
rule_allow = { "insert": { "rule": {
|
||||
"family": family,
|
||||
"table": table,
|
||||
"chain": self.chain_name,
|
||||
"comment": comment_filter_allow,
|
||||
"expr": _match_dict_allow
|
||||
}}}
|
||||
json_command["nftables"].append(rule_allow)
|
||||
|
||||
success = self.nft_exec_dict(json_command)
|
||||
if success == False:
|
||||
self.logger.logCrit(f"Error adding {self.chain_name} isolation")
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -1,6 +1,8 @@
|
||||
FROM alpine:3.17
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
FROM alpine:3.20
|
||||
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
WORKDIR /app
|
||||
|
||||
#RUN addgroup -S olefy && adduser -S olefy -G olefy \
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
FROM php:8.2-fpm-alpine3.17
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
FROM php:8.2-fpm-alpine3.18
|
||||
|
||||
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.22
|
||||
ARG APCU_PECL_VERSION=5.1.23
|
||||
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG IMAGICK_PECL_VERSION=3.7.0
|
||||
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||
@@ -10,9 +11,9 @@ ARG MAILPARSE_PECL_VERSION=3.1.6
|
||||
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
|
||||
ARG MEMCACHED_PECL_VERSION=3.2.0
|
||||
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG REDIS_PECL_VERSION=6.0.1
|
||||
ARG REDIS_PECL_VERSION=6.0.2
|
||||
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG COMPOSER_VERSION=2.6.5
|
||||
ARG COMPOSER_VERSION=2.6.6
|
||||
|
||||
RUN apk add -U --no-cache autoconf \
|
||||
aspell-dev \
|
||||
|
||||
@@ -23,7 +23,8 @@ done
|
||||
# Check mysql_upgrade (master and slave)
|
||||
CONTAINER_ID=
|
||||
until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do
|
||||
CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null)
|
||||
CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null)
|
||||
echo "Could not get mysql-mailcow container id... trying again"
|
||||
sleep 2
|
||||
done
|
||||
echo "MySQL @ ${CONTAINER_ID}"
|
||||
@@ -34,7 +35,7 @@ until [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; do
|
||||
echo "Tried to upgrade MySQL and failed, giving up after ${SQL_LOOP_C} retries and starting container (oops, not good)"
|
||||
break
|
||||
fi
|
||||
SQL_FULL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json')
|
||||
SQL_FULL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json')
|
||||
SQL_UPGRADE_STATUS=$(echo ${SQL_FULL_UPGRADE_RETURN} | jq -r .type)
|
||||
SQL_LOOP_C=$((SQL_LOOP_C+1))
|
||||
echo "SQL upgrade iteration #${SQL_LOOP_C}"
|
||||
@@ -59,12 +60,12 @@ done
|
||||
|
||||
# doing post-installation stuff, if SQL was upgraded (master and slave)
|
||||
if [ ${SQL_CHANGED} -eq 1 ]; then
|
||||
POSTFIX=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null)
|
||||
POSTFIX=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null)
|
||||
if [[ -z "${POSTFIX}" ]] || ! [[ "${POSTFIX}" =~ ^[[:alnum:]]*$ ]]; then
|
||||
echo "Could not determine Postfix container ID, skipping Postfix restart."
|
||||
else
|
||||
echo "Restarting Postfix"
|
||||
curl -X POST --silent --insecure https://dockerapi/containers/${POSTFIX}/restart | jq -r '.msg'
|
||||
curl -X POST --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/restart | jq -r '.msg'
|
||||
echo "Sleeping 5 seconds..."
|
||||
sleep 5
|
||||
fi
|
||||
@@ -73,7 +74,7 @@ fi
|
||||
# Check mysql tz import (master and slave)
|
||||
TZ_CHECK=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null)
|
||||
if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then
|
||||
SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json')
|
||||
SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json')
|
||||
echo "MySQL mysql_tzinfo_to_sql - debug output:"
|
||||
echo ${SQL_FULL_TZINFO_IMPORT_RETURN}
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
FROM debian:bullseye-slim
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV LC_ALL C
|
||||
|
||||
@@ -415,12 +415,6 @@ postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
|
||||
b.barracudacentral.org=127.0.0.2*7
|
||||
bl.mailspike.net=127.0.0.2*5
|
||||
bl.mailspike.net=127.0.0.[10;11;12]*4
|
||||
dnsbl.sorbs.net=127.0.0.10*8
|
||||
dnsbl.sorbs.net=127.0.0.5*6
|
||||
dnsbl.sorbs.net=127.0.0.7*3
|
||||
dnsbl.sorbs.net=127.0.0.8*2
|
||||
dnsbl.sorbs.net=127.0.0.6*2
|
||||
dnsbl.sorbs.net=127.0.0.9*2
|
||||
EOF
|
||||
fi
|
||||
DNSBL_CONFIG=$(grep -v '^#' /opt/postfix/conf/dns_blocklists.cf | grep '\S')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@version: 3.28
|
||||
@version: 3.38
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@version: 3.28
|
||||
@version: 3.38
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
FROM debian:bullseye-slim
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
FROM debian:bookworm-slim
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG CODENAME=bullseye
|
||||
ENV LC_ALL C
|
||||
ARG RSPAMD_VER=rspamd_3.9.1-1~82f43560f
|
||||
ARG CODENAME=bookworm
|
||||
ENV LC_ALL=C
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
tzdata \
|
||||
@@ -11,12 +12,16 @@ RUN apt-get update && apt-get install -y \
|
||||
gnupg2 \
|
||||
apt-transport-https \
|
||||
dnsutils \
|
||||
netcat \
|
||||
&& apt-key adv --fetch-keys https://rspamd.com/apt-stable/gpg.key \
|
||||
&& echo "deb [arch=amd64] https://rspamd.com/apt-stable/ $CODENAME main" > /etc/apt/sources.list.d/rspamd.list \
|
||||
&& apt-get update \
|
||||
&& apt-get --no-install-recommends -y install rspamd redis-tools procps nano \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
netcat-traditional \
|
||||
wget \
|
||||
redis-tools \
|
||||
procps \
|
||||
nano \
|
||||
lua-cjson \
|
||||
&& arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \
|
||||
&& wget -P /tmp https://rspamd.com/apt-stable/pool/main/r/rspamd/${RSPAMD_VER}~${CODENAME}_${arch}.deb\
|
||||
&& apt install -y /tmp/${RSPAMD_VER}~${CODENAME}_${arch}.deb \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/*\
|
||||
&& apt-get autoremove --purge \
|
||||
&& apt-get clean \
|
||||
&& mkdir -p /run/rspamd \
|
||||
@@ -25,7 +30,6 @@ RUN apt-get update && apt-get install -y \
|
||||
&& sed -i 's/#analysis_keyword_table > 0/analysis_cat_table.macro_exist == "M"/g' /usr/share/rspamd/lualib/lua_scanners/oletools.lua
|
||||
|
||||
COPY settings.conf /etc/rspamd/settings.conf
|
||||
COPY metadata_exporter.lua /usr/share/rspamd/plugins/metadata_exporter.lua
|
||||
COPY set_worker_password.sh /set_worker_password.sh
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
|
||||
@@ -124,4 +124,190 @@ for file in /hooks/*; do
|
||||
fi
|
||||
done
|
||||
|
||||
# If DQS KEY is set in mailcow.conf add Spamhaus DQS RBLs
|
||||
if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then
|
||||
cat <<EOF > /etc/rspamd/custom/dqs-rbl.conf
|
||||
# Autogenerated by mailcow. DO NOT TOUCH!
|
||||
spamhaus {
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
|
||||
from = false;
|
||||
}
|
||||
spamhaus_from {
|
||||
from = true;
|
||||
received = false;
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
|
||||
returncodes {
|
||||
SPAMHAUS_ZEN = [ "127.0.0.2", "127.0.0.3", "127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7", "127.0.0.9", "127.0.0.10", "127.0.0.11" ];
|
||||
}
|
||||
}
|
||||
spamhaus_authbl_received {
|
||||
# Check if the sender client is listed in AuthBL (AuthBL is *not* part of ZEN)
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.authbl.dq.spamhaus.net";
|
||||
from = false;
|
||||
received = true;
|
||||
ipv6 = true;
|
||||
returncodes {
|
||||
SH_AUTHBL_RECEIVED = "127.0.0.20"
|
||||
}
|
||||
}
|
||||
spamhaus_dbl {
|
||||
# Add checks on the HELO string
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||
helo = true;
|
||||
rdns = true;
|
||||
dkim = true;
|
||||
disable_monitoring = true;
|
||||
returncodes {
|
||||
RBL_DBL_SPAM = "127.0.1.2";
|
||||
RBL_DBL_PHISH = "127.0.1.4";
|
||||
RBL_DBL_MALWARE = "127.0.1.5";
|
||||
RBL_DBL_BOTNET = "127.0.1.6";
|
||||
RBL_DBL_ABUSED_SPAM = "127.0.1.102";
|
||||
RBL_DBL_ABUSED_PHISH = "127.0.1.104";
|
||||
RBL_DBL_ABUSED_MALWARE = "127.0.1.105";
|
||||
RBL_DBL_ABUSED_BOTNET = "127.0.1.106";
|
||||
RBL_DBL_DONT_QUERY_IPS = "127.0.1.255";
|
||||
}
|
||||
}
|
||||
spamhaus_dbl_fullurls {
|
||||
ignore_defaults = true;
|
||||
no_ip = true;
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||
selector = 'urls:get_host'
|
||||
disable_monitoring = true;
|
||||
returncodes {
|
||||
DBLABUSED_SPAM_FULLURLS = "127.0.1.102";
|
||||
DBLABUSED_PHISH_FULLURLS = "127.0.1.104";
|
||||
DBLABUSED_MALWARE_FULLURLS = "127.0.1.105";
|
||||
DBLABUSED_BOTNET_FULLURLS = "127.0.1.106";
|
||||
}
|
||||
}
|
||||
spamhaus_zrd {
|
||||
# Add checks on the HELO string also for DQS
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
|
||||
helo = true;
|
||||
rdns = true;
|
||||
dkim = true;
|
||||
disable_monitoring = true;
|
||||
returncodes {
|
||||
RBL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
|
||||
RBL_ZRD_FRESH_DOMAIN = [
|
||||
"127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
|
||||
];
|
||||
RBL_ZRD_DONT_QUERY_IPS = "127.0.2.255";
|
||||
}
|
||||
}
|
||||
"SPAMHAUS_ZEN_URIBL" {
|
||||
enabled = true;
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
|
||||
resolve_ip = true;
|
||||
checks = ['urls'];
|
||||
replyto = true;
|
||||
emails = true;
|
||||
ipv4 = true;
|
||||
ipv6 = true;
|
||||
emails_domainonly = true;
|
||||
returncodes {
|
||||
URIBL_SBL = "127.0.0.2";
|
||||
URIBL_SBL_CSS = "127.0.0.3";
|
||||
URIBL_XBL = ["127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7"];
|
||||
URIBL_PBL = ["127.0.0.10", "127.0.0.11"];
|
||||
URIBL_DROP = "127.0.0.9";
|
||||
}
|
||||
}
|
||||
SH_EMAIL_DBL {
|
||||
ignore_defaults = true;
|
||||
replyto = true;
|
||||
emails_domainonly = true;
|
||||
disable_monitoring = true;
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||
returncodes = {
|
||||
SH_EMAIL_DBL = [
|
||||
"127.0.1.2",
|
||||
"127.0.1.4",
|
||||
"127.0.1.5",
|
||||
"127.0.1.6"
|
||||
];
|
||||
SH_EMAIL_DBL_ABUSED = [
|
||||
"127.0.1.102",
|
||||
"127.0.1.104",
|
||||
"127.0.1.105",
|
||||
"127.0.1.106"
|
||||
];
|
||||
SH_EMAIL_DBL_DONT_QUERY_IPS = [ "127.0.1.255" ];
|
||||
}
|
||||
}
|
||||
SH_EMAIL_ZRD {
|
||||
ignore_defaults = true;
|
||||
replyto = true;
|
||||
emails_domainonly = true;
|
||||
disable_monitoring = true;
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
|
||||
returncodes = {
|
||||
SH_EMAIL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
|
||||
SH_EMAIL_ZRD_FRESH_DOMAIN = [
|
||||
"127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
|
||||
];
|
||||
SH_EMAIL_ZRD_DONT_QUERY_IPS = [ "127.0.2.255" ];
|
||||
}
|
||||
}
|
||||
"DBL" {
|
||||
# override the defaults for DBL defined in modules.d/rbl.conf
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
|
||||
disable_monitoring = true;
|
||||
}
|
||||
"ZRD" {
|
||||
ignore_defaults = true;
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
|
||||
no_ip = true;
|
||||
dkim = true;
|
||||
emails = true;
|
||||
emails_domainonly = true;
|
||||
urls = true;
|
||||
returncodes = {
|
||||
ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
|
||||
ZRD_FRESH_DOMAIN = ["127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"];
|
||||
}
|
||||
}
|
||||
spamhaus_sbl_url {
|
||||
ignore_defaults = true
|
||||
rbl = "${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net";
|
||||
checks = ['urls'];
|
||||
disable_monitoring = true;
|
||||
returncodes {
|
||||
SPAMHAUS_SBL_URL = "127.0.0.2";
|
||||
}
|
||||
}
|
||||
|
||||
SH_HBL_EMAIL {
|
||||
ignore_defaults = true;
|
||||
rbl = "_email.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net";
|
||||
emails_domainonly = false;
|
||||
selector = "from('smtp').lower;from('mime').lower";
|
||||
ignore_whitelist = true;
|
||||
checks = ['emails', 'replyto'];
|
||||
hash = "sha1";
|
||||
returncodes = {
|
||||
SH_HBL_EMAIL = [
|
||||
"127.0.3.2"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
spamhaus_dqs_hbl {
|
||||
symbol = "HBL_FILE_UNKNOWN";
|
||||
rbl = "_file.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net.";
|
||||
selector = "attachments('rbase32', 'sha256')";
|
||||
ignore_whitelist = true;
|
||||
ignore_defaults = true;
|
||||
returncodes {
|
||||
SH_HBL_FILE_MALICIOUS = "127.0.3.10";
|
||||
SH_HBL_FILE_SUSPICIOUS = "127.0.3.15";
|
||||
}
|
||||
}
|
||||
EOF
|
||||
else
|
||||
rm -rf /etc/rspamd/custom/dqs-rbl.conf
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
|
||||
@@ -1,632 +0,0 @@
|
||||
--[[
|
||||
Copyright (c) 2016, Andrew Lewis <nerf@judo.za.org>
|
||||
Copyright (c) 2016, Vsevolod Stakhov <vsevolod@highsecure.ru>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
]]--
|
||||
|
||||
if confighelp then
|
||||
return
|
||||
end
|
||||
|
||||
-- A plugin that pushes metadata (or whole messages) to external services
|
||||
|
||||
local redis_params
|
||||
local lua_util = require "lua_util"
|
||||
local rspamd_http = require "rspamd_http"
|
||||
local rspamd_util = require "rspamd_util"
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local ucl = require "ucl"
|
||||
local E = {}
|
||||
local N = 'metadata_exporter'
|
||||
|
||||
local settings = {
|
||||
pusher_enabled = {},
|
||||
pusher_format = {},
|
||||
pusher_select = {},
|
||||
mime_type = 'text/plain',
|
||||
defer = false,
|
||||
mail_from = '',
|
||||
mail_to = 'postmaster@localhost',
|
||||
helo = 'rspamd',
|
||||
email_template = [[From: "Rspamd" <$mail_from>
|
||||
To: $mail_to
|
||||
Subject: Spam alert
|
||||
Date: $date
|
||||
MIME-Version: 1.0
|
||||
Message-ID: <$our_message_id>
|
||||
Content-type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Authenticated username: $user
|
||||
IP: $ip
|
||||
Queue ID: $qid
|
||||
SMTP FROM: $from
|
||||
SMTP RCPT: $rcpt
|
||||
MIME From: $header_from
|
||||
MIME To: $header_to
|
||||
MIME Date: $header_date
|
||||
Subject: $header_subject
|
||||
Message-ID: $message_id
|
||||
Action: $action
|
||||
Score: $score
|
||||
Symbols: $symbols]],
|
||||
}
|
||||
|
||||
local function get_general_metadata(task, flatten, no_content)
|
||||
local r = {}
|
||||
local ip = task:get_from_ip()
|
||||
if ip and ip:is_valid() then
|
||||
r.ip = tostring(ip)
|
||||
else
|
||||
r.ip = 'unknown'
|
||||
end
|
||||
r.user = task:get_user() or 'unknown'
|
||||
r.qid = task:get_queue_id() or 'unknown'
|
||||
r.subject = task:get_subject() or 'unknown'
|
||||
r.action = task:get_metric_action('default')
|
||||
|
||||
local s = task:get_metric_score('default')[1]
|
||||
r.score = flatten and string.format('%.2f', s) or s
|
||||
|
||||
local fuzzy = task:get_mempool():get_variable("fuzzy_hashes", "fstrings")
|
||||
if fuzzy and #fuzzy > 0 then
|
||||
local fz = {}
|
||||
for _,h in ipairs(fuzzy) do
|
||||
table.insert(fz, h)
|
||||
end
|
||||
if not flatten then
|
||||
r.fuzzy = fz
|
||||
else
|
||||
r.fuzzy = table.concat(fz, ', ')
|
||||
end
|
||||
else
|
||||
r.fuzzy = 'unknown'
|
||||
end
|
||||
|
||||
local rcpt = task:get_recipients('smtp')
|
||||
if rcpt then
|
||||
local l = {}
|
||||
for _, a in ipairs(rcpt) do
|
||||
table.insert(l, a['addr'])
|
||||
end
|
||||
if not flatten then
|
||||
r.rcpt = l
|
||||
else
|
||||
r.rcpt = table.concat(l, ', ')
|
||||
end
|
||||
else
|
||||
r.rcpt = 'unknown'
|
||||
end
|
||||
local from = task:get_from('smtp')
|
||||
if ((from or E)[1] or E).addr then
|
||||
r.from = from[1].addr
|
||||
else
|
||||
r.from = 'unknown'
|
||||
end
|
||||
local syminf = task:get_symbols_all()
|
||||
if flatten then
|
||||
local l = {}
|
||||
for _, sym in ipairs(syminf) do
|
||||
local txt
|
||||
if sym.options then
|
||||
local topt = table.concat(sym.options, ', ')
|
||||
txt = sym.name .. '(' .. string.format('%.2f', sym.score) .. ')' .. ' [' .. topt .. ']'
|
||||
else
|
||||
txt = sym.name .. '(' .. string.format('%.2f', sym.score) .. ')'
|
||||
end
|
||||
table.insert(l, txt)
|
||||
end
|
||||
r.symbols = table.concat(l, '\n\t')
|
||||
else
|
||||
r.symbols = syminf
|
||||
end
|
||||
local function process_header(name)
|
||||
local hdr = task:get_header_full(name)
|
||||
if hdr then
|
||||
local l = {}
|
||||
for _, h in ipairs(hdr) do
|
||||
table.insert(l, h.decoded)
|
||||
end
|
||||
if not flatten then
|
||||
return l
|
||||
else
|
||||
return table.concat(l, '\n')
|
||||
end
|
||||
else
|
||||
return 'unknown'
|
||||
end
|
||||
end
|
||||
if not no_content then
|
||||
r.header_from = process_header('from')
|
||||
r.header_to = process_header('to')
|
||||
r.header_subject = process_header('subject')
|
||||
r.header_date = process_header('date')
|
||||
r.message_id = task:get_message_id()
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
local formatters = {
|
||||
default = function(task)
|
||||
return task:get_content(), {}
|
||||
end,
|
||||
email_alert = function(task, rule, extra)
|
||||
local meta = get_general_metadata(task, true)
|
||||
local display_emails = {}
|
||||
local mail_targets = {}
|
||||
meta.mail_from = rule.mail_from or settings.mail_from
|
||||
local mail_rcpt = rule.mail_to or settings.mail_to
|
||||
if type(mail_rcpt) ~= 'table' then
|
||||
table.insert(display_emails, string.format('<%s>', mail_rcpt))
|
||||
table.insert(mail_targets, mail_rcpt)
|
||||
else
|
||||
for _, e in ipairs(mail_rcpt) do
|
||||
table.insert(display_emails, string.format('<%s>', e))
|
||||
table.insert(mail_targets, mail_rcpt)
|
||||
end
|
||||
end
|
||||
if rule.email_alert_sender then
|
||||
local x = task:get_from('smtp')
|
||||
if x and string.len(x[1].addr) > 0 then
|
||||
table.insert(mail_targets, x)
|
||||
table.insert(display_emails, string.format('<%s>', x[1].addr))
|
||||
end
|
||||
end
|
||||
if rule.email_alert_user then
|
||||
local x = task:get_user()
|
||||
if x then
|
||||
table.insert(mail_targets, x)
|
||||
table.insert(display_emails, string.format('<%s>', x))
|
||||
end
|
||||
end
|
||||
if rule.email_alert_recipients then
|
||||
local x = task:get_recipients('smtp')
|
||||
if x then
|
||||
for _, e in ipairs(x) do
|
||||
if string.len(e.addr) > 0 then
|
||||
table.insert(mail_targets, e.addr)
|
||||
table.insert(display_emails, string.format('<%s>', e.addr))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
meta.mail_to = table.concat(display_emails, ', ')
|
||||
meta.our_message_id = rspamd_util.random_hex(12) .. '@rspamd'
|
||||
meta.date = rspamd_util.time_to_string(rspamd_util.get_time())
|
||||
return lua_util.template(rule.email_template or settings.email_template, meta), { mail_targets = mail_targets}
|
||||
end,
|
||||
json = function(task)
|
||||
return ucl.to_format(get_general_metadata(task), 'json-compact')
|
||||
end
|
||||
}
|
||||
|
||||
local function is_spam(action)
|
||||
return (action == 'reject' or action == 'add header' or action == 'rewrite subject')
|
||||
end
|
||||
|
||||
local selectors = {
|
||||
default = function(task)
|
||||
return true
|
||||
end,
|
||||
is_spam = function(task)
|
||||
local action = task:get_metric_action('default')
|
||||
return is_spam(action)
|
||||
end,
|
||||
is_spam_authed = function(task)
|
||||
if not task:get_user() then
|
||||
return false
|
||||
end
|
||||
local action = task:get_metric_action('default')
|
||||
return is_spam(action)
|
||||
end,
|
||||
is_reject = function(task)
|
||||
local action = task:get_metric_action('default')
|
||||
return (action == 'reject')
|
||||
end,
|
||||
is_reject_authed = function(task)
|
||||
if not task:get_user() then
|
||||
return false
|
||||
end
|
||||
local action = task:get_metric_action('default')
|
||||
return (action == 'reject')
|
||||
end,
|
||||
}
|
||||
|
||||
local function maybe_defer(task, rule)
|
||||
if rule.defer then
|
||||
rspamd_logger.warnx(task, 'deferring message')
|
||||
task:set_pre_result('soft reject', 'deferred', N)
|
||||
end
|
||||
end
|
||||
|
||||
local pushers = {
|
||||
redis_pubsub = function(task, formatted, rule)
|
||||
local _,ret,upstream
|
||||
local function redis_pub_cb(err)
|
||||
if err then
|
||||
rspamd_logger.errx(task, 'got error %s when publishing on server %s',
|
||||
err, upstream:get_addr())
|
||||
return maybe_defer(task, rule)
|
||||
end
|
||||
return true
|
||||
end
|
||||
ret,_,upstream = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
nil, -- hash key
|
||||
true, -- is write
|
||||
redis_pub_cb, --callback
|
||||
'PUBLISH', -- command
|
||||
{rule.channel, formatted} -- arguments
|
||||
)
|
||||
if not ret then
|
||||
rspamd_logger.errx(task, 'error connecting to redis')
|
||||
maybe_defer(task, rule)
|
||||
end
|
||||
end,
|
||||
http = function(task, formatted, rule)
|
||||
local function http_callback(err, code)
|
||||
if err then
|
||||
rspamd_logger.errx(task, 'got error %s in http callback', err)
|
||||
return maybe_defer(task, rule)
|
||||
end
|
||||
if code ~= 200 then
|
||||
rspamd_logger.errx(task, 'got unexpected http status: %s', code)
|
||||
return maybe_defer(task, rule)
|
||||
end
|
||||
return true
|
||||
end
|
||||
local hdrs = {}
|
||||
if rule.meta_headers then
|
||||
local gm = get_general_metadata(task, false, true)
|
||||
local pfx = rule.meta_header_prefix or 'X-Rspamd-'
|
||||
for k, v in pairs(gm) do
|
||||
if type(v) == 'table' then
|
||||
hdrs[pfx .. k] = ucl.to_format(v, 'json-compact')
|
||||
else
|
||||
hdrs[pfx .. k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url=rule.url,
|
||||
body=formatted,
|
||||
callback=http_callback,
|
||||
mime_type=rule.mime_type or settings.mime_type,
|
||||
headers=hdrs,
|
||||
})
|
||||
end,
|
||||
send_mail = function(task, formatted, rule, extra)
|
||||
local lua_smtp = require "lua_smtp"
|
||||
local function sendmail_cb(ret, err)
|
||||
if not ret then
|
||||
rspamd_logger.errx(task, 'SMTP export error: %s', err)
|
||||
maybe_defer(task, rule)
|
||||
end
|
||||
end
|
||||
|
||||
lua_smtp.sendmail({
|
||||
task = task,
|
||||
host = rule.smtp,
|
||||
port = rule.smtp_port or settings.smtp_port or 25,
|
||||
from = rule.mail_from or settings.mail_from,
|
||||
recipients = extra.mail_targets or rule.mail_to or settings.mail_to,
|
||||
helo = rule.helo or settings.helo,
|
||||
timeout = rule.timeout or settings.timeout,
|
||||
}, formatted, sendmail_cb)
|
||||
end,
|
||||
}
|
||||
|
||||
local opts = rspamd_config:get_all_opt(N)
|
||||
if not opts then return end
|
||||
local process_settings = {
|
||||
select = function(val)
|
||||
selectors.custom = assert(load(val))()
|
||||
end,
|
||||
format = function(val)
|
||||
formatters.custom = assert(load(val))()
|
||||
end,
|
||||
push = function(val)
|
||||
pushers.custom = assert(load(val))()
|
||||
end,
|
||||
custom_push = function(val)
|
||||
if type(val) == 'table' then
|
||||
for k, v in pairs(val) do
|
||||
pushers[k] = assert(load(v))()
|
||||
end
|
||||
end
|
||||
end,
|
||||
custom_select = function(val)
|
||||
if type(val) == 'table' then
|
||||
for k, v in pairs(val) do
|
||||
selectors[k] = assert(load(v))()
|
||||
end
|
||||
end
|
||||
end,
|
||||
custom_format = function(val)
|
||||
if type(val) == 'table' then
|
||||
for k, v in pairs(val) do
|
||||
formatters[k] = assert(load(v))()
|
||||
end
|
||||
end
|
||||
end,
|
||||
pusher_enabled = function(val)
|
||||
if type(val) == 'string' then
|
||||
if pushers[val] then
|
||||
settings.pusher_enabled[val] = true
|
||||
else
|
||||
rspamd_logger.errx(rspamd_config, 'Pusher type: %s is invalid', val)
|
||||
end
|
||||
elseif type(val) == 'table' then
|
||||
for _, v in ipairs(val) do
|
||||
if pushers[v] then
|
||||
settings.pusher_enabled[v] = true
|
||||
else
|
||||
rspamd_logger.errx(rspamd_config, 'Pusher type: %s is invalid', val)
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
for k, v in pairs(opts) do
|
||||
local f = process_settings[k]
|
||||
if f then
|
||||
f(opts[k])
|
||||
else
|
||||
settings[k] = v
|
||||
end
|
||||
end
|
||||
if type(settings.rules) ~= 'table' then
|
||||
-- Legacy config
|
||||
settings.rules = {}
|
||||
if not next(settings.pusher_enabled) then
|
||||
if pushers.custom then
|
||||
rspamd_logger.infox(rspamd_config, 'Custom pusher implicitly enabled')
|
||||
settings.pusher_enabled.custom = true
|
||||
else
|
||||
-- Check legacy options
|
||||
if settings.url then
|
||||
rspamd_logger.warnx(rspamd_config, 'HTTP pusher implicitly enabled')
|
||||
settings.pusher_enabled.http = true
|
||||
end
|
||||
if settings.channel then
|
||||
rspamd_logger.warnx(rspamd_config, 'Redis Pubsub pusher implicitly enabled')
|
||||
settings.pusher_enabled.redis_pubsub = true
|
||||
end
|
||||
if settings.smtp and settings.mail_to then
|
||||
rspamd_logger.warnx(rspamd_config, 'SMTP pusher implicitly enabled')
|
||||
settings.pusher_enabled.send_mail = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if not next(settings.pusher_enabled) then
|
||||
rspamd_logger.errx(rspamd_config, 'No push backend enabled')
|
||||
return
|
||||
end
|
||||
if settings.formatter then
|
||||
settings.format = formatters[settings.formatter]
|
||||
if not settings.format then
|
||||
rspamd_logger.errx(rspamd_config, 'No such formatter: %s', settings.formatter)
|
||||
return
|
||||
end
|
||||
end
|
||||
if settings.selector then
|
||||
settings.select = selectors[settings.selector]
|
||||
if not settings.select then
|
||||
rspamd_logger.errx(rspamd_config, 'No such selector: %s', settings.selector)
|
||||
return
|
||||
end
|
||||
end
|
||||
for k in pairs(settings.pusher_enabled) do
|
||||
local formatter = settings.pusher_format[k]
|
||||
local selector = settings.pusher_select[k]
|
||||
if not formatter then
|
||||
settings.pusher_format[k] = settings.formatter or 'default'
|
||||
rspamd_logger.infox(rspamd_config, 'Using default formatter for %s pusher', k)
|
||||
else
|
||||
if not formatters[formatter] then
|
||||
rspamd_logger.errx(rspamd_config, 'No such formatter: %s - disabling %s', formatter, k)
|
||||
settings.pusher_enabled.k = nil
|
||||
end
|
||||
end
|
||||
if not selector then
|
||||
settings.pusher_select[k] = settings.selector or 'default'
|
||||
rspamd_logger.infox(rspamd_config, 'Using default selector for %s pusher', k)
|
||||
else
|
||||
if not selectors[selector] then
|
||||
rspamd_logger.errx(rspamd_config, 'No such selector: %s - disabling %s', selector, k)
|
||||
settings.pusher_enabled.k = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
if settings.pusher_enabled.redis_pubsub then
|
||||
redis_params = rspamd_parse_redis_server(N)
|
||||
if not redis_params then
|
||||
rspamd_logger.errx(rspamd_config, 'No redis servers are specified')
|
||||
settings.pusher_enabled.redis_pubsub = nil
|
||||
else
|
||||
local r = {}
|
||||
r.backend = 'redis_pubsub'
|
||||
r.channel = settings.channel
|
||||
r.defer = settings.defer
|
||||
r.selector = settings.pusher_select.redis_pubsub
|
||||
r.formatter = settings.pusher_format.redis_pubsub
|
||||
settings.rules[r.backend:upper()] = r
|
||||
end
|
||||
end
|
||||
if settings.pusher_enabled.http then
|
||||
if not settings.url then
|
||||
rspamd_logger.errx(rspamd_config, 'No URL is specified')
|
||||
settings.pusher_enabled.http = nil
|
||||
else
|
||||
local r = {}
|
||||
r.backend = 'http'
|
||||
r.url = settings.url
|
||||
r.mime_type = settings.mime_type
|
||||
r.defer = settings.defer
|
||||
r.selector = settings.pusher_select.http
|
||||
r.formatter = settings.pusher_format.http
|
||||
settings.rules[r.backend:upper()] = r
|
||||
end
|
||||
end
|
||||
if settings.pusher_enabled.send_mail then
|
||||
if not (settings.mail_to and settings.smtp) then
|
||||
rspamd_logger.errx(rspamd_config, 'No mail_to and/or smtp setting is specified')
|
||||
settings.pusher_enabled.send_mail = nil
|
||||
else
|
||||
local r = {}
|
||||
r.backend = 'send_mail'
|
||||
r.mail_to = settings.mail_to
|
||||
r.mail_from = settings.mail_from
|
||||
r.helo = settings.hello
|
||||
r.smtp = settings.smtp
|
||||
r.smtp_port = settings.smtp_port
|
||||
r.email_template = settings.email_template
|
||||
r.defer = settings.defer
|
||||
r.selector = settings.pusher_select.send_mail
|
||||
r.formatter = settings.pusher_format.send_mail
|
||||
settings.rules[r.backend:upper()] = r
|
||||
end
|
||||
end
|
||||
if not next(settings.pusher_enabled) then
|
||||
rspamd_logger.errx(rspamd_config, 'No push backend enabled')
|
||||
return
|
||||
end
|
||||
elseif not next(settings.rules) then
|
||||
lua_util.debugm(N, rspamd_config, 'No rules enabled')
|
||||
return
|
||||
end
|
||||
if not settings.rules or not next(settings.rules) then
|
||||
rspamd_logger.errx(rspamd_config, 'No rules enabled')
|
||||
return
|
||||
end
|
||||
local backend_required_elements = {
|
||||
http = {
|
||||
'url',
|
||||
},
|
||||
smtp = {
|
||||
'mail_to',
|
||||
'smtp',
|
||||
},
|
||||
redis_pubsub = {
|
||||
'channel',
|
||||
},
|
||||
}
|
||||
local check_element = {
|
||||
selector = function(k, v)
|
||||
if not selectors[v] then
|
||||
rspamd_logger.errx(rspamd_config, 'Rule %s has invalid selector %s', k, v)
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end,
|
||||
formatter = function(k, v)
|
||||
if not formatters[v] then
|
||||
rspamd_logger.errx(rspamd_config, 'Rule %s has invalid formatter %s', k, v)
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end,
|
||||
}
|
||||
local backend_check = {
|
||||
default = function(k, rule)
|
||||
local reqset = backend_required_elements[rule.backend]
|
||||
if reqset then
|
||||
for _, e in ipairs(reqset) do
|
||||
if not rule[e] then
|
||||
rspamd_logger.errx(rspamd_config, 'Rule %s misses required setting %s', k, e)
|
||||
settings.rules[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
for sett, v in pairs(rule) do
|
||||
local f = check_element[sett]
|
||||
if f then
|
||||
if not f(sett, v) then
|
||||
settings.rules[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
backend_check.redis_pubsub = function(k, rule)
|
||||
if not redis_params then
|
||||
redis_params = rspamd_parse_redis_server(N)
|
||||
end
|
||||
if not redis_params then
|
||||
rspamd_logger.errx(rspamd_config, 'No redis servers are specified')
|
||||
settings.rules[k] = nil
|
||||
else
|
||||
backend_check.default(k, rule)
|
||||
end
|
||||
end
|
||||
setmetatable(backend_check, {
|
||||
__index = function()
|
||||
return backend_check.default
|
||||
end,
|
||||
})
|
||||
for k, v in pairs(settings.rules) do
|
||||
if type(v) == 'table' then
|
||||
local backend = v.backend
|
||||
if not backend then
|
||||
rspamd_logger.errx(rspamd_config, 'Rule %s has no backend', k)
|
||||
settings.rules[k] = nil
|
||||
elseif not pushers[backend] then
|
||||
rspamd_logger.errx(rspamd_config, 'Rule %s has invalid backend %s', k, backend)
|
||||
settings.rules[k] = nil
|
||||
else
|
||||
local f = backend_check[backend]
|
||||
f(k, v)
|
||||
end
|
||||
else
|
||||
rspamd_logger.errx(rspamd_config, 'Rule %s has bad type: %s', k, type(v))
|
||||
settings.rules[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function gen_exporter(rule)
|
||||
return function (task)
|
||||
if task:has_flag('skip') then return end
|
||||
local selector = rule.selector or 'default'
|
||||
local selected = selectors[selector](task)
|
||||
if selected then
|
||||
lua_util.debugm(N, task, 'Message selected for processing')
|
||||
local formatter = rule.formatter or 'default'
|
||||
local formatted, extra = formatters[formatter](task, rule)
|
||||
if formatted then
|
||||
pushers[rule.backend](task, formatted, rule, extra)
|
||||
else
|
||||
lua_util.debugm(N, task, 'Formatter [%s] returned non-truthy value [%s]', formatter, formatted)
|
||||
end
|
||||
else
|
||||
lua_util.debugm(N, task, 'Selector [%s] returned non-truthy value [%s]', selector, selected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not next(settings.rules) then
|
||||
rspamd_logger.errx(rspamd_config, 'No rules enabled')
|
||||
lua_util.disable_module(N, "config")
|
||||
end
|
||||
for k, r in pairs(settings.rules) do
|
||||
rspamd_config:register_symbol({
|
||||
name = 'EXPORT_METADATA_' .. k,
|
||||
type = 'idempotent',
|
||||
callback = gen_exporter(r),
|
||||
priority = 10,
|
||||
flags = 'empty,explicit_disable,ignore_passthrough',
|
||||
})
|
||||
end
|
||||
@@ -1,11 +1,13 @@
|
||||
FROM debian:bullseye-slim
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG SOGO_DEBIAN_REPOSITORY=http://packages.sogo.nu/nightly/5/debian/
|
||||
ARG DEBIAN_VERSION=bookworm
|
||||
ARG SOGO_DEBIAN_REPOSITORY=http://www.axis.cz/linux/debian
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
|
||||
ARG GOSU_VERSION=1.16
|
||||
ENV LC_ALL C
|
||||
ARG GOSU_VERSION=1.17
|
||||
ENV LC_ALL=C
|
||||
|
||||
# Prerequisites
|
||||
RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
|
||||
@@ -21,7 +23,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
|
||||
syslog-ng-core \
|
||||
syslog-ng-mod-redis \
|
||||
dirmngr \
|
||||
netcat \
|
||||
netcat-traditional \
|
||||
psmisc \
|
||||
wget \
|
||||
patch \
|
||||
@@ -32,7 +34,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
|
||||
&& mkdir /usr/share/doc/sogo \
|
||||
&& touch /usr/share/doc/sogo/empty.sh \
|
||||
&& apt-key adv --keyserver keys.openpgp.org --recv-key 74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 \
|
||||
&& echo "deb ${SOGO_DEBIAN_REPOSITORY} bullseye bullseye" > /etc/apt/sources.list.d/sogo.list \
|
||||
&& echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} sogo-v5" > /etc/apt/sources.list.d/sogo.list \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||
sogo \
|
||||
sogo-activesync \
|
||||
|
||||
@@ -150,6 +150,8 @@ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
|
||||
<string>YES</string>
|
||||
<key>SOGoEncryptionKey</key>
|
||||
<string>${RAND_PASS}</string>
|
||||
<key>OCSAdminURL</key>
|
||||
<string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_admin</string>
|
||||
<key>OCSCacheFolderURL</key>
|
||||
<string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder</string>
|
||||
<key>OCSEMailAlarmsFolderURL</key>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@version: 3.28
|
||||
@version: 3.38
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@version: 3.28
|
||||
@version: 3.38
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
|
||||
@@ -3,7 +3,7 @@ FROM solr:7.7-slim
|
||||
USER root
|
||||
|
||||
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?<version>.*)$
|
||||
ARG GOSU_VERSION=1.16
|
||||
ARG GOSU_VERSION=1.17
|
||||
|
||||
COPY solr.sh /
|
||||
COPY solr-config-7.7.0.xml /
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
if [[ "${FLATCURVE_EXPERIMENTAL}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
echo "FLATCURVE_EXPERIMENTAL=y, skipping Solr but enabling Flatcurve as FTS for Dovecot!"
|
||||
echo "Solr will be removed in the future!"
|
||||
sleep 365d
|
||||
exit 0
|
||||
elif [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
echo "SKIP_SOLR=y, skipping Solr..."
|
||||
echo "HINT: You could try the newer FTS Backend Flatcurve, which is currently in experimental state..."
|
||||
echo "Simply set FLATCURVE_EXPERIMENTAL=y inside your mailcow.conf and restart the stack afterwards!"
|
||||
echo "Solr will be removed in the future!"
|
||||
sleep 365d
|
||||
exit 0
|
||||
fi
|
||||
@@ -57,5 +65,11 @@ if [[ "${1}" == "--bootstrap" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Starting up Solr..."
|
||||
echo -e "\e[31mSolr is deprecated! You can try the new FTS System now by enabling FLATCURVE_EXPERIMENTAL=y inside mailcow.conf and restarting the stack\e[0m"
|
||||
echo -e "\e[31mSolr will be removed completely soon!\e[0m"
|
||||
|
||||
sleep 15
|
||||
|
||||
exec gosu solr solr-foreground
|
||||
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.20
|
||||
|
||||
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
RUN apk add --update --no-cache \
|
||||
curl \
|
||||
bind-tools \
|
||||
coreutils \
|
||||
unbound \
|
||||
bash \
|
||||
openssl \
|
||||
drill \
|
||||
tzdata \
|
||||
syslog-ng \
|
||||
supervisor \
|
||||
&& curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \
|
||||
&& chown root:unbound /etc/unbound \
|
||||
&& adduser unbound tty \
|
||||
&& adduser unbound tty \
|
||||
&& chmod 775 /etc/unbound
|
||||
|
||||
EXPOSE 53/udp 53/tcp
|
||||
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
|
||||
# healthcheck (nslookup)
|
||||
# healthcheck (dig, ping)
|
||||
COPY healthcheck.sh /healthcheck.sh
|
||||
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
|
||||
COPY supervisord.conf /etc/supervisor/supervisord.conf
|
||||
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
|
||||
|
||||
RUN chmod +x /healthcheck.sh
|
||||
HEALTHCHECK --interval=30s --timeout=10s CMD [ "/healthcheck.sh" ]
|
||||
HEALTHCHECK --interval=30s --timeout=10s \
|
||||
CMD sh -c '[ -f /tmp/healthcheck_status ] && [ "$(cat /tmp/healthcheck_status)" -eq 0 ] || exit 1'
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
CMD ["/usr/sbin/unbound"]
|
||||
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
||||
|
||||
@@ -1,12 +1,102 @@
|
||||
#!/bin/bash
|
||||
|
||||
nslookup mailcow.email 127.0.0.1 1> /dev/null
|
||||
STATUS_FILE="/tmp/healthcheck_status"
|
||||
RUNS=0
|
||||
|
||||
if [ $? == 0 ]; then
|
||||
echo "DNS resolution is working!"
|
||||
exit 0
|
||||
else
|
||||
echo "DNS resolution is not working correctly..."
|
||||
echo "Maybe check your outbound firewall, as it needs to resolve DNS over TCP AND UDP!"
|
||||
exit 1
|
||||
# Declare log function for logfile to stdout
|
||||
function log_to_stdout() {
|
||||
echo "$(date +"%Y-%m-%d %H:%M:%S"): $1"
|
||||
}
|
||||
|
||||
# General Ping function to check general pingability
|
||||
function check_ping() {
|
||||
declare -a ipstoping=("1.1.1.1" "8.8.8.8" "9.9.9.9")
|
||||
local fail_tolerance=1
|
||||
local failures=0
|
||||
|
||||
for ip in "${ipstoping[@]}" ; do
|
||||
success=false
|
||||
for ((i=1; i<=3; i++)); do
|
||||
ping -q -c 3 -w 5 "$ip" > /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
success=true
|
||||
break
|
||||
else
|
||||
log_to_stdout "Healthcheck: Failed to ping $ip on attempt $i. Trying again..."
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$success" = false ]; then
|
||||
log_to_stdout "Healthcheck: Couldn't ping $ip after 3 attempts. Marking this IP as failed."
|
||||
((failures++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $failures -gt $fail_tolerance ]; then
|
||||
log_to_stdout "Healthcheck: Too many ping failures ($fail_tolerance failures allowed, you got $failures failures), marking Healthcheck as unhealthy..."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
|
||||
}
|
||||
|
||||
# General DNS Resolve Check against Unbound Resolver himself
|
||||
function check_dns() {
|
||||
declare -a domains=("fuzzy.mailcow.email" "github.com" "hub.docker.com")
|
||||
local fail_tolerance=1
|
||||
local failures=0
|
||||
|
||||
for domain in "${domains[@]}" ; do
|
||||
success=false
|
||||
for ((i=1; i<=3; i++)); do
|
||||
dig_output=$(dig +short +timeout=2 +tries=1 "$domain" @127.0.0.1 2>/dev/null)
|
||||
dig_rc=$?
|
||||
|
||||
if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then
|
||||
log_to_stdout "Healthcheck: DNS Resolution Failed on attempt $i for $domain! Trying again..."
|
||||
else
|
||||
success=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$success" = false ]; then
|
||||
log_to_stdout "Healthcheck: DNS Resolution not possible after 3 attempts for $domain... Gave up!"
|
||||
((failures++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $failures -gt $fail_tolerance ]; then
|
||||
log_to_stdout "Healthcheck: Too many DNS failures ($fail_tolerance failures allowed, you got $failures failures), marking Healthcheck as unhealthy..."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
while true; do
|
||||
|
||||
if [[ ${SKIP_UNBOUND_HEALTHCHECK} == "y" ]]; then
|
||||
log_to_stdout "Healthcheck: ALL CHECKS WERE SKIPPED! Unbound is healthy!"
|
||||
echo "0" > $STATUS_FILE
|
||||
sleep 365d
|
||||
fi
|
||||
|
||||
# run checks, if check is not returning 0 (return value if check is ok), healthcheck will exit with 1 (marked in docker as unhealthy)
|
||||
check_ping
|
||||
PING_STATUS=$?
|
||||
|
||||
check_dns
|
||||
DNS_STATUS=$?
|
||||
|
||||
if [ $PING_STATUS -ne 0 ] || [ $DNS_STATUS -ne 0 ]; then
|
||||
echo "1" > $STATUS_FILE
|
||||
|
||||
else
|
||||
echo "0" > $STATUS_FILE
|
||||
fi
|
||||
|
||||
sleep 30
|
||||
|
||||
done
|
||||
10
data/Dockerfiles/unbound/stop-supervisor.sh
Executable file
10
data/Dockerfiles/unbound/stop-supervisor.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
printf "READY\n";
|
||||
|
||||
while read line; do
|
||||
echo "Processing Event: $line" >&2;
|
||||
kill -3 $(cat "/var/run/supervisord.pid")
|
||||
done < /dev/stdin
|
||||
|
||||
rm -rf /tmp/healthcheck_status
|
||||
32
data/Dockerfiles/unbound/supervisord.conf
Normal file
32
data/Dockerfiles/unbound/supervisord.conf
Normal file
@@ -0,0 +1,32 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
pidfile=/var/run/supervisord.pid
|
||||
|
||||
[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:unbound]
|
||||
command=/usr/sbin/unbound
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=true
|
||||
|
||||
[program:unbound-healthcheck]
|
||||
command=/bin/bash /healthcheck.sh
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=true
|
||||
|
||||
[eventlistener:processes]
|
||||
command=/usr/local/sbin/stop-supervisor.sh
|
||||
events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL
|
||||
21
data/Dockerfiles/unbound/syslog-ng.conf
Normal file
21
data/Dockerfiles/unbound/syslog-ng.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
@version: 4.5
|
||||
@include "scl.conf"
|
||||
options {
|
||||
chain_hostnames(off);
|
||||
flush_lines(0);
|
||||
use_dns(no);
|
||||
use_fqdn(no);
|
||||
owner("root"); group("adm"); perm(0640);
|
||||
stats(freq(0));
|
||||
keep_timestamp(no);
|
||||
bad_hostname("^gconfd$");
|
||||
};
|
||||
source s_dgram {
|
||||
unix-dgram("/dev/log");
|
||||
internal();
|
||||
};
|
||||
destination d_stdout { pipe("/dev/stdout"); };
|
||||
log {
|
||||
source(s_dgram);
|
||||
destination(d_stdout);
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
FROM alpine:3.17
|
||||
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
||||
FROM alpine:3.20
|
||||
|
||||
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
# Installation
|
||||
RUN apk add --update \
|
||||
|
||||
@@ -169,9 +169,13 @@ function notify_error() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Escape subject and body (https://stackoverflow.com/a/2705678)
|
||||
ESCAPED_SUBJECT=$(echo ${SUBJECT} | sed -e 's/[\/&]/\\&/g')
|
||||
ESCAPED_BODY=$(echo ${BODY} | sed -e 's/[\/&]/\\&/g')
|
||||
|
||||
# Replace subject and body placeholders
|
||||
WEBHOOK_BODY=$(echo ${WATCHDOG_NOTIFY_WEBHOOK_BODY} | sed "s|\$SUBJECT\|\${SUBJECT}|$SUBJECT|g" | sed "s|\$BODY\|\${BODY}|$BODY|")
|
||||
|
||||
WEBHOOK_BODY=$(echo ${WATCHDOG_NOTIFY_WEBHOOK_BODY} | sed -e "s/\$SUBJECT\|\${SUBJECT}/$ESCAPED_SUBJECT/g" -e "s/\$BODY\|\${BODY}/$ESCAPED_BODY/g")
|
||||
|
||||
# POST to webhook
|
||||
curl -X POST -H "Content-Type: application/json" ${CURL_VERBOSE} -d "${WEBHOOK_BODY}" ${WATCHDOG_NOTIFY_WEBHOOK}
|
||||
|
||||
@@ -191,12 +195,12 @@ get_container_ip() {
|
||||
else
|
||||
sleep 0.5
|
||||
# get long container id for exact match
|
||||
CONTAINER_ID=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id"))
|
||||
CONTAINER_ID=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id"))
|
||||
# returned id can have multiple elements (if scaled), shuffle for random test
|
||||
CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf))
|
||||
if [[ ! -z ${CONTAINER_ID} ]]; then
|
||||
for matched_container in "${CONTAINER_ID[@]}"; do
|
||||
CONTAINER_IPS=($(curl --silent --insecure https://dockerapi/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress'))
|
||||
CONTAINER_IPS=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress'))
|
||||
for ip_match in "${CONTAINER_IPS[@]}"; do
|
||||
# grep will do nothing if one of these vars is empty
|
||||
[[ -z ${ip_match} ]] && continue
|
||||
@@ -716,8 +720,8 @@ rspamd_checks() {
|
||||
From: watchdog@localhost
|
||||
|
||||
Empty
|
||||
' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan | jq -rc .default.required_score)
|
||||
if [[ ${SCORE} != "9999" ]]; then
|
||||
' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/scan | jq -rc .default.required_score | sed 's/\..*//' )
|
||||
if [[ ${SCORE} -ne 9999 ]]; then
|
||||
echo "Rspamd settings check failed, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2
|
||||
err_count=$(( ${err_count} + 1))
|
||||
else
|
||||
@@ -1095,12 +1099,12 @@ while true; do
|
||||
elif [[ ${com_pipe_answer} =~ .+-mailcow ]]; then
|
||||
kill -STOP ${BACKGROUND_TASKS[*]}
|
||||
sleep 10
|
||||
CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id")
|
||||
CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id")
|
||||
if [[ ! -z ${CONTAINER_ID} ]]; then
|
||||
if [[ "${com_pipe_answer}" == "php-fpm-mailcow" ]]; then
|
||||
HAS_INITDB=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/top | jq '.msg.Processes[] | contains(["php -c /usr/local/etc/php -f /web/inc/init_db.inc.php"])' | grep true)
|
||||
HAS_INITDB=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/top | jq '.msg.Processes[] | contains(["php -c /usr/local/etc/php -f /web/inc/init_db.inc.php"])' | grep true)
|
||||
fi
|
||||
S_RUNNING=$(($(date +%s) - $(curl --silent --insecure https://dockerapi/containers/${CONTAINER_ID}/json | jq .State.StartedAt | xargs -n1 date +%s -d)))
|
||||
S_RUNNING=$(($(date +%s) - $(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/json | jq .State.StartedAt | xargs -n1 date +%s -d)))
|
||||
if [ ${S_RUNNING} -lt 360 ]; then
|
||||
log_msg "Container is running for less than 360 seconds, skipping action..."
|
||||
elif [[ ! -z ${HAS_INITDB} ]]; then
|
||||
@@ -1108,7 +1112,7 @@ while true; do
|
||||
sleep 60
|
||||
else
|
||||
log_msg "Sending restart command to ${CONTAINER_ID}..."
|
||||
curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/restart
|
||||
curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/restart
|
||||
notify_error "${com_pipe_answer}"
|
||||
log_msg "Wait for restarted container to settle and continue watching..."
|
||||
sleep 35
|
||||
|
||||
29
data/assets/templates/pw_reset_html.tpl
Normal file
29
data/assets/templates/pw_reset_html.tpl
Normal file
@@ -0,0 +1,29 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
<style>
|
||||
body {
|
||||
font-family: Helvetica, Arial, Sans-Serif;
|
||||
}
|
||||
/* mobile devices */
|
||||
@media all and (max-width: 480px) {
|
||||
.mob {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
Hello {{username2}},<br><br>
|
||||
|
||||
Somebody requested a new password for the {{hostname}} account associated with {{username}}.<br>
|
||||
<small>Date of the password reset request: {{date}}</small><br><br>
|
||||
|
||||
You can reset your password by clicking the link below:<br>
|
||||
<a href="{{link}}">{{link}}</a><br><br>
|
||||
|
||||
The link will be valid for the next {{token_lifetime}} minutes.<br><br>
|
||||
|
||||
If you did not request a new password, please ignore this email.<br>
|
||||
</body>
|
||||
</html>
|
||||
11
data/assets/templates/pw_reset_text.tpl
Normal file
11
data/assets/templates/pw_reset_text.tpl
Normal file
@@ -0,0 +1,11 @@
|
||||
Hello {{username2}},
|
||||
|
||||
Somebody requested a new password for the {{hostname}} account associated with {{username}}.
|
||||
Date of the password reset request: {{date}}
|
||||
|
||||
You can reset your password by clicking the link below:
|
||||
{{link}}
|
||||
|
||||
The link will be valid for the next {{token_lifetime}} minutes.
|
||||
|
||||
If you did not request a new password, please ignore this email.
|
||||
@@ -10,6 +10,7 @@
|
||||
auth_mechanisms = plain login
|
||||
#mail_debug = yes
|
||||
#auth_debug = yes
|
||||
#log_debug = category=fts-flatcurve # Activate Logging for Flatcurve FTS Searchings
|
||||
log_path = syslog
|
||||
disable_plaintext_auth = yes
|
||||
# Uncomment on NFS share
|
||||
@@ -194,9 +195,6 @@ plugin {
|
||||
acl_shared_dict = file:/var/vmail/shared-mailboxes.db
|
||||
acl = vfile
|
||||
acl_user = %u
|
||||
fts = solr
|
||||
fts_autoindex = yes
|
||||
fts_solr = url=http://solr:8983/solr/dovecot-fts/
|
||||
quota = dict:Userquota::proxy::sqlquota
|
||||
quota_rule2 = Trash:storage=+100%%
|
||||
sieve = /var/vmail/sieve/%u.sieve
|
||||
@@ -247,6 +245,9 @@ plugin {
|
||||
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
|
||||
mail_log_fields = uid box msgid size
|
||||
mail_log_cached_only = yes
|
||||
|
||||
# Try set mail_replica
|
||||
!include_try /etc/dovecot/mail_replica.conf
|
||||
}
|
||||
service quota-warning {
|
||||
executable = script /usr/local/bin/quota_notify.py
|
||||
@@ -302,6 +303,7 @@ replication_dsync_parameters = -d -l 30 -U -n INBOX
|
||||
!include_try /etc/dovecot/extra.conf
|
||||
!include_try /etc/dovecot/sogo-sso.conf
|
||||
!include_try /etc/dovecot/shared_namespace.conf
|
||||
!include_try /etc/dovecot/conf.d/fts.conf
|
||||
# </Includes>
|
||||
default_client_limit = 10400
|
||||
default_vsz_limit = 1024 M
|
||||
|
||||
@@ -289,5 +289,20 @@ namespace inbox {
|
||||
mailbox "Kladde" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Πρόχειρα" {
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox "Απεσταλμένα" {
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox "Κάδος απορριμάτων" {
|
||||
special_use = \Trash
|
||||
}
|
||||
mailbox "Ανεπιθύμητα" {
|
||||
special_use = \Junk
|
||||
}
|
||||
mailbox "Αρχειοθετημένα" {
|
||||
special_use = \Archive
|
||||
}
|
||||
prefix =
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
if /^\s*Received:.*Authenticated sender.*\(Postcow\)/
|
||||
#/^Received: from .*? \([\w-.]* \[.*?\]\)\s+\(Authenticated sender: (.+)\)\s+by.+\(Postcow\) with (E?SMTPS?A?) id ([A-F0-9]+).+;.*?/
|
||||
/^Received: from .*? \([\w-.]* \[.*?\]\)(.*|\n.*)\(Authenticated sender: (.+)\)\s+by.+\(Postcow\) with (.*)/
|
||||
/^Received: from .*? \([\w\-.]* \[.*?\]\)(.*|\n.*)\(Authenticated sender: (.+)\)\s+by.+\(Postcow\) with (.*)/
|
||||
REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with $3
|
||||
endif
|
||||
if /^\s*Received: from.* \(.*dovecot-mailcow.*mailcow-network.*\).*\(Postcow\)/
|
||||
|
||||
@@ -85,6 +85,7 @@ smtp_tls_security_level = dane
|
||||
smtpd_data_restrictions = reject_unauth_pipelining, permit
|
||||
smtpd_delay_reject = yes
|
||||
smtpd_error_sleep_time = 10s
|
||||
smtpd_forbid_bare_newline = yes
|
||||
smtpd_hard_error_limit = ${stress?1}${stress:5}
|
||||
smtpd_helo_required = yes
|
||||
smtpd_proxy_timeout = 600s
|
||||
@@ -113,14 +114,14 @@ smtpd_tls_loglevel = 1
|
||||
|
||||
# Mandatory protocols and ciphers are used when a connections is enforced to use TLS
|
||||
# Does _not_ apply to enforced incoming TLS settings per mailbox
|
||||
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
|
||||
lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
|
||||
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
|
||||
smtp_tls_mandatory_protocols = >=TLSv1.2
|
||||
lmtp_tls_mandatory_protocols = >=TLSv1.2
|
||||
smtpd_tls_mandatory_protocols = >=TLSv1.2
|
||||
smtpd_tls_mandatory_ciphers = high
|
||||
|
||||
smtp_tls_protocols = !SSLv2, !SSLv3
|
||||
lmtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
|
||||
smtpd_tls_protocols = !SSLv2, !SSLv3
|
||||
smtp_tls_protocols = >=TLSv1.2
|
||||
lmtp_tls_protocols = >=TLSv1.2
|
||||
smtpd_tls_protocols = >=TLSv1.2
|
||||
|
||||
smtpd_tls_security_level = may
|
||||
tls_preempt_cipherlist = yes
|
||||
@@ -161,12 +162,13 @@ transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
|
||||
proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf
|
||||
smtp_sasl_auth_soft_bounce = no
|
||||
postscreen_discard_ehlo_keywords = silent-discard, dsn
|
||||
compatibility_level = 2
|
||||
postscreen_discard_ehlo_keywords = silent-discard, dsn, chunking
|
||||
smtpd_discard_ehlo_keywords = chunking, silent-discard
|
||||
compatibility_level = 3.7
|
||||
smtputf8_enable = no
|
||||
# Define protocols for SMTPS and submission service
|
||||
submission_smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
|
||||
smtps_smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
|
||||
submission_smtpd_tls_mandatory_protocols = >=TLSv1.2
|
||||
smtps_smtpd_tls_mandatory_protocols = >=TLSv1.2
|
||||
parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients
|
||||
|
||||
# DO NOT EDIT ANYTHING BELOW #
|
||||
|
||||
@@ -4,7 +4,6 @@ smtp inet n - n - 1 postscreen
|
||||
-o postscreen_upstream_proxy_protocol=haproxy
|
||||
-o syslog_name=haproxy
|
||||
smtpd pass - - n - - smtpd
|
||||
-o smtpd_helo_restrictions=permit_mynetworks,reject_non_fqdn_helo_hostname
|
||||
-o smtpd_sasl_auth_enable=no
|
||||
-o smtpd_sender_restrictions=permit_mynetworks,reject_unlisted_sender,reject_unknown_sender_domain
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Whitelist generated by Postwhite v3.4 on Fri Dec 1 00:15:18 UTC 2023
|
||||
# Whitelist generated by Postwhite v3.4 on Thu Aug 1 00:16:45 UTC 2024
|
||||
# https://github.com/stevejenkins/postwhite/
|
||||
# 2038 total rules
|
||||
# 1954 total rules
|
||||
2a00:1450:4000::/36 permit
|
||||
2a01:111:f400::/48 permit
|
||||
2a01:111:f403:8000::/50 permit
|
||||
2a01:111:f403:8000::/51 permit
|
||||
2a01:111:f403::/49 permit
|
||||
2a01:111:f403:c000::/51 permit
|
||||
2a01:111:f403:f000::/52 permit
|
||||
@@ -12,40 +13,39 @@
|
||||
2.207.151.53 permit
|
||||
3.70.123.177 permit
|
||||
3.93.157.0/24 permit
|
||||
3.94.40.108 permit
|
||||
3.129.120.190 permit
|
||||
3.137.78.75 permit
|
||||
3.210.190.0/24 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.40.222.0/23 permit
|
||||
10.162.0.0/16 permit
|
||||
12.130.86.238 permit
|
||||
13.70.32.43 permit
|
||||
13.72.50.45 permit
|
||||
13.74.143.28 permit
|
||||
13.78.233.182 permit
|
||||
13.92.31.129 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
|
||||
15.200.21.50 permit
|
||||
15.200.44.248 permit
|
||||
15.200.201.185 permit
|
||||
17.41.0.0/16 permit
|
||||
17.57.155.0/24 permit
|
||||
17.57.156.0/24 permit
|
||||
17.58.0.0/16 permit
|
||||
17.142.0.0/15 permit
|
||||
18.156.89.250 permit
|
||||
18.157.243.190 permit
|
||||
18.194.95.56 permit
|
||||
18.198.96.88 permit
|
||||
18.208.124.128/25 permit
|
||||
18.216.232.154 permit
|
||||
18.234.1.244 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
|
||||
@@ -63,10 +63,8 @@
|
||||
20.107.239.64/30 permit
|
||||
20.112.250.133 permit
|
||||
20.118.139.208/30 permit
|
||||
20.185.213.160/27 permit
|
||||
20.185.213.224/27 permit
|
||||
20.141.10.196 permit
|
||||
20.185.214.0/27 permit
|
||||
20.185.214.2 permit
|
||||
20.185.214.32/27 permit
|
||||
20.185.214.64/27 permit
|
||||
20.231.239.246 permit
|
||||
@@ -90,39 +88,37 @@
|
||||
27.123.204.172 permit
|
||||
27.123.204.188/30 permit
|
||||
27.123.204.192 permit
|
||||
27.123.206.0/24 permit
|
||||
27.123.206.50/31 permit
|
||||
27.123.206.56/29 permit
|
||||
27.123.206.76/30 permit
|
||||
27.123.206.80/28 permit
|
||||
31.25.48.222 permit
|
||||
34.195.217.107 permit
|
||||
34.202.239.6 permit
|
||||
34.212.163.75 permit
|
||||
34.215.104.144 permit
|
||||
34.218.116.3 permit
|
||||
34.225.212.172 permit
|
||||
34.247.168.44 permit
|
||||
35.161.32.253 permit
|
||||
35.167.93.243 permit
|
||||
35.176.132.251 permit
|
||||
35.190.247.0/24 permit
|
||||
35.191.0.0/16 permit
|
||||
35.205.92.9 permit
|
||||
35.242.169.159 permit
|
||||
37.218.248.47 permit
|
||||
37.218.249.47 permit
|
||||
37.218.251.62 permit
|
||||
39.156.163.64/29 permit
|
||||
40.71.187.0/24 permit
|
||||
40.92.0.0/15 permit
|
||||
40.92.0.0/16 permit
|
||||
40.107.0.0/16 permit
|
||||
40.112.65.63 permit
|
||||
40.117.80.0/24 permit
|
||||
43.228.184.0/22 permit
|
||||
44.206.138.57 permit
|
||||
44.209.42.157 permit
|
||||
44.217.45.156 permit
|
||||
44.236.56.93 permit
|
||||
44.238.220.251 permit
|
||||
46.19.168.0/23 permit
|
||||
46.19.170.16 permit
|
||||
46.226.48.0/21 permit
|
||||
46.228.36.37 permit
|
||||
46.228.36.38/31 permit
|
||||
@@ -162,7 +158,6 @@
|
||||
46.228.38.144/29 permit
|
||||
46.228.38.152/31 permit
|
||||
46.228.38.154 permit
|
||||
46.228.39.0/24 permit
|
||||
46.228.39.64/27 permit
|
||||
46.228.39.96/30 permit
|
||||
46.228.39.100/30 permit
|
||||
@@ -183,29 +178,28 @@
|
||||
50.18.125.237 permit
|
||||
50.18.126.162 permit
|
||||
50.31.32.0/19 permit
|
||||
51.137.58.21 permit
|
||||
51.140.75.55 permit
|
||||
51.144.100.179 permit
|
||||
50.31.36.205 permit
|
||||
50.56.130.220/30 permit
|
||||
52.1.14.157 permit
|
||||
52.5.230.59 permit
|
||||
52.27.5.72 permit
|
||||
52.27.28.47 permit
|
||||
52.28.63.81 permit
|
||||
52.36.138.31 permit
|
||||
52.37.142.146 permit
|
||||
52.50.24.208 permit
|
||||
52.58.216.183 permit
|
||||
52.59.143.3 permit
|
||||
52.60.41.5 permit
|
||||
52.60.115.116 permit
|
||||
52.61.91.9 permit
|
||||
52.71.0.205 permit
|
||||
52.82.172.0/22 permit
|
||||
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.172.98 permit
|
||||
52.96.214.50 permit
|
||||
52.96.222.194 permit
|
||||
52.96.222.226 permit
|
||||
52.96.223.2 permit
|
||||
@@ -214,8 +208,6 @@
|
||||
52.100.0.0/14 permit
|
||||
52.103.0.0/17 permit
|
||||
52.119.213.144/28 permit
|
||||
52.160.39.140 permit
|
||||
52.165.175.144 permit
|
||||
52.185.106.240/28 permit
|
||||
52.200.59.0/24 permit
|
||||
52.205.61.79 permit
|
||||
@@ -226,37 +218,29 @@
|
||||
52.222.75.85 permit
|
||||
52.222.89.228 permit
|
||||
52.234.172.96/28 permit
|
||||
52.235.253.128 permit
|
||||
52.236.28.240/28 permit
|
||||
52.244.206.214 permit
|
||||
52.247.53.144 permit
|
||||
52.250.107.196 permit
|
||||
52.250.126.174 permit
|
||||
54.90.148.255 permit
|
||||
54.165.19.38 permit
|
||||
54.172.97.247 permit
|
||||
54.174.52.0/24 permit
|
||||
54.174.53.128/30 permit
|
||||
54.174.57.0/24 permit
|
||||
54.174.59.0/24 permit
|
||||
54.174.60.0/23 permit
|
||||
54.174.63.0/24 permit
|
||||
54.186.193.102 permit
|
||||
54.191.223.56 permit
|
||||
54.194.61.95 permit
|
||||
54.195.113.45 permit
|
||||
54.213.20.246 permit
|
||||
54.214.39.184 permit
|
||||
54.216.77.168 permit
|
||||
54.221.227.204 permit
|
||||
54.240.0.0/18 permit
|
||||
54.240.64.0/19 permit
|
||||
54.240.96.0/19 permit
|
||||
54.241.16.209 permit
|
||||
54.244.54.130 permit
|
||||
54.244.242.0/24 permit
|
||||
54.246.232.180 permit
|
||||
54.255.61.23 permit
|
||||
62.13.128.0/24 permit
|
||||
62.13.128.150 permit
|
||||
62.13.128.196 permit
|
||||
62.13.129.128/25 permit
|
||||
62.13.136.0/22 permit
|
||||
62.13.140.0/22 permit
|
||||
@@ -264,14 +248,13 @@
|
||||
62.13.148.0/23 permit
|
||||
62.13.150.0/23 permit
|
||||
62.13.152.0/23 permit
|
||||
62.13.159.196 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
|
||||
63.32.13.159 permit
|
||||
63.80.14.0/23 permit
|
||||
63.111.28.137 permit
|
||||
63.128.21.0/24 permit
|
||||
63.143.57.128/25 permit
|
||||
63.143.59.128/25 permit
|
||||
@@ -284,24 +267,9 @@
|
||||
64.79.155.193 permit
|
||||
64.79.155.205 permit
|
||||
64.79.155.206 permit
|
||||
64.89.44.85 permit
|
||||
64.89.45.80 permit
|
||||
64.89.45.194 permit
|
||||
64.89.45.196 permit
|
||||
64.127.115.252 permit
|
||||
64.132.88.0/23 permit
|
||||
64.132.92.0/24 permit
|
||||
64.147.123.17 permit
|
||||
64.147.123.18 permit
|
||||
64.147.123.19 permit
|
||||
64.147.123.20 permit
|
||||
64.147.123.21 permit
|
||||
64.147.123.24 permit
|
||||
64.147.123.25 permit
|
||||
64.147.123.26 permit
|
||||
64.147.123.27 permit
|
||||
64.147.123.28 permit
|
||||
64.147.123.29 permit
|
||||
64.147.123.128/27 permit
|
||||
64.207.219.7 permit
|
||||
64.207.219.8 permit
|
||||
@@ -356,26 +324,10 @@
|
||||
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.111.4.25 permit
|
||||
66.111.4.26 permit
|
||||
66.111.4.27 permit
|
||||
66.111.4.28 permit
|
||||
66.111.4.29 permit
|
||||
66.111.4.221 permit
|
||||
66.111.4.222 permit
|
||||
66.111.4.224 permit
|
||||
66.111.4.225 permit
|
||||
66.111.4.229 permit
|
||||
66.111.4.230 permit
|
||||
66.119.150.192/26 permit
|
||||
66.135.202.0/27 permit
|
||||
66.135.215.0/24 permit
|
||||
66.135.222.1 permit
|
||||
66.162.193.226/31 permit
|
||||
66.163.184.0/21 permit
|
||||
66.163.184.0/24 permit
|
||||
66.163.185.0/24 permit
|
||||
66.163.186.0/24 permit
|
||||
@@ -388,7 +340,6 @@
|
||||
66.196.80.112/28 permit
|
||||
66.196.80.144/29 permit
|
||||
66.196.80.193 permit
|
||||
66.196.81.0/24 permit
|
||||
66.196.81.104/29 permit
|
||||
66.196.81.112/29 permit
|
||||
66.196.81.120 permit
|
||||
@@ -403,7 +354,6 @@
|
||||
66.196.81.228/30 permit
|
||||
66.196.81.232/31 permit
|
||||
66.196.81.234 permit
|
||||
66.211.168.230/31 permit
|
||||
66.211.170.88/29 permit
|
||||
66.211.184.0/23 permit
|
||||
66.218.74.64/30 permit
|
||||
@@ -432,12 +382,10 @@
|
||||
66.249.80.0/20 permit
|
||||
67.23.31.6 permit
|
||||
67.72.99.26 permit
|
||||
67.195.22.0/24 permit
|
||||
67.195.22.113 permit
|
||||
67.195.22.116/30 permit
|
||||
67.195.23.144/30 permit
|
||||
67.195.23.148 permit
|
||||
67.195.60.0/24 permit
|
||||
67.195.60.45 permit
|
||||
67.195.60.46/31 permit
|
||||
67.195.60.48/31 permit
|
||||
@@ -445,7 +393,6 @@
|
||||
67.195.60.146 permit
|
||||
67.195.60.155 permit
|
||||
67.195.60.156 permit
|
||||
67.195.87.0/24 permit
|
||||
67.195.87.64 permit
|
||||
67.195.87.81 permit
|
||||
67.195.87.82/31 permit
|
||||
@@ -470,7 +417,6 @@
|
||||
67.228.37.4/30 permit
|
||||
67.231.145.42 permit
|
||||
67.231.153.30 permit
|
||||
68.142.230.0/24 permit
|
||||
68.142.230.64/31 permit
|
||||
68.142.230.69 permit
|
||||
68.142.230.70/31 permit
|
||||
@@ -496,9 +442,7 @@
|
||||
69.171.232.0/24 permit
|
||||
69.171.244.0/23 permit
|
||||
70.37.151.128/25 permit
|
||||
70.42.149.0/24 permit
|
||||
70.42.149.35 permit
|
||||
72.3.237.64/28 permit
|
||||
72.14.192.0/18 permit
|
||||
72.21.192.0/19 permit
|
||||
72.21.217.142 permit
|
||||
@@ -529,7 +473,6 @@
|
||||
72.30.237.180/30 permit
|
||||
72.30.237.184/31 permit
|
||||
72.30.237.204/30 permit
|
||||
72.30.238.0/23 permit
|
||||
72.30.238.116/30 permit
|
||||
72.30.238.120/31 permit
|
||||
72.30.238.128 permit
|
||||
@@ -560,12 +503,7 @@
|
||||
72.30.239.228/31 permit
|
||||
72.30.239.244/30 permit
|
||||
72.30.239.248/31 permit
|
||||
72.34.168.76 permit
|
||||
72.34.168.80 permit
|
||||
72.34.168.85 permit
|
||||
72.34.168.86 permit
|
||||
72.52.72.32/28 permit
|
||||
74.6.128.0/21 permit
|
||||
74.6.128.0/24 permit
|
||||
74.6.129.0/24 permit
|
||||
74.6.130.0/24 permit
|
||||
@@ -592,13 +530,14 @@
|
||||
74.112.67.243 permit
|
||||
74.125.0.0/16 permit
|
||||
74.202.227.40 permit
|
||||
74.208.4.192/26 permit
|
||||
74.208.5.64/26 permit
|
||||
74.208.122.0/26 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/22 permit
|
||||
77.238.176.0/24 permit
|
||||
77.238.177.0/24 permit
|
||||
77.238.178.0/24 permit
|
||||
@@ -619,21 +558,26 @@
|
||||
77.238.189.142 permit
|
||||
77.238.189.146/31 permit
|
||||
77.238.189.148/30 permit
|
||||
81.7.169.128/25 permit
|
||||
81.223.46.0/27 permit
|
||||
82.165.159.0/24 permit
|
||||
82.165.159.0/26 permit
|
||||
82.165.229.31 permit
|
||||
82.165.229.130 permit
|
||||
82.165.230.21 permit
|
||||
82.165.230.22 permit
|
||||
82.165.159.2 permit
|
||||
82.165.159.3 permit
|
||||
82.165.159.4 permit
|
||||
82.165.159.12 permit
|
||||
82.165.159.13 permit
|
||||
82.165.159.14 permit
|
||||
82.165.159.34 permit
|
||||
82.165.159.35 permit
|
||||
82.165.159.40 permit
|
||||
82.165.159.41 permit
|
||||
82.165.159.42 permit
|
||||
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.158.136.0/21 permit
|
||||
86.61.88.25 permit
|
||||
87.198.219.130 permit
|
||||
87.198.219.153 permit
|
||||
87.238.80.0/21 permit
|
||||
87.248.103.12 permit
|
||||
87.248.103.21 permit
|
||||
@@ -673,6 +617,7 @@
|
||||
87.253.232.0/21 permit
|
||||
89.22.108.0/24 permit
|
||||
91.211.240.0/22 permit
|
||||
94.169.2.0/23 permit
|
||||
94.245.112.0/27 permit
|
||||
94.245.112.10/31 permit
|
||||
95.131.104.0/21 permit
|
||||
@@ -686,7 +631,6 @@
|
||||
98.136.44.181 permit
|
||||
98.136.44.182/31 permit
|
||||
98.136.44.184 permit
|
||||
98.136.164.0/24 permit
|
||||
98.136.164.36/31 permit
|
||||
98.136.164.64/29 permit
|
||||
98.136.164.72/30 permit
|
||||
@@ -694,7 +638,6 @@
|
||||
98.136.164.78 permit
|
||||
98.136.172.32/30 permit
|
||||
98.136.172.36/31 permit
|
||||
98.136.185.0/24 permit
|
||||
98.136.185.29 permit
|
||||
98.136.185.42/31 permit
|
||||
98.136.185.46 permit
|
||||
@@ -729,7 +672,6 @@
|
||||
98.136.215.184 permit
|
||||
98.136.215.208/30 permit
|
||||
98.136.215.212/31 permit
|
||||
98.136.217.0/24 permit
|
||||
98.136.217.1 permit
|
||||
98.136.217.2 permit
|
||||
98.136.217.3 permit
|
||||
@@ -739,14 +681,12 @@
|
||||
98.136.217.12/30 permit
|
||||
98.136.217.16/30 permit
|
||||
98.136.217.20/30 permit
|
||||
98.136.218.0/24 permit
|
||||
98.136.218.39 permit
|
||||
98.136.218.40/29 permit
|
||||
98.136.218.48/28 permit
|
||||
98.136.218.67 permit
|
||||
98.136.218.68/30 permit
|
||||
98.136.218.72/30 permit
|
||||
98.137.12.0/24 permit
|
||||
98.137.12.48/30 permit
|
||||
98.137.12.52/31 permit
|
||||
98.137.12.54 permit
|
||||
@@ -784,7 +724,6 @@
|
||||
98.137.13.132 permit
|
||||
98.137.13.137 permit
|
||||
98.137.13.138 permit
|
||||
98.137.64.0/21 permit
|
||||
98.137.64.0/24 permit
|
||||
98.137.65.0/24 permit
|
||||
98.137.66.0/24 permit
|
||||
@@ -808,7 +747,6 @@
|
||||
98.138.83.176/31 permit
|
||||
98.138.83.179 permit
|
||||
98.138.83.180/31 permit
|
||||
98.138.84.0/22 permit
|
||||
98.138.84.37 permit
|
||||
98.138.84.38/31 permit
|
||||
98.138.84.40/29 permit
|
||||
@@ -849,7 +787,6 @@
|
||||
98.138.87.148/31 permit
|
||||
98.138.87.192/30 permit
|
||||
98.138.87.196/31 permit
|
||||
98.138.88.0/22 permit
|
||||
98.138.88.105 permit
|
||||
98.138.88.106 permit
|
||||
98.138.88.128/30 permit
|
||||
@@ -889,7 +826,6 @@
|
||||
98.138.91.2/31 permit
|
||||
98.138.91.4/31 permit
|
||||
98.138.91.6 permit
|
||||
98.138.100.0/23 permit
|
||||
98.138.100.220/30 permit
|
||||
98.138.100.224/30 permit
|
||||
98.138.100.228/31 permit
|
||||
@@ -899,7 +835,6 @@
|
||||
98.138.104.100 permit
|
||||
98.138.104.112/30 permit
|
||||
98.138.104.116 permit
|
||||
98.138.120.0/24 permit
|
||||
98.138.120.36/30 permit
|
||||
98.138.120.48/28 permit
|
||||
98.138.197.46/31 permit
|
||||
@@ -992,12 +927,10 @@
|
||||
98.138.213.238/31 permit
|
||||
98.138.213.240/31 permit
|
||||
98.138.213.242 permit
|
||||
98.138.215.0/24 permit
|
||||
98.138.215.12/30 permit
|
||||
98.138.215.16/28 permit
|
||||
98.138.217.216/30 permit
|
||||
98.138.217.220/31 permit
|
||||
98.138.226.0/24 permit
|
||||
98.138.226.30/31 permit
|
||||
98.138.226.56/29 permit
|
||||
98.138.226.64/30 permit
|
||||
@@ -1023,21 +956,18 @@
|
||||
98.138.227.108/31 permit
|
||||
98.138.227.128/30 permit
|
||||
98.138.227.132/31 permit
|
||||
98.138.229.0/24 permit
|
||||
98.138.229.24/29 permit
|
||||
98.138.229.32/31 permit
|
||||
98.138.229.122/31 permit
|
||||
98.138.229.138/31 permit
|
||||
98.138.229.154/31 permit
|
||||
98.138.229.170/31 permit
|
||||
98.139.164.0/24 permit
|
||||
98.139.164.96/30 permit
|
||||
98.139.164.100/30 permit
|
||||
98.139.164.104/29 permit
|
||||
98.139.164.112/30 permit
|
||||
98.139.172.112/30 permit
|
||||
98.139.172.116/31 permit
|
||||
98.139.175.0/24 permit
|
||||
98.139.175.65 permit
|
||||
98.139.175.66/31 permit
|
||||
98.139.175.68/30 permit
|
||||
@@ -1062,10 +992,8 @@
|
||||
98.139.210.196/31 permit
|
||||
98.139.210.202/31 permit
|
||||
98.139.210.204/30 permit
|
||||
98.139.211.0/24 permit
|
||||
98.139.211.160/30 permit
|
||||
98.139.211.192/28 permit
|
||||
98.139.212.0/23 permit
|
||||
98.139.212.160/28 permit
|
||||
98.139.212.176/29 permit
|
||||
98.139.212.184/30 permit
|
||||
@@ -1080,7 +1008,6 @@
|
||||
98.139.214.155 permit
|
||||
98.139.214.156/30 permit
|
||||
98.139.214.221 permit
|
||||
98.139.215.0/24 permit
|
||||
98.139.215.228/31 permit
|
||||
98.139.215.230 permit
|
||||
98.139.215.248/30 permit
|
||||
@@ -1139,14 +1066,12 @@
|
||||
98.139.220.243 permit
|
||||
98.139.220.245 permit
|
||||
98.139.220.253 permit
|
||||
98.139.221.0/24 permit
|
||||
98.139.221.43 permit
|
||||
98.139.221.60/30 permit
|
||||
98.139.221.156/30 permit
|
||||
98.139.221.232/30 permit
|
||||
98.139.221.236/31 permit
|
||||
98.139.221.250 permit
|
||||
98.139.244.0/24 permit
|
||||
98.139.244.47 permit
|
||||
98.139.244.49 permit
|
||||
98.139.244.50/31 permit
|
||||
@@ -1186,6 +1111,7 @@
|
||||
98.139.245.208/30 permit
|
||||
98.139.245.212/31 permit
|
||||
99.78.197.208/28 permit
|
||||
99.83.190.102 permit
|
||||
103.2.140.0/22 permit
|
||||
103.9.96.0/22 permit
|
||||
103.28.42.0/24 permit
|
||||
@@ -1200,7 +1126,6 @@
|
||||
104.47.108.0/23 permit
|
||||
104.130.96.0/28 permit
|
||||
104.130.122.0/23 permit
|
||||
104.214.25.77 permit
|
||||
106.10.144.64/27 permit
|
||||
106.10.144.100/31 permit
|
||||
106.10.144.103 permit
|
||||
@@ -1226,7 +1151,6 @@
|
||||
106.10.146.52/31 permit
|
||||
106.10.146.224/30 permit
|
||||
106.10.146.228/31 permit
|
||||
106.10.148.0/24 permit
|
||||
106.10.148.48/30 permit
|
||||
106.10.148.52/31 permit
|
||||
106.10.148.68/30 permit
|
||||
@@ -1239,7 +1163,6 @@
|
||||
106.10.149.30 permit
|
||||
106.10.149.160/30 permit
|
||||
106.10.149.164/31 permit
|
||||
106.10.150.0/23 permit
|
||||
106.10.150.23 permit
|
||||
106.10.150.24/30 permit
|
||||
106.10.150.28/31 permit
|
||||
@@ -1278,7 +1201,6 @@
|
||||
106.10.151.250/31 permit
|
||||
106.10.151.252/31 permit
|
||||
106.10.151.254 permit
|
||||
106.10.167.0/24 permit
|
||||
106.10.167.72 permit
|
||||
106.10.167.128/27 permit
|
||||
106.10.167.160/28 permit
|
||||
@@ -1300,7 +1222,6 @@
|
||||
106.10.174.120/30 permit
|
||||
106.10.174.154/31 permit
|
||||
106.10.174.156/30 permit
|
||||
106.10.176.0/24 permit
|
||||
106.10.176.32/29 permit
|
||||
106.10.176.48 permit
|
||||
106.10.176.112 permit
|
||||
@@ -1319,7 +1240,6 @@
|
||||
106.10.196.43 permit
|
||||
106.10.196.44/30 permit
|
||||
106.10.196.48 permit
|
||||
106.10.240.0/22 permit
|
||||
106.10.240.0/24 permit
|
||||
106.10.241.0/24 permit
|
||||
106.10.242.0/24 permit
|
||||
@@ -1327,6 +1247,8 @@
|
||||
106.10.244.0/24 permit
|
||||
106.39.212.64/29 permit
|
||||
106.50.16.0/28 permit
|
||||
107.20.18.111 permit
|
||||
107.20.210.250 permit
|
||||
108.174.0.0/24 permit
|
||||
108.174.0.215 permit
|
||||
108.174.3.0/24 permit
|
||||
@@ -1337,6 +1259,7 @@
|
||||
108.175.30.45 permit
|
||||
108.177.8.0/21 permit
|
||||
108.177.96.0/19 permit
|
||||
108.179.144.0/20 permit
|
||||
109.237.142.0/24 permit
|
||||
111.221.23.128/25 permit
|
||||
111.221.26.0/27 permit
|
||||
@@ -1345,7 +1268,6 @@
|
||||
111.221.112.0/21 permit
|
||||
112.19.199.64/29 permit
|
||||
112.19.242.64/29 permit
|
||||
116.214.12.0/24 permit
|
||||
116.214.12.47 permit
|
||||
116.214.12.48/31 permit
|
||||
116.214.12.56/31 permit
|
||||
@@ -1361,10 +1283,7 @@
|
||||
117.120.16.0/21 permit
|
||||
119.42.242.52/31 permit
|
||||
119.42.242.156 permit
|
||||
121.244.91.48 permit
|
||||
122.15.156.182 permit
|
||||
123.126.78.64/29 permit
|
||||
124.108.96.0/24 permit
|
||||
124.108.96.24/31 permit
|
||||
124.108.96.28/31 permit
|
||||
124.108.96.70/31 permit
|
||||
@@ -1419,24 +1338,14 @@
|
||||
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.182.0/23 permit
|
||||
136.143.184.0/24 permit
|
||||
136.143.188.0/24 permit
|
||||
136.143.190.0/23 permit
|
||||
136.147.128.0/20 permit
|
||||
136.147.135.0/24 permit
|
||||
136.147.176.0/20 permit
|
||||
136.147.176.0/24 permit
|
||||
136.147.182.0/24 permit
|
||||
136.147.224.0/20 permit
|
||||
136.179.50.206 permit
|
||||
138.91.172.26 permit
|
||||
139.60.152.0/22 permit
|
||||
139.138.35.44 permit
|
||||
139.138.46.121 permit
|
||||
@@ -1447,6 +1356,12 @@
|
||||
139.180.17.0/24 permit
|
||||
141.148.159.229 permit
|
||||
141.193.32.0/23 permit
|
||||
141.193.184.32/27 permit
|
||||
141.193.184.64/26 permit
|
||||
141.193.184.128/25 permit
|
||||
141.193.185.32/27 permit
|
||||
141.193.185.64/26 permit
|
||||
141.193.185.128/25 permit
|
||||
143.55.224.0/21 permit
|
||||
143.55.232.0/22 permit
|
||||
143.55.236.0/22 permit
|
||||
@@ -1460,6 +1375,7 @@
|
||||
144.178.38.0/24 permit
|
||||
145.253.228.160/29 permit
|
||||
145.253.239.128/29 permit
|
||||
146.20.14.104/30 permit
|
||||
146.20.112.0/26 permit
|
||||
146.20.113.0/24 permit
|
||||
146.20.191.0/24 permit
|
||||
@@ -1474,11 +1390,13 @@
|
||||
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.248.236 permit
|
||||
149.97.173.180 permit
|
||||
150.230.98.160 permit
|
||||
152.67.105.195 permit
|
||||
152.69.200.236 permit
|
||||
152.70.155.126 permit
|
||||
155.248.208.51 permit
|
||||
157.55.0.192/26 permit
|
||||
157.55.1.128/26 permit
|
||||
@@ -1489,7 +1407,6 @@
|
||||
157.55.61.0/24 permit
|
||||
157.55.157.128/25 permit
|
||||
157.55.225.0/25 permit
|
||||
157.55.254.216 permit
|
||||
157.56.24.0/25 permit
|
||||
157.56.120.128/26 permit
|
||||
157.56.232.0/21 permit
|
||||
@@ -1531,10 +1448,11 @@
|
||||
161.71.64.0/20 permit
|
||||
162.247.216.0/22 permit
|
||||
163.47.180.0/22 permit
|
||||
163.47.180.0/23 permit
|
||||
163.114.130.16 permit
|
||||
163.114.132.120 permit
|
||||
165.173.128.0/24 permit
|
||||
163.114.134.16 permit
|
||||
163.114.135.16 permit
|
||||
164.177.132.168/30 permit
|
||||
166.78.68.0/22 permit
|
||||
166.78.68.221 permit
|
||||
166.78.69.169 permit
|
||||
@@ -1543,6 +1461,7 @@
|
||||
167.89.0.0/17 permit
|
||||
167.89.46.159 permit
|
||||
167.89.54.103 permit
|
||||
167.89.60.95 permit
|
||||
167.89.64.9 permit
|
||||
167.89.65.0 permit
|
||||
167.89.65.53 permit
|
||||
@@ -1554,8 +1473,6 @@
|
||||
167.89.75.164 permit
|
||||
167.89.101.2 permit
|
||||
167.89.101.192/28 permit
|
||||
167.216.129.210 permit
|
||||
167.216.131.180 permit
|
||||
167.220.67.232/29 permit
|
||||
168.138.5.36 permit
|
||||
168.138.73.51 permit
|
||||
@@ -1563,10 +1480,6 @@
|
||||
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
|
||||
170.10.68.0/22 permit
|
||||
170.10.128.0/24 permit
|
||||
170.10.129.0/24 permit
|
||||
@@ -1604,14 +1517,14 @@
|
||||
182.50.76.0/22 permit
|
||||
182.50.78.64/28 permit
|
||||
183.240.219.64/29 permit
|
||||
185.4.120.0/23 permit
|
||||
185.4.122.0/24 permit
|
||||
185.4.120.0/22 permit
|
||||
185.12.80.0/22 permit
|
||||
185.58.84.93 permit
|
||||
185.80.93.204 permit
|
||||
185.80.93.227 permit
|
||||
185.80.95.31 permit
|
||||
185.90.20.0/22 permit
|
||||
185.138.56.128/25 permit
|
||||
185.189.236.0/22 permit
|
||||
185.211.120.0/22 permit
|
||||
185.250.236.0/22 permit
|
||||
@@ -1628,7 +1541,6 @@
|
||||
188.125.68.184 permit
|
||||
188.125.68.186 permit
|
||||
188.125.68.192 permit
|
||||
188.125.69.0/24 permit
|
||||
188.125.69.105 permit
|
||||
188.125.69.110 permit
|
||||
188.125.69.112 permit
|
||||
@@ -1671,9 +1583,6 @@
|
||||
192.0.64.0/18 permit
|
||||
192.18.139.154 permit
|
||||
192.30.252.0/22 permit
|
||||
192.64.236.0/24 permit
|
||||
192.64.237.0/24 permit
|
||||
192.64.238.0/24 permit
|
||||
192.161.144.0/20 permit
|
||||
192.162.87.0/24 permit
|
||||
192.237.158.0/23 permit
|
||||
@@ -1689,7 +1598,6 @@
|
||||
193.122.128.100 permit
|
||||
193.123.56.63 permit
|
||||
194.19.134.0/25 permit
|
||||
194.64.234.128/27 permit
|
||||
194.64.234.129 permit
|
||||
194.106.220.0/23 permit
|
||||
194.113.24.0/22 permit
|
||||
@@ -1715,6 +1623,9 @@
|
||||
198.61.254.231 permit
|
||||
198.178.234.57 permit
|
||||
198.244.48.0/20 permit
|
||||
198.244.59.30 permit
|
||||
198.244.59.33 permit
|
||||
198.244.59.35 permit
|
||||
198.244.60.0/22 permit
|
||||
198.245.80.0/20 permit
|
||||
198.245.81.0/24 permit
|
||||
@@ -1723,12 +1634,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.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
|
||||
@@ -1753,7 +1659,6 @@
|
||||
203.188.194.251 permit
|
||||
203.188.195.240/30 permit
|
||||
203.188.195.244/31 permit
|
||||
203.188.197.0/24 permit
|
||||
203.188.197.193 permit
|
||||
203.188.197.194/31 permit
|
||||
203.188.197.196/30 permit
|
||||
@@ -1763,7 +1668,6 @@
|
||||
203.188.197.216/29 permit
|
||||
203.188.197.232/29 permit
|
||||
203.188.197.240/29 permit
|
||||
203.188.200.0/24 permit
|
||||
203.188.200.56/31 permit
|
||||
203.188.200.58 permit
|
||||
203.188.200.60/30 permit
|
||||
@@ -1779,18 +1683,14 @@
|
||||
203.209.230.76/31 permit
|
||||
204.11.168.0/21 permit
|
||||
204.13.11.48/29 permit
|
||||
204.13.11.48/30 permit
|
||||
204.14.232.0/21 permit
|
||||
204.14.232.64/28 permit
|
||||
204.14.234.64/28 permit
|
||||
204.29.186.0/23 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.220.160.0/20 permit
|
||||
204.232.168.0/24 permit
|
||||
205.139.110.0/24 permit
|
||||
@@ -1809,6 +1709,7 @@
|
||||
205.251.233.36 permit
|
||||
206.25.247.143 permit
|
||||
206.25.247.155 permit
|
||||
206.55.144.0/20 permit
|
||||
206.165.246.80/29 permit
|
||||
206.191.224.0/19 permit
|
||||
206.246.157.1 permit
|
||||
@@ -1826,12 +1727,12 @@
|
||||
207.46.132.128/27 permit
|
||||
207.46.198.0/25 permit
|
||||
207.46.200.0/27 permit
|
||||
207.46.225.107 permit
|
||||
207.58.147.64/28 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
|
||||
207.211.30.64/26 permit
|
||||
@@ -1845,11 +1746,7 @@
|
||||
208.43.21.28/30 permit
|
||||
208.43.21.64/29 permit
|
||||
208.43.21.72/30 permit
|
||||
208.46.212.80 permit
|
||||
208.46.212.208/31 permit
|
||||
208.46.212.210 permit
|
||||
208.64.132.0/22 permit
|
||||
208.71.40.0/24 permit
|
||||
208.71.40.63 permit
|
||||
208.71.40.64/31 permit
|
||||
208.71.40.174/31 permit
|
||||
@@ -1868,18 +1765,15 @@
|
||||
208.71.41.172/31 permit
|
||||
208.71.41.188/30 permit
|
||||
208.71.41.192/31 permit
|
||||
208.71.42.0/24 permit
|
||||
208.71.42.190/31 permit
|
||||
208.71.42.192/28 permit
|
||||
208.71.42.208/30 permit
|
||||
208.71.42.212/31 permit
|
||||
208.71.42.214 permit
|
||||
208.72.249.240/29 permit
|
||||
208.74.204.0/22 permit
|
||||
208.74.204.5 permit
|
||||
208.74.204.9 permit
|
||||
208.75.120.0/22 permit
|
||||
208.75.121.246 permit
|
||||
208.75.122.246 permit
|
||||
208.82.237.96/29 permit
|
||||
208.82.237.104/31 permit
|
||||
208.82.238.96/29 permit
|
||||
@@ -1898,10 +1792,8 @@
|
||||
209.67.98.46 permit
|
||||
209.67.98.59 permit
|
||||
209.85.128.0/17 permit
|
||||
212.82.96.0/24 permit
|
||||
212.82.96.32/27 permit
|
||||
212.82.96.64/29 permit
|
||||
212.82.98.0/24 permit
|
||||
212.82.98.32/29 permit
|
||||
212.82.98.64/27 permit
|
||||
212.82.98.96/30 permit
|
||||
@@ -1938,12 +1830,41 @@
|
||||
212.82.111.228/31 permit
|
||||
212.82.111.230 permit
|
||||
212.123.28.40 permit
|
||||
212.227.15.0/24 permit
|
||||
212.227.15.0/25 permit
|
||||
212.227.17.0/27 permit
|
||||
212.227.126.128/25 permit
|
||||
212.227.15.3 permit
|
||||
212.227.15.4 permit
|
||||
212.227.15.5 permit
|
||||
212.227.15.6 permit
|
||||
212.227.15.14 permit
|
||||
212.227.15.15 permit
|
||||
212.227.15.18 permit
|
||||
212.227.15.19 permit
|
||||
212.227.15.25 permit
|
||||
212.227.15.26 permit
|
||||
212.227.15.29 permit
|
||||
212.227.15.44 permit
|
||||
212.227.15.45 permit
|
||||
212.227.15.46 permit
|
||||
212.227.15.47 permit
|
||||
212.227.15.50 permit
|
||||
212.227.15.52 permit
|
||||
212.227.15.53 permit
|
||||
212.227.15.54 permit
|
||||
212.227.15.55 permit
|
||||
212.227.17.11 permit
|
||||
212.227.17.12 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.28 permit
|
||||
212.227.17.29 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.165.64.0/23 permit
|
||||
213.199.128.139 permit
|
||||
213.199.128.145 permit
|
||||
213.199.138.181 permit
|
||||
@@ -1954,7 +1875,6 @@
|
||||
216.17.150.251 permit
|
||||
216.22.15.224/27 permit
|
||||
216.24.224.0/20 permit
|
||||
216.39.60.0/23 permit
|
||||
216.39.60.154/31 permit
|
||||
216.39.60.156/30 permit
|
||||
216.39.60.160/30 permit
|
||||
@@ -1972,14 +1892,12 @@
|
||||
216.39.61.170 permit
|
||||
216.39.61.175 permit
|
||||
216.39.61.238/31 permit
|
||||
216.39.62.0/24 permit
|
||||
216.39.62.32/28 permit
|
||||
216.39.62.48/29 permit
|
||||
216.39.62.56/30 permit
|
||||
216.39.62.60/31 permit
|
||||
216.39.62.136/29 permit
|
||||
216.39.62.144/31 permit
|
||||
216.46.168.0/24 permit
|
||||
216.58.192.0/19 permit
|
||||
216.66.217.240/29 permit
|
||||
216.71.138.33 permit
|
||||
@@ -1992,12 +1910,8 @@
|
||||
216.98.158.0/24 permit
|
||||
216.99.5.67 permit
|
||||
216.99.5.68 permit
|
||||
216.109.114.0/24 permit
|
||||
216.109.114.32/27 permit
|
||||
216.109.114.64/29 permit
|
||||
216.113.160.0/24 permit
|
||||
216.113.172.0/25 permit
|
||||
216.113.175.0/24 permit
|
||||
216.128.126.97 permit
|
||||
216.136.162.65 permit
|
||||
216.136.162.120/29 permit
|
||||
@@ -2007,10 +1921,10 @@
|
||||
216.203.30.55 permit
|
||||
216.203.33.178/31 permit
|
||||
216.205.24.0/24 permit
|
||||
216.221.160.0/19 permit
|
||||
216.239.32.0/19 permit
|
||||
217.72.192.64/26 permit
|
||||
217.72.192.248/29 permit
|
||||
217.72.207.0/27 permit
|
||||
217.72.192.77 permit
|
||||
217.72.192.78 permit
|
||||
217.77.141.52 permit
|
||||
217.77.141.59 permit
|
||||
217.175.194.0/24 permit
|
||||
@@ -2036,6 +1950,8 @@
|
||||
2620:109:c00d:104::/64 permit
|
||||
2620:10d:c090:400::8:1 permit
|
||||
2620:10d:c091:400::8:1 permit
|
||||
2620:10d:c09b:400::8:1 permit
|
||||
2620:10d:c09c:400::8:1 permit
|
||||
2620:119:50c0:207::/64 permit
|
||||
2620:119:50c0:207::215 permit
|
||||
2800:3f0:4000::/36 permit
|
||||
|
||||
@@ -49,27 +49,49 @@ $from = $headers['From'];
|
||||
$empty_footer = json_encode(array(
|
||||
'html' => '',
|
||||
'plain' => '',
|
||||
'skip_replies' => 0,
|
||||
'vars' => array()
|
||||
));
|
||||
|
||||
error_log("FOOTER: checking for domain " . $domain . ", user " . $username . " and address " . $from . PHP_EOL);
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude` FROM `domain_wide_footer`
|
||||
// try get $target_domain if $domain is an alias_domain
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain`
|
||||
WHERE `alias_domain` = :alias_domain");
|
||||
$stmt->execute(array(
|
||||
':alias_domain' => $domain
|
||||
));
|
||||
$alias_domain = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$alias_domain) {
|
||||
$target_domain = $domain;
|
||||
} else {
|
||||
$target_domain = $alias_domain['target_domain'];
|
||||
}
|
||||
|
||||
// get footer associated with the domain
|
||||
$stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude`, `alias_domain_exclude`, `skip_replies` FROM `domain_wide_footer`
|
||||
WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain
|
||||
':domain' => $target_domain
|
||||
));
|
||||
$footer = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// check if the sender is excluded
|
||||
if (in_array($from, json_decode($footer['mbox_exclude']))){
|
||||
$footer = false;
|
||||
}
|
||||
if (in_array($domain, json_decode($footer['alias_domain_exclude']))){
|
||||
$footer = false;
|
||||
}
|
||||
if (empty($footer)){
|
||||
echo $empty_footer;
|
||||
exit;
|
||||
}
|
||||
error_log("FOOTER: " . json_encode($footer) . PHP_EOL);
|
||||
|
||||
// footer will be applied
|
||||
// get custom mailbox attributes to insert into the footer
|
||||
$stmt = $pdo->prepare("SELECT `custom_attributes` FROM `mailbox` WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username
|
||||
|
||||
@@ -21,6 +21,10 @@ FREEMAIL_TO_UNDISC_RCPT {
|
||||
SOGO_CONTACT_EXCLUDE {
|
||||
expression = "(-WHITELISTED_FWD_HOST | -g+:policies) & ^SOGO_CONTACT & !DMARC_POLICY_ALLOW";
|
||||
}
|
||||
# Remove MAILCOW_WHITE symbol for senders with broken policy recieved not from fwd hosts
|
||||
MAILCOW_WHITE_EXCLUDE {
|
||||
expression = "^MAILCOW_WHITE & (-DMARC_POLICY_REJECT | -DMARC_POLICY_QUARANTINE | -R_SPF_PERMFAIL) & !WHITELISTED_FWD_HOST";
|
||||
}
|
||||
# Spoofed header from and broken policy (excluding sieve host, rspamd host, whitelisted senders, authenticated senders and forward hosts)
|
||||
SPOOFED_UNAUTH {
|
||||
expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & !RSPAMD_HOST & !SIEVE_HOST & MAILCOW_DOMAIN_HEADER_FROM & !WHITELISTED_FWD_HOST & -g+:policies";
|
||||
@@ -103,4 +107,4 @@ CLAMD_JS_MALWARE {
|
||||
expression = "CLAM_SECI_JS & !MAILCOW_WHITE";
|
||||
description = "JS malware found, Securite JS malware Flag set through ClamAV";
|
||||
score = 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,3 +6,4 @@ disable_monitoring = true;
|
||||
# In case a task times out (like DNS lookup), soft reject the message
|
||||
# instead of silently accepting the message without further processing.
|
||||
soft_reject_on_timeout = true;
|
||||
local_addrs = /etc/rspamd/custom/mailcow_networks.map;
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
rbls {
|
||||
sorbs {
|
||||
symbol = "RBL_SORBS";
|
||||
rbl = "dnsbl.sorbs.net";
|
||||
returncodes {
|
||||
# http:// www.sorbs.net/general/using.shtml
|
||||
RBL_SORBS_HTTP = "127.0.0.2";
|
||||
RBL_SORBS_SOCKS = "127.0.0.3";
|
||||
RBL_SORBS_MISC = "127.0.0.4";
|
||||
RBL_SORBS_SMTP = "127.0.0.5";
|
||||
RBL_SORBS_RECENT = "127.0.0.6";
|
||||
RBL_SORBS_WEB = "127.0.0.7";
|
||||
RBL_SORBS_DUL = "127.0.0.10";
|
||||
RBL_SORBS_BLOCK = "127.0.0.8";
|
||||
RBL_SORBS_ZOMBIE = "127.0.0.9";
|
||||
}
|
||||
}
|
||||
interserver_ip {
|
||||
symbol = "RBL_INTERSERVER_IP";
|
||||
rbl = "rbl.interserver.net";
|
||||
from = true;
|
||||
ipv6 = false;
|
||||
returncodes {
|
||||
RBL_INTERSERVER_BAD_IP = "127.0.0.2";
|
||||
@@ -35,4 +20,7 @@ rbls {
|
||||
RBL_INTERSERVER_BAD_URI = "127.0.0.2";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.include(try=true,override=true,priority=5) "$LOCAL_CONFDIR/custom/dqs-rbl.conf"
|
||||
|
||||
}
|
||||
@@ -5,46 +5,6 @@ symbols = {
|
||||
"RBL_UCEPROTECT_LEVEL2" {
|
||||
score = 1.5;
|
||||
}
|
||||
"RBL_SORBS" {
|
||||
score = 0.0;
|
||||
description = "Unrecognised result from SORBS RBL";
|
||||
}
|
||||
"RBL_SORBS_HTTP" {
|
||||
score = 2.5;
|
||||
description = "List of Open HTTP Proxy Servers.";
|
||||
}
|
||||
"RBL_SORBS_SOCKS" {
|
||||
score = 2.5;
|
||||
description = "List of Open SOCKS Proxy Servers.";
|
||||
}
|
||||
"RBL_SORBS_MISC" {
|
||||
score = 1.0;
|
||||
description = "List of open Proxy Servers not listed in the SOCKS or HTTP lists.";
|
||||
}
|
||||
"RBL_SORBS_SMTP" {
|
||||
score = 4.0;
|
||||
description = "List of Open SMTP relay servers.";
|
||||
}
|
||||
"RBL_SORBS_RECENT" {
|
||||
score = 2.0;
|
||||
description = "List of hosts that have been noted as sending spam/UCE/UBE to the admins of SORBS within the last 28 days (includes new.spam.dnsbl.sorbs.net).";
|
||||
}
|
||||
"RBL_SORBS_WEB" {
|
||||
score = 2.0;
|
||||
description = "List of web (WWW) servers which have spammer abusable vulnerabilities (e.g. FormMail scripts)";
|
||||
}
|
||||
"RBL_SORBS_DUL" {
|
||||
score = 2.0;
|
||||
description = "Dynamic IP Address ranges (NOT a Dial Up list!)";
|
||||
}
|
||||
"RBL_SORBS_BLOCK" {
|
||||
score = 0.5;
|
||||
description = "List of hosts demanding that they never be tested by SORBS.";
|
||||
}
|
||||
"RBL_SORBS_ZOMBIE" {
|
||||
score = 2.0;
|
||||
description = "List of networks hijacked from their original owners, some of which have already used for spamming.";
|
||||
}
|
||||
"RECEIVED_SPAMHAUS_XBL" {
|
||||
weight = 0.0;
|
||||
description = "Received address is listed in ZEN XBL";
|
||||
@@ -57,4 +17,261 @@ symbols = {
|
||||
score = 4.0;
|
||||
description = "Listed on Interserver RBL";
|
||||
}
|
||||
|
||||
"SPAMHAUS_ZEN" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"SH_AUTHBL_RECEIVED" {
|
||||
weight = 4.0;
|
||||
}
|
||||
"RBL_DBL_SPAM" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"RBL_DBL_PHISH" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"RBL_DBL_MALWARE" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"RBL_DBL_BOTNET" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"RBL_DBL_ABUSED_SPAM" {
|
||||
weight = 3.0;
|
||||
}
|
||||
"RBL_DBL_ABUSED_PHISH" {
|
||||
weight = 3.0;
|
||||
}
|
||||
"RBL_DBL_ABUSED_MALWARE" {
|
||||
weight = 3.0;
|
||||
}
|
||||
"RBL_DBL_ABUSED_BOTNET" {
|
||||
weight = 3.0;
|
||||
}
|
||||
"RBL_ZRD_VERY_FRESH_DOMAIN" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"RBL_ZRD_FRESH_DOMAIN" {
|
||||
weight = 4.0;
|
||||
}
|
||||
"ZRD_VERY_FRESH_DOMAIN" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"ZRD_FRESH_DOMAIN" {
|
||||
weight = 4.0;
|
||||
}
|
||||
"SH_EMAIL_DBL" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"SH_EMAIL_DBL_ABUSED" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"SH_EMAIL_ZRD_VERY_FRESH_DOMAIN" {
|
||||
weight = 7.0;
|
||||
}
|
||||
"SH_EMAIL_ZRD_FRESH_DOMAIN" {
|
||||
weight = 4.0;
|
||||
}
|
||||
"RBL_DBL_DONT_QUERY_IPS" {
|
||||
weight = 0.0;
|
||||
}
|
||||
"RBL_ZRD_DONT_QUERY_IPS" {
|
||||
weight = 0.0;
|
||||
}
|
||||
"SH_EMAIL_ZRD_DONT_QUERY_IPS" {
|
||||
weight = 0.0;
|
||||
}
|
||||
"SH_EMAIL_DBL_DONT_QUERY_IPS" {
|
||||
weight = 0.0;
|
||||
}
|
||||
"DBL" {
|
||||
weight = 0.0;
|
||||
description = "DBL unknown result";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_SPAM" {
|
||||
weight = 7;
|
||||
description = "DBL uribl spam";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_PHISH" {
|
||||
weight = 7;
|
||||
description = "DBL uribl phishing";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_MALWARE" {
|
||||
weight = 7;
|
||||
description = "DBL uribl malware";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_BOTNET" {
|
||||
weight = 7;
|
||||
description = "DBL uribl botnet C&C domain";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
|
||||
"DBLABUSED_SPAM_FULLURLS" {
|
||||
weight = 5.5;
|
||||
description = "DBL uribl abused legit spam";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBLABUSED_PHISH_FULLURLS" {
|
||||
weight = 5.5;
|
||||
description = "DBL uribl abused legit phish";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBLABUSED_MALWARE_FULLURLS" {
|
||||
weight = 5.5;
|
||||
description = "DBL uribl abused legit malware";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBLABUSED_BOTNET_FULLURLS" {
|
||||
weight = 5.5;
|
||||
description = "DBL uribl abused legit botnet";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"DBL_ABUSE" {
|
||||
weight = 5.5;
|
||||
description = "DBL uribl abused legit spam";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_ABUSE_REDIR" {
|
||||
weight = 1.5;
|
||||
description = "DBL uribl abused spammed redirector domain";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_ABUSE_PHISH" {
|
||||
weight = 5.5;
|
||||
description = "DBL uribl abused legit phish";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_ABUSE_MALWARE" {
|
||||
weight = 5.5;
|
||||
description = "DBL uribl abused legit malware";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_ABUSE_BOTNET" {
|
||||
weight = 5.5;
|
||||
description = "DBL uribl abused legit botnet C&C";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_PROHIBIT" {
|
||||
weight = 0.0;
|
||||
description = "DBL uribl IP queries prohibited!";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_BLOCKED_OPENRESOLVER" {
|
||||
weight = 0.0;
|
||||
description = "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"DBL_BLOCKED" {
|
||||
weight = 0.0;
|
||||
description = "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"SPAMHAUS_ZEN_URIBL" {
|
||||
weight = 0.0;
|
||||
description = "Spamhaus ZEN URIBL: Filtered result";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"URIBL_SBL" {
|
||||
weight = 6.5;
|
||||
description = "A domain in the message body resolves to an IP listed in Spamhaus SBL";
|
||||
one_shot = true;
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"URIBL_SBL_CSS" {
|
||||
weight = 6.5;
|
||||
description = "A domain in the message body resolves to an IP listed in Spamhaus SBL CSS";
|
||||
one_shot = true;
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"URIBL_PBL" {
|
||||
weight = 0.01;
|
||||
description = "A domain in the message body resolves to an IP listed in Spamhaus PBL";
|
||||
one_shot = true;
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"URIBL_DROP" {
|
||||
weight = 6.5;
|
||||
description = "A domain in the message body resolves to an IP listed in Spamhaus DROP";
|
||||
one_shot = true;
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"URIBL_XBL" {
|
||||
weight = 5.0;
|
||||
description = "A domain in the message body resolves to an IP listed in Spamhaus XBL";
|
||||
one_shot = true;
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
"SPAMHAUS_SBL_URL" {
|
||||
weight = 6.5;
|
||||
description = "A numeric URL in the message body is listed in Spamhaus SBL";
|
||||
one_shot = true;
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"SH_HBL_EMAIL" {
|
||||
weight = 7;
|
||||
description = "Email listed in HBL";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"SH_HBL_FILE_MALICIOUS" {
|
||||
weight = 7;
|
||||
description = "An attachment hash is listed in Spamhaus HBL as malicious";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"SH_HBL_FILE_SUSPICIOUS" {
|
||||
weight = 5;
|
||||
description = "An attachment hash is listed in Spamhaus HBL as suspicious";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"RBL_SPAMHAUS_CW_BTC" {
|
||||
score = 7;
|
||||
description = "Bitcoin found in Spamhaus cryptowallet list";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"RBL_SPAMHAUS_CW_ETH" {
|
||||
score = 7;
|
||||
description = "Ethereum found in Spamhaus cryptowallet list";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"RBL_SPAMHAUS_CW_BCH" {
|
||||
score = 7;
|
||||
description = "Bitcoinhash found in Spamhaus cryptowallet list";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"RBL_SPAMHAUS_CW_XMR" {
|
||||
score = 7;
|
||||
description = "Monero found in Spamhaus cryptowallet list";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"RBL_SPAMHAUS_CW_LTC" {
|
||||
score = 7;
|
||||
description = "Litecoin found in Spamhaus cryptowallet list";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"RBL_SPAMHAUS_CW_XRP" {
|
||||
score = 7;
|
||||
description = "Ripple found in Spamhaus cryptowallet list";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
"RBL_SPAMHAUS_HBL_URL" {
|
||||
score = 7;
|
||||
description = "URL found in spamhaus HBL blocklist";
|
||||
groups = ["spamhaus"];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
classifier "bayes" {
|
||||
# name = "custom"; # 'name' parameter must be set if multiple classifiers are defined
|
||||
learn_condition = 'return require("lua_bayes_learn").can_learn';
|
||||
new_schema = true;
|
||||
tokenizer {
|
||||
name = "osb";
|
||||
}
|
||||
backend = "redis";
|
||||
min_tokens = 11;
|
||||
min_learns = 5;
|
||||
new_schema = true;
|
||||
expire = 2592000;
|
||||
expire = 7776000;
|
||||
statfile {
|
||||
symbol = "BAYES_HAM";
|
||||
spam = false;
|
||||
|
||||
@@ -567,6 +567,14 @@ rspamd_config:register_symbol({
|
||||
if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then
|
||||
rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars)
|
||||
|
||||
if footer.skip_replies ~= 0 then
|
||||
in_reply_to = task:get_header_raw('in-reply-to')
|
||||
if in_reply_to then
|
||||
rspamd_logger.infox(rspamd_config, "mail is a reply - skip footer")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local envfrom_mime = task:get_from(2)
|
||||
local from_name = ""
|
||||
if envfrom_mime and envfrom_mime[1].name then
|
||||
@@ -613,10 +621,24 @@ rspamd_config:register_symbol({
|
||||
local nct = string.format('%s: %s/%s; charset=utf-8',
|
||||
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
|
||||
out[#out + 1] = nct
|
||||
-- update Content-Type header
|
||||
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)}
|
||||
})
|
||||
return
|
||||
elseif name:lower() == 'content-transfer-encoding' then
|
||||
out[#out + 1] = string.format('%s: %s',
|
||||
'Content-Transfer-Encoding', 'quoted-printable')
|
||||
-- update Content-Transfer-Encoding header
|
||||
task:set_milter_reply({
|
||||
remove_headers = {['Content-Transfer-Encoding'] = 0},
|
||||
})
|
||||
task:set_milter_reply({
|
||||
add_headers = {['Content-Transfer-Encoding'] = 'quoted-printable'}
|
||||
})
|
||||
seen_cte = true
|
||||
return
|
||||
end
|
||||
|
||||
@@ -12,9 +12,13 @@
|
||||
SOGoJunkFolderName= "Junk";
|
||||
SOGoMailDomain = "sogo.local";
|
||||
SOGoEnableEMailAlarms = YES;
|
||||
SOGoMailHideInlineAttachments = YES;
|
||||
SOGoFoldersSendEMailNotifications = YES;
|
||||
SOGoForwardEnabled = YES;
|
||||
|
||||
// Option to set Users as admin to globally manage calendar permissions etc. Disabled by default
|
||||
// SOGoSuperUsernames = ("moo@example.com");
|
||||
|
||||
SOGoUIAdditionalJSFiles = (
|
||||
js/theme.js,
|
||||
js/custom-sogo.js
|
||||
@@ -37,6 +41,7 @@
|
||||
|
||||
SOGoLanguage = English;
|
||||
SOGoMailAuxiliaryUserAccountsEnabled = YES;
|
||||
// SOGoCreateIdentitiesDisabled = NO;
|
||||
SOGoMailCustomFromEnabled = YES;
|
||||
SOGoMailingMechanism = smtp;
|
||||
SOGoSMTPAuthenticationType = plain;
|
||||
|
||||
@@ -107,6 +107,7 @@ $template_data = [
|
||||
'f2b_banlist_url' => getBaseUrl() . "/api/v1/get/fail2ban/banlist/" . $f2b_data['banlist_id'],
|
||||
'q_data' => quarantine('settings'),
|
||||
'qn_data' => quota_notification('get'),
|
||||
'pw_reset_data' => reset_password('get_notification'),
|
||||
'rsettings_map' => file_get_contents('http://nginx:8081/settings.php'),
|
||||
'rsettings' => $rsettings,
|
||||
'rspamd_regex_maps' => $rspamd_regex_maps,
|
||||
|
||||
@@ -228,8 +228,8 @@ legend {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.slave-info {
|
||||
padding: 15px 0px 15px 15px;
|
||||
font-weight: bold;
|
||||
color: orange;
|
||||
}
|
||||
.alert-hr {
|
||||
margin:3px 0px;
|
||||
|
||||
@@ -175,6 +175,9 @@ pre {
|
||||
background-color: #282828;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
.form-control {
|
||||
background-color: transparent;
|
||||
}
|
||||
input.form-control, textarea.form-control {
|
||||
color: #e2e2e2 !important;
|
||||
background-color: #424242 !important;
|
||||
|
||||
@@ -39,9 +39,13 @@ foreach ($containers as $container => $container_info) {
|
||||
$StartedAt['month'],
|
||||
$StartedAt['day'],
|
||||
$StartedAt['year']));
|
||||
$user_tz = new DateTimeZone(getenv('TZ'));
|
||||
$date->setTimezone($user_tz);
|
||||
$started = $date->format('r');
|
||||
try {
|
||||
$user_tz = new DateTimeZone(getenv('TZ'));
|
||||
$date->setTimezone($user_tz);
|
||||
$started = $date->format('r');
|
||||
} catch(Exception $e) {
|
||||
$started = '?';
|
||||
}
|
||||
}
|
||||
else {
|
||||
$started = '?';
|
||||
|
||||
@@ -59,7 +59,8 @@ if (isset($_SESSION['mailcow_cc_role'])) {
|
||||
'domain_details' => $result,
|
||||
'domain_footer' => $domain_footer,
|
||||
'mailboxes' => mailbox('get', 'mailboxes', $_GET["domain"]),
|
||||
'aliases' => mailbox('get', 'aliases', $_GET["domain"], 'address')
|
||||
'aliases' => mailbox('get', 'aliases', $_GET["domain"], 'address'),
|
||||
'alias_domains' => mailbox('get', 'alias_domains', $_GET["domain"])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
<?php
|
||||
|
||||
// Block requests by checking the 'Sec-Fetch-Dest' header.
|
||||
if (isset($_SERVER['HTTP_SEC_FETCH_DEST']) && $_SERVER['HTTP_SEC_FETCH_DEST'] !== 'empty') {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
||||
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != 'admin') {
|
||||
exit();
|
||||
|
||||
@@ -12,7 +12,8 @@ $alertbox_log_parser = alertbox_log_parser($_SESSION);
|
||||
$alerts = [];
|
||||
if (is_array($alertbox_log_parser)) {
|
||||
foreach ($alertbox_log_parser as $log) {
|
||||
$message = strtr($log['msg'], ["\n" => '', "\r" => '', "\t" => '<br>']);
|
||||
$message = htmlspecialchars($log['msg'], ENT_QUOTES);
|
||||
$message = strtr($message, ["\n" => '', "\r" => '', "\t" => '<br>']);
|
||||
$alerts[trim($log['type'], '"')][] = trim($message, '"');
|
||||
}
|
||||
$alert = array_filter(array_unique($alerts));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
function customize($_action, $_item, $_data = null) {
|
||||
global $redis;
|
||||
global $lang;
|
||||
global $LOGO_LIMITS;
|
||||
|
||||
switch ($_action) {
|
||||
case 'add':
|
||||
@@ -35,6 +36,23 @@ function customize($_action, $_item, $_data = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if ($_data[$_item]['size'] > $LOGO_LIMITS['max_size']) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'img_size_exceeded'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
list($width, $height) = getimagesize($_data[$_item]['tmp_name']);
|
||||
if ($width > $LOGO_LIMITS['max_width'] || $height > $LOGO_LIMITS['max_height']) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_item, $_data),
|
||||
'msg' => 'img_dimensions_exceeded'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$image = new Imagick($_data[$_item]['tmp_name']);
|
||||
if ($image->valid() !== true) {
|
||||
$_SESSION['return'][] = array(
|
||||
|
||||
@@ -284,17 +284,17 @@ function last_login($action, $username, $sasl_limit_days = 7, $ui_offset = 1) {
|
||||
}
|
||||
if (!$sasl[$k]['location']) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL,"https://dfdata.bella.network/lookup/" . $sasl[$k]['real_rip']);
|
||||
curl_setopt($curl, CURLOPT_URL,"https://dfdata.bella.network/country/" . $sasl[$k]['real_rip']);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, 'Moocow');
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
|
||||
$ip_data = curl_exec($curl);
|
||||
if (!curl_errno($curl)) {
|
||||
$ip_data_array = json_decode($ip_data, true);
|
||||
if ($ip_data_array !== false and !empty($ip_data_array['location']['shortcountry'])) {
|
||||
$sasl[$k]['location'] = $ip_data_array['location']['shortcountry'];
|
||||
if ($ip_data_array !== false and !empty($ip_data_array['shortcountry'])) {
|
||||
$sasl[$k]['location'] = $ip_data_array['shortcountry'];
|
||||
try {
|
||||
$redis->hSet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip'], $ip_data_array['location']['shortcountry']);
|
||||
$redis->hSet('IP_SHORTCOUNTRY', $sasl[$k]['real_rip'], $ip_data_array['shortcountry']);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -1073,13 +1073,17 @@ function update_sogo_static_view($mailbox = null) {
|
||||
function edit_user_account($_data) {
|
||||
global $lang;
|
||||
global $pdo;
|
||||
|
||||
$_data_log = $_data;
|
||||
!isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*';
|
||||
!isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*';
|
||||
!isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*';
|
||||
|
||||
$username = $_SESSION['mailcow_cc_username'];
|
||||
$role = $_SESSION['mailcow_cc_role'];
|
||||
$password_old = $_data['user_old_pass'];
|
||||
$pw_recovery_email = $_data['pw_recovery_email'];
|
||||
|
||||
if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -1088,20 +1092,24 @@ function edit_user_account($_data) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
||||
WHERE `kind` NOT REGEXP 'location|thing|group'
|
||||
AND `username` = :user");
|
||||
$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;
|
||||
}
|
||||
if (!empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) {
|
||||
|
||||
// edit password
|
||||
if (!empty($password_old) && !empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) {
|
||||
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
||||
WHERE `kind` NOT REGEXP 'location|thing|group'
|
||||
AND `username` = :user");
|
||||
$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;
|
||||
}
|
||||
|
||||
$password_new = $_data['user_new_pass'];
|
||||
$password_new2 = $_data['user_new_pass2'];
|
||||
if (password_check($password_new, $password_new2) !== true) {
|
||||
@@ -1116,8 +1124,29 @@ function edit_user_account($_data) {
|
||||
':password_hashed' => $password_hashed,
|
||||
':username' => $username
|
||||
));
|
||||
|
||||
update_sogo_static_view();
|
||||
}
|
||||
update_sogo_static_view();
|
||||
// edit password recovery email
|
||||
elseif (isset($pw_recovery_email)) {
|
||||
if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'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");
|
||||
$stmt->execute(array(
|
||||
':recovery_email' => $pw_recovery_email,
|
||||
':username' => $username
|
||||
));
|
||||
}
|
||||
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $_data_log),
|
||||
@@ -1560,7 +1589,7 @@ function unset_tfa_key($_data) {
|
||||
}
|
||||
function get_tfa($username = null, $id = null) {
|
||||
global $pdo;
|
||||
if (isset($_SESSION['mailcow_cc_username'])) {
|
||||
if (empty($username) && isset($_SESSION['mailcow_cc_username'])) {
|
||||
$username = $_SESSION['mailcow_cc_username'];
|
||||
}
|
||||
elseif (empty($username)) {
|
||||
@@ -2261,6 +2290,386 @@ function uuid4() {
|
||||
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
||||
function reset_password($action, $data = null) {
|
||||
global $pdo;
|
||||
global $redis;
|
||||
global $mailcow_hostname;
|
||||
global $PW_RESET_TOKEN_LIMIT;
|
||||
global $PW_RESET_TOKEN_LIFETIME;
|
||||
|
||||
$_data_log = $data;
|
||||
if (isset($_data_log['new_password'])) $_data_log['new_password'] = '*';
|
||||
if (isset($_data_log['new_password2'])) $_data_log['new_password2'] = '*';
|
||||
|
||||
switch ($action) {
|
||||
case 'check':
|
||||
$token = $data;
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `t1`.`username` FROM `reset_password` AS `t1` JOIN `mailbox` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL :lifetime MINUTE) AND `t2`.`active` = 1;");
|
||||
$stmt->execute(array(
|
||||
':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token),
|
||||
':lifetime' => $PW_RESET_TOKEN_LIFETIME
|
||||
));
|
||||
$return = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return empty($return['username']) ? false : $return['username'];
|
||||
break;
|
||||
case 'issue':
|
||||
$username = $data;
|
||||
|
||||
// perform cleanup
|
||||
$stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE created < DATE_SUB(NOW(), INTERVAL :lifetime MINUTE);");
|
||||
$stmt->execute(array(':lifetime' => $PW_RESET_TOKEN_LIFETIME));
|
||||
|
||||
if (filter_var($username, FILTER_VALIDATE_EMAIL) === false) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$pw_reset_notification = reset_password('get_notification', 'raw');
|
||||
if (!$pw_reset_notification) return false;
|
||||
if (empty($pw_reset_notification['from']) || empty($pw_reset_notification['subject'])) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'password_reset_na'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$mailbox_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($mailbox_data)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'password_reset_invalid_user'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$mailbox_attr = json_decode($mailbox_data['attributes'], true);
|
||||
if (empty($mailbox_attr['recovery_email']) || filter_var($mailbox_attr['recovery_email'], FILTER_VALIDATE_EMAIL) === false) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => "password_reset_invalid_user"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM `reset_password`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$generated_token_count = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
if ($generated_token_count >= $PW_RESET_TOKEN_LIMIT) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => "reset_token_limit_exceeded"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = implode('-', array(
|
||||
strtoupper(bin2hex(random_bytes(3))),
|
||||
strtoupper(bin2hex(random_bytes(3))),
|
||||
strtoupper(bin2hex(random_bytes(3))),
|
||||
strtoupper(bin2hex(random_bytes(3))),
|
||||
strtoupper(bin2hex(random_bytes(3)))
|
||||
));
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO `reset_password` (`username`, `token`)
|
||||
VALUES (:username, :token)");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':token' => $token
|
||||
));
|
||||
|
||||
$reset_link = getBaseURL() . "/reset-password?token=" . $token;
|
||||
|
||||
$request_date = new DateTime();
|
||||
$locale_date = locale_get_default();
|
||||
$date_formatter = new IntlDateFormatter(
|
||||
$locale_date,
|
||||
IntlDateFormatter::FULL,
|
||||
IntlDateFormatter::FULL
|
||||
);
|
||||
$formatted_request_date = $date_formatter->format($request_date);
|
||||
|
||||
// set template vars
|
||||
// subject
|
||||
$pw_reset_notification['subject'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['subject']);
|
||||
$pw_reset_notification['subject'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['subject']);
|
||||
$pw_reset_notification['subject'] = str_replace('{{username}}', $username, $pw_reset_notification['subject']);
|
||||
$pw_reset_notification['subject'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['subject']);
|
||||
$pw_reset_notification['subject'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['subject']);
|
||||
$pw_reset_notification['subject'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['subject']);
|
||||
// text
|
||||
$pw_reset_notification['text_tmpl'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['text_tmpl']);
|
||||
$pw_reset_notification['text_tmpl'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['text_tmpl']);
|
||||
$pw_reset_notification['text_tmpl'] = str_replace('{{username}}', $username, $pw_reset_notification['text_tmpl']);
|
||||
$pw_reset_notification['text_tmpl'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['text_tmpl']);
|
||||
$pw_reset_notification['text_tmpl'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['text_tmpl']);
|
||||
$pw_reset_notification['text_tmpl'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['text_tmpl']);
|
||||
// html
|
||||
$pw_reset_notification['html_tmpl'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['html_tmpl']);
|
||||
$pw_reset_notification['html_tmpl'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['html_tmpl']);
|
||||
$pw_reset_notification['html_tmpl'] = str_replace('{{username}}', $username, $pw_reset_notification['html_tmpl']);
|
||||
$pw_reset_notification['html_tmpl'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['html_tmpl']);
|
||||
$pw_reset_notification['html_tmpl'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['html_tmpl']);
|
||||
$pw_reset_notification['html_tmpl'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['html_tmpl']);
|
||||
|
||||
|
||||
$email_sent = reset_password('send_mail', array(
|
||||
"from" => $pw_reset_notification['from'],
|
||||
"to" => $mailbox_attr['recovery_email'],
|
||||
"subject" => $pw_reset_notification['subject'],
|
||||
"text" => $pw_reset_notification['text_tmpl'],
|
||||
"html" => $pw_reset_notification['html_tmpl']
|
||||
));
|
||||
|
||||
if (!$email_sent){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => "recovery_email_failed"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
list($localPart, $domainPart) = explode('@', $mailbox_attr['recovery_email']);
|
||||
if (strlen($localPart) > 1) {
|
||||
$maskedLocalPart = $localPart[0] . str_repeat('*', strlen($localPart) - 1);
|
||||
} else {
|
||||
$maskedLocalPart = "*";
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => array("recovery_email_sent", $maskedLocalPart . '@' . $domainPart)
|
||||
);
|
||||
return array(
|
||||
"username" => $username,
|
||||
"issue" => "success"
|
||||
);
|
||||
break;
|
||||
case 'reset':
|
||||
$token = $data['token'];
|
||||
$new_password = $data['new_password'];
|
||||
$new_password2 = $data['new_password2'];
|
||||
$username = $data['username'];
|
||||
$check_tfa = $data['check_tfa'];
|
||||
|
||||
if (!$username || !$token) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'invalid_reset_token'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
# check new password
|
||||
if (!password_check($new_password, $new_password2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($check_tfa){
|
||||
// check for tfa authenticators
|
||||
$authenticators = get_tfa($username);
|
||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
||||
$_SESSION['pending_mailcow_cc_username'] = $username;
|
||||
$_SESSION['pending_pw_reset_token'] = $token;
|
||||
$_SESSION['pending_pw_new_password'] = $new_password;
|
||||
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'info',
|
||||
'log' => array(__FUNCTION__, $user, '*'),
|
||||
'msg' => 'awaiting_tfa_confirmation'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
# set new password
|
||||
$password_hashed = hash_password($new_password);
|
||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET
|
||||
`password` = :password_hashed,
|
||||
`attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW())
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':password_hashed' => $password_hashed,
|
||||
':username' => $username
|
||||
));
|
||||
|
||||
// perform cleanup
|
||||
$stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE `username` = :username;");
|
||||
$stmt->execute(array(
|
||||
':username' => $username
|
||||
));
|
||||
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'password_changed_success'
|
||||
);
|
||||
return true;
|
||||
break;
|
||||
case 'get_notification':
|
||||
$type = $data;
|
||||
|
||||
try {
|
||||
$settings['from'] = $redis->Get('PW_RESET_FROM');
|
||||
$settings['subject'] = $redis->Get('PW_RESET_SUBJ');
|
||||
$settings['html_tmpl'] = $redis->Get('PW_RESET_HTML');
|
||||
$settings['text_tmpl'] = $redis->Get('PW_RESET_TEXT');
|
||||
if (empty($settings['html_tmpl']) && empty($settings['text_tmpl'])) {
|
||||
$settings['html_tmpl'] = file_get_contents("/tpls/pw_reset_html.tpl");
|
||||
$settings['text_tmpl'] = file_get_contents("/tpls/pw_reset_text.tpl");
|
||||
}
|
||||
|
||||
if ($type != "raw") {
|
||||
$settings['html_tmpl'] = htmlspecialchars($settings['html_tmpl']);
|
||||
$settings['text_tmpl'] = htmlspecialchars($settings['text_tmpl']);
|
||||
}
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $settings;
|
||||
break;
|
||||
case 'send_mail':
|
||||
$from = $data['from'];
|
||||
$to = $data['to'];
|
||||
$text = $data['text'];
|
||||
$html = $data['html'];
|
||||
$subject = $data['subject'];
|
||||
|
||||
if (!filter_var($from, FILTER_VALIDATE_EMAIL)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'from_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'to_invalid'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($subject)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'subject_empty'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($text)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'text_empty'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
ini_set('max_execution_time', 0);
|
||||
ini_set('max_input_time', 0);
|
||||
$mail = new PHPMailer;
|
||||
$mail->Timeout = 10;
|
||||
$mail->SMTPOptions = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
'allow_self_signed' => true
|
||||
)
|
||||
);
|
||||
$mail->isSMTP();
|
||||
$mail->Host = 'postfix-mailcow';
|
||||
$mail->SMTPAuth = false;
|
||||
$mail->Port = 25;
|
||||
$mail->setFrom($from);
|
||||
$mail->Subject = $subject;
|
||||
$mail->CharSet ="UTF-8";
|
||||
if (!empty($html)) {
|
||||
$mail->Body = $html;
|
||||
$mail->AltBody = $text;
|
||||
}
|
||||
else {
|
||||
$mail->Body = $text;
|
||||
}
|
||||
$mail->XMailer = 'MooMail';
|
||||
$mail->AddAddress($to);
|
||||
if (!$mail->send()) {
|
||||
return false;
|
||||
}
|
||||
$mail->ClearAllRecipients();
|
||||
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($_SESSION['mailcow_cc_role'] != "admin") {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'edit_notification':
|
||||
$subject = $data['subject'];
|
||||
$from = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $data['from']);
|
||||
|
||||
$from = (!filter_var($from, FILTER_VALIDATE_EMAIL)) ? "" : $from;
|
||||
$subject = (empty($subject)) ? "" : $subject;
|
||||
$text = (empty($data['text_tmpl'])) ? "" : $data['text_tmpl'];
|
||||
$html = (empty($data['html_tmpl'])) ? "" : $data['html_tmpl'];
|
||||
|
||||
try {
|
||||
$redis->Set('PW_RESET_FROM', $from);
|
||||
$redis->Set('PW_RESET_SUBJ', $subject);
|
||||
$redis->Set('PW_RESET_HTML', $html);
|
||||
$redis->Set('PW_RESET_TEXT', $text);
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => array('redis_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $action, $_data_log),
|
||||
'msg' => 'saved_settings'
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function get_logs($application, $lines = false) {
|
||||
if ($lines === false) {
|
||||
|
||||
@@ -184,6 +184,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
'msg' => 'global_filter_written'
|
||||
);
|
||||
return true;
|
||||
break;
|
||||
case 'filter':
|
||||
$sieve = new Sieve\SieveParser();
|
||||
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
|
||||
@@ -478,16 +479,24 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$DOMAIN_DEFAULT_ATTRIBUTES = null;
|
||||
if ($_data['template']){
|
||||
$DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates', $_data['template'])['attributes'];
|
||||
}
|
||||
if (empty($DOMAIN_DEFAULT_ATTRIBUTES)) {
|
||||
$DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates')[0]['attributes'];
|
||||
}
|
||||
|
||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
$description = $_data['description'];
|
||||
if (empty($description)) $description = $domain;
|
||||
$tags = (array)$_data['tags'];
|
||||
$aliases = (int)$_data['aliases'];
|
||||
$mailboxes = (int)$_data['mailboxes'];
|
||||
$defquota = (int)$_data['defquota'];
|
||||
$maxquota = (int)$_data['maxquota'];
|
||||
$tags = (isset($_data['tags'])) ? (array)$_data['tags'] : $DOMAIN_DEFAULT_ATTRIBUTES['tags'];
|
||||
$aliases = (isset($_data['aliases'])) ? (int)$_data['aliases'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_num_aliases_for_domain'];
|
||||
$mailboxes = (isset($_data['mailboxes'])) ? (int)$_data['mailboxes'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_num_mboxes_for_domain'];
|
||||
$defquota = (isset($_data['defquota'])) ? (int)$_data['defquota'] : $DOMAIN_DEFAULT_ATTRIBUTES['def_quota_for_mbox'] / 1024 ** 2;
|
||||
$maxquota = (isset($_data['maxquota'])) ? (int)$_data['maxquota'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_quota_for_mbox'] / 1024 ** 2;
|
||||
$restart_sogo = (int)$_data['restart_sogo'];
|
||||
$quota = (int)$_data['quota'];
|
||||
$quota = (isset($_data['quota'])) ? (int)$_data['quota'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_quota_for_domain'] / 1024 ** 2;
|
||||
if ($defquota > $maxquota) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -520,11 +529,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$active = intval($_data['active']);
|
||||
$relay_all_recipients = intval($_data['relay_all_recipients']);
|
||||
$relay_unknown_only = intval($_data['relay_unknown_only']);
|
||||
$backupmx = intval($_data['backupmx']);
|
||||
$gal = intval($_data['gal']);
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $DOMAIN_DEFAULT_ATTRIBUTES['active'];
|
||||
$relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_all_recipients'];
|
||||
$relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_unknown_only'];
|
||||
$backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $DOMAIN_DEFAULT_ATTRIBUTES['backupmx'];
|
||||
$gal = (isset($_data['gal'])) ? intval($_data['gal']) : $DOMAIN_DEFAULT_ATTRIBUTES['gal'];
|
||||
if ($relay_all_recipients == 1) {
|
||||
$backupmx = '1';
|
||||
}
|
||||
@@ -625,9 +634,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!empty(intval($_data['rl_value']))) {
|
||||
$_data['rl_value'] = (isset($_data['rl_value'])) ? intval($_data['rl_value']) : $DOMAIN_DEFAULT_ATTRIBUTES['rl_value'];
|
||||
$_data['rl_frame'] = (isset($_data['rl_frame'])) ? $_data['rl_frame'] : $DOMAIN_DEFAULT_ATTRIBUTES['rl_frame'];
|
||||
if (!empty($_data['rl_value']) && !empty($_data['rl_frame'])){
|
||||
ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $domain));
|
||||
}
|
||||
$_data['key_size'] = (isset($_data['key_size'])) ? intval($_data['key_size']) : $DOMAIN_DEFAULT_ATTRIBUTES['key_size'];
|
||||
$_data['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : $DOMAIN_DEFAULT_ATTRIBUTES['dkim_selector'];
|
||||
if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) {
|
||||
if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -1006,11 +1019,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($name)) {
|
||||
$name = $local_part;
|
||||
}
|
||||
$template_attr = null;
|
||||
if ($_data['template']){
|
||||
$template_attr = mailbox('get', 'mailbox_templates', $_data['template'])['attributes'];
|
||||
}
|
||||
if (empty($template_attr)) {
|
||||
$template_attr = mailbox('get', 'mailbox_templates')[0]['attributes'];
|
||||
}
|
||||
$MAILBOX_DEFAULT_ATTRIBUTES = array_merge($MAILBOX_DEFAULT_ATTRIBUTES, $template_attr);
|
||||
|
||||
$password = $_data['password'];
|
||||
$password2 = $_data['password2'];
|
||||
$name = ltrim(rtrim($_data['name'], '>'), '<');
|
||||
$tags = $_data['tags'];
|
||||
$quota_m = intval($_data['quota']);
|
||||
$tags = (isset($_data['tags'])) ? $_data['tags'] : $MAILBOX_DEFAULT_ATTRIBUTES['tags'];
|
||||
$quota_m = (isset($_data['quota'])) ? intval($_data['quota']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['quota']) / 1024 ** 2;
|
||||
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -1019,9 +1044,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (empty($name)) {
|
||||
$name = $local_part;
|
||||
}
|
||||
|
||||
if (isset($_data['protocol_access'])) {
|
||||
$_data['protocol_access'] = (array)$_data['protocol_access'];
|
||||
$_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
|
||||
@@ -1029,7 +1052,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
|
||||
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||
}
|
||||
$active = intval($_data['active']);
|
||||
$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']);
|
||||
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']);
|
||||
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
|
||||
@@ -1227,12 +1250,31 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
|
||||
$_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
|
||||
$_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
|
||||
$_data['pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0;
|
||||
} else {
|
||||
$_data['spam_alias'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_alias']);
|
||||
$_data['tls_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_tls_policy']);
|
||||
$_data['spam_score'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_score']);
|
||||
$_data['spam_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_policy']);
|
||||
$_data['delimiter_action'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_delimiter_action']);
|
||||
$_data['syncjobs'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_syncjobs']);
|
||||
$_data['eas_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_eas_reset']);
|
||||
$_data['sogo_profile_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_sogo_profile_reset']);
|
||||
$_data['pushover'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pushover']);
|
||||
$_data['quarantine'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine']);
|
||||
$_data['quarantine_attachments'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_attachments']);
|
||||
$_data['quarantine_notification'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_notification']);
|
||||
$_data['quarantine_category'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_category']);
|
||||
$_data['app_passwds'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_app_passwds']);
|
||||
$_data['pw_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pw_reset']);
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO `user_acl`
|
||||
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
|
||||
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`)
|
||||
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`, `pw_reset`)
|
||||
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
|
||||
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) ");
|
||||
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds, :pw_reset) ");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':spam_alias' => $_data['spam_alias'],
|
||||
@@ -1248,34 +1290,21 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':quarantine_attachments' => $_data['quarantine_attachments'],
|
||||
':quarantine_notification' => $_data['quarantine_notification'],
|
||||
':quarantine_category' => $_data['quarantine_category'],
|
||||
':app_passwds' => $_data['app_passwds']
|
||||
':app_passwds' => $_data['app_passwds'],
|
||||
':pw_reset' => $_data['pw_reset']
|
||||
));
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("INSERT INTO `user_acl`
|
||||
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
|
||||
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`)
|
||||
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
|
||||
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) ");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':spam_alias' => 0,
|
||||
':tls_policy' => 0,
|
||||
':spam_score' => 0,
|
||||
':spam_policy' => 0,
|
||||
':delimiter_action' => 0,
|
||||
':syncjobs' => 0,
|
||||
':eas_reset' => 0,
|
||||
':sogo_profile_reset' => 0,
|
||||
':pushover' => 0,
|
||||
':quarantine' => 0,
|
||||
':quarantine_attachments' => 0,
|
||||
':quarantine_notification' => 0,
|
||||
':quarantine_category' => 0,
|
||||
':app_passwds' => 0
|
||||
));
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => $e->getMessage()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$_data['rl_frame'] = (isset($_data['rl_frame'])) ? $_data['rl_frame'] : $MAILBOX_DEFAULT_ATTRIBUTES['rl_frame'];
|
||||
$_data['rl_value'] = (isset($_data['rl_value'])) ? $_data['rl_value'] : $MAILBOX_DEFAULT_ATTRIBUTES['rl_value'];
|
||||
if (isset($_data['rl_frame']) && isset($_data['rl_value'])){
|
||||
ratelimit('edit', 'mailbox', array(
|
||||
'object' => $username,
|
||||
@@ -1524,17 +1553,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']);
|
||||
if (isset($_data['protocol_access'])) {
|
||||
$_data['protocol_access'] = (array)$_data['protocol_access'];
|
||||
$attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
|
||||
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
|
||||
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']);
|
||||
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']);
|
||||
$attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
|
||||
$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;
|
||||
}
|
||||
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']);
|
||||
}
|
||||
}
|
||||
if (isset($_data['acl'])) {
|
||||
$_data['acl'] = (array)$_data['acl'];
|
||||
$attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
|
||||
@@ -1551,6 +1580,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
|
||||
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
|
||||
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
|
||||
$attr['acl_pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0;
|
||||
} else {
|
||||
$_data['acl'] = (array)$_data['acl'];
|
||||
$attr['acl_spam_alias'] = 0;
|
||||
@@ -2840,21 +2870,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
|
||||
}
|
||||
if (!empty($is_now)) {
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
|
||||
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
|
||||
(int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
|
||||
(int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
|
||||
(int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
|
||||
(int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
|
||||
(int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
|
||||
(int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? 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'];
|
||||
$domain = $is_now['domain'];
|
||||
$quota_b = $quota_m * 1048576;
|
||||
$password = (!empty($_data['password'])) ? $_data['password'] : null;
|
||||
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
|
||||
$tags = (is_array($_data['tags']) ? $_data['tags'] : array());
|
||||
(int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
|
||||
(int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
|
||||
(int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
|
||||
(int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
|
||||
(int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
|
||||
(int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? 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'];
|
||||
$domain = $is_now['domain'];
|
||||
$quota_b = $quota_m * 1048576;
|
||||
$password = (!empty($_data['password'])) ? $_data['password'] : null;
|
||||
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
|
||||
$pw_recovery_email = (isset($_data['pw_recovery_email'])) ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
|
||||
$tags = (is_array($_data['tags']) ? $_data['tags'] : array());
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -3107,31 +3138,43 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
':address' => $username,
|
||||
':active' => $active
|
||||
));
|
||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET
|
||||
`active` = :active,
|
||||
`name`= :name,
|
||||
`quota` = :quota_b,
|
||||
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
|
||||
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),
|
||||
`attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access)
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':active' => $active,
|
||||
':name' => $name,
|
||||
':quota_b' => $quota_b,
|
||||
':force_pw_update' => $force_pw_update,
|
||||
':sogo_access' => $sogo_access,
|
||||
':imap_access' => $imap_access,
|
||||
':pop3_access' => $pop3_access,
|
||||
':sieve_access' => $sieve_access,
|
||||
':smtp_access' => $smtp_access,
|
||||
':relayhost' => $relayhost,
|
||||
':username' => $username
|
||||
));
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET
|
||||
`active` = :active,
|
||||
`name`= :name,
|
||||
`quota` = :quota_b,
|
||||
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
|
||||
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
|
||||
`attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access),
|
||||
`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`, '$.recovery_email', :recovery_email)
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':active' => $active,
|
||||
':name' => $name,
|
||||
':quota_b' => $quota_b,
|
||||
':force_pw_update' => $force_pw_update,
|
||||
':sogo_access' => $sogo_access,
|
||||
':imap_access' => $imap_access,
|
||||
':pop3_access' => $pop3_access,
|
||||
':sieve_access' => $sieve_access,
|
||||
':smtp_access' => $smtp_access,
|
||||
':recovery_email' => $pw_recovery_email,
|
||||
':relayhost' => $relayhost,
|
||||
':username' => $username
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => $e->getMessage()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// save tags
|
||||
foreach($tags as $index => $tag){
|
||||
if (empty($tag)) continue;
|
||||
@@ -3238,6 +3281,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
|
||||
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
|
||||
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
|
||||
$attr['acl_pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0;
|
||||
} else {
|
||||
foreach ($is_now as $key => $value){
|
||||
$attr[$key] = $is_now[$key];
|
||||
@@ -3411,31 +3455,56 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$footers = array();
|
||||
$footers['html'] = isset($_data['html']) ? $_data['html'] : '';
|
||||
$footers['plain'] = isset($_data['plain']) ? $_data['plain'] : '';
|
||||
$footers['skip_replies'] = isset($_data['skip_replies']) ? (int)$_data['skip_replies'] : 0;
|
||||
$footers['mbox_exclude'] = array();
|
||||
if (isset($_data["mbox_exclude"])){
|
||||
if (!is_array($_data["mbox_exclude"])) {
|
||||
$_data["mbox_exclude"] = array($_data["mbox_exclude"]);
|
||||
$footers['alias_domain_exclude'] = array();
|
||||
if (isset($_data["exclude"])){
|
||||
if (!is_array($_data["exclude"])) {
|
||||
$_data["exclude"] = array($_data["exclude"]);
|
||||
}
|
||||
foreach ($_data["mbox_exclude"] as $mailbox) {
|
||||
if (!filter_var($mailbox, FILTER_VALIDATE_EMAIL)) {
|
||||
foreach ($_data["exclude"] as $exclude) {
|
||||
if (filter_var($exclude, FILTER_VALIDATE_EMAIL)) {
|
||||
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `address` = :address
|
||||
UNION
|
||||
SELECT `username` FROM `mailbox` WHERE `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':address' => $exclude,
|
||||
':username' => $exclude,
|
||||
));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if(!$row){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('username_invalid', $exclude)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
array_push($footers['mbox_exclude'], $exclude);
|
||||
}
|
||||
elseif (is_valid_domain_name($exclude)) {
|
||||
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain` = :alias_domain");
|
||||
$stmt->execute(array(
|
||||
':alias_domain' => $exclude,
|
||||
));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if(!$row){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('username_invalid', $exclude)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
array_push($footers['alias_domain_exclude'], $exclude);
|
||||
}
|
||||
else {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('username_invalid', $mailbox)
|
||||
'msg' => array('username_invalid', $exclude)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$is_now = mailbox('get', 'mailbox_details', $mailbox);
|
||||
if(empty($is_now)){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
|
||||
'msg' => array('username_invalid', $mailbox)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
array_push($footers['mbox_exclude'], $mailbox);
|
||||
}
|
||||
}
|
||||
foreach ($domains as $domain) {
|
||||
@@ -3460,12 +3529,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM `domain_wide_footer` WHERE `domain`= :domain");
|
||||
$stmt->execute(array(':domain' => $domain));
|
||||
$stmt = $pdo->prepare("INSERT INTO `domain_wide_footer` (`domain`, `html`, `plain`, `mbox_exclude`) VALUES (:domain, :html, :plain, :mbox_exclude)");
|
||||
$stmt = $pdo->prepare("INSERT INTO `domain_wide_footer` (`domain`, `html`, `plain`, `mbox_exclude`, `alias_domain_exclude`, `skip_replies`) VALUES (:domain, :html, :plain, :mbox_exclude, :alias_domain_exclude, :skip_replies)");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain,
|
||||
':html' => $footers['html'],
|
||||
':plain' => $footers['plain'],
|
||||
':mbox_exclude' => json_encode($footers['mbox_exclude']),
|
||||
':alias_domain_exclude' => json_encode($footers['alias_domain_exclude']),
|
||||
':skip_replies' => $footers['skip_replies'],
|
||||
));
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
@@ -4289,6 +4360,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$domaindata['mboxes_in_domain'] = $MailboxDataDomain['count'];
|
||||
$domaindata['mboxes_left'] = $row['mailboxes'] - $MailboxDataDomain['count'];
|
||||
$domaindata['domain_name'] = $row['domain'];
|
||||
$domaindata['domain_h_name'] = idn_to_utf8($row['domain']);
|
||||
$domaindata['description'] = $row['description'];
|
||||
$domaindata['max_num_aliases_for_domain'] = $row['aliases'];
|
||||
$domaindata['max_num_mboxes_for_domain'] = $row['mailboxes'];
|
||||
@@ -4435,7 +4507,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$mailboxdata['active'] = $row['active'];
|
||||
$mailboxdata['active_int'] = $row['active'];
|
||||
$mailboxdata['domain'] = $row['domain'];
|
||||
$mailboxdata['relayhost'] = $row['relayhost'];
|
||||
$mailboxdata['name'] = $row['name'];
|
||||
$mailboxdata['local_part'] = $row['local_part'];
|
||||
$mailboxdata['quota'] = $row['quota'];
|
||||
@@ -4622,7 +4693,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `html`, `plain`, `mbox_exclude` FROM `domain_wide_footer`
|
||||
$stmt = $pdo->prepare("SELECT `html`, `plain`, `mbox_exclude`, `alias_domain_exclude`, `skip_replies` FROM `domain_wide_footer`
|
||||
WHERE `domain` = :domain");
|
||||
$stmt->execute(array(
|
||||
':domain' => $domain
|
||||
@@ -5160,7 +5231,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
'msg' => 'Could not move maildir to garbage collector: variables local_part and/or domain empty'
|
||||
);
|
||||
}
|
||||
if (strtolower(getenv('SKIP_SOLR')) == 'n') {
|
||||
if (strtolower(getenv('SKIP_SOLR')) == 'n' && strtolower(getenv('FLATCURVE_EXPERIMENTAL')) != 'y') {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot-fts/update?commit=true');
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml'));
|
||||
|
||||
@@ -143,17 +143,26 @@ function rspamd_maps($_action, $_data = null) {
|
||||
return false;
|
||||
}
|
||||
$maps = (array)$_data['map'];
|
||||
$valid_maps = array();
|
||||
foreach ($maps as $map) {
|
||||
$is_valid = false;
|
||||
foreach ($RSPAMD_MAPS as $rspamd_map_type) {
|
||||
if (!in_array($map, $rspamd_map_type)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, '-'),
|
||||
'msg' => array('global_map_invalid', $map)
|
||||
);
|
||||
continue;
|
||||
if (in_array($map, $rspamd_map_type)) {
|
||||
$is_valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($is_valid) {
|
||||
array_push($valid_maps, $map);
|
||||
} else {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_action, '-'),
|
||||
'msg' => array('global_map_invalid', $map)
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach ($valid_maps as $map) {
|
||||
try {
|
||||
if (file_exists('/rspamd_custom_maps/' . $map)) {
|
||||
$map_content = trim($_data['rspamd_map_data']);
|
||||
|
||||
@@ -49,7 +49,6 @@ $globalVariables = [
|
||||
'app_links' => customize('get', 'app_links'),
|
||||
'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'),
|
||||
'uri' => $_SERVER['REQUEST_URI'],
|
||||
'last_login' => last_login('get', $_SESSION['mailcow_cc_username'], 7, 0)['ui']['time']
|
||||
];
|
||||
|
||||
foreach ($globalVariables as $globalVariableName => $globalVariableValue) {
|
||||
|
||||
@@ -3,7 +3,7 @@ function init_db_schema() {
|
||||
try {
|
||||
global $pdo;
|
||||
|
||||
$db_version = "21112023_1644";
|
||||
$db_version = "29072024_1000";
|
||||
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
@@ -273,6 +273,8 @@ function init_db_schema() {
|
||||
"html" => "LONGTEXT",
|
||||
"plain" => "LONGTEXT",
|
||||
"mbox_exclude" => "JSON NOT NULL DEFAULT ('[]')",
|
||||
"alias_domain_exclude" => "JSON NOT NULL DEFAULT ('[]')",
|
||||
"skip_replies" => "TINYINT(1) NOT NULL DEFAULT '0'"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
@@ -481,6 +483,7 @@ function init_db_schema() {
|
||||
"quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"quarantine_category" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
"pw_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
@@ -692,6 +695,19 @@ function init_db_schema() {
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"reset_password" => array(
|
||||
"cols" => array(
|
||||
"username" => "VARCHAR(255) NOT NULL",
|
||||
"token" => "VARCHAR(255) NOT NULL",
|
||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("token", "created")
|
||||
),
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"imapsync" => array(
|
||||
"cols" => array(
|
||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||
@@ -977,6 +993,18 @@ function init_db_schema() {
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"sogo_admin" => array(
|
||||
"cols" => array(
|
||||
"c_key" => "VARCHAR(255) NOT NULL DEFAULT ''",
|
||||
"c_content" => "mediumtext NOT NULL",
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
"" => array("c_key")
|
||||
)
|
||||
),
|
||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||
),
|
||||
"pushover" => array(
|
||||
"cols" => array(
|
||||
"username" => "VARCHAR(255) NOT NULL",
|
||||
|
||||
33
data/web/inc/lib/sieve/extensions/enotify.xml
Normal file
33
data/web/inc/lib/sieve/extensions/enotify.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="enotify">
|
||||
<command name="notify">
|
||||
<parameter type="tag" name="from" occurrence="optional">
|
||||
<parameter type="string" name="from-address" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="importance" regex="(1|2|3)" occurrence="optional" />
|
||||
|
||||
<parameter type="tag" name="options" occurrence="optional">
|
||||
<parameter type="stringlist" name="option-strings" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="tag" name="message" occurrence="optional">
|
||||
<parameter type="string" name="message-text" />
|
||||
</parameter>
|
||||
|
||||
<parameter type="string" name="method" />
|
||||
</command>
|
||||
|
||||
<test name="valid_notify_method">
|
||||
<parameter type="stringlist" name="notification-uris" />
|
||||
</test>
|
||||
|
||||
<test name="notify_method_capability">
|
||||
<parameter type="string" name="notification-uri" />
|
||||
<parameter type="string" name="notification-capability" />
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</test>
|
||||
|
||||
<modifier name="encodeurl" />
|
||||
</extension>
|
||||
58
data/web/inc/lib/sieve/extensions/mime.xml
Normal file
58
data/web/inc/lib/sieve/extensions/mime.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
|
||||
<extension name="mime">
|
||||
<command name="foreverypart">
|
||||
<parameter type="string" name="name" occurrence="optional" />
|
||||
<block />
|
||||
</command>
|
||||
|
||||
<command name="break">
|
||||
<parameter type="string" name="name" occurrence="optional" />
|
||||
</command>
|
||||
|
||||
<tagged-argument extends="(header|address|exists)">
|
||||
<parameter type="tag" name="mime" regex="mime" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header|address|exists)">
|
||||
<parameter type="tag" name="anychild" regex="anychild" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="tag" name="type" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="tag" name="subtype" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="tag" name="contenttype" occurrence="optional" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="tag" name="param" regex="param" occurrence="optional">
|
||||
<parameter type="stringlist" name="param-list" />
|
||||
</parameter>
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header|address|exists)">
|
||||
<parameter type="stringlist" name="header-names" />
|
||||
</tagged-argument>
|
||||
<tagged-argument extends="(header)">
|
||||
<parameter type="stringlist" name="key-list" />
|
||||
</tagged-argument>
|
||||
|
||||
<action name="replace">
|
||||
<parameter type="tag" name="mime" regex="mime" occurrence="optional" />
|
||||
<parameter type="string" name="subject" occurrence="optional" />
|
||||
<parameter type="string" name="from" occurrence="optional" />
|
||||
<parameter type="string" name="replacement" />
|
||||
</action>
|
||||
|
||||
<action name="enclose">
|
||||
<parameter type="string" name="subject" occurrence="optional" />
|
||||
<parameter type="stringlist" name="headers" occurrence="optional" />
|
||||
<parameter type="string" name="text" />
|
||||
</action>
|
||||
|
||||
<action name="extracttext">
|
||||
<parameter type="tag" name="first" regex="first" occurrence="optional" />
|
||||
<parameter type="number" name="number" occurrence="optional" />
|
||||
<parameter type="string" name="varname" />
|
||||
</action>
|
||||
</extension>
|
||||
622
data/web/inc/lib/ssp.class.php
Normal file
622
data/web/inc/lib/ssp.class.php
Normal file
@@ -0,0 +1,622 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Helper functions for building a DataTables server-side processing SQL query
|
||||
*
|
||||
* The static functions in this class are just helper functions to help build
|
||||
* the SQL used in the DataTables demo server-side processing scripts. These
|
||||
* functions obviously do not represent all that can be done with server-side
|
||||
* processing, they are intentionally simple to show how it works. More complex
|
||||
* server-side processing operations will likely require a custom script.
|
||||
*
|
||||
* See https://datatables.net/usage/server-side for full details on the server-
|
||||
* side processing requirements of DataTables.
|
||||
*
|
||||
* @license MIT - https://datatables.net/license_mit
|
||||
*/
|
||||
|
||||
class SSP {
|
||||
/**
|
||||
* Create the data output array for the DataTables rows
|
||||
*
|
||||
* @param array $columns Column information array
|
||||
* @param array $data Data from the SQL get
|
||||
* @return array Formatted data in a row based format
|
||||
*/
|
||||
static function data_output ( $columns, $data )
|
||||
{
|
||||
$out = array();
|
||||
|
||||
for ( $i=0, $ien=count($data) ; $i<$ien ; $i++ ) {
|
||||
$row = array();
|
||||
|
||||
for ( $j=0, $jen=count($columns) ; $j<$jen ; $j++ ) {
|
||||
$column = $columns[$j];
|
||||
|
||||
// Is there a formatter?
|
||||
if ( isset( $column['formatter'] ) ) {
|
||||
if(empty($column['db'])){
|
||||
$row[ $column['dt'] ] = $column['formatter']( $data[$i] );
|
||||
}
|
||||
else{
|
||||
$row[ $column['dt'] ] = $column['formatter']( $data[$i][ $column['db'] ], $data[$i] );
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(!empty($column['db']) && (!isset($column['dummy']) || $column['dummy'] !== true)){
|
||||
$row[ $column['dt'] ] = $data[$i][ $columns[$j]['db'] ];
|
||||
}
|
||||
else{
|
||||
$row[ $column['dt'] ] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$out[] = $row;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Database connection
|
||||
*
|
||||
* Obtain an PHP PDO connection from a connection details array
|
||||
*
|
||||
* @param array $conn SQL connection details. The array should have
|
||||
* the following properties
|
||||
* * host - host name
|
||||
* * db - database name
|
||||
* * user - user name
|
||||
* * pass - user password
|
||||
* * Optional: `'charset' => 'utf8'` - you might need this depending on your PHP / MySQL config
|
||||
* @return resource PDO connection
|
||||
*/
|
||||
static function db ( $conn )
|
||||
{
|
||||
if ( is_array( $conn ) ) {
|
||||
return self::sql_connect( $conn );
|
||||
}
|
||||
|
||||
return $conn;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Paging
|
||||
*
|
||||
* Construct the LIMIT clause for server-side processing SQL query
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array $columns Column information array
|
||||
* @return string SQL limit clause
|
||||
*/
|
||||
static function limit ( $request, $columns )
|
||||
{
|
||||
$limit = '';
|
||||
|
||||
if ( isset($request['start']) && $request['length'] != -1 ) {
|
||||
$limit = "LIMIT ".intval($request['start']).", ".intval($request['length']);
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ordering
|
||||
*
|
||||
* Construct the ORDER BY clause for server-side processing SQL query
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array $columns Column information array
|
||||
* @return string SQL order by clause
|
||||
*/
|
||||
static function order ( $tableAS, $request, $columns )
|
||||
{
|
||||
$select = '';
|
||||
$order = '';
|
||||
|
||||
if ( isset($request['order']) && count($request['order']) ) {
|
||||
$selects = [];
|
||||
$orderBy = [];
|
||||
$dtColumns = self::pluck( $columns, 'dt' );
|
||||
|
||||
for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) {
|
||||
// Convert the column index into the column data property
|
||||
$columnIdx = intval($request['order'][$i]['column']);
|
||||
$requestColumn = $request['columns'][$columnIdx];
|
||||
|
||||
$columnIdx = array_search( $columnIdx, $dtColumns );
|
||||
$column = $columns[ $columnIdx ];
|
||||
|
||||
if ( $requestColumn['orderable'] == 'true' ) {
|
||||
$dir = $request['order'][$i]['dir'] === 'asc' ?
|
||||
'ASC' :
|
||||
'DESC';
|
||||
|
||||
if(isset($column['order_subquery'])) {
|
||||
$selects[] = '('.$column['order_subquery'].') AS `'.$column['db'].'_count`';
|
||||
$orderBy[] = '`'.$column['db'].'_count` '.$dir;
|
||||
} else {
|
||||
$orderBy[] = '`'.$tableAS.'`.`'.$column['db'].'` '.$dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $selects ) ) {
|
||||
$select = ', '.implode(', ', $selects);
|
||||
}
|
||||
|
||||
if ( count( $orderBy ) ) {
|
||||
$order = 'ORDER BY '.implode(', ', $orderBy);
|
||||
}
|
||||
}
|
||||
|
||||
return [$select, $order];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searching / Filtering
|
||||
*
|
||||
* Construct the WHERE clause for server-side processing SQL query.
|
||||
*
|
||||
* NOTE this does not match the built-in DataTables filtering which does it
|
||||
* word by word on any field. It's possible to do here performance on large
|
||||
* databases would be very poor
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array $columns Column information array
|
||||
* @param array $bindings Array of values for PDO bindings, used in the
|
||||
* sql_exec() function
|
||||
* @return string SQL where clause
|
||||
*/
|
||||
static function filter ( $tablesAS, $request, $columns, &$bindings )
|
||||
{
|
||||
$globalSearch = array();
|
||||
$columnSearch = array();
|
||||
$joins = array();
|
||||
$dtColumns = self::pluck( $columns, 'dt' );
|
||||
|
||||
if ( isset($request['search']) && $request['search']['value'] != '' ) {
|
||||
$str = $request['search']['value'];
|
||||
|
||||
for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
|
||||
$requestColumn = $request['columns'][$i];
|
||||
$columnIdx = array_search( $i, $dtColumns );
|
||||
$column = $columns[ $columnIdx ];
|
||||
|
||||
if ( $requestColumn['searchable'] == 'true' ) {
|
||||
if(!empty($column['db'])){
|
||||
$binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
|
||||
|
||||
if(isset($column['search']['join'])) {
|
||||
$joins[] = $column['search']['join'];
|
||||
$globalSearch[] = $column['search']['where_column'].' LIKE '.$binding;
|
||||
} else {
|
||||
$globalSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Individual column filtering
|
||||
if ( isset( $request['columns'] ) ) {
|
||||
for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
|
||||
$requestColumn = $request['columns'][$i];
|
||||
$columnIdx = array_search( $requestColumn['data'], $dtColumns );
|
||||
$column = $columns[ $columnIdx ];
|
||||
|
||||
$str = $requestColumn['search']['value'];
|
||||
|
||||
if ( $requestColumn['searchable'] == 'true' &&
|
||||
$str != '' ) {
|
||||
if(!empty($column['db'])){
|
||||
$binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
|
||||
$columnSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combine the filters into a single string
|
||||
$where = '';
|
||||
|
||||
if ( count( $globalSearch ) ) {
|
||||
$where = '('.implode(' OR ', $globalSearch).')';
|
||||
}
|
||||
|
||||
if ( count( $columnSearch ) ) {
|
||||
$where = $where === '' ?
|
||||
implode(' AND ', $columnSearch) :
|
||||
$where .' AND '. implode(' AND ', $columnSearch);
|
||||
}
|
||||
|
||||
$join = '';
|
||||
if( count($joins) ) {
|
||||
$join = implode(' ', $joins);
|
||||
}
|
||||
|
||||
if ( $where !== '' ) {
|
||||
$where = 'WHERE '.$where;
|
||||
}
|
||||
|
||||
return [$join, $where];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform the SQL queries needed for an server-side processing requested,
|
||||
* utilising the helper functions of this class, limit(), order() and
|
||||
* filter() among others. The returned array is ready to be encoded as JSON
|
||||
* in response to an SSP request, or can be modified if needed before
|
||||
* sending back to the client.
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array|PDO $conn PDO connection resource or connection parameters array
|
||||
* @param string $table SQL table to query
|
||||
* @param string $primaryKey Primary key of the table
|
||||
* @param array $columns Column information array
|
||||
* @return array Server-side processing response array
|
||||
*/
|
||||
static function simple ( $request, $conn, $table, $primaryKey, $columns )
|
||||
{
|
||||
$bindings = array();
|
||||
$db = self::db( $conn );
|
||||
|
||||
// Allow for a JSON string to be passed in
|
||||
if (isset($request['json'])) {
|
||||
$request = json_decode($request['json'], true);
|
||||
}
|
||||
|
||||
// table AS
|
||||
$tablesAS = null;
|
||||
if(is_array($table)) {
|
||||
$tablesAS = $table[1];
|
||||
$table = $table[0];
|
||||
}
|
||||
|
||||
// Build the SQL query string from the request
|
||||
list($select, $order) = self::order( $tablesAS, $request, $columns );
|
||||
$limit = self::limit( $request, $columns );
|
||||
list($join, $where) = self::filter( $tablesAS, $request, $columns, $bindings );
|
||||
|
||||
// Main query to actually get the data
|
||||
$data = self::sql_exec( $db, $bindings,
|
||||
"SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."`
|
||||
$select
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$where
|
||||
GROUP BY `{$tablesAS}`.`{$primaryKey}`
|
||||
$order
|
||||
$limit"
|
||||
);
|
||||
|
||||
// Data set length after filtering
|
||||
$resFilterLength = self::sql_exec( $db, $bindings,
|
||||
"SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`)
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$where"
|
||||
);
|
||||
$recordsFiltered = $resFilterLength[0][0];
|
||||
|
||||
// Total data set length
|
||||
$resTotalLength = self::sql_exec( $db,
|
||||
"SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`)
|
||||
FROM `$table` AS `$tablesAS`"
|
||||
);
|
||||
$recordsTotal = $resTotalLength[0][0];
|
||||
|
||||
/*
|
||||
* Output
|
||||
*/
|
||||
return array(
|
||||
"draw" => isset ( $request['draw'] ) ?
|
||||
intval( $request['draw'] ) :
|
||||
0,
|
||||
"recordsTotal" => intval( $recordsTotal ),
|
||||
"recordsFiltered" => intval( $recordsFiltered ),
|
||||
"data" => self::data_output( $columns, $data )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The difference between this method and the `simple` one, is that you can
|
||||
* apply additional `where` conditions to the SQL queries. These can be in
|
||||
* one of two forms:
|
||||
*
|
||||
* * 'Result condition' - This is applied to the result set, but not the
|
||||
* overall paging information query - i.e. it will not effect the number
|
||||
* of records that a user sees they can have access to. This should be
|
||||
* used when you want apply a filtering condition that the user has sent.
|
||||
* * 'All condition' - This is applied to all queries that are made and
|
||||
* reduces the number of records that the user can access. This should be
|
||||
* used in conditions where you don't want the user to ever have access to
|
||||
* particular records (for example, restricting by a login id).
|
||||
*
|
||||
* In both cases the extra condition can be added as a simple string, or if
|
||||
* you are using external values, as an assoc. array with `condition` and
|
||||
* `bindings` parameters. The `condition` is a string with the SQL WHERE
|
||||
* condition and `bindings` is an assoc. array of the binding names and
|
||||
* values.
|
||||
*
|
||||
* @param array $request Data sent to server by DataTables
|
||||
* @param array|PDO $conn PDO connection resource or connection parameters array
|
||||
* @param string|array $table SQL table to query, if array second key is AS
|
||||
* @param string $primaryKey Primary key of the table
|
||||
* @param array $columns Column information array
|
||||
* @param string $join JOIN sql string
|
||||
* @param string|array $whereResult WHERE condition to apply to the result set
|
||||
* @return array Server-side processing response array
|
||||
*/
|
||||
static function complex (
|
||||
$request,
|
||||
$conn,
|
||||
$table,
|
||||
$primaryKey,
|
||||
$columns,
|
||||
$join=null,
|
||||
$whereResult=null
|
||||
) {
|
||||
$bindings = array();
|
||||
$db = self::db( $conn );
|
||||
|
||||
// table AS
|
||||
$tablesAS = null;
|
||||
if(is_array($table)) {
|
||||
$tablesAS = $table[1];
|
||||
$table = $table[0];
|
||||
}
|
||||
|
||||
// Build the SQL query string from the request
|
||||
list($select, $order) = self::order( $tablesAS, $request, $columns );
|
||||
$limit = self::limit( $request, $columns );
|
||||
list($join_filter, $where) = self::filter( $tablesAS, $request, $columns, $bindings );
|
||||
|
||||
// whereResult can be a simple string, or an assoc. array with a
|
||||
// condition and bindings
|
||||
if ( $whereResult ) {
|
||||
$str = $whereResult;
|
||||
|
||||
if ( is_array($whereResult) ) {
|
||||
$str = $whereResult['condition'];
|
||||
|
||||
if ( isset($whereResult['bindings']) ) {
|
||||
self::add_bindings($bindings, $whereResult);
|
||||
}
|
||||
}
|
||||
|
||||
$where = $where ?
|
||||
$where .' AND '.$str :
|
||||
'WHERE '.$str;
|
||||
}
|
||||
|
||||
// Main query to actually get the data
|
||||
$data = self::sql_exec( $db, $bindings,
|
||||
"SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."`
|
||||
$select
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$join_filter
|
||||
$where
|
||||
GROUP BY `{$tablesAS}`.`{$primaryKey}`
|
||||
$order
|
||||
$limit"
|
||||
);
|
||||
|
||||
// Data set length after filtering
|
||||
$resFilterLength = self::sql_exec( $db, $bindings,
|
||||
"SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`)
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$join_filter
|
||||
$where"
|
||||
);
|
||||
$recordsFiltered = (isset($resFilterLength[0])) ? $resFilterLength[0][0] : 0;
|
||||
|
||||
// Total data set length
|
||||
$resTotalLength = self::sql_exec( $db, $bindings,
|
||||
"SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`)
|
||||
FROM `$table` AS `$tablesAS`
|
||||
$join
|
||||
$join_filter
|
||||
$where"
|
||||
);
|
||||
$recordsTotal = (isset($resTotalLength[0])) ? $resTotalLength[0][0] : 0;
|
||||
|
||||
/*
|
||||
* Output
|
||||
*/
|
||||
return array(
|
||||
"draw" => isset ( $request['draw'] ) ?
|
||||
intval( $request['draw'] ) :
|
||||
0,
|
||||
"recordsTotal" => intval( $recordsTotal ),
|
||||
"recordsFiltered" => intval( $recordsFiltered ),
|
||||
"data" => self::data_output( $columns, $data )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connect to the database
|
||||
*
|
||||
* @param array $sql_details SQL server connection details array, with the
|
||||
* properties:
|
||||
* * host - host name
|
||||
* * db - database name
|
||||
* * user - user name
|
||||
* * pass - user password
|
||||
* @return resource Database connection handle
|
||||
*/
|
||||
static function sql_connect ( $sql_details )
|
||||
{
|
||||
try {
|
||||
$db = @new PDO(
|
||||
"mysql:host={$sql_details['host']};dbname={$sql_details['db']}",
|
||||
$sql_details['user'],
|
||||
$sql_details['pass'],
|
||||
array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION )
|
||||
);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
self::fatal(
|
||||
"An error occurred while connecting to the database. ".
|
||||
"The error reported by the server was: ".$e->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute an SQL query on the database
|
||||
*
|
||||
* @param resource $db Database handler
|
||||
* @param array $bindings Array of PDO binding values from bind() to be
|
||||
* used for safely escaping strings. Note that this can be given as the
|
||||
* SQL query string if no bindings are required.
|
||||
* @param string $sql SQL query to execute.
|
||||
* @return array Result from the query (all rows)
|
||||
*/
|
||||
static function sql_exec ( $db, $bindings, $sql=null )
|
||||
{
|
||||
// Argument shifting
|
||||
if ( $sql === null ) {
|
||||
$sql = $bindings;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare( $sql );
|
||||
|
||||
// Bind parameters
|
||||
if ( is_array( $bindings ) ) {
|
||||
for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) {
|
||||
$binding = $bindings[$i];
|
||||
$stmt->bindValue( $binding['key'], $binding['val'], $binding['type'] );
|
||||
}
|
||||
}
|
||||
|
||||
// Execute
|
||||
try {
|
||||
$stmt->execute();
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
self::fatal( "An SQL error occurred: ".$e->getMessage() );
|
||||
}
|
||||
|
||||
// Return all
|
||||
return $stmt->fetchAll( PDO::FETCH_BOTH );
|
||||
}
|
||||
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* Internal methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Throw a fatal error.
|
||||
*
|
||||
* This writes out an error message in a JSON string which DataTables will
|
||||
* see and show to the user in the browser.
|
||||
*
|
||||
* @param string $msg Message to send to the client
|
||||
*/
|
||||
static function fatal ( $msg )
|
||||
{
|
||||
echo json_encode( array(
|
||||
"error" => $msg
|
||||
) );
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a PDO binding key which can be used for escaping variables safely
|
||||
* when executing a query with sql_exec()
|
||||
*
|
||||
* @param array &$a Array of bindings
|
||||
* @param * $val Value to bind
|
||||
* @param int $type PDO field type
|
||||
* @return string Bound key to be used in the SQL where this parameter
|
||||
* would be used.
|
||||
*/
|
||||
static function bind ( &$a, $val, $type )
|
||||
{
|
||||
$key = ':binding_'.count( $a );
|
||||
|
||||
$a[] = array(
|
||||
'key' => $key,
|
||||
'val' => $val,
|
||||
'type' => $type
|
||||
);
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
static function add_bindings(&$bindings, $vals)
|
||||
{
|
||||
foreach($vals['bindings'] as $key => $value) {
|
||||
$bindings[] = array(
|
||||
'key' => $key,
|
||||
'val' => $value,
|
||||
'type' => PDO::PARAM_STR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pull a particular property from each assoc. array in a numeric array,
|
||||
* returning and array of the property values from each item.
|
||||
*
|
||||
* @param array $a Array to get data from
|
||||
* @param string $prop Property to read
|
||||
* @return array Array of property values
|
||||
*/
|
||||
static function pluck ( $a, $prop )
|
||||
{
|
||||
$out = array();
|
||||
|
||||
for ( $i=0, $len=count($a) ; $i<$len ; $i++ ) {
|
||||
if ( empty($a[$i][$prop]) && $a[$i][$prop] !== 0 ) {
|
||||
continue;
|
||||
}
|
||||
if ( $prop == 'db' && isset($a[$i]['dummy']) && $a[$i]['dummy'] === true ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//removing the $out array index confuses the filter method in doing proper binding,
|
||||
//adding it ensures that the array data are mapped correctly
|
||||
$out[$i] = $a[$i][$prop];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a string from an array or a string
|
||||
*
|
||||
* @param array|string $a Array to join
|
||||
* @param string $join Glue for the concatenation
|
||||
* @return string Joined string
|
||||
*/
|
||||
static function _flatten ( $a, $join = ' AND ' )
|
||||
{
|
||||
if ( ! $a ) {
|
||||
return '';
|
||||
}
|
||||
else if ( $a && is_array($a) ) {
|
||||
return implode( $join, $a );
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
headline: lang.sieve_preset_1
|
||||
content: |
|
||||
require ["reject","body","regex"];
|
||||
if anyof (body :raw :regex ["filename=.*\.doc","filename=.*\.exe","filename=.*\.moo"]) {
|
||||
if anyof (body :raw :regex ["filename=\".*\\.(doc|exe|moo)\""]) {
|
||||
reject text:
|
||||
doc, exe and moo are dangerous file extensions.
|
||||
Why would you do that? I am a sad cow.
|
||||
|
||||
@@ -10,16 +10,54 @@ if (!empty($_GET['sso_token'])) {
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST["pw_reset_request"]) && !empty($_POST['username'])) {
|
||||
reset_password("issue", $_POST['username']);
|
||||
header("Location: /");
|
||||
exit;
|
||||
}
|
||||
if (isset($_POST["pw_reset"])) {
|
||||
$username = reset_password("check", $_POST['token']);
|
||||
$reset_result = reset_password("reset", array(
|
||||
'new_password' => $_POST['new_password'],
|
||||
'new_password2' => $_POST['new_password2'],
|
||||
'token' => $_POST['token'],
|
||||
'username' => $username,
|
||||
'check_tfa' => True
|
||||
));
|
||||
|
||||
if ($reset_result){
|
||||
header("Location: /");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
if (isset($_POST["verify_tfa_login"])) {
|
||||
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
|
||||
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
|
||||
$_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
if (isset($_SESSION['pending_mailcow_cc_username']) && isset($_SESSION['pending_pw_reset_token']) && isset($_SESSION['pending_pw_new_password'])) {
|
||||
reset_password("reset", array(
|
||||
'new_password' => $_SESSION['pending_pw_new_password'],
|
||||
'new_password2' => $_SESSION['pending_pw_new_password'],
|
||||
'token' => $_SESSION['pending_pw_reset_token'],
|
||||
'username' => $_SESSION['pending_mailcow_cc_username']
|
||||
));
|
||||
unset($_SESSION['pending_pw_reset_token']);
|
||||
unset($_SESSION['pending_pw_new_password']);
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
|
||||
header("Location: /user");
|
||||
header("Location: /");
|
||||
exit;
|
||||
} else {
|
||||
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
|
||||
$_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
|
||||
header("Location: /user");
|
||||
}
|
||||
} else {
|
||||
unset($_SESSION['pending_pw_reset_token']);
|
||||
unset($_SESSION['pending_pw_new_password']);
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
@@ -27,11 +65,13 @@ if (isset($_POST["verify_tfa_login"])) {
|
||||
}
|
||||
|
||||
if (isset($_GET["cancel_tfa_login"])) {
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
unset($_SESSION['pending_pw_reset_token']);
|
||||
unset($_SESSION['pending_pw_new_password']);
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
|
||||
header("Location: /");
|
||||
header("Location: /");
|
||||
}
|
||||
|
||||
if (isset($_POST["quick_release"])) {
|
||||
|
||||
@@ -95,6 +95,8 @@ $AVAILABLE_LANGUAGES = array(
|
||||
'it-it' => 'Italiano (Italian)',
|
||||
'ko-kr' => '한국어 (Korean)',
|
||||
'lv-lv' => 'latviešu (Latvian)',
|
||||
'lt-lt' => 'Lietuvių (Lithuanian)',
|
||||
'nb-no' => 'Norsk (Norwegian)',
|
||||
'nl-nl' => 'Nederlands (Dutch)',
|
||||
'pl-pl' => 'Język Polski (Polish)',
|
||||
'pt-br' => 'Português brasileiro (Brazilian Portuguese)',
|
||||
@@ -126,6 +128,15 @@ $MAILCOW_APPS = array(
|
||||
)
|
||||
);
|
||||
|
||||
// Logo max file size in bytes
|
||||
$LOGO_LIMITS['max_size'] = 15 * 1024 * 1024; // 15MB
|
||||
|
||||
// Logo max width in pixels
|
||||
$LOGO_LIMITS['max_width'] = 1920;
|
||||
|
||||
// Logo max height in pixels
|
||||
$LOGO_LIMITS['max_height'] = 1920;
|
||||
|
||||
// Rows until pagination begins
|
||||
$PAGINATION_SIZE = 25;
|
||||
|
||||
@@ -199,6 +210,12 @@ $MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format'] = 'maildir:';
|
||||
// Show last IMAP and POP3 logins
|
||||
$SHOW_LAST_LOGIN = true;
|
||||
|
||||
// Maximum number of password reset tokens that can be generated at once per user
|
||||
$PW_RESET_TOKEN_LIMIT = 3;
|
||||
|
||||
// Maximum time in minutes a password reset token is valid
|
||||
$PW_RESET_TOKEN_LIFETIME = 15;
|
||||
|
||||
// UV flag handling in FIDO2/WebAuthn - defaults to false to allow iOS logins
|
||||
// true = required
|
||||
// false = preferred
|
||||
|
||||
@@ -397,7 +397,10 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.host,
|
||||
data: 'hostname',
|
||||
defaultContent: ''
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
return escapeHtml(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.username,
|
||||
|
||||
@@ -325,7 +325,10 @@ jQuery(function($){
|
||||
title: 'URI',
|
||||
data: 'uri',
|
||||
defaultContent: '',
|
||||
className: 'dtr-col-md dtr-break-all'
|
||||
className: 'dtr-col-md dtr-break-all',
|
||||
render: function (data, type) {
|
||||
return escapeHtml(data);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Method',
|
||||
@@ -1294,13 +1297,7 @@ function update_stats(timeout=5){
|
||||
$("#host_cpu_usage").text(parseInt(data.cpu.usage).toString() + "%");
|
||||
$("#host_memory_total").text((data.memory.total / (1024 ** 3)).toFixed(2).toString() + "GB");
|
||||
$("#host_memory_usage").text(parseInt(data.memory.usage).toString() + "%");
|
||||
if (data.architecture == "aarch64"){
|
||||
$("#host_architecture").html('<span data-bs-toggle="tooltip" data-bs-placement="top" title="' + lang_debug.wip +'">' + data.architecture + ' ⚠️</span>');
|
||||
}
|
||||
else {
|
||||
$("#host_architecture").html(data.architecture);
|
||||
}
|
||||
|
||||
$("#host_architecture").html(data.architecture);
|
||||
// update cpu and mem chart
|
||||
var cpu_chart = Chart.getChart("host_cpu_chart");
|
||||
var mem_chart = Chart.getChart("host_mem_chart");
|
||||
|
||||
@@ -380,6 +380,9 @@ $(document).ready(function() {
|
||||
if (template.acl_app_passwds == 1){
|
||||
acl.push("app_passwds");
|
||||
}
|
||||
if (template.acl_pw_reset == 1){
|
||||
acl.push("pw_reset");
|
||||
}
|
||||
$('#user_acl').selectpicker('val', acl);
|
||||
|
||||
$('#rl_value').val(template.rl_value);
|
||||
@@ -435,7 +438,7 @@ jQuery(function($){
|
||||
var table = $('#domain_table').DataTable({
|
||||
responsive: true,
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
serverSide: true,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
@@ -447,10 +450,14 @@ jQuery(function($){
|
||||
},
|
||||
ajax: {
|
||||
type: "GET",
|
||||
url: "/api/v1/get/domain/all",
|
||||
url: "/api/v1/get/domain/datatables",
|
||||
dataSrc: function(json){
|
||||
$.each(json, function(i, item) {
|
||||
$.each(json.data, function(i, item) {
|
||||
item.domain_name = escapeHtml(item.domain_name);
|
||||
item.domain_h_name = escapeHtml(item.domain_h_name);
|
||||
if (item.domain_name != item.domain_h_name){
|
||||
item.domain_h_name = item.domain_h_name + '<small class="d-block">' + item.domain_name + '</small>';
|
||||
}
|
||||
|
||||
item.aliases = item.aliases_in_domain + " / " + item.max_num_aliases_for_domain;
|
||||
item.mailboxes = item.mboxes_in_domain + " / " + item.max_num_mboxes_for_domain;
|
||||
@@ -489,16 +496,16 @@ jQuery(function($){
|
||||
|
||||
if (item.backupmx == 1) {
|
||||
if (item.relay_unknown_only == 1) {
|
||||
item.domain_name = '<div class="badge fs-6 bg-info">Relay Non-Local</div> ' + item.domain_name;
|
||||
item.domain_h_name = '<div class="badge fs-7 bg-info">Relay Non-Local</div> ' + item.domain_h_name;
|
||||
} else if (item.relay_all_recipients == 1) {
|
||||
item.domain_name = '<div class="badge fs-6 bg-info">Relay All</div> ' + item.domain_name;
|
||||
item.domain_h_name = '<div class="badge fs-7 bg-info">Relay All</div> ' + item.domain_h_name;
|
||||
} else {
|
||||
item.domain_name = '<div class="badge fs-6 bg-info">Relay</div> ' + item.domain_name;
|
||||
item.domain_h_name = '<div class="badge fs-7 bg-info">Relay</div> ' + item.domain_h_name;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return json;
|
||||
return json.data;
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
@@ -521,24 +528,27 @@ jQuery(function($){
|
||||
},
|
||||
{
|
||||
title: lang.domain,
|
||||
data: 'domain_name',
|
||||
data: 'domain_h_name',
|
||||
responsivePriority: 3,
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang.aliases,
|
||||
data: 'aliases',
|
||||
searchable: false,
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang.mailboxes,
|
||||
data: 'mailboxes',
|
||||
searchable: false,
|
||||
responsivePriority: 4,
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang.domain_quota,
|
||||
data: 'quota',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
data = data.split("/");
|
||||
@@ -548,6 +558,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.stats,
|
||||
data: 'stats',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
render: function (data, type) {
|
||||
data = data.split("/");
|
||||
@@ -557,53 +568,67 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.mailbox_defquota,
|
||||
data: 'def_quota_for_mbox',
|
||||
searchable: false,
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang.mailbox_quota,
|
||||
data: 'max_quota_for_mbox',
|
||||
searchable: false,
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: 'RL',
|
||||
data: 'rl',
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang.backup_mx,
|
||||
data: 'backupmx',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
redner: function (data, type){
|
||||
return 1==value ? '<i class="bi bi-check-lg"></i>' : 0==value && '<i class="bi bi-x-lg"></i>';
|
||||
render: function (data, type){
|
||||
return 1==data ? '<i class="bi bi-check-lg"></i>' : 0==data && '<i class="bi bi-x-lg"></i>';
|
||||
}
|
||||
},
|
||||
{
|
||||
title: lang.domain_admins,
|
||||
data: 'domain_admins',
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
defaultContent: '',
|
||||
className: 'none'
|
||||
},
|
||||
{
|
||||
title: lang.created_on,
|
||||
data: 'created',
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
defaultContent: '',
|
||||
className: 'none'
|
||||
},
|
||||
{
|
||||
title: lang.last_modified,
|
||||
data: 'modified',
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
defaultContent: '',
|
||||
className: 'none'
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
data: 'tags',
|
||||
searchable: true,
|
||||
orderable: false,
|
||||
defaultContent: '',
|
||||
className: 'none'
|
||||
},
|
||||
{
|
||||
title: lang.active,
|
||||
data: 'active',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
responsivePriority: 6,
|
||||
render: function (data, type) {
|
||||
@@ -613,6 +638,8 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 5,
|
||||
defaultContent: ''
|
||||
@@ -844,7 +871,7 @@ jQuery(function($){
|
||||
var table = $('#mailbox_table').DataTable({
|
||||
responsive: true,
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
serverSide: true,
|
||||
stateSave: true,
|
||||
pageLength: pagination_size,
|
||||
dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" +
|
||||
@@ -853,13 +880,12 @@ jQuery(function($){
|
||||
language: lang_datatables,
|
||||
initComplete: function(settings, json){
|
||||
hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table');
|
||||
filterByDomain(json, 8, table);
|
||||
},
|
||||
ajax: {
|
||||
type: "GET",
|
||||
url: "/api/v1/get/mailbox/reduced",
|
||||
url: "/api/v1/get/mailbox/datatables",
|
||||
dataSrc: function(json){
|
||||
$.each(json, function (i, item) {
|
||||
$.each(json.data, function (i, item) {
|
||||
item.quota = {
|
||||
sortBy: item.quota_used,
|
||||
value: item.quota
|
||||
@@ -945,7 +971,7 @@ jQuery(function($){
|
||||
}
|
||||
});
|
||||
|
||||
return json;
|
||||
return json.data;
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
@@ -975,13 +1001,14 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.domain_quota,
|
||||
data: 'quota.value',
|
||||
searchable: false,
|
||||
responsivePriority: 8,
|
||||
defaultContent: '',
|
||||
orderData: 23
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang.last_mail_login,
|
||||
data: 'last_mail_login',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
responsivePriority: 7,
|
||||
render: function (data, type) {
|
||||
@@ -994,15 +1021,16 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.last_pw_change,
|
||||
data: 'last_pw_change',
|
||||
searchable: false,
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: lang.in_use,
|
||||
data: 'in_use.value',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
responsivePriority: 9,
|
||||
className: 'dt-data-w100',
|
||||
orderData: 24
|
||||
className: 'dt-data-w100'
|
||||
},
|
||||
{
|
||||
title: lang.fname,
|
||||
@@ -1067,6 +1095,7 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.msg_num,
|
||||
data: 'messages',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
responsivePriority: 5
|
||||
},
|
||||
@@ -1085,12 +1114,14 @@ jQuery(function($){
|
||||
{
|
||||
title: 'Tags',
|
||||
data: 'tags',
|
||||
searchable: true,
|
||||
defaultContent: '',
|
||||
className: 'none'
|
||||
},
|
||||
{
|
||||
title: lang.active,
|
||||
data: 'active',
|
||||
searchable: false,
|
||||
defaultContent: '',
|
||||
responsivePriority: 4,
|
||||
render: function (data, type) {
|
||||
@@ -1100,22 +1131,12 @@ jQuery(function($){
|
||||
{
|
||||
title: lang.action,
|
||||
data: 'action',
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right',
|
||||
responsivePriority: 6,
|
||||
defaultContent: ''
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
data: 'quota.sortBy',
|
||||
defaultContent: '',
|
||||
className: "d-none"
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
data: 'in_use.sortBy',
|
||||
defaultContent: '',
|
||||
className: "d-none"
|
||||
},
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
@@ -676,5 +676,5 @@ jQuery(function($){
|
||||
onVisible("[id^=wl_policy_mailbox_table]", () => draw_wl_policy_mailbox_table());
|
||||
onVisible("[id^=sync_job_table]", () => draw_sync_job_table());
|
||||
onVisible("[id^=app_passwd_table]", () => draw_app_passwd_table());
|
||||
last_logins('get');
|
||||
onVisible("[id^=recent-logins]", () => last_logins('get'));
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ function api_log($_data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = json_decode($value, true);
|
||||
$value = json_decode($value, true);
|
||||
if ($value) {
|
||||
if (is_array($value)) unset($value["csrf_token"]);
|
||||
foreach ($value as $key => &$val) {
|
||||
@@ -23,7 +23,7 @@ function api_log($_data) {
|
||||
$val = '*';
|
||||
}
|
||||
}
|
||||
$value = json_encode($value);
|
||||
$value = json_encode($value);
|
||||
}
|
||||
$data_var[] = $data . "='" . $value . "'";
|
||||
}
|
||||
@@ -44,7 +44,13 @@ function api_log($_data) {
|
||||
'msg' => 'Redis: '.$e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Block requests not intended for direct API use by checking the 'Sec-Fetch-Dest' header.
|
||||
if (isset($_SERVER['HTTP_SEC_FETCH_DEST']) && $_SERVER['HTTP_SEC_FETCH_DEST'] !== 'empty') {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_GET['query'])) {
|
||||
@@ -178,12 +184,12 @@ if (isset($_GET['query'])) {
|
||||
// parse post data
|
||||
$post = trim(file_get_contents('php://input'));
|
||||
if ($post) $post = json_decode($post);
|
||||
|
||||
|
||||
// process registration data from authenticator
|
||||
try {
|
||||
// decode base64 strings
|
||||
$clientDataJSON = base64_decode($post->clientDataJSON);
|
||||
$attestationObject = base64_decode($post->attestationObject);
|
||||
$attestationObject = base64_decode($post->attestationObject);
|
||||
|
||||
// processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true)
|
||||
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true);
|
||||
@@ -250,7 +256,7 @@ if (isset($_GET['query'])) {
|
||||
default:
|
||||
process_add_return(mailbox('add', 'domain', $attr));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "resource":
|
||||
process_add_return(mailbox('add', 'resource', $attr));
|
||||
@@ -470,7 +476,7 @@ if (isset($_GET['query'])) {
|
||||
// false, if only internal is allowed
|
||||
// null, if internal and cross-platform is allowed
|
||||
$createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, false, $GLOBALS['WEBAUTHN_UV_FLAG_REGISTER'], null, $excludeCredentialIds);
|
||||
|
||||
|
||||
print(json_encode($createArgs));
|
||||
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||
return;
|
||||
@@ -533,9 +539,50 @@ if (isset($_GET['query'])) {
|
||||
|
||||
case "domain":
|
||||
switch ($object) {
|
||||
case "datatables":
|
||||
$table = ['domain', 'd'];
|
||||
$primaryKey = 'domain';
|
||||
$columns = [
|
||||
['db' => 'domain', 'dt' => 2],
|
||||
['db' => 'aliases', 'dt' => 3, 'order_subquery' => "SELECT COUNT(*) FROM `alias` WHERE (`domain`= `d`.`domain` OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = `d`.`domain`)) AND `address` NOT IN (SELECT `username` FROM `mailbox`)"],
|
||||
['db' => 'mailboxes', 'dt' => 4, 'order_subquery' => "SELECT COUNT(*) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"],
|
||||
['db' => 'quota', 'dt' => 5, 'order_subquery' => "SELECT COALESCE(SUM(`mailbox`.`quota`), 0) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"],
|
||||
['db' => 'stats', 'dt' => 6, 'dummy' => true, 'order_subquery' => "SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` IN (SELECT `username` FROM `mailbox` WHERE `domain` = `d`.`domain`)"],
|
||||
['db' => 'defquota', 'dt' => 7],
|
||||
['db' => 'maxquota', 'dt' => 8],
|
||||
['db' => 'backupmx', 'dt' => 10],
|
||||
['db' => 'tags', 'dt' => 14, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_domain` AS `td` ON `td`.`domain` = `d`.`domain`', 'where_column' => '`td`.`tag_name`']],
|
||||
['db' => 'active', 'dt' => 15],
|
||||
];
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php';
|
||||
global $pdo;
|
||||
if($_SESSION['mailcow_cc_role'] === 'admin') {
|
||||
$data = SSP::simple($_GET, $pdo, $table, $primaryKey, $columns);
|
||||
} elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') {
|
||||
$data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns,
|
||||
'INNER JOIN domain_admins as da ON da.domain = d.domain',
|
||||
[
|
||||
'condition' => 'da.active = 1 and da.username = :username',
|
||||
'bindings' => ['username' => $_SESSION['mailcow_cc_username']]
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($data['data'])) {
|
||||
$domainsData = [];
|
||||
foreach ($data['data'] as $domain) {
|
||||
if ($details = mailbox('get', 'domain_details', $domain[2])) {
|
||||
$domainsData[] = $details;
|
||||
}
|
||||
}
|
||||
$data['data'] = $domainsData;
|
||||
}
|
||||
|
||||
process_get_return($data);
|
||||
break;
|
||||
case "all":
|
||||
$tags = null;
|
||||
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
||||
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
||||
$tags = explode(',', $_GET['tags']);
|
||||
|
||||
$domains = mailbox('get', 'domains', null, $tags);
|
||||
@@ -1021,10 +1068,49 @@ if (isset($_GET['query'])) {
|
||||
break;
|
||||
case "mailbox":
|
||||
switch ($object) {
|
||||
case "datatables":
|
||||
$table = ['mailbox', 'm'];
|
||||
$primaryKey = 'username';
|
||||
$columns = [
|
||||
['db' => 'username', 'dt' => 2],
|
||||
['db' => 'quota', 'dt' => 3],
|
||||
['db' => 'last_mail_login', 'dt' => 4, 'dummy' => true, 'order_subquery' => "SELECT MAX(`datetime`) FROM `sasl_log` WHERE `service` != 'SSO' AND `username` = `m`.`username`"],
|
||||
['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' => 'messages', 'dt' => 17, '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]
|
||||
];
|
||||
|
||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php';
|
||||
global $pdo;
|
||||
if($_SESSION['mailcow_cc_role'] === 'admin') {
|
||||
$data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, null, "(`m`.`kind` = '' OR `m`.`kind` = NULL)");
|
||||
} elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') {
|
||||
$data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns,
|
||||
'INNER JOIN domain_admins as da ON da.domain = m.domain',
|
||||
[
|
||||
'condition' => "(`m`.`kind` = '' OR `m`.`kind` = NULL) AND `da`.`active` = 1 AND `da`.`username` = :username",
|
||||
'bindings' => ['username' => $_SESSION['mailcow_cc_username']]
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($data['data'])) {
|
||||
$mailboxData = [];
|
||||
foreach ($data['data'] as $mailbox) {
|
||||
if ($details = mailbox('get', 'mailbox_details', $mailbox[2])) {
|
||||
$mailboxData[] = $details;
|
||||
}
|
||||
}
|
||||
$data['data'] = $mailboxData;
|
||||
}
|
||||
|
||||
process_get_return($data);
|
||||
break;
|
||||
case "all":
|
||||
case "reduced":
|
||||
$tags = null;
|
||||
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
||||
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
||||
$tags = explode(',', $_GET['tags']);
|
||||
|
||||
if (empty($extra)) $domains = mailbox('get', 'domains');
|
||||
@@ -1058,7 +1144,7 @@ if (isset($_GET['query'])) {
|
||||
break;
|
||||
default:
|
||||
$tags = null;
|
||||
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
||||
if (isset($_GET['tags']) && $_GET['tags'] != '')
|
||||
$tags = explode(',', $_GET['tags']);
|
||||
|
||||
if ($tags === null) {
|
||||
@@ -1068,7 +1154,7 @@ if (isset($_GET['query'])) {
|
||||
$mailboxes = mailbox('get', 'mailboxes', $object, $tags);
|
||||
if (is_array($mailboxes)) {
|
||||
foreach ($mailboxes as $mailbox) {
|
||||
if ($details = mailbox('get', 'mailbox_details', $mailbox))
|
||||
if ($details = mailbox('get', 'mailbox_details', $mailbox))
|
||||
$data[] = $details;
|
||||
}
|
||||
}
|
||||
@@ -1571,15 +1657,15 @@ if (isset($_GET['query'])) {
|
||||
'solr_size' => $solr_size,
|
||||
'solr_documents' => $solr_documents
|
||||
));
|
||||
break;
|
||||
break;
|
||||
case "host":
|
||||
if (!$extra){
|
||||
$stats = docker("host_stats");
|
||||
echo json_encode($stats);
|
||||
}
|
||||
}
|
||||
else if ($extra == "ip") {
|
||||
// get public ips
|
||||
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_POST, 0);
|
||||
@@ -1887,7 +1973,6 @@ if (isset($_GET['query'])) {
|
||||
case "quota_notification_bcc":
|
||||
process_edit_return(quota_notification_bcc('edit', $attr));
|
||||
break;
|
||||
break;
|
||||
case "mailq":
|
||||
process_edit_return(mailq('edit', array_merge(array('qid' => $items), $attr)));
|
||||
break;
|
||||
@@ -1983,6 +2068,9 @@ if (isset($_GET['query'])) {
|
||||
case "cors":
|
||||
process_edit_return(cors('edit', $attr));
|
||||
break;
|
||||
case "reset-password-notification":
|
||||
process_edit_return(reset_password('edit_notification', $attr));
|
||||
break;
|
||||
// return no route found if no case is matched
|
||||
default:
|
||||
http_response_code(404);
|
||||
@@ -2003,7 +2091,7 @@ if (isset($_GET['query'])) {
|
||||
exit();
|
||||
}
|
||||
}
|
||||
if ($_SESSION['mailcow_cc_api'] === true) {
|
||||
if (array_key_exists('mailcow_cc_api', $_SESSION) && $_SESSION['mailcow_cc_api'] === true) {
|
||||
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
|
||||
unset($_SESSION['return']);
|
||||
}
|
||||
|
||||
@@ -547,7 +547,7 @@
|
||||
"dns_records": "DNS záznamy",
|
||||
"dns_records_24hours": "Upozornění: Změnám v systému DNS může trvat až 24 hodin, než se zde správně zobrazí jejich aktuální stav. Můžete zde snadno zjistit, jak nastavit DNS záznamy a zda jsou všechny záznamy správně uloženy.",
|
||||
"dns_records_data": "Správný záznam",
|
||||
"dns_records_docs": "Přečtěte si prosím <a target=\"_blank\" href=\"https://docs.mailcow.email/prerequisite/prerequisite-dns/\">dokumentaci</a>.",
|
||||
"dns_records_docs": "Přečtěte si prosím <a target=\"_blank\" href=\"https://docs.mailcow.email/getstarted/prerequisite-dns\">dokumentaci</a>.",
|
||||
"dns_records_name": "Název",
|
||||
"dns_records_status": "Současný stav",
|
||||
"dns_records_type": "Typ",
|
||||
|
||||
@@ -459,7 +459,7 @@
|
||||
"cname_from_a": "Værdi afledt af A / AAAA-post. Dette understøttes, så længe posten peger på den korrekte ressource.",
|
||||
"dns_records": "DNS-poster",
|
||||
"dns_records_24hours": "Bemærk, at ændringer, der foretages i DNS, kan tage op til 24 timer for at få deres aktuelle status korrekt reflekteret på denne side. Det er beregnet som en måde for dig let at se, hvordan du konfigurerer dine DNS-poster og kontrollere, om alle dine poster er korrekt gemt i DNS.",
|
||||
"dns_records_docs": "Se også <a target=\"_blank\" href=\"https://docs.mailcow.email/prerequisite/prerequisite-dns/\">dokumentationen</a>.",
|
||||
"dns_records_docs": "Se også <a target=\"_blank\" href=\"https://docs.mailcow.email/getstarted/prerequisite-dns\">dokumentationen</a>.",
|
||||
"dns_records_data": "Korrekte data",
|
||||
"dns_records_name": "Navn",
|
||||
"dns_records_status": "Nuværende tilstand",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user