Compare commits

..

208 Commits

Author SHA1 Message Date
DerLinkman
37beed6ad9 update FUNDING.yml 2024-08-26 09:56:49 +02:00
DerLinkman
75f18df143 Revert "Before update on 2024-08-20_14_22_10"
This reverts commit 89398c4726.
2024-08-23 09:54:10 +02:00
DerLinkman
89398c4726 Before update on 2024-08-20_14_22_10 2024-08-20 14:22:55 +02:00
DerLinkman
8971b11c49 Merge branch 'staging' 2024-08-20 14:08:57 +02:00
Hassan A Hashim
bb7fd483f7 Fix: Escape a ' character in update.sh (#6034) 2024-08-20 14:08:08 +02:00
Niklas Meyer
439a936fd8 Merge pull request #6033 from mailcow/staging
2024-08a
2024-08-20 13:44:51 +02:00
Délano
567ebbc324 Pushover/Quarantine utf 8 fix - fixes #6028 (#6031)
* Decode rspamd-subject for pushover notifications

Fixes #6028

* Apply iconv_mime_decode to the quarantine function as well
This might contain utf-8 encoded text as well

* Moved the iconv_mime_decode "fix" back to pipe.php
2024-08-20 13:39:20 +02:00
Hassan A Hashim
f9a7712025 Replace weird character to the correct ' (#6029)
* Replace weird character to the correct `'`

* Replace final weird character, just found.
2024-08-20 08:08:34 +02:00
Hassan A Hashim
3d62869664 Fix: bash variables are not quoted (#6022)
* Fix: Double quote variables to prevent word splitting

* Fix `update.sh`: Double quote to prevent word splitting

* Refactor: Remove unnecessary white-spaces.
2024-08-19 15:47:55 +02:00
Niklas Meyer
b70bcd36fb containers: use mariadb-admin instead of deprecated mysqladmin (#6026)
* dockerfiles: use mariadb-admin instead of deprecated mysqladmin command

* compose: bump compose tags
2024-08-19 11:33:28 +02:00
Niklas Meyer
cb50d08605 dovecot: added timeout option when sa-rules cannot be downloaded (#6025)
* dovecot: added timeout option when sa-rules cannot be downloaded

* dovecot: changed sa-rules exit code to 0 to allow dovecot to start afterwards
2024-08-19 11:08:13 +02:00
Hassan A Hashim
f3da8bb85f Refactor/Change Dockerfiles cmd from shell to exec form (#6019)
* Update `dockerapi/Dockerfile` CMD from shell to exec format

* Update `postfix/Dockerfile` CMD from shell to exec format

* Update `sogo/Dockerfile` CMD from shell to exec format

* Update `unbound/Dockerfile` CMD from shell to exec format

* Update `watchdog/Dockerfile` CMD from shell to exec format
2024-08-19 10:42:11 +02:00
Niklas Meyer
12e4d639f0 Merge pull request #6016 from jkrgr0/fix/ParseDockerVersion 2024-08-16 10:50:04 +02:00
Janek
eb3f88fc91 fix: 🚑 Fixed version parsing of docker
Only the first result (the major version) is relevant

Closes #6015
2024-08-16 08:47:03 +02:00
Niklas Meyer
9a729d89bf Merge pull request #6012 from mailcow/staging
2024-08
2024-08-15 14:46:50 +02:00
Niklas Meyer
74b4097ee0 Merge pull request #6011 from mailcow/gh/add_pull_request_template
.github: Add pull_request_template.md
2024-08-15 11:51:37 +02:00
DerLinkman
e00d0d5f8d Updated contributing.md 2024-08-15 11:32:28 +02:00
DerLinkman
c5e399ebc2 .github: Add pull_request_template.md 2024-08-15 11:09:37 +02:00
FreddleSpl0it
cb9ca772b1 Merge pull request #6009 from mailcow/feat/pw-reset
[Web] Add a forgot password flow
2024-08-15 11:06:30 +02:00
Niklas Meyer
162f05ccda Merge pull request #6007 from mailcow/revert-5945-master
Revert "Don't expose SMTP/IMAP if announced "not provided" via SRV"
2024-08-15 09:51:19 +02:00
Niklas Meyer
6c97c4f372 Revert "Don't expose SMTP/IMAP if announced "not provided" via SRV" 2024-08-15 09:50:36 +02:00
Niklas Meyer
6d4fcacd83 Merge pull request #6006 from mailcow/fix/issue-5986
flatcurve-fts: limit tokenizers size in e-mail adress
2024-08-14 10:06:17 +02:00
DerLinkman
1994f706c0 dovecot: optimized dockerfile syntax 2024-08-14 10:03:42 +02:00
DerLinkman
e34afd3fdd flatcurve-fts: limit tokenizers for email adresses 2024-08-14 10:02:59 +02:00
DerLinkman
a6f71faf46 github-actions: compacted auto nightly pr 2024-08-13 16:07:09 +02:00
Niklas Meyer
b26ccc2019 unbound: fix healthcheck logging + added fail tolerance to checks (#6004)
* unbound: fix healthcheck logging to stdout + rewrote healthcheck logic

* compose: bump unbound tag

* unbound: fixed healthcheck logic
2024-08-13 15:59:57 +02:00
Niklas Meyer
b1c1e403d2 sogo: update to 5.11.0 + Rebase on Bookworm (#6002)
* sogo: update to 5.11.0

* compose: bump sogo compose tag
2024-08-13 09:43:59 +02:00
Dmitriy Alekseev
8753ea2be6 [Rspamd] Fix bayes config (#6000)
* [Rspamd] Fix bayes config

Add hint about classifier name, and add missing learn_condition

* Update statistic.conf
2024-08-12 10:05:08 +02:00
milkmaker
9fee568082 Translations update from Weblate (#5999)
* [Web] Updated lang.ru-ru.json

Co-authored-by: Oleksii Kruhlenko <a.kruglenko@gmail.com>

* [Web] Updated lang.uk-ua.json

Co-authored-by: Oleksii Kruhlenko <a.kruglenko@gmail.com>

---------

Co-authored-by: Oleksii Kruhlenko <a.kruglenko@gmail.com>
2024-08-10 20:44:40 +02:00
DerLinkman
294a406b91 fix: disabled api call to solr in ui when mailbox deleted but using flatcurve 2024-08-08 09:25:52 +02:00
Niklas Meyer
8b933f1967 Merge pull request #5934 from muhlba91/fix/restore-docker 2024-08-08 08:47:12 +02:00
Kitof
824a473fea ofelia: limit scope to mailcow project (#5776)
* Filter to limit ofelia scope

See https://github.com/mailcow/mailcow-dockerized/issues/5775

* compose: added ${COMPOSE_PROJECT_NAME} ENV to ofelia container
2024-08-08 08:42:50 +02:00
milkmaker
7f790c5360 [Web] Updated lang.si-si.json (#5995)
Co-authored-by: gomiunik <boris@gomiunik.net>
2024-08-07 18:39:38 +02:00
DerLinkman
52431a3942 compose: bump watchdog image 2024-08-07 14:50:12 +02:00
Niklas Meyer
8017394e9d Merge pull request #5773 from mrclschstr/staging
[Fix] Watchdog: escape subject and body for webhooks
2024-08-07 14:48:11 +02:00
Niklas Meyer
76194be7dd Merge pull request #5991 from h3ssan/refactor/update-script-help-exit
Refactor: `update.sh` script with `--help` should exit with status code 0
2024-08-07 14:03:32 +02:00
Niklas Meyer
3b23afa0ff Merge pull request #5661 from mailcow/feat/rspamd-3.8
rspamd: upgrade to rspamd 3.9.1
2024-08-07 14:01:39 +02:00
DerLinkman
6e00d653ce compose: bumped rspamd tag 2024-08-07 14:00:04 +02:00
DerLinkman
b6c036496d rspamd: fixed dqs rbl insertion handling 2024-08-07 14:00:04 +02:00
DerLinkman
5d7c9b20bc rspamd: upgrade to 3.9.1 + upgrade to bookworm 2024-08-07 14:00:04 +02:00
DerLinkman
4b400eadb1 rspamd: Added DQS RBLs when key is set 2024-08-07 13:59:26 +02:00
Niklas Meyer
ab2abda8cc Merge pull request #5967 from Doozy134/fix/curl-hostname
fix: change internal urls for containers using curl on alpine
2024-08-07 13:58:11 +02:00
Hassan A Hashim
2fe21e9641 Refactor: update.sh script with --help should exit with status code 0 2024-08-07 14:57:36 +03:00
Niklas Meyer
b7ed6982d8 Merge pull request #5945 from SailReal/master
Don't expose SMTP/IMAP if announced "not provided" via SRV
2024-08-07 13:51:10 +02:00
Niklas Meyer
fd927853cb Merge pull request #5990 from h3ssan/fix/dockerfile-label-fix
Fix `LABEL` in Dockerfile, should be key=value
2024-08-07 13:49:07 +02:00
Niklas Meyer
c48f4f4ab8 Merge pull request #5989 from h3ssan/fix/update-script-procceding-typo
Fix typo in `update.sh`: word Proceeding
2024-08-07 13:47:57 +02:00
DerLinkman
a4c006828e compose: bump container tags 2024-08-07 09:51:47 +02:00
DerLinkman
b56291f62b adapt scheme to affected curl containers (dirty way... but workaround) 2024-08-07 09:50:57 +02:00
Kasim
0cdf7647c4 Include COMPOSE_PROJECT_NAME in Nginx url 2024-08-07 09:40:08 +02:00
Kasim
8fe1cc4961 change nginx address
#5962
2024-08-07 09:40:04 +02:00
Niklas Meyer
bf050f17c4 Merge pull request #5987 from h3ssan/fix/validate-mailcow-conf-before-source
Bug Fix: Check `mailcow.conf` exists before work with it
2024-08-07 09:33:16 +02:00
Hassan A Hashim
edd85dea8d Fix LABEL in Dockerfile, should be key=value
Refering to the [Official Docker Docs](`https://docs.docker.com/reference/dockerfile/#label`), clearly said the format of LABEL is `LABEL <key>=<value> <key>=<value> <key>=<value> ...`.
2024-08-06 22:44:59 +03:00
Hassan A Hashim
3bf90c1f73 Fix typo for word Potential in update.sh file. 2024-08-06 21:22:30 +03:00
Hassan A Hashim
292306b191 Fix typos and English grammar in update.sh
German is different in using upper-case than English lol
2024-08-06 21:12:20 +03:00
Hassan A Hashim
b3e0a66222 Fix typo: receiving updates from an unsupported branch 2024-08-06 21:03:17 +03:00
Hassan A Hashim
e994cf4d05 Fix typo in update.sh: Proceeding 2024-08-06 20:38:18 +03:00
Hassan A Hashim
cc0dc2eae0 Add color-coded error message for missing mailcow.conf 2024-08-06 17:51:46 +03:00
DerLinkman
a001a0584f update.sh: fix text for min. docker ver 2024-08-06 16:21:28 +02:00
DerLinkman
926af87cfb scripts: adding docker version check to align to docs (24.X) 2024-08-06 16:20:28 +02:00
Hassan A Hashim
b0339372b5 Check mailcow.conf exists before source it 2024-08-06 17:12:54 +03:00
Niklas Meyer
e398cb91e9 Merge pull request #5985 from mailcow/feat/improve-sieve-parser
ui: added enotify and mime as valid options for ui
2024-08-06 15:36:00 +02:00
DerLinkman
6ee0303b0f ui: added enotify and mime as valid options for ui 2024-08-06 15:33:40 +02:00
Niklas Meyer
68616c2d57 Merge pull request #5972 from rallisf1/dovecot-folders-greek
Greek names of dovecot folders
2024-08-06 12:28:23 +02:00
Niklas Meyer
f8de520d29 Merge pull request #5983 from mailcow/fix/sieve-compiling
dovecot: fix precompiling of sieve scripts
2024-08-06 12:27:41 +02:00
Niklas Meyer
10077ece31 Merge pull request #5804 from Ayowel/feat/unattended-install
Allow prompt-less install on low-resource systems
2024-08-06 12:26:51 +02:00
DerLinkman
c918726143 dovecot: fix precompiling of sieve scripts 2024-08-06 12:04:04 +02:00
milkmaker
3885b07a99 [Web] Updated lang.nb-no.json (#5980)
Co-authored-by: Christer Solstrand Johannessen <csjoh@users.noreply.translate.mailcow.email>
2024-08-05 19:36:55 +02:00
FreddleSpl0it
fcf27d640d Merge pull request #5979 from mailcow/staging
2024-07
2024-08-05 08:55:59 +02:00
Marcel Schuster
82fde23cc1 Bump watchdog to v2.03 2024-08-01 19:14:29 +02:00
FreddleSpl0it
cbca306fc1 Merge pull request #5976 from mailcow/fix/get-tfa
2024-07 fixes
2024-08-01 11:04:04 +02:00
Niklas Meyer
6a8986fe4f Merge pull request #5974 from mailcow:update/postscreen_access.cidr
[Postfix] update postscreen_access.cidr
2024-08-01 09:06:42 +02:00
milkmaker
ff34eb12e2 update postscreen_access.cidr 2024-08-01 00:16:46 +00:00
FreddleSpl0it
fbecd60e56 [Web] add new pw_reset acl to mailbox templates 2024-07-31 09:23:53 +02:00
FreddleSpl0it
c37bf0bb32 [Web] improve error handling for user password resets 2024-07-31 09:22:52 +02:00
FreddleSpl0it
2208d7e6fb [Web] add function to reset user passwords 2024-07-30 14:46:08 +02:00
John Rallis
e426c3a7e7 Greek names of dovecot folders
Names taken from MSO 2016
2024-07-29 16:46:03 +03:00
Niklas Meyer
03fccb28e9 Merge pull request #5971 from mailcow/dragoangel-patch-1
Do not add MAILCOW_WHITE on failed DMARC
2024-07-29 09:51:16 +02:00
Dmitriy Alekseev
8fbfd99dd6 Update composites.conf 2024-07-28 13:20:24 +02:00
Dmitriy Alekseev
7f7a869678 Do not add MAILCOW_WHITE on failed DMARC 2024-07-28 13:19:03 +02:00
DerLinkman
73257151c4 postfix: remove forced helo restrictions from master.cf 2024-07-24 15:29:28 +02:00
FreddleSpl0it
efb2572f0f [Web] escapeHtml in relayhosts table 2024-07-22 15:05:43 +02:00
FreddleSpl0it
66aa28b5de [Web] escapeHtml in api_log table 2024-07-22 15:04:29 +02:00
Niklas Meyer
987a027339 Merge pull request #5957 from mailcow/staging
2024-06c
2024-07-12 16:25:01 +02:00
Niklas Meyer
eea81e21f6 Revert "php: Rebase on Debian 12" (#5956)
* Revert "php: Rebase on Debian 12 (#5951)"

This reverts commit 9b478b3859.

* Revert all before "the storm" in php world
2024-07-12 16:21:53 +02:00
Niklas Meyer
a689109f44 Merge pull request #5955 from mailcow/revert-5875-staging_cml
Revert "Update debug.twig to include a link to the git project URL for the mailcow version tag"
2024-07-12 16:05:01 +02:00
Niklas Meyer
58c0a46459 Revert "Update debug.twig to include a link to the git project URL for the mailcow version tag" 2024-07-12 16:04:19 +02:00
Niklas Meyer
2dbe8bf4ca Merge pull request #5952 from mailcow/staging
2024-06b
2024-07-12 10:17:46 +02:00
Niklas Meyer
ef7ec06947 Merge pull request #5930 from mailcow/update/postscreen_access.cidr
[Postfix] update postscreen_access.cidr
2024-07-12 10:16:44 +02:00
DerLinkman
fc7ea7a247 web: remove WIP notice for ARM64 from ui 2024-07-12 10:15:06 +02:00
Niklas Meyer
9b478b3859 php: Rebase on Debian 12 (#5951)
* php: rebuild on debian 12

* Restored one build dockerfile

* cleanup Dockerfile
2024-07-12 09:40:10 +02:00
Julian Raufelder
384e5a2e64 Don't expose SMTP/IMAP if announced "not provided" via SRV
Fixes #5944
2024-07-09 19:57:32 +02:00
Niklas Meyer
aadeeb0df3 Merge pull request #5634 from torzech/proper-threads-regex
Enhanced regular expression for THREADS parameter
2024-07-09 10:10:42 +02:00
FreddleSpl0it
f33d82ffc1 [Web] use correct user to fetch TFA authenticators 2024-07-03 15:50:17 +02:00
Daniel Muehlbachler-Pietrzykowski
ffeeb179e1 restore: remove tty requirement from restore process to allow for automated restores 2024-07-03 10:53:37 +02:00
milkmaker
8e2d3a6db5 update postscreen_access.cidr 2024-07-01 00:16:56 +00:00
Niklas Meyer
70126e1f0c Merge pull request #5926 from mailcow/staging
🌙🐄 Moone Update 2024 | Revision A
2024-06-27 18:07:19 +02:00
Niklas Meyer
b9ae174a6a Merge pull request #5925 from mailcow/revert-5912-weblate-translated
Revert "Translations update from Weblate"
2024-06-27 18:04:41 +02:00
Niklas Meyer
9715c57314 Revert "Translations update from Weblate (#5912)"
This reverts commit 1af9c21a50.
2024-06-27 18:03:01 +02:00
Niklas Meyer
b9f8959d92 Update CONTRIBUTING.md
Added language terms
2024-06-27 13:11:19 +02:00
Niklas Meyer
9c814cc182 Merge pull request #5922 from mailcow/staging
2024-06
2024-06-27 11:15:53 +02:00
Niklas Meyer
cf6594220c dovecot: add Flatcurve FTS Engine as EXPERIMENTAL (#5920)
* dovecot: experimental added flatcurve backend + switch

* dovecot: bump docker image
2024-06-26 11:28:18 +02:00
Niklas Meyer
2cf952eb36 [Postfix] Upgrade to Deb12 + PF to 3.7.10 & Drop TLS 1.0/1.1 per default (#5635)
* postfix: removed TLS1.0/1.1 support (natively)

* postfix: upgrade to deb12 + pf to 3.7.9

* compose: increased postfix tag

* postfix: shortened TLS syntax with new format of 3.6+
2024-06-26 10:44:07 +02:00
DerLinkman
6fc86dd7d3 acme: corrected acme-tiny download path 2024-06-24 10:00:30 +02:00
DerLinkman
bf13af9691 increased rspamd image tag 2024-06-24 10:00:16 +02:00
milkmaker
1af9c21a50 Translations update from Weblate (#5912)
* [Web] Updated lang.ca-es.json

[Web] Updated lang.ca-es.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.cs-cz.json

[Web] Updated lang.cs-cz.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.da-dk.json

[Web] Updated lang.da-dk.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.es-es.json

[Web] Updated lang.es-es.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.fi-fi.json

[Web] Updated lang.fi-fi.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.fr-fr.json

[Web] Updated lang.fr-fr.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.hu-hu.json

[Web] Updated lang.hu-hu.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.ro-ro.json

[Web] Updated lang.ro-ro.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.it-it.json

[Web] Updated lang.it-it.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.lv-lv.json

[Web] Updated lang.lv-lv.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.ko-kr.json

[Web] Updated lang.ko-kr.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.nl-nl.json

[Web] Updated lang.nl-nl.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.ru-ru.json

[Web] Updated lang.ru-ru.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.sk-sk.json

[Web] Updated lang.sk-sk.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.sv-se.json

[Web] Updated lang.sv-se.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.zh-cn.json

[Web] Updated lang.zh-cn.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.uk-ua.json

Co-authored-by: Anonymous <noreply@weblate.org>

* [Web] Updated lang.zh-tw.json

[Web] Updated lang.zh-tw.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.pt-pt.json

[Web] Updated lang.pt-pt.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.tr-tr.json

[Web] Updated lang.tr-tr.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.si-si.json

[Web] Updated lang.si-si.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.gr-gr.json

[Web] Updated lang.gr-gr.json

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.nb-no.json

Co-authored-by: Anonymous <noreply@weblate.org>

* [Web] Updated lang.lt-lt.json

Co-authored-by: Anonymous <noreply@weblate.org>

---------

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Peter <magic@kthx.at>
2024-06-24 09:07:46 +02:00
realizelol
443941e687 [Rspamd] Delete overriding obsolete rspamd plugin (#5900)
* [Dockerfiles] rspamd: Delete COPY of metadata_exporter.lua plugin

* [Dockerfiles] rspamd: Delete metadata_exporter.lua plugin file

* Dockerfile: changed way of installing rspamd (granular version)

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2024-06-24 09:07:12 +02:00
renovate[bot]
527577b438 chore(deps): update docker/build-push-action action to v6 (#5910)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 17:38:21 +02:00
milkmaker
9daf2d80c0 Translations update from Weblate (#5908)
* [Web] Updated lang.fr-fr.json

Co-authored-by: Paul FERA <paulfera17@gmail.com>

* [Web] Updated lang.lv-lv.json

[Web] Updated lang.lv-lv.json

Co-authored-by: Deniss <mailcow@rigaden.me>
Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>

---------

Co-authored-by: Paul FERA <paulfera17@gmail.com>
Co-authored-by: Deniss <mailcow@rigaden.me>
Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
2024-06-16 19:21:46 +02:00
Daniel
38b0641742 Remove unnecessary log lines in Postfix's log (#5817)
* Update main.cf

In order to avoid unnecessary log lines, changed:

smtpd_discard_ehlo_keywords = chunking
to this one:

# The non-logging alternative:
smtpd_discard_ehlo_keywords = chunking, silent-discard

Update main.cf to remove unnecessary log lines in Postfix log
2024-06-10 14:51:55 +02:00
Niklas Meyer
f675af5bb0 Merge pull request #5902 from mailcow/feat/nextcloud-deprecation
nextcloud: add deprecation notice once script start
2024-06-10 14:33:53 +02:00
DerLinkman
533c4e7956 nextcloud: add deprecation notice once script start 2024-06-10 14:21:13 +02:00
Niklas Meyer
1b2c2c0037 Merge pull request #5690 from mailcow:renovate/nextcloud-server-28.x
chore(deps): update dependency nextcloud/server to v28.0.6
2024-06-10 13:57:09 +02:00
Niklas Meyer
97768494e1 Merge pull request #5880 from PierrePlt:fix/blocking-last-logins
Fix blocking last logins fetching
2024-06-10 12:40:43 +02:00
Lasagne
4a052da289 Add switch to skip fetching certificates auto{config,discover} subdomains (#5838)
* Add ACME_DONT_FETCH_CERTS_FOR_HTTP_SUBDOMAINS to acme.sh

* Add ACME_DONT_FETCH_CERTS_FOR_HTTP_SUBDOMAINS to docker-compose.yml

* Add ACME_DONT_FETCH_CERTS_FOR_HTTP_SUBDOMAINS to generate_config.sh

* Add ACME_DONT_FETCH_CERTS_FOR_HTTP_SUBDOMAINS to update.sh

* AUTODISCOVER_SAN instead of long string

default on,
default is fetching certs for auto{discover,conf}

* AUTODISCOVER_SAN instead of long string

also flipped

* AUTODISCOVER_SAN instead of long string

flipped default meaning

* fix explanation for AUTODISCOVER_SAN

* AUTODISCOVER_SAN instead of long string

and flipped meaning of the bool

* fix AUTODISCOVER_SAN explanation

* Merge branch 'mailcow:staging' into staging

* update.sh: corrected syntax for mailcow.conf insertion
2024-06-10 12:33:02 +02:00
Niklas Meyer
18d7a55b15 Merge pull request #5901 from mailcow:sorbs
Remove discontinued SORBS DNSBL
2024-06-10 12:18:43 +02:00
Michael Kuron
9ca2fb7ccf Remove discontinued SORBS DNSBL 2024-06-08 12:29:08 +02:00
Niklas Meyer
b4e8355827 Merge pull request #5845 from iamspido:patch-1
remove version from docker-compose.yml
2024-06-06 15:30:15 +02:00
DerLinkman
e0bde1c459 compose: removed all versions declarations (DEPRECATED) 2024-06-06 15:29:34 +02:00
Niklas Meyer
27c007ebd3 Merge pull request #5750 from DocFraggle:staging
Fix unbound healthcheck.sh to log all messages to logfile
2024-06-06 15:27:40 +02:00
Niklas Meyer
8f3ea09732 Merge pull request #5893 from mailcow/feat/base-os
os: updated all Alpine containers to 3.20
2024-06-05 13:10:10 +02:00
DerLinkman
af626d98d3 dovecot: fixed sa-rules download 2024-06-05 13:07:12 +02:00
Niklas Meyer
34b0574e56 Merge pull request #5886 from Thomas2500:patch-1
Switch IP2Country lookup backend to shortened version
2024-06-05 12:37:31 +02:00
Niklas Meyer
49d738809b Merge pull request #5863 from mailcow:update/postscreen_access.cidr
[Postfix] update postscreen_access.cidr
2024-06-05 12:32:56 +02:00
Niklas Meyer
2fa3a22eca Merge pull request #5875 from CallMeLeon167:staging_cml
Update debug.twig to include a link to the git project URL for the mailcow version tag
2024-06-05 12:31:33 +02:00
Niklas Meyer
dc5eb6f92e Merge pull request #5883 from mailcow:renovate/alpine-3.x
chore(deps): update alpine docker tag to v3.20
2024-06-05 12:27:28 +02:00
DerLinkman
ba8902f0b1 os: updated all Alpine containers to 3.20 2024-06-05 11:52:48 +02:00
milkmaker
11e9a77840 update postscreen_access.cidr 2024-06-01 00:15:03 +00:00
Thomas Bella
64cd7e74c5 Switch IP2Country lookup backend to shortened version
Improves performance of #5880
2024-05-28 20:29:05 +02:00
renovate[bot]
cac65d081e chore(deps): update dependency nextcloud/server to v28.0.6
Signed-off-by: milkmaker <milkmaker@mailcow.de>
2024-05-28 12:54:13 +00:00
renovate[bot]
e5ada994be Update alpine Docker tag to v3.20
Signed-off-by: milkmaker <milkmaker@mailcow.de>
2024-05-22 18:17:51 +00:00
Pierre Pelletier
6ba2459645 Fixed blocking last connection fetching 2024-05-18 11:38:41 +00:00
DerLinkman
58f63aad08 [UI] Corrected Sieve Preset 1 (Fixed Regex) 2024-05-13 15:02:41 +02:00
milkmaker
8a8687a63c [Web] Updated lang.zh-cn.json (#5876)
Co-authored-by: Koala Ng <tonghoil@hotmail.com>
2024-05-10 22:38:08 +02:00
Leon Schmidt
f7f93c360d fix formatting of the mailcow version tag link 2024-05-10 19:56:31 +02:00
Leon
c160e1f68e Update debug.twig 2024-05-10 15:57:36 +02:00
Leon Schmidt
47c08ab8d2 Update debug.twig target="_blank" attribute for the mailcow version tag link 2024-05-10 15:17:49 +02:00
Leon Schmidt
cd83ffbaa2 Update debug.twig to include a link to the git project URL for the mailcow version tag 2024-05-10 15:09:27 +02:00
milkmaker
e12981a821 [Web] Updated lang.zh-cn.json (#5873)
Co-authored-by: Koala Ng <tonghoil@hotmail.com>
2024-05-07 17:44:37 +02:00
Ramis
47fd1bb894 Update lang.ru-ru.json (#5865)
Update lang

Co-authored-by: Patrick Schult <75116288+FreddleSpl0it@users.noreply.github.com>
2024-05-03 14:05:43 +02:00
milkmaker
20582b6353 [Web] Updated lang.lv-lv.json (#5862)
Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
2024-04-30 18:28:09 +02:00
IamSpido
c8ff5387c0 remove version from docker-compose.yml
With docker version 25.05 the version 2.1 in docker-compose.yml will be obsolete.
docker-compose.yml: `version` is obsolete
2024-04-16 14:10:44 +02:00
Mitchell van Bijleveld
7cb138d515 Improve Dutch translation (#5840) 2024-04-11 21:36:45 +02:00
milkmaker
3dd4c45fab Translations update from Weblate (#5839)
* [Web] Updated lang.hu-hu.json

Co-authored-by: David Csillag <csillag.david.istvan@gmail.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.lv-lv.json

Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: David Csillag <csillag.david.istvan@gmail.com>
Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
2024-04-10 21:55:31 +02:00
polido
549539bec9 Update lang.pt-pt.json (#5832)
Co-authored-by: Patrick Schult <75116288+FreddleSpl0it@users.noreply.github.com>
2024-04-08 17:48:41 +02:00
milkmaker
e449cac464 Translations update from Weblate (#5835)
* [Web] Updated lang.fr-fr.json

Co-authored-by: Quiwy <github@quiwy.ninja>

* [Web] Updated lang.sv-se.json

Co-authored-by: André J <aj@nadox.se>

---------

Co-authored-by: Quiwy <github@quiwy.ninja>
Co-authored-by: André J <aj@nadox.se>
2024-04-08 17:47:43 +02:00
milkmaker
62e458f39b [Web] Updated lang.fr-fr.json (#5824)
Co-authored-by: Quiwy <github@quiwy.ninja>
2024-04-04 19:23:02 +02:00
FreddleSpl0it
b37caaf9e5 [Web] secure container_ctrl.php 2024-04-04 16:30:35 +02:00
FreddleSpl0it
7660ca89ae [Web] break loop if rspamd_map is valid 2024-04-04 16:29:58 +02:00
Patrick Schult
36b5cccd18 Merge pull request #5819 from mailcow/staging
2024-04
2024-04-04 08:50:58 +02:00
Patrick Schult
9decfa9c31 Merge pull request #5818 from mailcow/fix/web
[Web] fix exception handler and rspamd_maps function
2024-04-04 08:19:58 +02:00
FreddleSpl0it
3aee2b6cf5 [Web] use SEC_FETCH_DEST header instead of Referer to block api requests 2024-04-03 11:43:48 +02:00
Patrick Schult
17d797cee4 Merge pull request #5751 from mailcow/fix/rspamd-rewrite-ct
[Rspamd] milter update Content-Type and Content-Transfer-Encoding header
2024-04-03 10:49:21 +02:00
Patrick Schult
75550eeea3 Merge pull request #5812 from mailcow/limit-local-addrs
[Rspamd] Set local_addrs lo mailcow networks
2024-04-03 10:48:46 +02:00
FreddleSpl0it
0d09c86c12 [Web] fix invalid rspamd map check 2024-04-03 10:08:18 +02:00
FreddleSpl0it
2db8f482db [Web] escape html of alert messages 2024-04-03 10:07:36 +02:00
FreddleSpl0it
00d4b32a1b [Web] deny api calls from sogo 2024-04-03 10:06:43 +02:00
milkmaker
8a82bab1f3 [Web] Updated lang.tr-tr.json (#5815)
Co-authored-by: Uğurcan Albayrak <canalbayrakugur@gmail.com>
2024-04-02 18:04:30 +02:00
milkmaker
237a25e6b0 update postscreen_access.cidr (#5811) 2024-04-02 02:20:31 +02:00
milkmaker
5dc836671d [Web] Updated lang.tr-tr.json (#5813)
[Web] Updated lang.tr-tr.json

Co-authored-by: Uğurcan Albayrak <canalbayrakugur@gmail.com>
Co-authored-by: evrenkoksal <evrenkoksal@gmail.com>
2024-04-01 21:57:15 +02:00
Dmitriy Alekseev
26be1cb602 Set local_addrs in Rspamd 2024-04-01 11:28:06 +03:00
yvan-algoo
dc7a48cbf9 Update French translation (#5805)
* Fix some typo in French translation

* Fix typo error introduced in last commit

* Fixed another typo introduced in my first commit
2024-03-30 01:10:12 +01:00
milkmaker
52455be815 Translations update from Weblate (#5810)
* [Web] Updated lang.lt-lt.json

[Web] Updated lang.lt-lt.json

[Web] Updated lang.lt-lt.json

[Web] Updated lang.lt-lt.json

[Web] Updated lang.lt-lt.json

[Web] Updated lang.lt-lt.json

[Web] Added lang.lt-lt.json

Co-authored-by: Ari Archer <ari@ari.lt>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.lv-lv.json

[Web] Updated lang.lv-lv.json

[Web] Updated lang.lv-lv.json

[Web] Updated lang.lv-lv.json

Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
Co-authored-by: Edgars Počs <edgars.pocs@dna.lv>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.tr-tr.json

[Web] Updated lang.tr-tr.json

[Web] Updated lang.tr-tr.json

[Web] Updated lang.tr-tr.json

[Web] Updated lang.tr-tr.json

[Web] Updated lang.tr-tr.json

Co-authored-by: evrenkoksal <evrenkoksal@gmail.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* Add lt-lt in vars.inc.php

---------

Co-authored-by: Ari Archer <ari@ari.lt>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
Co-authored-by: Edgars Počs <edgars.pocs@dna.lv>
Co-authored-by: evrenkoksal <evrenkoksal@gmail.com>
2024-03-30 01:09:22 +01:00
Ayowel
5c851f2935 Allow prompt-less install on low-resource systems 2024-03-26 08:19:24 +01:00
Niklas Meyer
bbbdcfb625 Merge pull request #5743 from mailcow/fix-5742
Remove one GmbH in Dockerfiles
2024-03-20 09:37:45 +01:00
Niklas Meyer
b054a57e16 Merge pull request #5770 from mailcow/update/postscreen_access.cidr
[Postfix] update postscreen_access.cidr
2024-03-19 10:19:54 +01:00
aaadddfgh
fd73b3ad88 Update lang.zh-cn.json (#5789)
Change a better translation
2024-03-13 15:53:37 +01:00
milkmaker
8c0637b556 [Web] Updated lang.lv-lv.json (#5777)
Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
2024-03-05 17:57:55 +01:00
Marcel Schuster
914a8204d4 Watchdog: escape subject and body for webhooks 2024-03-01 23:07:05 +01:00
DerLinkman
d92ffe8fc7 helper: remove old SOGo repo to not break builds on ARM64 2024-03-01 11:41:11 +01:00
milkmaker
e0eb3a4f13 update postscreen_access.cidr 2024-03-01 00:14:54 +00:00
Niklas Meyer
1fb0060a73 Merge pull request #5765 from mailcow/feat/sogo-5.10
sogo: upgrade to 5.10.0
2024-02-27 08:22:19 +01:00
DerLinkman
d7430bf516 sogo: add new options to sogo.conf for update 5.10.0 2024-02-26 17:17:34 +01:00
DerLinkman
35f039a119 sogo: update to 5.10.0 2024-02-26 16:55:13 +01:00
milkmaker
79432a40d7 Translations update from Weblate (#5762)
* [Web] Updated lang.es-es.json

Co-authored-by: Fernando Dilland <fernandodilland@gmail.com>

* [Web] Updated lang.nb-no.json

Co-authored-by: Christer Solstrand Johannessen <csjoh@users.noreply.translate.mailcow.email>

---------

Co-authored-by: Fernando Dilland <fernandodilland@gmail.com>
Co-authored-by: Christer Solstrand Johannessen <csjoh@users.noreply.translate.mailcow.email>
2024-02-25 19:51:57 +01:00
FreddleSpl0it
98cdb95bc0 [Rspamd] milter update Content-Type and Content-Transfer-Encoding header after need_rewrite_ct 2024-02-19 11:20:19 +01:00
Hailer, Christian
02a55ce9db Fix unbound healthcheck.sh to log all messages to logfile 2024-02-19 09:26:29 +01:00
renovate[bot]
6f4720e1ea chore(deps): update thollander/actions-comment-pull-request action to v2.5.0 (#5747) 2024-02-17 11:42:30 +01:00
Peter
6a807b7799 Remove one GmbH 2024-02-15 17:43:01 +01:00
Patrick Schult
8d4ef147d2 Merge pull request #5741 from mailcow/staging
2024-02
2024-02-15 11:27:09 +01:00
milkmaker
8ed6217d1c Translations update from Weblate (#5740)
* [Web] Language file updated by 'Cleanup translation files' addon

[Web] Updated lang.it-it.json

ui: fixed broken Links to docs

ui: fix wrong docs links

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
Co-authored-by: Michele Caputo <michele@caputoweb.xyz>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Language file updated by 'Cleanup translation files' addon

Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
Co-authored-by: Michele Caputo <michele@caputoweb.xyz>
2024-02-14 20:11:51 +01:00
milkmaker
7dae4a976d Translations update from Weblate (#5732)
* ui: fix wrong docs links

* ui: fixed broken Links to docs

* [Web] Updated lang.nb-no.json

[Web] Updated lang.nb-no.json

[Web] Updated lang.nb-no.json

[Web] Added lang.nb-no.json

Co-authored-by: Christer Solstrand Johannessen <csjoh@users.noreply.translate.mailcow.email>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.fr-fr.json

[Web] Updated lang.fr-fr.json

Co-authored-by: Alix ANNERAUD <alix.anneraud@outlook.fr>
Co-authored-by: William Blondel <contact@williamblondel.fr>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.it-it.json

Co-authored-by: Michele Caputo <michele@caputoweb.xyz>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.zh-tw.json

Co-authored-by: BallBill <BallBill@users.noreply.translate.mailcow.email>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.pt-br.json

[Web] Updated lang.pt-br.json

[Web] Updated lang.pt-br.json

[Web] Updated lang.pt-br.json

Co-authored-by: Abner Santana <abnerss@outlook.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* Add Norwegian in vars.inc.php

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
Co-authored-by: Christer Solstrand Johannessen <csjoh@users.noreply.translate.mailcow.email>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: Alix ANNERAUD <alix.anneraud@outlook.fr>
Co-authored-by: William Blondel <contact@williamblondel.fr>
Co-authored-by: Michele Caputo <michele@caputoweb.xyz>
Co-authored-by: BallBill <BallBill@users.noreply.translate.mailcow.email>
Co-authored-by: Abner Santana <abnerss@outlook.com>
2024-02-14 20:09:10 +01:00
FreddleSpl0it
3b83949ba3 [Netfilter] Update to 1.58 2024-02-14 13:58:07 +01:00
Patrick Schult
d8baadb991 Merge pull request #5679 from Habetdin/staging
[Netfilter] respect ban time limits
2024-02-14 11:37:24 +01:00
Patrick Schult
7d3f9fa407 Merge pull request #5727 from mailcow/fix/domain-wide-footer
[Rspamd] apply domain wide footer to alias domains
2024-02-14 09:43:04 +01:00
Patrick Schult
705d144a85 Merge pull request #5729 from mailcow/feat/readable-domainnames
[Web] display human readable domainnames instead of punycode
2024-02-14 09:36:20 +01:00
Patrick Schult
ff05cff36c Merge pull request #5730 from mailcow/fix/add-domain-gal
[Web] fix setting unchecked checkboxes on domain adding
2024-02-14 09:34:21 +01:00
Patrick Schult
861fa7b145 Merge pull request #5728 from mailcow/fix/debug-tz
[Web] fix blank /debug page with invalid timezone
2024-02-14 09:32:17 +01:00
FreddleSpl0it
d65a0bba44 [ClamAV] Update to 1.2.2 2024-02-13 09:16:38 +01:00
FreddleSpl0it
dac1bd88dc [Web] fix setting unchecked checkboxes 2024-02-09 15:17:02 +01:00
FreddleSpl0it
288dbfa37c [Web] display human readable domainnames instead of punycode 2024-02-09 15:13:45 +01:00
FreddleSpl0it
a0e55cb9b1 [Web] fix blank /debug page with invalid timezone 2024-02-09 15:08:21 +01:00
FreddleSpl0it
86ba019ca0 [Rspamd] apply domain wide footer to alias domains 2024-02-09 14:59:14 +01:00
DerLinkman
3cb9c2ece5 ui: fix wrong docs links
ui: fixed broken Links to docs
2024-02-09 08:11:20 +01:00
Habetdin
1787c53d98 [Netfilter] respect ban time limits 2024-02-09 01:57:09 +03:00
Niklas Meyer
8ae762a8c8 Merge pull request #5717 from mailcow/staging
2024-01e
2024-02-08 15:58:47 +01:00
DerLinkman
63426c3cd0 unbound: remove netcat check & package 2024-02-08 15:55:26 +01:00
DerLinkman
e184713c67 added action for support label in issues 2024-02-08 13:06:02 +01:00
Niklas Meyer
1926625297 Merge pull request #5711 from amorfo77/master
[Netfilter] set IP check more relaxed on NFTables.py
2024-02-08 12:36:03 +01:00
DerLinkman
63bb8e8cef unbound: increase check interval to 30s 2024-02-08 12:23:46 +01:00
DerLinkman
583c5b48a0 dovecot: bump to docker image 1.28.1 2024-02-07 17:29:36 +01:00
DerLinkman
d08ccbce78 dovecot: fix wrong timestamps inside logs 2024-02-07 17:28:49 +01:00
DerLinkman
5a9702771c [SOGo] Fixed SOGo crash on older kernels < 5.10.0-X 2024-02-07 17:18:20 +01:00
eb91d9905b fix typpo in chain order message 2024-02-07 15:48:49 +01:00
38cc85fa4c set strict=False 2024-02-07 15:36:04 +01:00
Niklas Meyer
20c90642f9 Merge pull request #5700 from mailcow/staging
[Netfilter] fix mailcow isolation rule for iptables
2024-02-02 17:49:49 +01:00
Niklas Meyer
c9e9628383 Merge pull request #5699 from mailcow/staging
2024-01d
2024-02-02 17:08:45 +01:00
Niklas Meyer
1e09df20b6 Merge pull request #5689 from mailcow/staging
2024-01c
2024-02-02 15:52:33 +01:00
Tomasz Orzechowski
6dc0bdbfa3 Proper number of threads regex. 2024-01-09 22:03:24 +01:00
382 changed files with 8196 additions and 34932 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1 +1,2 @@
github: mailcow
custom: ["https://www.servercow.de/mailcow?lang=en#sal"]

View File

@@ -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
View 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? -->

View 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"

View File

@@ -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: |

View File

@@ -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

1
.gitignore vendored
View File

@@ -69,4 +69,3 @@ rebuild-images.sh
refresh_images.sh
update_diffs/
create_cold_standby.sh
!data/conf/nginx/mailcow_auth.conf

View File

@@ -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.

View File

@@ -1,8 +1,8 @@
FROM alpine:3.18
FROM alpine:3.20
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
ARG PIP_BREAK_SYSTEM_PACKAGES=1
RUN apk upgrade --no-cache \
&& apk add --update --no-cache \
bash \
@@ -15,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

View File

@@ -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
@@ -113,13 +117,13 @@ fi
chmod 600 ${ACME_BASE}/key.pem
log_f "Waiting for database..."
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent > /dev/null; do
while ! /usr/bin/mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent > /dev/null; do
sleep 2
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

View File

@@ -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
}

View File

@@ -1,3 +1,3 @@
FROM debian:bullseye-slim
FROM debian:bookworm-slim
RUN apt update && apt install pigz

View File

@@ -1,6 +1,6 @@
FROM alpine:3.19
FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
RUN apk upgrade --no-cache \
&& apk add --update --no-cache \

View File

@@ -11,4 +11,4 @@ if [ "${CLAMAV_NO_CLAMD:-}" != "false" ]; then
echo "Clamd is up"
fi
exit 0
exit 0

View File

@@ -1,6 +1,6 @@
FROM alpine:3.19
FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
ARG PIP_BREAK_SYSTEM_PACKAGES=1
WORKDIR /app
@@ -24,4 +24,4 @@ COPY main.py /app/main.py
COPY modules/ /app/modules/
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]
CMD exec python main.py
CMD ["python", "main.py"]

View File

@@ -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")

View File

@@ -1,10 +1,12 @@
FROM alpine:3.19
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
FROM alpine:3.20
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 addgroup -g 5000 vmail \
@@ -23,6 +25,7 @@ RUN addgroup -g 5000 vmail \
envsubst \
ca-certificates \
curl \
coreutils \
jq \
lua \
lua-cjson \
@@ -31,13 +34,9 @@ RUN addgroup -g 5000 vmail \
lua5.3-sql-mysql \
icu-data-full \
mariadb-connector-c \
lua-sec \
mariadb-dev \
glib-dev \
gcompat \
mariadb-client \
perl \
perl-dev \
perl-ntlm \
perl-cgi \
perl-crypt-openssl-rsa \
@@ -65,7 +64,7 @@ RUN addgroup -g 5000 vmail \
perl-package-stash-xs \
perl-par-packer \
perl-parse-recdescent \
perl-lockfile-simple --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/ \
perl-lockfile-simple \
libproc \
perl-readonly \
perl-regexp-common \
@@ -107,13 +106,12 @@ RUN addgroup -g 5000 vmail \
dovecot-pigeonhole-plugin \
dovecot-pop3d \
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
#RUN cpan LockFile::Simple
COPY trim_logs.sh /usr/local/bin/trim_logs.sh
COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
@@ -132,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"]

View File

@@ -2,7 +2,7 @@
set -e
# Wait for MySQL to warm-up
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for database to come up..."
sleep 2
done
@@ -28,7 +28,8 @@ ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
# Create missing directories
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
[[ ! -d /etc/dovecot/auth/ ]] && mkdir -p /etc/dovecot/auth/
[[ ! -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
@@ -128,6 +136,169 @@ user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format
iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2';
EOF
cat <<EOF > /etc/dovecot/lua/passwd-verify.lua
function auth_password_verify(req, pass)
if req.domain == nil then
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
end
if cur == nil then
script_init()
end
if req.user == nil then
req.user = ''
end
respbody = {}
-- check against mailbox passwds
local cur,errorString = con:execute(string.format([[SELECT password FROM mailbox
WHERE username = '%s'
AND active = '1'
AND domain IN (SELECT domain FROM domain WHERE domain='%s' AND active='1')
AND IFNULL(JSON_UNQUOTE(JSON_VALUE(mailbox.attributes, '$.force_pw_update')), 0) != '1'
AND IFNULL(JSON_UNQUOTE(JSON_VALUE(attributes, '$.%s_access')), 1) = '1']], con:escape(req.user), con:escape(req.domain), con:escape(req.service)))
local row = cur:fetch ({}, "a")
while row do
if req.password_verify(req, row.password, pass) == 1 then
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
VALUES ("%s", 0, "%s", "%s")]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip)))
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_OK, ""
end
row = cur:fetch (row, "a")
end
-- check against app passwds for imap and smtp
-- app passwords are only available for imap, smtp, sieve and pop3 when using sasl
if req.service == "smtp" or req.service == "imap" or req.service == "sieve" or req.service == "pop3" then
local cur,errorString = con:execute(string.format([[SELECT app_passwd.id, %s_access AS has_prot_access, app_passwd.password FROM app_passwd
INNER JOIN mailbox ON mailbox.username = app_passwd.mailbox
WHERE mailbox = '%s'
AND app_passwd.active = '1'
AND mailbox.active = '1'
AND app_passwd.domain IN (SELECT domain FROM domain WHERE domain='%s' AND active='1')]], con:escape(req.service), con:escape(req.user), con:escape(req.domain)))
local row = cur:fetch ({}, "a")
while row do
if req.password_verify(req, row.password, pass) == 1 then
-- if password is valid and protocol access is 1 OR real_rip matches SOGo, proceed
if tostring(req.real_rip) == "__IPV4_SOGO__" then
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_OK, ""
elseif row.has_prot_access == "1" then
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
VALUES ("%s", %d, "%s", "%s")]], con:escape(req.service), row.id, con:escape(req.user), con:escape(req.real_rip)))
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_OK, ""
end
end
row = cur:fetch (row, "a")
end
end
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
-- PoC
-- local reqbody = string.format([[{
-- "success":0,
-- "service":"%s",
-- "app_password":false,
-- "username":"%s",
-- "real_rip":"%s"
-- }]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip))
-- http.request {
-- method = "POST",
-- url = "http://nginx:8081/sasl_log.php",
-- source = ltn12.source.string(reqbody),
-- headers = {
-- ["content-type"] = "application/json",
-- ["content-length"] = tostring(#reqbody)
-- },
-- sink = ltn12.sink.table(respbody)
-- }
end
function auth_passdb_lookup(req)
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
end
function script_init()
mysql = require "luasql.mysql"
http = require "socket.http"
http.TIMEOUT = 5
ltn12 = require "ltn12"
env = mysql.mysql()
con = env:connect("__DBNAME__","__DBUSER__","__DBPASS__","localhost")
return 0
end
function script_deinit()
con:close()
env:close()
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
sed -i "s/__DBNAME__/${DBNAME}/g" /etc/dovecot/lua/passwd-verify.lua
sed -i "s/__IPV4_SOGO__/${IPV4_NETWORK}.248/g" /etc/dovecot/lua/passwd-verify.lua
# Migrate old sieve_after file
[[ -f /etc/dovecot/sieve_after ]] && mv /etc/dovecot/sieve_after /etc/dovecot/global_sieve_after
# Create global sieve scripts
@@ -225,7 +396,6 @@ 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
@@ -241,18 +411,10 @@ 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/auth/passwd-verify.lua
chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/auth/passwd-verify.lua
chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/lua/passwd-verify.lua
chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/lua/passwd-verify.lua
chown -R vmail:vmail /var/vmail/sieve
chown -R vmail:vmail /var/volatile
chown -R vmail:vmail /var/vmail_index
@@ -269,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
@@ -321,7 +484,7 @@ done
# For some strange, unknown and stupid reason, Dovecot may run into a race condition, when this file is not touched before it is read by dovecot/auth
# May be related to something inside Docker, I seriously don't know
touch /etc/dovecot/auth/passwd-verify.lua
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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -11,21 +11,25 @@ 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
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
if 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; then
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
fi
else
echo "Failed to download SA rules. Exiting."
exit 0 # Must be 0 otherwise dovecot would not start at all
fi
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

View File

@@ -7,6 +7,7 @@ options {
use_fqdn(no);
owner("root"); group("adm"); perm(0640);
stats(freq(0));
keep_timestamp(no);
bad_hostname("^gconfd$");
};
source s_dgram {

View File

@@ -7,6 +7,7 @@ options {
use_fqdn(no);
owner("root"); group("adm"); perm(0640);
stats(freq(0));
keep_timestamp(no);
bad_hostname("^gconfd$");
};
source s_dgram {

View File

@@ -23,4 +23,3 @@ catch_non_zero "${REDIS_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM CRON_LOG 0 ${LOG_LINES}"

View File

@@ -1,5 +1,6 @@
FROM alpine:3.19
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
FROM alpine:3.20
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
WORKDIR /app

View File

@@ -80,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:
@@ -114,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'])
@@ -150,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:
@@ -277,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:
@@ -290,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():
@@ -306,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)

View File

@@ -13,9 +13,12 @@ class Logger:
tolog['time'] = int(round(time.time()))
tolog['priority'] = priority
tolog['message'] = message
if self.r is not None:
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)

View File

@@ -41,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
@@ -309,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"]
@@ -321,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,
@@ -417,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'} }
@@ -451,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
@@ -466,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"]

View File

@@ -1,5 +1,6 @@
FROM alpine:3.19
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

View File

@@ -1,5 +1,6 @@
FROM php:8.2-fpm-alpine3.18
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG APCU_PECL_VERSION=5.1.23

View File

@@ -3,7 +3,7 @@
function array_by_comma { local IFS=","; echo "$*"; }
# Wait for containers
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for SQL..."
sleep 2
done
@@ -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}"
@@ -43,7 +44,7 @@ until [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; do
echo "MySQL applied an upgrade, debug output:"
echo ${SQL_FULL_UPGRADE_RETURN}
sleep 3
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for SQL to return, please wait"
sleep 2
done
@@ -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
@@ -204,17 +205,6 @@ chown -R 82:82 /web/templates/cache
# Clear cache
find /web/templates/cache/* -not -name '.gitkeep' -delete
# list client ca of all domains for
CA_LIST="/etc/nginx/conf.d/client_cas.crt"
# Clear the output file
> "$CA_LIST"
# Execute the query and append each value to the output file
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT ssl_client_ca FROM domain;" | while read -r ca; do
echo "$ca" >> "$CA_LIST"
done
echo "SSL client CAs have been appended to $CA_LIST"
# Run hooks
for file in /hooks/*; do
if [ -x "${file}" ]; then

View File

@@ -1,5 +1,6 @@
FROM debian:bullseye-slim
LABEL maintainer "The Infrastructure Company GmbH 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
@@ -59,4 +60,4 @@ EXPOSE 588
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

View File

@@ -5,7 +5,7 @@ trap "postfix stop" EXIT
[[ ! -d /opt/postfix/conf/sql/ ]] && mkdir -p /opt/postfix/conf/sql/
# Wait for MySQL to warm-up
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for database to come up..."
sleep 2
done
@@ -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')

View File

@@ -1,4 +1,4 @@
@version: 3.28
@version: 3.38
@include "scl.conf"
options {
chain_hostnames(off);

View File

@@ -1,4 +1,4 @@
@version: 3.28
@version: 3.38
@include "scl.conf"
options {
chain_hostnames(off);

View File

@@ -1,22 +1,27 @@
FROM debian:bookworm-slim
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ARG RSPAMD_VER=rspamd_3.9.1-1~82f43560f
ARG CODENAME=bookworm
ENV LC_ALL C
ENV LC_ALL=C
RUN apt-get update && apt-get install -y --no-install-recommends \
RUN apt-get update && apt-get install -y \
tzdata \
ca-certificates \
gnupg2 \
apt-transport-https \
dnsutils \
netcat-traditional \
&& apt-key adv --fetch-keys https://rspamd.com/apt-stable/gpg.key \
&& echo "deb 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/* \
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 --no-install-recommends \
&& 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

View File

@@ -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 "$@"

View File

@@ -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

View File

@@ -1,12 +1,13 @@
FROM debian:bookworm-slim
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
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.17
ENV LC_ALL C
ENV LC_ALL=C
# Prerequisites
RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
@@ -54,4 +55,4 @@ RUN chmod +x /bootstrap-sogo.sh \
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

View File

@@ -1,7 +1,7 @@
#!/bin/bash
# Wait for MySQL to warm-up
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for database to come up..."
sleep 2
done
@@ -24,6 +24,110 @@ while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
done
echo "DB schema is ${DBV_NOW}"
# Recreate view
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing sogo_view..."
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
while [[ ${VIEW_OK} != 'OK' ]]; do
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) AS
SELECT
mailbox.username,
mailbox.domain,
mailbox.username,
IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.force_pw_update')) = '0', IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.sogo_access')) = 1, password, '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'), '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
mailbox.name,
mailbox.username,
IFNULL(GROUP_CONCAT(ga.aliases ORDER BY ga.aliases SEPARATOR ' '), ''),
IFNULL(gda.ad_alias, ''),
IFNULL(external_acl.send_as_acl, ''),
mailbox.kind,
mailbox.multiple_bookings
FROM
mailbox
LEFT OUTER JOIN
grouped_mail_aliases ga
ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
LEFT OUTER JOIN
grouped_domain_alias_address gda
ON gda.username = mailbox.username
LEFT OUTER JOIN
grouped_sender_acl_external external_acl
ON external_acl.username = mailbox.username
WHERE
mailbox.active = '1'
GROUP BY
mailbox.username;
EOF
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
VIEW_OK=OK
else
echo "Will retry to setup SOGo view in 3s..."
sleep 3
fi
done
else
while [[ ${VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
VIEW_OK=OK
else
echo "Waiting for SOGo view to be created by master..."
sleep 3
fi
done
fi
# Wait for static view table if missing after update and update content
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing _sogo_static_view..."
while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
STATIC_VIEW_OK=OK
echo "Updating _sogo_static_view content..."
# If changed, also update init_db.inc.php
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) SELECT c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings from sogo_view;"
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')"
else
echo "Waiting for database initialization..."
sleep 3
fi
done
else
while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
STATIC_VIEW_OK=OK
else
echo "Waiting for database initialization by master..."
sleep 3
fi
done
fi
# Recreate password update trigger
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing update trigger..."
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
while [[ ${TRIGGER_OK} != 'OK' ]]; do
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELIMITER -
CREATE TRIGGER sogo_update_password AFTER UPDATE ON _sogo_static_view
FOR EACH ROW
BEGIN
UPDATE mailbox SET password = NEW.c_password WHERE NEW.c_uid = username;
END;
-
DELIMITER ;
EOF
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then
TRIGGER_OK=OK
else
echo "Will retry to setup SOGo password update trigger in 3s"
sleep 3
fi
done
fi
# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
@@ -46,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>

View File

@@ -1,4 +1,4 @@
@version: 3.28
@version: 3.38
@include "scl.conf"
options {
chain_hostnames(off);

View File

@@ -1,4 +1,4 @@
@version: 3.28
@version: 3.38
@include "scl.conf"
options {
chain_hostnames(off);

View File

@@ -1,5 +1,4 @@
FROM solr:7.7-slim
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
USER root

View File

@@ -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

View File

@@ -1,30 +1,36 @@
FROM alpine:3.18
FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
RUN apk add --update --no-cache \
curl \
bind-tools \
netcat-openbsd \
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=5s --timeout=30s 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 ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

View File

@@ -1,99 +1,102 @@
#!/bin/bash
# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!)
if [[ "${SKIP_UNBOUND_HEALTHCHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
SKIP_UNBOUND_HEALTHCHECK=y
fi
STATUS_FILE="/tmp/healthcheck_status"
RUNS=0
# Declare log function for logfile inside container
function log_to_file() {
echo "$(date +"%Y-%m-%d %H:%M:%S"): $1" > /var/log/healthcheck.log
# 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")
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
ping -q -c 3 -w 5 "$ip"
if [ $? -ne 0 ]; then
log_to_file "Healthcheck: Couldn't ping $ip for 5 seconds... Gave up!"
log_to_file "Please check your internet connection or firewall rules to fix this error, because a simple ping test should always go through from the unbound container!"
return 1
fi
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
log_to_file "Healthcheck: Ping Checks WORKING properly!"
return 0
}
# General DNS Resolve Check against Unbound Resolver himself
function check_dns() {
declare -a domains=("mailcow.email" "github.com" "hub.docker.com")
declare -a domains=("fuzzy.mailcow.email" "github.com" "hub.docker.com")
local fail_tolerance=1
local failures=0
for domain in "${domains[@]}" ; do
for ((i=1; i<=3; i++)); do
dig +short +timeout=2 +tries=1 "$domain" @127.0.0.1 > /dev/null
if [ $? -ne 0 ]; then
log_to_file "Healthcheck: DNS Resolution Failed on $i attempt! Trying again..."
if [ $i -eq 3 ]; then
log_to_file "Healthcheck: DNS Resolution not possible after $i attempts... Gave up!"
log_to_file "Maybe check your outbound firewall, as it needs to resolve DNS over TCP AND UDP!"
return 1
fi
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
done
log_to_file "Healthcheck: DNS Resolver WORKING properly!"
return 0
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
}
# Simple Netcat Check to connect to common webports
function check_netcat() {
declare -a domains=("mailcow.email" "github.com" "hub.docker.com")
declare -a ports=("80" "443")
while true; do
for domain in "${domains[@]}" ; do
for port in "${ports[@]}" ; do
nc -z -w 2 $domain $port
if [ $? -ne 0 ]; then
log_to_file "Healthcheck: Could not reach $domain on Port $port... Gave up!"
log_to_file "Please check your internet connection or firewall rules to fix this error."
return 1
fi
done
done
if [[ ${SKIP_UNBOUND_HEALTHCHECK} == "y" ]]; then
log_to_stdout "Healthcheck: ALL CHECKS WERE SKIPPED! Unbound is healthy!"
echo "0" > $STATUS_FILE
sleep 365d
fi
log_to_file "Healthcheck: Netcat Checks WORKING properly!"
return 0
# 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 [[ ${SKIP_UNBOUND_HEALTHCHECK} == "y" ]]; then
log_to_file "Healthcheck: ALL CHECKS WERE SKIPPED! Unbound is healthy!"
exit 0
fi
if [ $PING_STATUS -ne 0 ] || [ $DNS_STATUS -ne 0 ]; then
echo "1" > $STATUS_FILE
# 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
else
echo "0" > $STATUS_FILE
fi
if [ $? -ne 0 ]; then
exit 1
fi
sleep 30
check_dns
if [ $? -ne 0 ]; then
exit 1
fi
check_netcat
if [ $? -ne 0 ]; then
exit 1
fi
log_to_file "Healthcheck: ALL CHECKS WERE SUCCESSFUL! Unbound is healthy!"
exit 0
done

View 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

View 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

View 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);
};

View File

@@ -1,5 +1,6 @@
FROM alpine:3.18
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
FROM alpine:3.20
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
# Installation
RUN apk add --update \
@@ -36,4 +37,4 @@ RUN apk add --update \
COPY watchdog.sh /watchdog.sh
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
CMD /watchdog.sh
CMD ["/watchdog.sh"]

View File

@@ -33,7 +33,7 @@ if [[ ! -p /tmp/com_pipe ]]; then
fi
# Wait for containers
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for SQL..."
sleep 2
done
@@ -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/g")
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,7 +720,7 @@ 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 | sed 's/\..*//' )
' | 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))
@@ -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

View 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>

View 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.

View File

@@ -1,75 +0,0 @@
<?php
ini_set('error_reporting', 0);
header('Content-Type: application/json');
$post = trim(file_get_contents('php://input'));
if ($post) {
$post = json_decode($post, true);
}
$return = array("success" => false);
if(!isset($post['username']) || !isset($post['password']) || !isset($post['real_rip'])){
error_log("MAILCOWAUTH: Bad Request");
http_response_code(400); // Bad Request
echo json_encode($return);
exit();
}
require_once('../../../web/inc/vars.inc.php');
if (file_exists('../../../web/inc/vars.local.inc.php')) {
include_once('../../../web/inc/vars.local.inc.php');
}
require_once '../../../web/inc/lib/vendor/autoload.php';
// Init database
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
}
catch (PDOException $e) {
error_log("MAILCOWAUTH: " . $e . PHP_EOL);
http_response_code(500); // Internal Server Error
echo json_encode($return);
exit;
}
// Load core functions first
require_once 'functions.inc.php';
require_once 'functions.auth.inc.php';
require_once 'sessions.inc.php';
require_once 'functions.mailbox.inc.php';
// Init provider
$iam_provider = identity_provider('init');
$protocol = $post['protocol'];
if ($post['real_rip'] == getenv('IPV4_NETWORK') . '.248') {
$protocol = null;
}
$result = user_login($post['username'], $post['password'], $protocol, array('is_internal' => true));
if ($result === false){
$result = apppass_login($post['username'], $post['password'], $protocol, array(
'is_internal' => true,
'remote_addr' => $post['real_rip']
));
}
if ($result) {
http_response_code(200); // OK
$return['success'] = true;
} else {
error_log("MAILCOWAUTH: Login failed for user " . $post['username']);
http_response_code(401); // Unauthorized
}
echo json_encode($return);
session_destroy();
exit;

View File

@@ -1,42 +0,0 @@
function auth_password_verify(request, password)
if request.domain == nil then
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
end
json = require "cjson"
ltn12 = require "ltn12"
https = require "ssl.https"
https.TIMEOUT = 5
local req = {
username = request.user,
password = password,
real_rip = request.real_rip,
protocol = {}
}
req.protocol[request.service] = true
local req_json = json.encode(req)
local res = {}
local b, c = https.request {
method = "POST",
url = "https://nginx:9082",
source = ltn12.source.string(req_json),
headers = {
["content-type"] = "application/json",
["content-length"] = tostring(#req_json)
},
sink = ltn12.sink.table(res),
insecure = true
}
local api_response = json.decode(table.concat(res))
if api_response.success == true then
return dovecot.auth.PASSDB_RESULT_OK, ""
end
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
end
function auth_passdb_lookup(req)
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
end

View File

@@ -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
@@ -52,7 +53,7 @@ mail_shared_explicit_inbox = yes
mail_prefetch_count = 30
passdb {
driver = lua
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes
result_success = return-ok
result_failure = continue
result_internalfail = continue
@@ -68,7 +69,7 @@ passdb {
# a return of the following passdb is mandatory
passdb {
driver = lua
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes
}
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
service doveadm {
@@ -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
@@ -241,7 +239,7 @@ plugin {
mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
mail_crypt_save_version = 2
# Enable compression while saving, lz4 Dovecot v2.3.17+
# Enable compression while saving, lz4 Dovecot v2.2.11+
zlib_save = lz4
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
@@ -305,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

View File

@@ -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 =
}
}

View File

@@ -13,8 +13,6 @@
ssl_session_timeout 1d;
ssl_session_tickets off;
include /etc/nginx/conf.d/includes/ssl_client_auth.conf;
add_header Strict-Transport-Security "max-age=15768000;";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
@@ -103,10 +101,6 @@
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param TLS_SUCCESS $ssl_client_verify;
fastcgi_param TLS_ISSUER $ssl_client_i_dn;
fastcgi_param TLS_DN $ssl_client_s_dn;
fastcgi_param TLS_CERT $ssl_client_cert;
fastcgi_read_timeout 3600;
fastcgi_send_timeout 3600;
}

View File

@@ -1,4 +0,0 @@
ssl_verify_client optional;
ssl_client_certificate /etc/nginx/conf.d/client_cas.crt;

View File

@@ -1,23 +0,0 @@
server {
listen 9082 ssl http2;
ssl_certificate /etc/ssl/mail/cert.pem;
ssl_certificate_key /etc/ssl/mail/key.pem;
index mailcowauth.php;
server_name _;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /mailcowauth;
client_max_body_size 10M;
location ~ \.php$ {
client_max_body_size 10M;
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9001;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}

View File

@@ -1,23 +0,0 @@
apk add mariadb-client
# List client CA of all domains
CA_LIST="/etc/nginx/conf.d/client_cas.crt"
> "$CA_LIST"
# Define your SQL query
query="SELECT DISTINCT ssl_client_ca FROM domain WHERE ssl_client_ca IS NOT NULL;"
result=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "$query" -B -N)
if [ -n "$result" ]; then
echo "$result" | while IFS= read -r line; do
echo -e "$line"
done > $CA_LIST
#tail -n 1 "$CA_LIST" | wc -c | xargs -I {} truncate "$CA_LIST" -s -{}
echo "
ssl_verify_client optional;
ssl_client_certificate /etc/nginx/conf.d/client_cas.crt;
" > /etc/nginx/conf.d/includes/ssl_client_auth.conf
echo "SSL client CAs have been appended to $CA_LIST"
else
> /etc/nginx/conf.d/includes/ssl_client_auth.conf
echo "No SSL client CAs found"
fi

View File

@@ -1,222 +0,0 @@
<?php
require_once(__DIR__ . '/../web/inc/vars.inc.php');
if (file_exists(__DIR__ . '/../web/inc/vars.local.inc.php')) {
include_once(__DIR__ . '/../web/inc/vars.local.inc.php');
}
require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';
// Init database
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
}
catch (PDOException $e) {
logMsg("danger", $e->getMessage());
session_destroy();
exit;
}
// Init Redis
$redis = new Redis();
try {
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
}
else {
$redis->connect('redis-mailcow', 6379);
}
}
catch (Exception $e) {
echo "Exiting: " . $e->getMessage();
session_destroy();
exit;
}
function logMsg($priority, $message, $task = "Keycloak Sync") {
global $redis;
$finalMsg = array(
"time" => time(),
"priority" => $priority,
"task" => $task,
"message" => $message
);
$redis->lPush('CRON_LOG', json_encode($finalMsg));
}
// Load core functions first
require_once __DIR__ . '/../web/inc/functions.inc.php';
require_once __DIR__ . '/../web/inc/functions.auth.inc.php';
require_once __DIR__ . '/../web/inc/sessions.inc.php';
require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php';
require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php';
require_once __DIR__ . '/../web/inc/functions.acl.inc.php';
$_SESSION['mailcow_cc_username'] = "admin";
$_SESSION['mailcow_cc_role'] = "admin";
$_SESSION['acl']['tls_policy'] = "1";
$_SESSION['acl']['quarantine_notification'] = "1";
$_SESSION['acl']['quarantine_category'] = "1";
$_SESSION['acl']['ratelimit'] = "1";
$_SESSION['acl']['sogo_access'] = "1";
$_SESSION['acl']['protocol_access'] = "1";
$_SESSION['acl']['mailbox_relayhost'] = "1";
// Init Keycloak Provider
$iam_provider = identity_provider('init');
$iam_settings = identity_provider('get');
if (intval($iam_settings['periodic_sync']) != 1 && $iam_settings['import_users'] != 1) {
session_destroy();
exit;
}
// Set pagination variables
$start = 0;
$max = 25;
// lock sync if already running
$lock_file = '/tmp/iam-sync.lock';
if (file_exists($lock_file)) {
$lock_file_parts = explode("\n", file_get_contents($lock_file));
$pid = $lock_file_parts[0];
if (count($lock_file_parts) > 1){
$last_execution = $lock_file_parts[1];
$elapsed_time = (time() - $last_execution) / 60;
if ($elapsed_time < intval($iam_settings['sync_interval'])) {
logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)");
session_destroy();
exit;
}
}
if (posix_kill($pid, 0)) {
logMsg("warning", "Sync is already running");
session_destroy();
exit;
} else {
unlink($lock_file);
}
}
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid());
fclose($lock_file_handle);
// Loop until all users have been retrieved
while (true) {
// Get admin access token
$admin_token = identity_provider("get-keycloak-admin-token");
// Make the API request to retrieve the users
$url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users?first=$start&max=$max";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"Authorization: Bearer " . $admin_token
]);
$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code != 200){
logMsg("danger", "Recieved HTTP {$code}");
session_destroy();
exit;
}
try {
$response = json_decode($response, true);
} catch (Exception $e) {
logMsg("danger", $e->getMessage());
break;
}
if (!is_array($response)){
logMsg("danger", "Recieved malformed response from keycloak api");
break;
}
if (count($response) == 0) {
break;
}
// Process the batch of users
foreach ($response as $user) {
if (empty($user['email'])){
logMsg("warning", "No email address in keycloak found for user " . $user['name']);
continue;
}
if (!isset($user['attributes'])){
logMsg("warning", "No attributes in keycloak found for user " . $user['email']);
continue;
}
if (!isset($user['attributes']['mailcow_template']) ||
!is_array($user['attributes']['mailcow_template']) ||
count($user['attributes']['mailcow_template']) == 0) {
logMsg("warning", "No mailcow_template in keycloak found for user " . $user['email']);
continue;
}
$mailcow_template = $user['attributes']['mailcow_template'];
// try get mailbox user
$stmt = $pdo->prepare("SELECT `mailbox`.* FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `domain`.`active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user['email']));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// check if matching attribute mapping exists
$mbox_template = null;
foreach ($iam_settings['mappers'] as $index => $mapper){
if (in_array($mapper, $user['attributes']['mailcow_template'])) {
$mbox_template = $mapper;
break;
}
}
if (!$mbox_template){
logMsg("warning", "No matching mapper found for mailbox_template");
continue;
}
if (!$row && intval($iam_settings['import_users']) == 1){
// mailbox user does not exist, create...
logMsg("info", "Creating user " . $user['email']);
mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user['email'])[1],
'local_part' => explode('@', $user['email'])[0],
'authsource' => 'keycloak',
'template' => $mbox_template
));
} else if ($row) {
// mailbox user does exist, sync attribtues...
logMsg("info", "Syncing attributes for user " . $user['email']);
mailbox('edit', 'mailbox_from_template', array(
'username' => $user['email'],
'template' => $mbox_template
));
} else {
// skip mailbox user
logMsg("info", "Skipping user " . $user['email']);
}
sleep(0.025);
}
// Update the pagination variables for the next batch
$start += $max;
sleep(1);
}
logMsg("info", "DONE!");
// add last execution time to lock file
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid() . "\n" . time());
fclose($lock_file_handle);
session_destroy();

View File

@@ -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\)/

View File

@@ -114,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
@@ -163,12 +163,12 @@ transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf
smtp_sasl_auth_soft_bounce = no
postscreen_discard_ehlo_keywords = silent-discard, dsn, chunking
smtpd_discard_ehlo_keywords = chunking
compatibility_level = 2
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 #

View File

@@ -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

View File

@@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Thu Feb 1 00:13:50 UTC 2024
# Whitelist generated by Postwhite v3.4 on Thu Aug 1 00:16:45 UTC 2024
# https://github.com/stevejenkins/postwhite/
# 2089 total rules
# 1954 total rules
2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit
2a01:111:f403:8000::/50 permit
@@ -13,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.16.58 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
@@ -64,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
@@ -91,38 +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
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,30 +178,28 @@
50.18.125.237 permit
50.18.126.162 permit
50.31.32.0/19 permit
50.56.130.220 permit
50.56.130.221 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.214.50 permit
52.96.172.98 permit
52.96.222.194 permit
52.96.222.226 permit
52.96.223.2 permit
@@ -215,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
@@ -227,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
@@ -265,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
@@ -285,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
@@ -357,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
@@ -389,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
@@ -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
@@ -600,7 +538,6 @@
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
@@ -621,7 +558,6 @@
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.2 permit
82.165.159.3 permit
@@ -642,8 +578,6 @@
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
@@ -683,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
@@ -696,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
@@ -704,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
@@ -739,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
@@ -749,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
@@ -794,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
@@ -818,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
@@ -859,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
@@ -899,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
@@ -909,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
@@ -1002,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
@@ -1033,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
@@ -1072,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
@@ -1090,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
@@ -1149,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
@@ -1211,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
@@ -1237,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
@@ -1250,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
@@ -1289,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
@@ -1311,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
@@ -1330,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
@@ -1338,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
@@ -1348,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
@@ -1356,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
@@ -1372,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
@@ -1430,25 +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.178.49 permit
136.143.182.0/23 permit
136.143.184.0/24 permit
136.143.188.0/24 permit
136.143.190.0/23 permit
136.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
@@ -1459,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
@@ -1472,8 +1375,7 @@
144.178.38.0/24 permit
145.253.228.160/29 permit
145.253.239.128/29 permit
146.20.14.105 permit
146.20.14.107 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
@@ -1488,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
@@ -1503,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
@@ -1545,14 +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
164.177.132.168 permit
164.177.132.169 permit
164.177.132.170 permit
164.177.132.171 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
@@ -1561,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
@@ -1572,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
@@ -1581,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
@@ -1622,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
@@ -1646,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
@@ -1689,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
@@ -1707,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
@@ -1733,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
@@ -1741,13 +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.82.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
@@ -1772,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
@@ -1782,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
@@ -1798,19 +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.132.224.66 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
@@ -1829,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
@@ -1846,14 +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 permit
207.97.204.97 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
@@ -1867,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
@@ -1890,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
@@ -1920,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
@@ -2005,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
@@ -2023,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
@@ -2043,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
@@ -2087,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

View File

@@ -56,21 +56,42 @@ $empty_footer = json_encode(array(
error_log("FOOTER: checking for domain " . $domain . ", user " . $username . " and address " . $from . PHP_EOL);
try {
$stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude`, `skip_replies` 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

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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"
}

View File

@@ -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"];
}
}

View File

@@ -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;

View File

@@ -621,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

View File

@@ -52,7 +52,7 @@ $headers = getallheaders();
$qid = $headers['X-Rspamd-Qid'];
$fuzzy = $headers['X-Rspamd-Fuzzy'];
$subject = $headers['X-Rspamd-Subject'];
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
$score = $headers['X-Rspamd-Score'];
$rcpts = $headers['X-Rspamd-Rcpt'];
$user = $headers['X-Rspamd-User'];

View File

@@ -53,7 +53,7 @@ $qid = $headers['X-Rspamd-Qid'];
$rcpts = $headers['X-Rspamd-Rcpt'];
$sender = $headers['X-Rspamd-From'];
$ip = $headers['X-Rspamd-Ip'];
$subject = $headers['X-Rspamd-Subject'];
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
$messageid= $json_body->message_id;
$priority = 0;

View File

@@ -16,6 +16,9 @@
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
@@ -38,6 +41,7 @@
SOGoLanguage = English;
SOGoMailAuxiliaryUserAccountsEnabled = YES;
// SOGoCreateIdentitiesDisabled = NO;
SOGoMailCustomFromEnabled = YES;
SOGoMailingMechanism = smtp;
SOGoSMTPAuthenticationType = plain;

View File

@@ -1,36 +0,0 @@
server {
ssl_certificate /etc/ssl/mail/cert.pem;
ssl_certificate_key /etc/ssl/mail/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_ecdh_curve X25519:X448:secp384r1:secp256k1;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
index index.php index.html;
client_max_body_size 0;
root /web;
include /etc/nginx/conf.d/listen_plain.active;
include /etc/nginx/conf.d/listen_ssl.active;
server_name dmarcparse.derlinkman.de;
server_tokens off;
location ^~ /.well-known/acme-challenge/ {
allow all;
default_type "text/plain";
}
if ($scheme = http) {
return 301 https://$host$request_uri;
}
location / {
proxy_pass http://dmarcparser:8080/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 0;
}
}

View File

@@ -86,10 +86,6 @@ $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allo
$cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
$f2b_data = fail2ban('get');
// identity provider
$iam_settings = identity_provider('get');
// mbox templates
$mbox_templates = mailbox('get', 'mailbox_templates');
$template = 'admin.twig';
$template_data = [
@@ -111,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,
@@ -121,8 +118,6 @@ $template_data = [
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'cors_settings' => $cors_settings,
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
'iam_settings' => $iam_settings,
'mbox_templates' => $mbox_templates,
'lang_admin' => json_encode($lang['admin']),
'lang_datatables' => json_encode($lang['datatables'])
];

View File

@@ -1,7 +1,6 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
$default_autodiscover_config = $autodiscover_config;
if(file_exists('inc/vars.local.inc.php')) {
include_once 'inc/vars.local.inc.php';

View File

@@ -8,6 +8,9 @@
.dtr-details {
width: 100%;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #F2F2F2;
}
td.child>ul>li {
display: flex;
}

View File

@@ -33,13 +33,6 @@
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2') format('woff2'),
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff');
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #fbfbfb;
}
#maxmsgsize { min-width: 80px; }
#slider1 .slider-selection {
background: #FFD700;
@@ -85,19 +78,6 @@ body {
.navbar-fixed-top .navbar-collapse {
max-height: 1000px
}
.nav-tabs .nav-link, .nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus {
border-color: #dfdfdf;
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
border-color: #dfdfdf;
border-bottom: 1px solid #ffffff;
}
.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
border-color: #dfdfdf;
}
.nav-tabs {
border-bottom: 1px solid #dfdfdf;
}
.bi {
display: inline-block;
font-size: 12pt;
@@ -386,7 +366,6 @@ button[aria-expanded='true'] > .caret {
background-color: #f0f0f0;
}
.btn.btn-outline-secondary {
color: #000000 !important;
border-color: #cfcfcf !important;
}
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {

View File

@@ -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 = '?';

View File

@@ -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"])
];
}
}
@@ -118,7 +119,6 @@ if (isset($_SESSION['mailcow_cc_role'])) {
$quarantine_category = mailbox('get', 'quarantine_category', $mailbox);
$get_tls_policy = mailbox('get', 'tls_policy', $mailbox);
$rlyhosts = relayhost('get');
$iam_settings = identity_provider('get');
$template = 'edit/mailbox.twig';
$template_data = [
'acl' => $_SESSION['acl'],
@@ -131,8 +131,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
'rlyhosts' => $rlyhosts,
'sender_acl_handles' => mailbox('get', 'sender_acl_handles', $mailbox),
'user_acls' => acl('get', 'user', $mailbox),
'mailbox_details' => $result,
'iam_settings' => $iam_settings,
'mailbox_details' => $result
];
}
}

View File

@@ -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();

View File

@@ -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));
@@ -65,8 +66,6 @@ $globalVariables = [
'lang_acl' => json_encode($lang['acl']),
'lang_tfa' => json_encode($lang['tfa']),
'lang_fido2' => json_encode($lang['fido2']),
'lang_success' => json_encode($lang['success']),
'lang_danger' => json_encode($lang['danger']),
'docker_timeout' => $DOCKER_TIMEOUT,
'session_lifetime' => (int)$SESSION_LIFETIME,
'csrf_token' => $_SESSION['CSRF']['TOKEN'],

View File

@@ -1,472 +0,0 @@
<?php
function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
global $pdo;
global $redis;
$is_internal = $extra['is_internal'];
// Try validate admin
$result = admin_login($user, $pass);
if ($result !== false) return $result;
// Try validate domain admin
$result = domainadmin_login($user, $pass);
if ($result !== false) return $result;
// Try validate user
$result = user_login($user, $pass);
if ($result !== false) return $result;
// Try validate app password
$result = apppass_login($user, $pass, $app_passwd_data);
if ($result !== false) return $result;
// skip log and only return false if it's an internal request
if ($is_internal == true) return false;
if (!isset($_SESSION['ldelay'])) {
$_SESSION['ldelay'] = "0";
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
elseif (!isset($_SESSION['mailcow_cc_username'])) {
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'login_failed'
);
sleep($_SESSION['ldelay']);
return false;
}
function admin_login($user, $pass){
global $pdo;
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$user = strtolower(trim($user));
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `superadmin` = '1'
AND `active` = '1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// verify password
if (verify_hash($row['password'], $pass)) {
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
// active tfa authenticators found, set pending user login
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "admin";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
} else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "admin";
}
}
return false;
}
function domainadmin_login($user, $pass){
global $pdo;
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `superadmin` = '0'
AND `active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// verify password
if (verify_hash($row['password'], $pass) !== false) {
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "domainadmin";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
}
else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "domainadmin";
}
}
return false;
}
function user_login($user, $pass, $extra = null){
global $pdo;
$is_internal = $extra['is_internal'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `domain`.`active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// user does not exist, try call keycloak login and create user if possible via rest flow
if (!$row){
$iam_settings = identity_provider('get');
if ($iam_settings['authsource'] == 'keycloak' && intval($iam_settings['mailpassword_flow']) == 1){
$result = keycloak_mbox_login_rest($user, $pass, $iam_settings, array('is_internal' => $is_internal, 'create' => true));
if ($result !== false) return $result;
}
}
if ($row['active'] != 1) {
return false;
}
if ($row['authsource'] == 'keycloak'){
// user authsource is keycloak, try using via rest flow
$iam_settings = identity_provider('get');
if (intval($iam_settings['mailpassword_flow']) == 1){
$result = keycloak_mbox_login_rest($user, $pass, $iam_settings, array('is_internal' => $is_internal));
return $result;
} else {
return false;
}
}
// verify password
if (verify_hash($row['password'], $pass) !== false) {
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
// authenticators found, init TFA flow
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "user";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "pending";
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
// no authenticators found, login successfull
if (!$is_internal){
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
}
return "user";
}
}
return false;
}
function user_mutualtls_login() {
global $pdo;
if (empty($_SERVER["TLS_SUCCESS"]) || empty($_SERVER["TLS_DN"]) || empty($_SERVER["TLS_ISSUER"])) {
// missing info
return false;
}
if (!$_SERVER["TLS_SUCCESS"]) {
// mutual tls login failed
return false;
}
// parse dn
$pairs = explode(',', $_SERVER["TLS_DN"]);
$dn_details = [];
foreach ($pairs as $pair) {
$keyValue = explode('=', $pair);
$dn_details[$keyValue[0]] = $keyValue[1];
}
// parse dn
$pairs = explode(',', $_SERVER["TLS_ISSUER"]);
$issuer_details = [];
foreach ($pairs as $pair) {
$keyValue = explode('=', $pair);
$issuer_details[$keyValue[0]] = $keyValue[1];
}
$user = $dn_details['emailAddress'];
if (empty($user)){
// no user specified
return false;
}
$search = "";
ksort($issuer_details);
foreach ($issuer_details as $key => $value) {
$search .= "{$key}={$value},";
}
$search = rtrim($search, ',');
if (empty($search)){
// incomplete issuer details
return false;
}
$user_split = explode('@', $user);
$local_part = $user_split[0];
$domain = $user_split[1];
// search for match
$stmt = $pdo->prepare("SELECT * FROM `domain` AS d1
INNER JOIN `mailbox` ON mailbox.domain = d1.domain
INNER JOIN `domain` AS d2 ON mailbox.domain = d2.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND d2.`ssl_client_issuer` = :search
AND d2.`active`='1'
AND mailbox.`active`='1'
AND mailbox.`username` = :user");
$stmt->execute(array(
':search' => $search,
':user' => $user
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// user not found
if (!$row){
return false;
}
return $user;
}
function apppass_login($user, $pass, $app_passwd_data, $extra = null){
global $pdo;
$is_internal = $extra['is_internal'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$protocol = false;
if ($app_passwd_data['eas']){
$protocol = 'eas';
} else if ($app_passwd_data['dav']){
$protocol = 'dav';
} else if ($app_passwd_data['smtp']){
$protocol = 'smtp';
} else if ($app_passwd_data['imap']){
$protocol = 'imap';
} else if ($app_passwd_data['sieve']){
$protocol = 'sieve';
} else if ($app_passwd_data['pop3']){
$protocol = 'pop3';
} else if (!$is_internal) {
return false;
}
// fetch app password data
$stmt = $pdo->prepare("SELECT `app_passwd`.*, `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd`
INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox`
INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`
WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active` = '1'
AND `domain`.`active` = '1'
AND `app_passwd`.`active` = '1'
AND `app_passwd`.`mailbox` = :user"
);
// fetch password data
$stmt->execute(array(
':user' => $user,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if ($protocol && $row[$protocol . '_access'] != '1'){
continue;
}
// verify password
if (verify_hash($row['password'], $pass) !== false) {
if ($is_internal){
$remote_addr = $extra['remote_addr'];
} else {
$remote_addr = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
}
$service = strtoupper($is_app_passwd);
$stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
$stmt->execute(array(
':service' => $service,
':app_id' => $row['app_passwd_id'],
':username' => $user,
':remote_addr' => $remote_addr
));
unset($_SESSION['ldelay']);
return "user";
}
}
return false;
}
// Keycloak REST Api Flow - auth user by mailcow_password attribute
// This password will be used for direct UI, IMAP and SMTP Auth
// To use direct user credentials, only Authorization Code Flow is valid
function keycloak_mbox_login_rest($user, $pass, $iam_settings, $extra = null){
global $pdo;
$is_internal = $extra['is_internal'];
$create = $extra['create'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
// get access_token for service account of mailcow client
$admin_token = identity_provider("get-keycloak-admin-token");
// get the mailcow_password attribute from keycloak user
$url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users";
$queryParams = array('email' => $user, 'exact' => true);
$queryString = http_build_query($queryParams);
$curl = curl_init();
curl_setopt($curl, CURLOPT_TIMEOUT, 7);
curl_setopt($curl, CURLOPT_URL, $url . '?' . $queryString);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Authorization: Bearer ' . $admin_token,
'Content-Type: application/json'
));
$user_res = json_decode(curl_exec($curl), true)[0];
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($code != 200) {
return false;
}
if (!isset($user_res['attributes']['mailcow_password']) || !is_array($user_res['attributes']['mailcow_password'])){
return false;
}
if (empty($user_res['attributes']['mailcow_password'][0])){
return false;
}
// validate mailcow_password
$mailcow_password = $user_res['attributes']['mailcow_password'][0];
if (!verify_hash($mailcow_password, $pass)) {
return false;
}
// get mapped template, if not set return false
// also return false if no mappers were defined
$user_template = $user_res['attributes']['mailcow_template'][0];
if ($create && (empty($iam_settings['mappers']) || !$user_template)){
return false;
} else if (!$create) {
// login success - dont create mailbox
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return 'user';
}
// check if matching attribute exist
$mapper_key = array_search($user_template, $iam_settings['mappers']);
if ($mapper_key === false) return false;
// create mailbox
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user)[1],
'local_part' => explode('@', $user)[0],
'authsource' => 'keycloak',
'template' => $iam_settings['mappers'][$mapper_key]
));
if (!$create_res) return false;
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return 'user';
}

View File

@@ -122,16 +122,10 @@ function customize($_action, $_item, $_data = null) {
case 'app_links':
$apps = (array)$_data['app'];
$links = (array)$_data['href'];
$user_links = (array)$_data['user_href'];
$hide = (array)$_data['hide'];
$out = array();
if (count($apps) == count($links) && count($apps) == count($user_links) && count($apps) == count($hide)) {
if (count($apps) == count($links)) {
for ($i = 0; $i < count($apps); $i++) {
$out[] = array($apps[$i] => array(
'link' => $links[$i],
'user_link' => $user_links[$i],
'hide' => ($hide[$i] === '0' || $hide[$i] === 0) ? false : true
));
$out[] = array($apps[$i] => $links[$i]);
}
try {
$redis->set('APP_LINKS', json_encode($out));
@@ -262,22 +256,7 @@ function customize($_action, $_item, $_data = null) {
);
return false;
}
if (empty($app_links)){
return false;
}
foreach($app_links as $key => $value){
foreach($value as $app => $details){
if (empty($details['user_link']) || empty($_SESSION['mailcow_cc_username'])){
$app_links[$key][$app]['user_link'] = $app_links[$key][$app]['link'];
} else {
$app_links[$key][$app]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $app_links[$key][$app]['user_link']);
}
}
}
return $app_links;
return ($app_links) ? $app_links : false;
break;
case 'main_logo':
case 'main_logo_dark':

File diff suppressed because it is too large Load Diff

View File

@@ -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" ) {
@@ -528,24 +529,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
return false;
}
$active = (isset($_data['active'])) ? intval($_data['active']) : $DOMAIN_DEFAULT_ATTRIBUTES['active'];
$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'];
$ssl_client_ca = (is_valid_ssl_cert(trim($_data['ssl_client_ca']))) ? trim($_data['ssl_client_ca']) : null;
$ssl_client_issuer = "";
if (isset($ssl_client_ca)) {
$ca_issuer = openssl_x509_parse($ssl_client_ca);
if (!empty($ca_issuer) && is_array($ca_issuer['issuer'])){
$ca_issuer = $ca_issuer['issuer'];
ksort($ca_issuer);
foreach ($ca_issuer as $key => $value) {
$ssl_client_issuer .= "{$key}={$value},";
}
$ssl_client_issuer = rtrim($ssl_client_issuer, ',');
}
}
$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';
}
@@ -601,33 +589,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':domain' => '%@' . $domain
));
// save domain
try {
$stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`, `ssl_client_issuer`, `ssl_client_ca`)
VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients, :ssl_client_issuer, :ssl_client_ca)");
$stmt->execute(array(
':domain' => $domain,
':description' => $description,
':aliases' => $aliases,
':mailboxes' => $mailboxes,
':defquota' => $defquota,
':maxquota' => $maxquota,
':quota' => $quota,
':backupmx' => $backupmx,
':gal' => $gal,
':active' => $active,
':relay_unknown_only' => $relay_unknown_only,
':relay_all_recipients' => $relay_all_recipients,
':ssl_client_issuer' => $ssl_client_issuer,
'ssl_client_ca' => $ssl_client_ca
));
} catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
return false;
}
$stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`)
VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients)");
$stmt->execute(array(
':domain' => $domain,
':description' => $description,
':aliases' => $aliases,
':mailboxes' => $mailboxes,
':defquota' => $defquota,
':maxquota' => $maxquota,
':quota' => $quota,
':backupmx' => $backupmx,
':gal' => $gal,
':active' => $active,
':relay_unknown_only' => $relay_unknown_only,
':relay_all_recipients' => $relay_all_recipients
));
// save tags
foreach($tags as $index => $tag){
if (empty($tag)) continue;
@@ -678,7 +655,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
if (!empty($restart_sogo)) {
$restart_response = json_decode(docker('post', 'sogo-mailcow', 'restart'), true);
if ($restart_response['type'] != "success") {
if ($restart_response['type'] == "success") {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_added', htmlspecialchars($domain))
);
return true;
}
else {
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -687,18 +672,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false;
}
}
if (!empty($ssl_client_ca) && !empty($ssl_client_issuer)) {
// restart nginx
$restart_response = json_decode(docker('post', 'nginx-mailcow', 'restart'), true);
if ($restart_response['type'] != "success") {
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'nginx_restart_failed'
);
return false;
}
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1030,7 +1003,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$local_part = strtolower(trim($_data['local_part']));
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
$username = $local_part . '@' . $domain;
$authsource = 'mailcow';
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -1047,18 +1019,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
return false;
}
if (in_array($_data['authsource'], array('mailcow', 'keycloak', 'generic-oidc'))){
$authsource = $_data['authsource'];
}
if (empty($name)) {
$name = $local_part;
}
$template_attr = null;
if ($_data['template']){
$template_attr = mailbox('get', 'mailbox_templates', $_data['template'], $_extra)['attributes'];
$template_attr = mailbox('get', 'mailbox_templates', $_data['template'])['attributes'];
}
if (empty($template_attr)) {
$template_attr = mailbox('get', 'mailbox_templates', null, $_extra)[0]['attributes'];
$template_attr = mailbox('get', 'mailbox_templates')[0]['attributes'];
}
$MAILBOX_DEFAULT_ATTRIBUTES = array_merge($MAILBOX_DEFAULT_ATTRIBUTES, $template_attr);
@@ -1067,12 +1036,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$name = ltrim(rtrim($_data['name'], '>'), '<');
$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 ($authsource != 'mailcow'){
$password = '';
$password2 = '';
$password_hashed = '';
}
if (!$_extra['iam_create_login'] && ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0)) {
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1101,7 +1065,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
$quota_b = ($quota_m * 1048576);
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
$mailbox_attrs = json_encode(
array(
'force_pw_update' => strval($force_pw_update),
@@ -1116,8 +1079,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'passwd_update' => time(),
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
'quarantine_notification' => strval($quarantine_notification),
'quarantine_category' => strval($quarantine_category),
'attribute_hash' => $attribute_hash
'quarantine_category' => strval($quarantine_category)
)
);
if (!is_valid_domain_name($domain)) {
@@ -1128,7 +1090,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain) && !$_extra['iam_create_login']) {
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1192,12 +1154,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
return false;
}
if ($authsource == 'mailcow'){
if (password_check($password, $password2) !== true) {
return false;
}
$password_hashed = hash_password($password);
if (password_check($password, $password2) !== true) {
return false;
}
$password_hashed = hash_password($password);
if ($MailboxData['count'] >= $DomainData['mailboxes']) {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -1223,8 +1183,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
return false;
}
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `authsource`, `active`)
VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :authsource, :active)");
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `active`)
VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
@@ -1233,7 +1193,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':local_part' => $local_part,
':domain' => $domain,
':mailbox_attrs' => $mailbox_attrs,
':authsource' => $authsource,
':active' => $active
));
$stmt = $pdo->prepare("UPDATE `mailbox` SET
@@ -1253,14 +1212,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
break;
}
try {
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
$stmt->execute(array(
':username' => $username,
':tag_name' => $tag,
));
} catch (Exception $e) {
}
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
$stmt->execute(array(
':username' => $username,
':tag_name' => $tag,
));
}
$stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`)
VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';");
@@ -1294,6 +1250,7 @@ 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']);
@@ -1309,14 +1266,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_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'],
@@ -1332,7 +1290,8 @@ 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']
));
}
catch (PDOException $e) {
@@ -1351,61 +1310,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'object' => $username,
'rl_frame' => $_data['rl_frame'],
'rl_value' => $_data['rl_value']
), $_extra);
}
try {
update_sogo_static_view($username);
}catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
));
}
update_sogo_static_view($username);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_added', htmlspecialchars($username))
);
break;
case 'mailbox_from_template':
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `template` = :template AND type = 'mailbox'");
$stmt->execute(array(
":template" => $_data['template']
));
$mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($mbox_template_data)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'template_missing'
);
return false;
}
$attribute_hash = sha1(json_encode($mbox_template_data["attributes"]));
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
$mbox_template_data['domain'] = $_data['domain'];
$mbox_template_data['local_part'] = $_data['local_part'];
$mbox_template_data['authsource'] = $_data['authsource'];
$mbox_template_data['attribute_hash'] = $attribute_hash;
$mbox_template_data['quota'] = intval($mbox_template_data['quota'] / 1048576);
$mailbox_attributes = array('acl' => array());
foreach ($mbox_template_data as $key => $value){
switch (true) {
case (strpos($key, 'acl_') === 0 && $value != 0):
array_push($mailbox_attributes['acl'], str_replace('acl_' , '', $key));
break;
default:
$mailbox_attributes[$key] = $value;
break;
}
}
return mailbox('add', 'mailbox', $mailbox_attributes, array('iam_create_login' => true));
return true;
break;
case 'resource':
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
@@ -1666,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;
@@ -2701,22 +2616,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576);
$quota = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['max_quota_for_domain'] / 1048576);
$description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description'];
$tags = (is_array($_data['tags']) ? $_data['tags'] : array());
$ssl_client_ca = (is_valid_ssl_cert(trim($_data['ssl_client_ca']))) ? trim($_data['ssl_client_ca']) : $is_now['ssl_client_ca'];
$ssl_client_issuer = $is_now['ssl_client_issuer'];
if (is_valid_ssl_cert(trim($_data['ssl_client_ca']))){
if (isset($ssl_client_ca)) {
$ca_issuer = openssl_x509_parse($ssl_client_ca);
if (!empty($ca_issuer) && is_array($ca_issuer['issuer'])){
$ca_issuer = $ca_issuer['issuer'];
ksort($ca_issuer);
foreach ($ca_issuer as $key => $value) {
$ssl_client_issuer .= "{$key}={$value},";
}
$ssl_client_issuer = rtrim($ssl_client_issuer, ',');
}
}
}
$tags = (is_array($_data['tags']) ? $_data['tags'] : array());
if ($relay_all_recipients == '1') {
$backupmx = '1';
}
@@ -2816,47 +2716,35 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
continue;
}
try {
$stmt = $pdo->prepare("UPDATE `domain` SET
`relay_all_recipients` = :relay_all_recipients,
`relay_unknown_only` = :relay_unknown_only,
`backupmx` = :backupmx,
`gal` = :gal,
`active` = :active,
`quota` = :quota,
`defquota` = :defquota,
`maxquota` = :maxquota,
`relayhost` = :relayhost,
`mailboxes` = :mailboxes,
`aliases` = :aliases,
`description` = :description,
`ssl_client_ca` = :ssl_client_ca,
`ssl_client_issuer` = :ssl_client_issuer
WHERE `domain` = :domain");
$stmt->execute(array(
':relay_all_recipients' => $relay_all_recipients,
':relay_unknown_only' => $relay_unknown_only,
':backupmx' => $backupmx,
':gal' => $gal,
':active' => $active,
':quota' => $quota,
':defquota' => $defquota,
':maxquota' => $maxquota,
':relayhost' => $relayhost,
':mailboxes' => $mailboxes,
':aliases' => $aliases,
':description' => $description,
':ssl_client_ca' => $ssl_client_ca,
':ssl_client_issuer' => $ssl_client_issuer,
':domain' => $domain
));
}catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
$stmt = $pdo->prepare("UPDATE `domain` SET
`relay_all_recipients` = :relay_all_recipients,
`relay_unknown_only` = :relay_unknown_only,
`backupmx` = :backupmx,
`gal` = :gal,
`active` = :active,
`quota` = :quota,
`defquota` = :defquota,
`maxquota` = :maxquota,
`relayhost` = :relayhost,
`mailboxes` = :mailboxes,
`aliases` = :aliases,
`description` = :description
WHERE `domain` = :domain");
$stmt->execute(array(
':relay_all_recipients' => $relay_all_recipients,
':relay_unknown_only' => $relay_unknown_only,
':backupmx' => $backupmx,
':gal' => $gal,
':active' => $active,
':quota' => $quota,
':defquota' => $defquota,
':maxquota' => $maxquota,
':relayhost' => $relayhost,
':mailboxes' => $mailboxes,
':aliases' => $aliases,
':description' => $description,
':domain' => $domain
));
// save tags
foreach($tags as $index => $tag){
if (empty($tag)) continue;
@@ -2982,26 +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());
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
$authsource = $is_now['authsource'];
if (in_array($_data['authsource'], array('mailcow', 'keycloak', 'generic-oidc'))){
$authsource = $_data['authsource'];
}
(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(
@@ -3240,7 +3124,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed,
`attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW())
WHERE `username` = :username AND authsource = 'mailcow'");
WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
@@ -3254,35 +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,
`authsource` = :authsource,
`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`, '$.attribute_hash', :attribute_hash)
WHERE `username` = :username");
$stmt->execute(array(
':active' => $active,
':name' => $name,
':quota_b' => $quota_b,
':attribute_hash' => $attribute_hash,
':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,
':authsource' => $authsource
));
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;
@@ -3294,14 +3186,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
break;
}
try {
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
$stmt->execute(array(
':username' => $username,
':tag_name' => $tag,
));
} catch (Exception $e) {
}
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
$stmt->execute(array(
':username' => $username,
':tag_name' => $tag,
));
}
$_SESSION['return'][] = array(
@@ -3310,84 +3199,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('mailbox_modified', $username)
);
try {
update_sogo_static_view($username);
}catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
update_sogo_static_view($username);
}
return true;
break;
case 'mailbox_from_template':
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `template` = :template AND type = 'mailbox'");
$stmt->execute(array(
":template" => $_data['template']
));
$mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($mbox_template_data)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'template_missing'
);
return false;
}
$attribute_hash = sha1(json_encode($mbox_template_data["attributes"]));
$is_now = mailbox('get', 'mailbox_details', $_data['username']);
if ($is_now['attributes']['attribute_hash'] == $attribute_hash)
return true;
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
$mbox_template_data['attribute_hash'] = $attribute_hash;
$quarantine_attributes = array('username' => $_data['username']);
$tls_attributes = array('username' => $_data['username']);
$ratelimit_attributes = array('object' => $_data['username']);
$acl_attributes = array('username' => $_data['username'], 'user_acl' => array());
$mailbox_attributes = array('username' => $_data['username']);
foreach ($mbox_template_data as $key => $value){
switch (true) {
case (strpos($key, 'quarantine_') === 0):
$quarantine_attributes[$key] = $value;
break;
case (strpos($key, 'tls_') === 0):
if ($value == null)
$value = 0;
$tls_attributes[$key] = $value;
break;
case (strpos($key, 'rl_') === 0):
$ratelimit_attributes[$key] = $value;
break;
case (strpos($key, 'acl_') === 0 && $value != 0):
array_push($acl_attributes['user_acl'], str_replace('acl_' , '', $key));
break;
default:
$mailbox_attributes[$key] = $value;
break;
}
}
$mailbox_attributes['quota'] = intval($mailbox_attributes['quota'] / 1048576);
$result = mailbox('edit', 'mailbox', $mailbox_attributes);
if ($result === false) return $result;
$result = mailbox('edit', 'tls_policy', $tls_attributes);
if ($result === false) return $result;
$result = mailbox('edit', 'quarantine_notification', $quarantine_attributes);
if ($result === false) return $result;
$result = mailbox('edit', 'quarantine_category', $quarantine_attributes);
if ($result === false) return $result;
$result = ratelimit('edit', 'mailbox', $ratelimit_attributes);
if ($result === false) return $result;
$result = acl('edit', 'user', $acl_attributes);
if ($result === false) return $result;
return true;
break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
@@ -3466,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];
@@ -3641,30 +3457,54 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$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) {
@@ -3689,12 +3529,13 @@ 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`, `skip_replies`) VALUES (:domain, :html, :plain, :mbox_exclude, :skip_replies)");
$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'],
));
}
@@ -4471,8 +4312,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`relay_unknown_only`,
`backupmx`,
`gal`,
`active`,
`ssl_client_ca`
`active`
FROM `domain` WHERE `domain`= :domain");
$stmt->execute(array(
':domain' => $_data
@@ -4520,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'];
@@ -4540,7 +4381,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$domaindata['relay_unknown_only_int'] = $row['relay_unknown_only'];
$domaindata['created'] = $row['created'];
$domaindata['modified'] = $row['modified'];
$domaindata['ssl_client_ca'] = $row['ssl_client_ca'];
$stmt = $pdo->prepare("SELECT COUNT(`address`) AS `alias_count` FROM `alias`
WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2))
AND `address` NOT IN (
@@ -4627,7 +4467,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`quota`,
`mailbox`.`created`,
`mailbox`.`modified`,
`mailbox`.`authsource`,
`quota2`.`bytes`,
`attributes`,
`custom_attributes`,
@@ -4649,7 +4488,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`quota`,
`mailbox`.`created`,
`mailbox`.`modified`,
`mailbox`.`authsource`,
`quota2replica`.`bytes`,
`attributes`,
`custom_attributes`,
@@ -4679,7 +4517,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
$mailboxdata['created'] = $row['created'];
$mailboxdata['modified'] = $row['modified'];
$mailboxdata['authsource'] = ($row['authsource']) ? $row['authsource'] : 'mailcow';
if ($mailboxdata['percent_in_use'] === '- ') {
$mailboxdata['percent_class'] = "info";
@@ -4768,7 +4605,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return $mailboxdata;
break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin" && !$_extra['iam_create_login']) {
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
$_data = (isset($_data)) ? intval($_data) : null;
@@ -4856,7 +4693,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
try {
$stmt = $pdo->prepare("SELECT `html`, `plain`, `mbox_exclude`, `skip_replies` 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
@@ -5394,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'));
@@ -5540,16 +5377,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
continue;
}
try {
update_sogo_static_view($username);
}catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
update_sogo_static_view($username);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -5762,17 +5591,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
break;
}
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") {
try {
update_sogo_static_view();
}catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") {
update_sogo_static_view();
}
return true;
}

View File

@@ -1,10 +1,10 @@
<?php
function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
function ratelimit($_action, $_scope, $_data = null) {
global $redis;
$_data_log = $_data;
switch ($_action) {
case 'edit':
if ((!isset($_SESSION['acl']['ratelimit']) || $_SESSION['acl']['ratelimit'] != "1") && !$_extra['iam_create_login']) {
if (!isset($_SESSION['acl']['ratelimit']) || $_SESSION['acl']['ratelimit'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -92,8 +92,8 @@ function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
);
continue;
}
if ((!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)
|| ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) && !$_extra['iam_create_login']) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)
|| ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),

View File

@@ -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']);

View File

@@ -30,32 +30,6 @@ if(!file_exists($CSSPath)) {
cleanupCSS($hash);
}
$mailcow_apps_processed = $MAILCOW_APPS;
$app_links = customize('get', 'app_links');
$app_links_processed = $app_links;
$hide_mailcow_apps = true;
for ($i = 0; $i < count($mailcow_apps_processed); $i++) {
if ($hide_mailcow_apps && !$mailcow_apps_processed[$i]['hide']){
$hide_mailcow_apps = false;
}
if (!empty($_SESSION['mailcow_cc_username'])){
$mailcow_apps_processed[$i]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $mailcow_apps_processed[$i]['user_link']);
}
}
if ($app_links_processed){
for ($i = 0; $i < count($app_links_processed); $i++) {
$key = array_key_first($app_links_processed[$i]);
if ($hide_mailcow_apps && !$app_links_processed[$i][$key]['hide']){
$hide_mailcow_apps = false;
}
if (!empty($_SESSION['mailcow_cc_username'])){
$app_links_processed[$i][$key]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $app_links_processed[$i][$key]['user_link']);
}
}
}
$globalVariables = [
'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'),
'mailcow_locale' => @$_SESSION['mailcow_locale'],
@@ -71,14 +45,10 @@ $globalVariables = [
'lang' => $lang,
'skip_sogo' => (getenv('SKIP_SOGO') == 'y'),
'allow_admin_email_login' => (getenv('ALLOW_ADMIN_EMAIL_LOGIN') == 'n'),
'hide_mailcow_apps' => $hide_mailcow_apps,
'mailcow_apps' => $MAILCOW_APPS,
'mailcow_apps_processed' => $mailcow_apps_processed,
'app_links' => $app_links,
'app_links_processed' => $app_links_processed,
'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) {

View File

@@ -3,7 +3,7 @@ function init_db_schema() {
try {
global $pdo;
$db_version = "08022024_1302";
$db_version = "29072024_1000";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -256,8 +256,6 @@ function init_db_schema() {
"gal" => "TINYINT(1) NOT NULL DEFAULT '1'",
"relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'",
"relay_unknown_only" => "TINYINT(1) NOT NULL DEFAULT '0'",
"ssl_client_issuer" => "TEXT",
"ssl_client_ca" => "TEXT",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
@@ -275,6 +273,7 @@ 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(
@@ -364,7 +363,6 @@ function init_db_schema() {
"custom_attributes" => "JSON NOT NULL DEFAULT ('{}')",
"kind" => "VARCHAR(100) NOT NULL DEFAULT ''",
"multiple_bookings" => "INT NOT NULL DEFAULT -1",
"authsource" => "ENUM('mailcow', 'keycloak', 'generic-oidc') DEFAULT 'mailcow'",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
@@ -485,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(
@@ -570,20 +569,6 @@ function init_db_schema() {
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"identity_provider" => array(
"cols" => array(
"key" => "VARCHAR(255) NOT NULL",
"value" => "TEXT NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
"keys" => array(
"primary" => array(
"" => array("key")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"logs" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
@@ -710,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",
@@ -995,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",
@@ -1445,9 +1455,6 @@ function init_db_schema() {
));
}
// remove old sogo views and triggers
$pdo->query("DROP TRIGGER IF EXISTS sogo_update_password");
if (php_sapi_name() == "cli") {
echo "DB initialization completed" . PHP_EOL;
} else {
@@ -1472,7 +1479,6 @@ function init_db_schema() {
}
if (php_sapi_name() == "cli") {
include '/web/inc/vars.inc.php';
include '/web/inc/functions.inc.php';
include '/web/inc/functions.docker.inc.php';
// $now = new DateTime();
// $mins = $now->getOffset() / 60;
@@ -1494,7 +1500,9 @@ if (php_sapi_name() == "cli") {
if (intval($res['OK_C']) === 2) {
// Be more precise when replacing into _sogo_static_view, col orders may change
try {
update_sogo_static_view();
$stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view");
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
echo "Fixed _sogo_static_view" . PHP_EOL;
}
catch ( Exception $e ) {

View File

@@ -1,6 +1,7 @@
{
"require": {
"robthree/twofactorauth": "^1.6",
"yubico/u2flib-server": "^1.0",
"phpmailer/phpmailer": "^6.1",
"php-mime-mail-parser/php-mime-mail-parser": "^7",
"soundasleep/html2text": "^0.5.0",
@@ -9,8 +10,6 @@
"bshaffer/oauth2-server-php": "^1.11",
"mustangostang/spyc": "^0.6.3",
"directorytree/ldaprecord": "^2.4",
"twig/twig": "^3.0",
"stevenmaguire/oauth2-keycloak": "^4.0",
"league/oauth2-client": "^2.7"
"twig/twig": "^3.0"
}
}

View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8f5a147cdb147b935a158b86f47a4747",
"content-hash": "139c1e5dec323144cd778ce80fd1847e",
"packages": [
{
"name": "bshaffer/oauth2-server-php",
@@ -216,397 +216,6 @@
],
"time": "2022-02-25T16:00:51+00:00"
},
{
"name": "firebase/php-jwt",
"version": "v6.5.0",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
"reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2",
"reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2",
"shasum": ""
},
"require": {
"php": "^7.4||^8.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.5||^7.4",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.5",
"psr/cache": "^1.0||^2.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
"suggest": {
"ext-sodium": "Support EdDSA (Ed25519) signatures",
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"type": "library",
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Neuman Vong",
"email": "neuman+pear@twilio.com",
"role": "Developer"
},
{
"name": "Anant Narayanan",
"email": "anant@php.net",
"role": "Developer"
}
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
"keywords": [
"jwt",
"php"
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
"source": "https://github.com/firebase/php-jwt/tree/v6.5.0"
},
"time": "2023-05-12T15:47:07+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "7.5.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba",
"reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.5",
"guzzlehttp/psr7": "^1.9 || ^2.4",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1",
"ext-curl": "*",
"php-http/client-integration-tests": "^3.0",
"phpunit/phpunit": "^8.5.29 || ^9.5.23",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
"ext-curl": "Required for CURL handler support",
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
},
"branch-alias": {
"dev-master": "7.5-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Jeremy Lindblom",
"email": "jeremeamia@gmail.com",
"homepage": "https://github.com/jeremeamia"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle is a PHP HTTP client library",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"psr-18",
"psr-7",
"rest",
"web service"
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.5.0"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
"type": "tidelift"
}
],
"time": "2022-08-28T15:39:27+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "b94b2807d85443f9719887892882d0329d1e2598"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
"reference": "b94b2807d85443f9719887892882d0329d1e2598",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.4 || ^5.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.5-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/1.5.2"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
"type": "tidelift"
}
],
"time": "2022-08-28T14:55:35+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "2.4.5",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/0454e12ef0cd597ccd2adb036f7bda4e7fface66",
"reference": "0454e12ef0cd597ccd2adb036f7bda4e7fface66",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0",
"ralouphie/getallheaders": "^3.0"
},
"provide": {
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1",
"http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^8.5.29 || ^9.5.23"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.4.5"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
"type": "tidelift"
}
],
"time": "2023-04-17T16:00:45+00:00"
},
{
"name": "illuminate/contracts",
"version": "v9.3.0",
@@ -655,76 +264,6 @@
},
"time": "2022-02-22T14:45:39+00:00"
},
{
"name": "league/oauth2-client",
"version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth2-client.git",
"reference": "160d6274b03562ebeb55ed18399281d8118b76c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8",
"reference": "160d6274b03562ebeb55ed18399281d8118b76c8",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0 || ^7.0",
"paragonie/random_compat": "^1 || ^2 || ^9.99",
"php": "^5.6 || ^7.0 || ^8.0"
},
"require-dev": {
"mockery/mockery": "^1.3.5",
"php-parallel-lint/php-parallel-lint": "^1.3.1",
"phpunit/phpunit": "^5.7 || ^6.0 || ^9.5",
"squizlabs/php_codesniffer": "^2.3 || ^3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-2.x": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alex Bilbie",
"email": "hello@alexbilbie.com",
"homepage": "http://www.alexbilbie.com",
"role": "Developer"
},
{
"name": "Woody Gilk",
"homepage": "https://github.com/shadowhand",
"role": "Contributor"
}
],
"description": "OAuth 2.0 Client Library",
"keywords": [
"Authentication",
"SSO",
"authorization",
"identity",
"idp",
"oauth",
"oauth2",
"single sign on"
],
"support": {
"issues": "https://github.com/thephpleague/oauth2-client/issues",
"source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0"
},
"time": "2023-04-16T18:19:15+00:00"
},
{
"name": "matthiasmullie/minify",
"version": "1.3.66",
@@ -1274,166 +813,6 @@
},
"time": "2021-11-05T16:47:00+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client/tree/master"
},
"time": "2020-06-29T06:28:15+00:00"
},
{
"name": "psr/http-factory",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
"shasum": ""
},
"require": {
"php": ">=7.0.0",
"psr/http-message": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory/tree/master"
},
"time": "2019-04-30T12:38:16+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/master"
},
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "psr/log",
"version": "3.0.0",
@@ -1535,50 +914,6 @@
},
"time": "2021-10-29T13:22:09+00:00"
},
{
"name": "ralouphie/getallheaders",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "120b605dfeb996808c31b6477290a714d356e822"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
"reference": "120b605dfeb996808c31b6477290a714d356e822",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5 || ^6.5"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"support": {
"issues": "https://github.com/ralouphie/getallheaders/issues",
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "robthree/twofactorauth",
"version": "1.8.1",
@@ -1704,134 +1039,6 @@
},
"time": "2017-04-19T22:01:50+00:00"
},
{
"name": "stevenmaguire/oauth2-keycloak",
"version": "4.0.0",
"source": {
"type": "git",
"url": "https://github.com/stevenmaguire/oauth2-keycloak.git",
"reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d",
"reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d",
"shasum": ""
},
"require": {
"firebase/php-jwt": "^4.0 || ^5.0 || ^6.0",
"league/oauth2-client": "^2.0",
"php": "~7.2 || ~8.0"
},
"require-dev": {
"mockery/mockery": "~1.5.0",
"phpunit/phpunit": "~9.6.4",
"squizlabs/php_codesniffer": "~3.7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Stevenmaguire\\OAuth2\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Steven Maguire",
"email": "stevenmaguire@gmail.com",
"homepage": "https://github.com/stevenmaguire"
}
],
"description": "Keycloak OAuth 2.0 Client Provider for The PHP League OAuth2-Client",
"keywords": [
"authorisation",
"authorization",
"client",
"keycloak",
"oauth",
"oauth2"
],
"support": {
"issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues",
"source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/4.0.0"
},
"time": "2023-03-14T09:43:47+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
"reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.3-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-03-01T10:25:55+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.24.0",
@@ -2470,6 +1677,47 @@
}
],
"time": "2022-09-28T08:42:51+00:00"
},
{
"name": "yubico/u2flib-server",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/Yubico/php-u2flib-server.git",
"reference": "55d813acf68212ad2cadecde07551600d6971939"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/55d813acf68212ad2cadecde07551600d6971939",
"reference": "55d813acf68212ad2cadecde07551600d6971939",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"paragonie/random_compat": ">= 1",
"php": ">=5.6"
},
"require-dev": {
"phpunit/phpunit": "~5.7",
"vimeo/psalm": "^0|^1|^2"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"description": "Library for U2F implementation",
"homepage": "https://developers.yubico.com/php-u2flib-server",
"support": {
"issues": "https://github.com/Yubico/php-u2flib-server/issues",
"source": "https://github.com/Yubico/php-u2flib-server/tree/1.0.2"
},
"time": "2018-09-07T08:16:44+00:00"
}
],
"packages-dev": [],

View 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>

View 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>

View File

@@ -11,4 +11,9 @@ return array(
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
'u2flib_server\\Error' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\RegisterRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\Registration' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\SignRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\U2F' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
);

View File

@@ -7,10 +7,6 @@ $baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',

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