Compare commits

...

588 commits

Author SHA1 Message Date
Ashitaka
e8d9315d4a
Merge pull request #6905 from Ashitaka57/6646-pbkdf2-sha512-verify-hash
Support for PBKDF2-SHA512 hash algorithm in verify_hash() (FreeIPA compatibility) (issue 6646)
2025-12-12 14:08:21 +01:00
DerLinkman
d977ddb501
backup: add image prefetch function to verify latest image is used 2025-12-12 14:07:57 +01:00
DerLinkman
e76f5237ed
ofelia: revert fixed cron syntax for sa-rules download 2025-12-12 14:07:47 +01:00
Copilot
c11ed5dd1e
Prevent duplicate/plaintext login announcement rendering (#6963)
* Initial plan

* Fix duplicate login announcement display

Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com>
2025-12-12 14:07:36 +01:00
DerLinkman
4ef65fc382
Merge pull request #6948 from mailcow/staging
2025-12
2025-12-09 13:29:15 +01:00
DerLinkman
dbb9e474b0
pf-tlspol: upgrade to 1.8.22 (#6951)
* postfix-tlspol: upgrade to 1.8.20

* pf-tlspol: update to 1.8.22
2025-12-09 13:25:50 +01:00
Khurram Malik
f8eed8c786
fix(api): add missing break in CORS switch block causing save to hang (#6926) 2025-12-09 11:54:20 +01:00
DerLinkman
ef010aa39c
Update CONTRIBUTING.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-08 15:08:25 +01:00
milkmaker
79171ea6f5
[Web] Updated lang.fr-fr.json (#6943)
Co-authored-by: Neuronnexion <support@nnx.com>
2025-12-05 14:40:45 +01:00
milkmaker
4e3294b273
[Web] Updated lang.fr-fr.json (#6941)
[Web] Updated lang.fr-fr.json

[Web] Updated lang.fr-fr.json

Co-authored-by: Neuronnexion <support@nnx.com>
2025-12-03 23:31:37 +01:00
renovate[bot]
32a6ecddb6
chore(deps): update alpine docker tag to v3.23 (#6940)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 23:30:46 +01:00
DerLinkman
f3d9833ecf
Merge branch 'master' into staging 2025-12-03 16:55:12 +01:00
DerLinkman
930ca76ea7
update: moved _modules initialization and update at the beginning of update script 2025-12-03 16:54:26 +01:00
DerLinkman
9a2887cf46
core: improved docker compose version check 2025-12-03 16:27:04 +01:00
DerLinkman
9950914086
core: improved docker compose version check 2025-12-03 16:26:22 +01:00
renovate[bot]
470cfb0026
chore(deps): update actions/stale action to v10.1.1 (#6937)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 13:53:05 +01:00
milkmaker
6c106b4e4d
[Web] Updated lang.fr-fr.json (#6936)
Co-authored-by: Neuronnexion <support@nnx.com>
2025-12-02 16:40:36 +01:00
milkmaker
3d6253a2b2
update postscreen_access.cidr (#6933) 2025-12-01 08:48:22 +01:00
milkmaker
b873812588
[Web] Updated lang.gr-gr.json (#6930)
Co-authored-by: ChD Computers <chdcomputers@gmail.com>
2025-11-29 13:20:02 +01:00
milkmaker
514fefd2ed
Translations update from Weblate (#6924)
* [Web] Updated lang.ca-es.json

Co-authored-by: Pere Montpeó <peremontpeo@gmail.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

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

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

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

Co-authored-by: Filip Hajny <filip@hajny.net>

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

[Web] Updated lang.pl-pl.json

Co-authored-by: Monika Bark <rychert.monika@wp.pl>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: Pere Montpeó <peremontpeo@gmail.com>
Co-authored-by: Chris <chrismfz@gmail.com>
Co-authored-by: Filip Hajny <filip@hajny.net>
Co-authored-by: Monika Bark <rychert.monika@wp.pl>
2025-11-24 16:50:03 +01:00
renovate[bot]
6f9ee2d151
chore(deps): update actions/checkout action to v6 (#6920)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 10:55:12 +01:00
milkmaker
9832006141
Translations update from Weblate (#6916)
* [Web] Updated lang.si-si.json

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>

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

Co-authored-by: Habetdin <15926758+Habetdin@users.noreply.github.com>

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

[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

Co-authored-by: Monika Bark <rychert.monika@wp.pl>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: Habetdin <15926758+Habetdin@users.noreply.github.com>
Co-authored-by: Monika Bark <rychert.monika@wp.pl>
Co-authored-by: Peter <magic@kthx.at>
2025-11-17 23:23:07 +01:00
Josh
0413d26855
Allow making spam aliases permanent (#6888)
* Allow making spam aliases permanent

* added german translation

* updated Spamalias Twig + Rename in Spam Alias

* compose: update image tags to align to vendor version

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-11-13 16:05:01 +01:00
Patrik Kernstock
7b29c1f304
Disable nginx server_tokens in http context (#6873) 2025-11-13 15:19:11 +01:00
Patrik Kernstock
ae3ef391ee
Remove deprecated 'X-XSS-Protection' header (#6871) 2025-11-13 15:16:44 +01:00
Peter
7313f996d3
Update to trixie (#6907) 2025-11-13 15:16:00 +01:00
DerLinkman
62d16c9e56
compose: changes cronjobs to regular cron syntax + fixed sogo creds for cronjobs (#6866)
* cron: restructure cron timer to time on second (instead of random)

* dovecot: fix clearance for cron.creds file
2025-11-13 14:59:49 +01:00
DerLinkman
674b41ce08
updated the Contributing Guidelines 2025-11-12 10:16:54 +01:00
Claas Flint
1b833be760
Replace pigz with zstd for backup compression (#6897)
* Replace pigz with zstd for backup compression

This change replaces pigz (parallel gzip) with zstd (Zstandard) as the
compression algorithm for mailcow backups while maintaining full backward
compatibility with existing .tar.gz backups.

Benefits:
- Better compression ratios (12-37% improvement in tests)
- Improved compression speed with modern algorithm
- Maintains rsyncable functionality for incremental backups
- Full backward compatibility for restoring old .tar.gz backups
- Wide industry adoption and active development

Changes:
- Backup compression: pigz --rsyncable -p → zstd --rsyncable -T
- Backup decompression: pigz -d -p → zstd -d -T
- File extensions: .tar.gz → .tar.zst
- Added get_archive_info() function for intelligent format detection
- Updated backup Dockerfile to install zstd alongside pigz
- Restore function now auto-detects and handles both formats
- Updated FILE_SELECTION regex to recognize both .tar.zst and .tar.gz
- Updated comments to reflect new file extension

Backward Compatibility:
- Restore automatically detects .tar.zst (preferred) or .tar.gz (legacy)
- Existing .tar.gz backups can still be restored without issues
- pigz remains installed in backup image for legacy support
- Graceful fallback if backup file format not found

Testing:
- Added comprehensive test suite (test_backup_and_restore.sh)
- 12 automated tests covering all scenarios:
  * Backup creation (both formats)
  * Restore (both formats)
  * Format detection and priority
  * Error handling (missing files, empty dirs)
  * Content integrity verification
  * Multi-threading configuration
  * Large file compression (8.59 MB realistic data)

Test Results:
✓ zstd compression working
✓ pigz compression working (legacy)
✓ zstd decompression working
✓ pigz decompression working (backward compatible)
✓ Archive detection working
✓ Content integrity verified
✓ Format priority correct (.tar.zst preferred)
✓ Error handling for missing files
✓ Error handling for empty directories
✓ Multi-threading configuration verified
✓ Large file compression: 37.05% improvement
✓ Small file compression: 12.18% improvement

* move testing script into development folder

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-11-12 10:06:36 +01:00
DerLinkman
88adb1adf5
remove dev docker volume from upstream 2025-11-12 09:54:35 +01:00
DerLinkman
ec472f13cf
sogo: removed URLDecrpytion by default, make it configurable in sogo.conf 2025-11-12 09:50:41 +01:00
milkmaker
2e1d98cc7c
[Web] Updated lang.pl-pl.json (#6908)
[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

Co-authored-by: Monika Bark <rychert.monika@wp.pl>
2025-11-10 21:06:13 +01:00
milkmaker
07d7e3dc30
[Web] Updated lang.pl-pl.json (#6906)
[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

[Web] Updated lang.pl-pl.json

Co-authored-by: Monika Bark <rychert.monika@wp.pl>
2025-11-09 23:03:00 +01:00
milkmaker
b0f5aee628
[Web] Updated lang.pl-pl.json (#6898)
Co-authored-by: Monika Bark <rychert.monika@wp.pl>
2025-11-05 17:37:26 +01:00
milkmaker
d3065612fd
update postscreen_access.cidr (#6886) 2025-11-03 21:07:40 +01:00
Josh
9912e41f78
[Web] Correct order of Dansk/Danish in UI (#6887) 2025-11-03 21:07:20 +01:00
milkmaker
04200c99a4
Translations update from Weblate (#6880)
* [Web] Updated lang.vi-vn.json

Co-authored-by: Nguyễn Thái Dũng <nguyenthaidung.work+mailcow.email@gmail.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

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

Co-authored-by: Runar Ingebrigtsen <runar@rin.no>

---------

Co-authored-by: Nguyễn Thái Dũng <nguyenthaidung.work+mailcow.email@gmail.com>
Co-authored-by: Runar Ingebrigtsen <runar@rin.no>
2025-10-27 20:00:29 +01:00
FreddleSpl0it
45666d2c4e
Merge pull request #6874 from mailcow/staging
Update 2025-10a Hotfix
2025-10-24 08:27:23 +02:00
FreddleSpl0it
9a806e64ce
[PHP] remove opcache.revalidate_freq 2025-10-24 08:18:49 +02:00
FreddleSpl0it
22a09b9795
[PHP] re-add opcache.revalidate_freq setting 2025-10-23 15:16:24 +02:00
FreddleSpl0it
04d5c43550
Merge pull request #6847 from patschi/disable-opcache-jit
Disable PHP opcache.jit
2025-10-23 09:32:02 +02:00
milkmaker
fbcb8cbeb9
[Web] Updated lang.vi-vn.json (#6861)
Co-authored-by: Nguyễn Thái Dũng <nguyenthaidung.work+mailcow.email@gmail.com>
2025-10-21 18:03:22 +02:00
renovate[bot]
0338a36ecf
chore(deps): update alpine docker tag to v3.22 (#6417)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-21 18:03:02 +02:00
milkmaker
23fb5e2fca
Add Vietnamese language (#6854)
* [Web] Updated lang.vi-vn.json

[Web] Added lang.vi-vn.json

Co-authored-by: Nguyễn Thái Dũng <nguyenthaidung.work+mailcow.email@gmail.com>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* Add Vietnamese language

---------

Co-authored-by: Nguyễn Thái Dũng <nguyenthaidung.work+mailcow.email@gmail.com>
Co-authored-by: Peter <magic@kthx.at>
2025-10-20 18:35:00 +02:00
renovate[bot]
3507ff2773
chore(deps): update devops-infra/action-pull-request action to v1.0.2 (#6850) 2025-10-19 23:18:09 +02:00
Patrik Kernstock
a4970397f1 Disable PHP opcache.jit 2025-10-17 13:17:56 +02:00
renovate[bot]
4132f6bd48
chore(deps): update devops-infra/action-pull-request action to v1 (#6840) 2025-10-16 07:28:57 +02:00
FreddleSpl0it
586b3a2ed1
Merge pull request #6838 from mailcow/staging
Update 2025-10
2025-10-15 08:11:08 +02:00
FreddleSpl0it
6af2addf3c
[PHPFPM] Update Image to Version 1.94 2025-10-14 10:25:06 +02:00
FreddleSpl0it
f6eed6c441
Merge pull request #6836 from mailcow/fix/6802
[Web] Add password verification when setting recovery email
2025-10-13 12:10:57 +02:00
FreddleSpl0it
b85837c803
[Web] Add password verification when setting recovery email 2025-10-13 12:05:17 +02:00
FreddleSpl0it
653fc40d4c
Merge pull request #6783 from patschi/phpfpm-moar-speeeed
Optimize phpfpm opcache: more aggressive caching, enable JIT
2025-10-13 11:43:07 +02:00
FreddleSpl0it
c17d80a6fd
Merge pull request #6821 from tjmills-dev/feat/show-app-passwd-logins
Show app passwords for successful logins on user page
2025-10-13 11:41:39 +02:00
FreddleSpl0it
980bfa3aa0
Merge pull request #6696 from mailcow/renovate/krakjoe-apcu-5.x
chore(deps): update dependency krakjoe/apcu to v5.1.27
2025-10-10 14:07:24 +02:00
FreddleSpl0it
664a954393
Merge pull request #6798 from mailcow/renovate/php-pecl-mail-mailparse-3.x
chore(deps): update dependency php/pecl-mail-mailparse to v3.1.9
2025-10-10 14:07:05 +02:00
FreddleSpl0it
d5a27c4ccb
Merge pull request #6830 from mailcow/feat/rspamd-3.13.2
[Rspamd] Update to 3.13.2
2025-10-10 13:10:54 +02:00
FreddleSpl0it
6a8a2e2136
Merge pull request #6829 from mailcow/feat/redis-7.4.6
[Redis] Update to Redis 7.4.6
2025-10-10 13:09:47 +02:00
FreddleSpl0it
b859a52b8e
Merge pull request #6828 from mailcow/fix/6818
[Web] Fix SOGo redirection after login
2025-10-10 13:08:22 +02:00
FreddleSpl0it
10e0c42eff
Merge pull request #6797 from Hobby-Student/fix/autodiscover-with-ldap-attribute-mapping
fix autodiscover when using ldap with attribute mapping templates
2025-10-10 13:07:58 +02:00
FreddleSpl0it
f47df263d7
[Rspamd] Update to 3.13.2 2025-10-10 13:04:01 +02:00
FreddleSpl0it
2642d9109e
[Redis] Update to Redis 7.4.6 2025-10-10 12:48:57 +02:00
FreddleSpl0it
6708b94ebb
[Web] Fix SOGo redirection after login 2025-10-10 10:05:56 +02:00
milkmaker
79cf0abc6e
[Web] Updated lang.zh-cn.json (#6826)
Co-authored-by: Easton Man <me@eastonman.com>
2025-10-09 19:54:12 +02:00
Olavo Rocha Neto
7de70322d6
Update pt-br lang (#6803)
* [Web] Updated lang.si-si.json

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>

* Update pt-br lang

* Complimentary adjustments

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

This reverts commit b23848e0f2.

---------

Co-authored-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-10-09 19:36:36 +02:00
DerLinkman
417835dea8
netfilter: improve logging and mark iptables-legacy as deprecated 2025-10-09 16:37:05 +02:00
Thomas Mills
3dcacc4187
Change icon to filled key 2025-10-09 11:39:24 +01:00
Thomas Mills
69f0552d4f
Decrease margin size 2025-10-08 21:48:03 +01:00
Thomas Mills
c443a9400a
Move flag in front of IP 2025-10-08 21:48:03 +01:00
Thomas Mills
5c9f387d94
Add margin 2025-10-08 21:48:02 +01:00
Thomas Mills
e9414d17e4
Show app password for last logins 2025-10-08 21:47:50 +01:00
FreddleSpl0it
6bfa58611e
Merge pull request #6813 from mailcow/staging
Update 2025-09c
2025-10-07 11:43:15 +02:00
FreddleSpl0it
df4d3bb6e0
[Web] Fix dashboard host stats 2025-10-07 11:41:57 +02:00
FreddleSpl0it
e31b6d9a07
Merge pull request #6812 from mailcow/staging
Update 2025-09c
2025-10-07 10:47:19 +02:00
FreddleSpl0it
455ef084b4
[Web] clear old app_passwd log entries 2025-10-07 10:37:44 +02:00
FreddleSpl0it
c2948735f2
[Web] clear old app_passwd log entries 2025-10-07 10:18:07 +02:00
FreddleSpl0it
24c62b2f09
Merge pull request #6810 from mailcow/staging
Update 2025-09c
2025-10-07 10:03:47 +02:00
FreddleSpl0it
1ef0149076
[Web] make SameSite policy and cookie name configurable via vars.local.inc 2025-10-06 11:00:03 +02:00
FreddleSpl0it
922d173540
[Web] include hostname in default website title 2025-10-06 10:58:35 +02:00
renovate[bot]
fd088cb504
chore(deps): update actions/stale action to v10.1.0 (#6806) 2025-10-04 14:13:48 +02:00
Valentin Brandl
721ee2394e
Update variable name for prometheus-exporter security token (#6776)
* update variable name for prometheus-exporter security token

* update `MAILCOW_EXPORTER_TOKEN_DISABLE` variable name
2025-10-03 18:03:03 +01:00
Colin Kubon
c217be06c6
scripts: make sure /etc/docker exists (#6791) 2025-10-02 09:24:06 +02:00
Jonas
871c422ec1
Fix typos in config (#6792)
Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-10-02 09:22:35 +02:00
sdsys-ch
3cc28af607
[Helper] Fix cold-standby script to support digits and override files (#6800)
This commit fixes two bugs in the cold-standby script:

1. Support digits in COMPOSE_PROJECT_NAME
   The script was stripping digits from COMPOSE_PROJECT_NAME, while
   backup_and_restore.sh (fixed in a71d991c) correctly supports them.
   Added '0-9' to the tr character set to align behavior.

2. Support docker-compose.override.yml on remote
   Lines 172 and 287 explicitly used '-f docker-compose.yml' which
   causes Docker Compose to ignore docker-compose.override.yml even
   when present. Changed to 'cd && compose' pattern (matching line 296)
   to auto-discover override files.

   Impact: Users with custom volumes/services in override file would
   experience silent failures - volumes not created, images not pulled,
   data syncing to wrong locations.

Both fixes ensure cold-standby works correctly with standard Docker
Compose conventions and user customizations.

Co-authored-by: Christophe Neuerburg <c.neuerburg@sdsys.ch>
2025-10-02 09:21:26 +02:00
milkmaker
796e131c3a
update postscreen_access.cidr (#6801) 2025-10-01 11:14:57 +02:00
renovate[bot]
dd160cd508
Update dependency php/pecl-mail-mailparse to v3.1.9
Signed-off-by: milkmaker <milkmaker@mailcow.de>
2025-09-30 13:42:36 +00:00
Hobby-Student
732b321962
fix autodiscover when using ldap with attribute mapping templates 2025-09-30 14:37:19 +02:00
milkmaker
c51a769aec
[Web] Updated lang.si-si.json (#6794)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-29 18:10:39 +02:00
FreddleSpl0it
45a61755a5
Merge pull request #6777 from patschi/enable-https-redirect-default
Enable HTTPS redirect by default on new setups
2025-09-29 11:56:46 +02:00
FreddleSpl0it
769c57c355
Merge pull request #6779 from patschi/remove-debug-consolelog
Remove debug console.log calls
2025-09-29 11:54:23 +02:00
FreddleSpl0it
2e7eb7c0fd
Merge pull request #6780 from patschi/fix-pwcomplexity-apppasswds
Fixed password complexity check for AppPasswords creation/edit
2025-09-29 11:53:26 +02:00
FreddleSpl0it
4c83147d01
Merge pull request #6781 from patschi/pw-field-name-consistency
Rename password fields for AppPasswords same way for consistency
2025-09-29 11:52:08 +02:00
FreddleSpl0it
ca0bec4fc2
Merge pull request #6782 from patschi/fix-footer-escape
Fixed wrong footer escaping for certain characters
2025-09-29 11:45:42 +02:00
FreddleSpl0it
6f50dd17da
Merge pull request #6786 from patschi/fix-sql-typo
Fix several SQL statements
2025-09-29 11:39:30 +02:00
FreddleSpl0it
4a331929d0
Merge pull request #6787 from patschi/hide-relayhosts-if-no-acl
Hide relayhosts when ACL does not allow
2025-09-29 11:38:52 +02:00
FreddleSpl0it
748bc893b6
Merge pull request #6788 from patschi/lastmodified-default-value
Show "Never" by default if no last-modified date saved
2025-09-29 11:37:52 +02:00
FreddleSpl0it
e462602ddc
Merge pull request #6789 from patschi/domain-descr-readonly-when-no-acl
Make domain description field readonly when no ACL
2025-09-29 11:36:42 +02:00
milkmaker
4e0f435d12
[Web] Updated lang.si-si.json (#6793) 2025-09-28 15:12:14 +02:00
milkmaker
46f0581936
[Web] Updated lang.si-si.json (#6790)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-26 19:14:07 +02:00
Patrik Kernstock
20f04ecf6b Make domain description field readonly when no ACL 2025-09-26 17:13:24 +02:00
Patrik Kernstock
ff43799763 Show "Never" by default if no last-modified date 2025-09-26 17:02:22 +02:00
Patrik Kernstock
85ca197615 Hide relayhosts when ACL does not allow 2025-09-26 16:50:58 +02:00
Patrik Kernstock
d06d23bbaf Fix several SQL statements 2025-09-26 14:58:04 +02:00
Patrik Kernstock
702ed85dfd Fixed footer escaping 2025-09-26 14:41:19 +02:00
milkmaker
8abe74a562
[Web] Updated lang.si-si.json (#6785)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-26 10:57:32 +02:00
Patrik Kernstock
2f8a181281 Fix comments, added some comments 2025-09-26 04:16:57 +02:00
Patrik Kernstock
5c5287ca21 Fixed wrong footer escaping 2025-09-26 04:04:45 +02:00
Patrik Kernstock
83ba8d5840 Optimize opcache settings, enable JIT 2025-09-26 04:01:17 +02:00
Patrik Kernstock
ce219668cf Rename AppPasswds fields uniquely like 'add' 2025-09-26 03:37:49 +02:00
Patrik Kernstock
5b1b49a418 Fixed password complexity check for AppPasswords 2025-09-26 02:37:02 +02:00
Patrik Kernstock
8978a9ad79 Remove debug console.log() lines 2025-09-26 02:13:22 +02:00
Patrik Kernstock
5f4a4fd759 Removed new lines for consistency 2025-09-26 01:14:33 +02:00
Patrik Kernstock
171c591da4 Enable REDIRECT_HTTP=y by default 2025-09-26 01:14:23 +02:00
FreddleSpl0it
9133b9899c
Merge pull request #6764 from patschi/tools-install-clear-msg
Clearer message to install required tool, e.g. jq
2025-09-25 09:00:41 +02:00
FreddleSpl0it
701c9fb1b4
Merge pull request #6772 from patschi/update-issue-template
Update GitHub's issue template
2025-09-25 08:53:18 +02:00
Patrik Kernstock
eabd22188b Re-intend checkboxes 2025-09-24 21:20:48 +02:00
Patrik Kernstock
7028619742 Update GitHub's issue template 2025-09-24 21:17:29 +02:00
Patrik Kernstock
c915bf2ee2 Add docs link to get_installed_tools() message 2025-09-24 19:06:47 +02:00
milkmaker
011edd5ac9
[Web] Updated lang.si-si.json (#6771)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-24 17:36:09 +02:00
FreddleSpl0it
7ba3de4ced
Merge pull request #6767 from mailcow/fix/rename-phpsessid
[Web] Rename PHP Cookie to MCSESSID
2025-09-23 12:41:01 +02:00
FreddleSpl0it
8ead77083f
[Web] Rename PHP Cookie to MCSESSID 2025-09-23 12:39:48 +02:00
FreddleSpl0it
b2774fb50b
Merge pull request #6766 from mailcow/fix/samesite-cookie
[Web] set cookie SameSite attribute to Lax
2025-09-23 12:36:11 +02:00
FreddleSpl0it
4440bd46ad
[Web] set cookie SameSite attribute to Lax 2025-09-23 12:24:25 +02:00
FreddleSpl0it
28985973eb
[Web] Revert - allow "*" as wildcard domain 2025-09-23 10:07:33 +02:00
Christian 🦄
f2c4697ca3
Fixed typo in lang de-de (#6765) 2025-09-22 22:45:54 +01:00
Patrik Kernstock
383b5affb5 More clearer message to install required tool 2025-09-22 19:49:31 +02:00
FreddleSpl0it
ed4dcff63b
[Web] allow "*" as wildcard domain 2025-09-22 14:42:14 +02:00
FreddleSpl0it
caca32bbba
Merge pull request #6759 from mailcow/fix/6720
[Web] Allow wildcard subdomains for MTA-STS
2025-09-22 14:20:36 +02:00
FreddleSpl0it
d31e74c778
Merge pull request #6760 from mailcow/fix/6739
[Web] Remove Port from HTTP_HOST
2025-09-22 14:20:15 +02:00
FreddleSpl0it
6c00e29276
Merge pull request #6762 from mailcow/fix/6740
[Nginx] do not invert ENABLE_IPV6
2025-09-22 14:19:57 +02:00
FreddleSpl0it
9940c503a2
[Nginx] do not invert ENABLE_IPV6 2025-09-22 14:16:42 +02:00
FreddleSpl0it
4b2862cb3c
[Web] Remove Port from HTTP_HOST 2025-09-22 14:07:17 +02:00
FreddleSpl0it
a36485f0f1
[Web] Allow wildcard subdomains for MTA-STS 2025-09-22 13:55:18 +02:00
FreddleSpl0it
78168ee80a
Merge pull request #6758 from mailcow/feat/sogo-url-encryption
[SOGo][Web] SOGo URL Encryption support
2025-09-22 13:32:58 +02:00
FreddleSpl0it
610609378f
[SOGo][Web] Set URL encryption key in mailcow.conf 2025-09-22 12:58:05 +02:00
FreddleSpl0it
260906e350
[SOGo][Web] Enable SOGo URL Encryption 2025-09-22 12:28:09 +02:00
milkmaker
2891bbf82a
Translations update from Weblate (#6749)
* [Web] Updated lang.cs-cz.json

Co-authored-by: Filip Hajny <filip@hajny.net>

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

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

---------

Co-authored-by: Filip Hajny <filip@hajny.net>
Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
2025-09-16 18:24:12 +02:00
milkmaker
eb26bcbc94
Translations update from Weblate (#6743)
* [Web] Updated lang.zh-cn.json

Co-authored-by: Easton Man <me@eastonman.com>

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

[Web] Updated lang.si-si.json

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: Easton Man <me@eastonman.com>
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-09-13 21:41:59 +02:00
FreddleSpl0it
ef0f366d1c
Merge pull request #6738 from mailcow/staging
Update 2025-09b
2025-09-12 11:29:26 +02:00
patr_
84e230de8f
[Nginx] fix: Disable IPv6 support in Nginx configuration (#6736)
Co-authored-by: patr_ <patbernh@gmail.com>
2025-09-12 11:17:18 +02:00
FreddleSpl0it
f67a12d157
Merge pull request #6726 from mailcow/fix/6135
[Web] remove unused bcc dest column from alias table
2025-09-11 13:50:25 +02:00
FreddleSpl0it
34b48eedfc
Merge pull request #6727 from mailcow/fix/6423
[SOGo] Drop deprecated `sogo_update_password` sql trigger if it still exists
2025-09-11 13:50:05 +02:00
FreddleSpl0it
0d900d4fc8
[SOGo] Drop deprecated sogo_update_password sql trigger if it still exists 2025-09-11 11:01:50 +02:00
FreddleSpl0it
642ac6d02c
[Web] remove unused bcc dest column from alias table 2025-09-11 10:34:35 +02:00
DerLinkman
4db1569c93
Squashed commit of the following:
commit 94c1a6c4e1
Author: DerLinkman <niklas.meyer@servercow.de>
Date:   Wed Sep 10 16:20:58 2025 +0200

    scripts:  ipv6_controller improvement + fix modules handling (#6722)

    * Fix subscript handling for modules

    * ipv6: detect case when link local is present

    * v6-controller: removed fixed-cidr for docker 28+
2025-09-10 16:22:19 +02:00
DerLinkman
94c1a6c4e1
scripts: ipv6_controller improvement + fix modules handling (#6722)
* Fix subscript handling for modules

* ipv6: detect case when link local is present

* v6-controller: removed fixed-cidr for docker 28+
2025-09-10 16:20:58 +02:00
FreddleSpl0it
7ce3b0faed
Merge pull request #6719 from mailcow/staging
Update 2025-09
2025-09-10 11:18:17 +02:00
FreddleSpl0it
262fe04286
change MAJOR_VERSIONS 2025-08 to 2025-09 2025-09-10 11:17:34 +02:00
FreddleSpl0it
b1c088a57f
Merge pull request #6718 from mailcow/staging
Update 2025-09
2025-09-10 11:05:09 +02:00
FreddleSpl0it
1c438330c6
[postfix-tlspol] build with NOOPT=1 for wider CPU compatibility 2025-09-10 10:14:37 +02:00
FreddleSpl0it
8cb25709ae
[Clamd] update to 1.71 2025-09-10 08:23:22 +02:00
FreddleSpl0it
221f2989b0
Merge pull request #6698 from mailcow/6644_clamd-tmp-folder-naming-change
Changed clamavs tmp folder structure
2025-09-09 13:12:54 +02:00
FreddleSpl0it
3d05207bc7
Merge pull request #6717 from mailcow/fix/6664
[Rspamd] only recreate external_services.conf file if it was deleted
2025-09-09 12:53:03 +02:00
FreddleSpl0it
8c8497d885
[Rspamd] only recreate external_services.conf file if it was deleted 2025-09-09 12:50:19 +02:00
FreddleSpl0it
56d083ced4
Merge pull request #6682 from psuet/fix/imapsync
fix: imapsync gets correct timeouts from imapsync_runner.pl
2025-09-09 12:34:02 +02:00
FreddleSpl0it
a90b3544a7
Merge pull request #6651 from psuet/fix/php-warnings
Fix multiple PHP Warnings present in "stock" installation
2025-09-09 12:27:14 +02:00
FreddleSpl0it
08aea7fb26
Merge pull request #6716 from mailcow/fix/6610
Prevent user login if protocol access has been disabled
2025-09-09 12:16:09 +02:00
FreddleSpl0it
13f7f9830b
Prevent user login if protocol access has been disabled 2025-09-09 12:11:19 +02:00
FreddleSpl0it
2f75039194
Merge pull request #6715 from mailcow/fix/version-tag
[Web] Only include mailcow_info in JS when mailcow_cc_username is set
2025-09-09 11:10:25 +02:00
FreddleSpl0it
1e192e14f4
[Web] Only include mailcow_info in JS when mailcow_cc_username is set 2025-09-09 11:09:09 +02:00
FreddleSpl0it
9cd1f931fc
Merge pull request #6714 from mailcow/fix/domain-wide-footer
[RSPAMD] Add boundary if present when applying domain-wide footer
2025-09-09 10:57:18 +02:00
FreddleSpl0it
8d7235b535
[RSPAMD] Add boundary if present when applying domain-wide footer 2025-09-09 10:52:19 +02:00
FreddleSpl0it
8446abd484
Merge pull request #6713 from mailcow/feat/internal-alias
[Rspamd][Web] Internal alias support
2025-09-09 10:45:56 +02:00
FreddleSpl0it
f67c0530f5
[Rspamd][Web] Internal alias support 2025-09-09 10:37:54 +02:00
Dmitriy Alekseev
06db1d6a72
[Rspamd] Do not increment rate limit for emails from user to himself (#6706)
* [Rspamd] Do not increment rate limit for emails from user to himself

* Lowercase username and recipient address for comparison

Normalize username and recipient address comparison to lowercase.
2025-09-05 03:37:59 +02:00
renovate[bot]
81775ab4d5
chore(deps): update actions/stale action to v10 (#6708)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 18:02:11 +02:00
DerLinkman
34877ecf9c
watchdog: added postfix-tlspol check (#6691) 2025-09-03 08:18:04 +02:00
milkmaker
dbde144014
update postscreen_access.cidr (#6703) 2025-09-03 08:14:14 +02:00
DerLinkman
5361a4a4ee
updated sponsors in Readme.md 2025-09-01 12:32:27 +02:00
milkmaker
0997548d7f
Translations update from Weblate (#6699)
* [Web] Updated lang.de-de.json

Co-authored-by: Peter <magic@kthx.at>

* [Web] Updated lang.en-gb.json

Co-authored-by: Peter <magic@kthx.at>

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

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

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

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

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>

---------

Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-08-29 22:38:06 +02:00
Sándor
921de02a2b
Update lang.hu-hu.json (#6697)
Extended Hungarian translation
2025-08-29 18:32:17 +02:00
Peter
48e90a72dc
Changed clamavs tmp folder structure 2025-08-29 18:27:34 +02:00
renovate[bot]
c0b7a98e6c
chore(deps): update actions/checkout action to v5 (#6671)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-29 18:23:56 +02:00
renovate[bot]
6dc90186f9
chore(deps): update dependency krakjoe/apcu to v5.1.27
Signed-off-by: milkmaker <milkmaker@mailcow.de>
2025-08-29 16:22:28 +00:00
DerLinkman
0b0a65a3f3
web: rename login placeholder for mailbox to email address (#6693) 2025-08-28 17:02:16 +02:00
DerLinkman
6c5d82c4df
expanded postscreen whitelist with modern freemailers + included checks.mailcow.email 2025-08-28 14:06:17 +02:00
maxi322
5e66ffa366
watchdog: use dig instead of check_dns (#6685)
* watchdog: use dig instead of check_dns

check_dns is slower and uses more system resources,
dig wrapped in a script is a more performant approach and uses
fewer system resources

* added debug mode + compose image bump

---------

Co-authored-by: maxi322 <maxi322@users.noreply.github.com>
Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-08-28 12:56:37 +02:00
Sajjad hassanzadeh
4d88e19106
Feat/prometheus-exporter : Add prometheus exporter and grafana dashboard for mailcow. (#6314)
* add : readme for prometheus exporter configs

* add : grafana dashboard json file

* add: prometheus exporter service on docker-compose.override.yml

* migrate: doc files into docs.mailcow.email project

* add : security configs in prometheus exporter compose file

* add : explain more in my comment part in prometheus override compose file

* remove : mailcow dockerized docs

---------

Co-authored-by: Saji <saji@abrnoc>
2025-08-28 12:36:43 +02:00
DerLinkman
29e28b47ed
compose: add depends on for postfix-tlspol 2025-08-28 10:20:21 +02:00
DerLinkman
1cb38bacdb
Postfix: Split TLSPol companion app into separate container (#6688)
* postfix: split postfix-tlspol service into new container

* postfix-tls-pol: added debug mode

* pf-tlspol: removed obsoleted standalone conf from Dockerfiles

* pf-tlspol: use git instead of wget
2025-08-28 10:18:18 +02:00
DerLinkman
169aafec50
compose: fix dovecot image tag 2025-08-27 14:09:29 +02:00
DerLinkman
3826c4b5be
fix postfix tlspol missing folders for config 2025-08-26 10:10:16 +02:00
DerLinkman
e1410baaeb
fix gitignore 2025-08-26 09:58:18 +02:00
DerLinkman
c39712af67
pf/php: add mta-sts support (outbound) (#6686)
* added mta-sts-resolver into postfix config + daemon

* [Web] Add MTA-STS support

* [Web] Fix mta-sts server_name

* updated .gitignore

* [ACME] fetch cert for mta-sts subdomain

* [Web] change MTA-STS id to human-readable timestamp

* [Web] Remove MTA-STS version STSv2

* [Web] Fix MTA-STS DNS check

* [Web] add max_age limit for MTA-STS policy

* Added tooltips and info texts to mta-sts webui page

* postfix: replace mta-sts-resolver with postfix-tlspol

---------

Co-authored-by: FreddleSpl0it <75116288+FreddleSpl0it@users.noreply.github.com>
2025-08-26 09:57:05 +02:00
Paul Sütterlin
53c35493a5 fix: imapsync gets correct timeouts
Previously imapsync only attached the timeout1 / timeout2 arguments if the argument was negative (which is not even possible). Now the argument is added for every positive number.

Fixes #6590
2025-08-21 18:36:01 +00:00
renovate[bot]
af871fdacb
chore(deps): update devops-infra/action-pull-request action to v0.6.1 (#6676)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-13 18:47:28 +02:00
DerLinkman
2b93b59cdd
db: change qhash varchar to 64 instead of 255 2025-08-06 16:11:23 +02:00
Christoph Lechleitner
2b2da1679e
[DB][Web] optimize qhandler by keeping SHA2 in new column qhash (#6556)
* [DB][Web] optimize qhandler by keeping SHA2(id+qid) in new column quarantine.qhash, for feature #6555, might also help with #6361

* rspamd: only add qhash to new entries while passing rspamd not all existing

* compose: bump dovecot image + push to registry

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-08-06 16:02:25 +02:00
CodeShell
8cdb0b869e
fixed favicon.png (#6570) 2025-08-06 09:42:43 +02:00
FreddleSpl0it
1e42b8dd21
[Web] Add delimiter_action to mailbox and mailbox_template add/edit admin forms (#6620) 2025-08-06 09:40:47 +02:00
Dmitriy Alekseev
842cb235b6
[Rspamd] Fill module name for set_pre_result actions (#6630)
* [Rspamd] Fill module name for postmaster handler

* Update rspamd.local.lua
2025-08-06 09:38:22 +02:00
DerLinkman
e91d678bd1
fix docker version detection 2025-08-06 09:36:05 +02:00
DerLinkman
ef5739c32f
add 2025-08 as breaking major release 2025-08-06 08:39:21 +02:00
DerLinkman
88bf9b02e1
core: modules splitting + ipv6 nat rewrite (#6634)
* ipv6: added ipv6 detection + removed ip6 nat container

* nginx: renamed DISABLE_IPv6 to ENABLE_IPV6 to align

* initial commit for script overhauls

* rewrite to scripts after testing (improved error handling)

* fixed missing fi in update.sh

* fixed/added comments for modules

* fix broken EXIT_CODE var handling

* added jq as dependancy

* fixed docker version check for daemon

* improved _modules handling while running

* reintegrated module loading (update.sh)

* added error handling for blank daemon.json

* adapted removal of ACME_CONTACT for nightly

* move detect_major_update func to core submodule

* removed unnecessary message on every call of function

* Update _modules/scripts/new_options.sh

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update _modules/scripts/core.sh

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* improve ENABLE_IPV6 check in nginx bootstrap

* improve detection of ENABLE_IPV6

* ip6_controller: moved docker major detection upwards

* Update _modules/scripts/new_options.sh

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update _modules/scripts/new_options.sh

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* reuse DOCKER_MAJOR Variable in ip6_controller

* fix some smaller typos in update.sh

* smaller bugfixes in submodules

* completely remove ACME_CONTACT Variable

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-06 08:36:40 +02:00
renovate[bot]
3803b5d351
Update dependency php-memcached-dev/php-memcached to v3.3.0 (#6638)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 08:35:14 +02:00
renovate[bot]
14d58c8163
Update dependency phpredis/phpredis to v6.2.0 (#6639)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 08:34:53 +02:00
renovate[bot]
728fcdb375
Update dependency tianon/gosu to v1.17 (#6640)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 08:34:30 +02:00
renovate[bot]
1fc36263dc
chore(deps): update dependency krakjoe/apcu to v5.1.26 (#6656)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 08:33:41 +02:00
Markus Machatschek
69420113f7
rspamd: update rspamd to 3.12.1 (#6643)
* rspamd: update rspamd to 3.12.1

* compose: correct rspamd tag + pushed image

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-08-06 08:33:11 +02:00
DerLinkman
360fe03497
sogo: update to 5.12.3 2025-08-05 16:01:47 +02:00
milkmaker
7557802933
[Web] Updated lang.de-de.json (#6661)
Co-authored-by: whitehotaru <whitehotaru@posteo.net>
2025-08-05 06:37:55 +02:00
milkmaker
2e9ba1e9b3
update postscreen_access.cidr (#6660) 2025-08-05 00:37:47 +02:00
milkmaker
795bcdc5d2
[Web] Updated lang.ru-ru.json (#6654) 2025-07-27 19:30:10 +02:00
Paul Sütterlin
ad9b328ed5 fix: Undefined array key "pending_tfa_methods" in
/web/inc/footer.inc.php
on line
29
2025-07-26 01:12:48 +00:00
Paul Sütterlin
3d5b57889a fix: Empty App Links
The return value of the function caused a warning
in header.inc.php:42 if no additional links were set.

header.inc.php is the only caller of this function,
thus it is safe to return an empty array here.
2025-07-26 01:08:28 +00:00
Paul Sütterlin
6b8e981bdc fix: Only use HTTP_ORIGIN if it is sent. 2025-07-26 01:06:24 +00:00
milkmaker
2f1eb4b004
Translations update from Weblate (#6649)
* [Web] Updated lang.es-es.json

Co-authored-by: sariegos <informatica@sariegos.es>

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

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>

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

[Web] Updated lang.pt-br.json

Co-authored-by: Bruno Zouein Pereira <zopostyle@gmail.com>
Co-authored-by: Peter <magic@kthx.at>

---------

Co-authored-by: sariegos <informatica@sariegos.es>
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: Bruno Zouein Pereira <zopostyle@gmail.com>
Co-authored-by: Peter <magic@kthx.at>
2025-07-24 21:42:00 +02:00
milkmaker
3ee3d7d969
Translations update from Weblate (#6637)
* [Web] Language file updated by 'Cleanup translation files' addon

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

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

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

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

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

---------

Co-authored-by: Anonymous <noreply@weblate.org>
2025-07-16 20:31:58 +02:00
Denis Evers
95eb350f15
[netfilter] fix negative timer, no unbanning of IPs (#6575)
* [netfilter] added debug logs and updated autopurge

* updated "Allow/Blacklist" terms

* netfilter: bumped compose version

* netfilter: changed black/whitelist terms in code

---------

Co-authored-by: Denis Evers <git@evers.sh>
Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-07-16 10:08:55 +02:00
Peter
1e5fcfe392
Bulgarian language added (#6623) 2025-07-16 09:29:35 +02:00
FreddleSpl0it
527f27d249
Merge pull request #6632 from mailcow/staging
Update 2025-07
2025-07-15 07:48:37 +02:00
FreddleSpl0it
02557b2098
Merge pull request #6631 from mailcow/fix/jinja2-rendering
[Dovecot] Use Jinja2 sandbox for rendering quota and quarantine notif…
2025-07-15 07:38:20 +02:00
milkmaker
4c7a9ed195
Translations update from Weblate (#6629)
* [Web] Updated lang.fr-fr.json

Co-authored-by: Tagada <githubtagada@cant.at>

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

Co-authored-by: robsonek <admin@licytant.pl>

---------

Co-authored-by: Tagada <githubtagada@cant.at>
Co-authored-by: robsonek <admin@licytant.pl>
2025-07-13 16:20:40 +02:00
milkmaker
d5b30a7a08
[Web] Updated lang.pt-pt.json (#6622)
Co-authored-by: luiscanato <luiscanato@gmail.com>
2025-07-06 16:42:58 +02:00
FreddleSpl0it
b7acef4d9d
Merge pull request #6617 from mailcow/fix/acme-contact
[ACME] Remove deprecated ACME_CONTACT variable
2025-07-03 12:49:17 +02:00
FreddleSpl0it
fc43c26c48
Remove obsolete ACME_CONTACT option and related comments from mailcow.conf 2025-07-03 12:38:28 +02:00
milkmaker
b12ce1eacd
update postscreen_access.cidr (#6611) 2025-07-02 17:12:06 +02:00
FreddleSpl0it
ec6dbb099a
[ACME] Remove deprecated ACME_CONTACT variable 2025-07-02 10:37:23 +02:00
FreddleSpl0it
2fbbbbe9a9
[Dovecot] Use Jinja2 sandbox for rendering quota and quarantine notifications 2025-07-02 08:59:29 +02:00
milkmaker
1e4f3c55d8
[Web] Updated lang.pt-pt.json (#6614)
Co-authored-by: luiscanato <luiscanato@gmail.com>
2025-07-01 17:14:24 +02:00
milkmaker
a0f5454c2a
[Web] Updated lang.si-si.json (#6609)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-06-29 18:12:59 +02:00
milkmaker
4e7adacda9
[Web] Updated lang.si-si.json (#6601)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-06-24 20:06:38 +02:00
milkmaker
4c64cf18a6
[Web] Updated lang.si-si.json (#6600)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-06-23 21:58:50 +02:00
DerLinkman
8a89f5c685
updated Readme (sponsors) 2025-06-23 10:36:49 +02:00
milkmaker
cc0e4fee9d
[Web] Updated lang.si-si.json (#6599)
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
2025-06-20 18:14:22 +02:00
milkmaker
5861c9af29
[Web] Updated lang.cs-cz.json (#6589) 2025-06-16 19:45:13 +02:00
milkmaker
dd475c0ab3
update postscreen_access.cidr (#6573) 2025-06-12 13:14:20 +02:00
milkmaker
407e9d3584
Translations update from Weblate (#6582)
* [Web] Updated lang.en-gb.json

[Web] Updated lang.en-gb.json

Co-authored-by: Filip Hajny <filip@hajny.net>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

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

Co-authored-by: Filip Hajny <filip@hajny.net>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

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

[Web] Updated lang.si-si.json

Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

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

Co-authored-by: Stefano <stefano.vassena@gmail.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: Filip Hajny <filip@hajny.net>
Co-authored-by: Matjaž Tekavec <matjaz@moj-svet.si>
Co-authored-by: Stefano <stefano.vassena@gmail.com>
2025-06-12 13:13:57 +02:00
DerLinkman
d4f899b091
compose: add selinux label to mysql-socket-vol to prevent "access denied" (#6560) 2025-05-26 11:35:34 +02:00
milkmaker
372923ae2f
[Web] Updated lang.zh-cn.json (#6552)
Co-authored-by: Easton Man <me@eastonman.com>
2025-05-19 13:36:15 +02:00
milkmaker
3bd01190bf
Translations update from Weblate (#6548)
* [Web] Updated lang.fr-fr.json

[Web] Updated lang.fr-fr.json

Co-authored-by: Samuel F. <20537389+samuelfranzini@users.noreply.github.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

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

[Web] Updated lang.ru-ru.json

[Web] Updated lang.ru-ru.json

Co-authored-by: Habetdin <15926758+Habetdin@users.noreply.github.com>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

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

Co-authored-by: Easton Man <me@eastonman.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

* [Web] Updated lang.en-gb.json

Co-authored-by: Habetdin <15926758+Habetdin@users.noreply.github.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: Samuel F. <20537389+samuelfranzini@users.noreply.github.com>
Co-authored-by: Habetdin <15926758+Habetdin@users.noreply.github.com>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: Easton Man <me@eastonman.com>
2025-05-16 23:07:25 +02:00
FreddleSpl0it
1994b9895b
Merge pull request #6537 from mailcow/staging
Update 2025-05_2
2025-05-13 10:16:30 +02:00
FreddleSpl0it
03d979c089
[Web] Fix get custom_login 2025-05-13 10:14:58 +02:00
FreddleSpl0it
798e6a4c00
Merge pull request #6535 from mailcow/staging
Update 2025-05
2025-05-13 09:58:32 +02:00
FreddleSpl0it
ffa2933873
increase Olefy, Rspamd and Watchdog docker images 2025-05-13 09:49:53 +02:00
FreddleSpl0it
7f47a3f00e
Merge pull request #6530 from mailcow/feat/auto-create-user-option
[Web] Add identity_provider option to disable auto-creation of users …
2025-05-12 13:24:34 +02:00
FreddleSpl0it
1bcab9a9a5
Merge pull request #6518 from seclution/patch-2
fix: typo in default_template
2025-05-12 13:08:07 +02:00
FreddleSpl0it
1b2f424edc
[Web] Add identity_provider option to disable auto-creation of users on login 2025-05-12 12:20:23 +02:00
krzsztf1
486b297409
Fix typo in rspamd rule in composites.conf (#6515)
Co-authored-by: Krzysztof Nowak <k.nowak@intalio.pl>
2025-05-09 18:33:19 +02:00
FreddleSpl0it
75d7f06b25
Merge pull request #6521 from mailcow/feat/login-quicklinks
[Web] Add quick links to other login pages and mailcow login toggle
2025-05-09 15:24:36 +02:00
FreddleSpl0it
ea0944d743
[Web] Add quick links to other login pages and option to disable mailcow login form 2025-05-09 15:13:44 +02:00
Kai Biebel
cb6ffe65c8
fix: typo in default_template 2025-05-09 11:24:49 +02:00
FreddleSpl0it
580dabd276
Merge pull request #6509 from mailcow/update/postscreen_access.cidr
[Postfix] update postscreen_access.cidr
2025-05-09 10:02:50 +02:00
FreddleSpl0it
846862aa80
Merge pull request #6506 from mrclschstr/staging
[Fix] Moving mails by functions.quarantine.inc.php to inbox failed
2025-05-09 10:00:56 +02:00
FreddleSpl0it
e7a1f24c78
Merge pull request #6483 from PseudoResonance/oauth2-redirect-extra-domain
Allow additional domains in OAuth2 redirect URLs
2025-05-09 09:48:08 +02:00
FreddleSpl0it
8ff0e029f0
Merge pull request #6398 from marvinruder/feat/skip-olefy
feat(olefy): Allow disabling Olefy
2025-05-08 15:34:51 +02:00
FreddleSpl0it
0680b21938
Merge pull request #6342 from mailcow/fix/6339
rspamd: remove .info from fishy tlds (default)
2025-05-08 14:36:39 +02:00
FreddleSpl0it
0c8e7bfeca
Merge pull request #6376 from NickBouwhuis/staging
feat/replace bgp.he.net with bgp.tools
2025-05-08 14:31:57 +02:00
FreddleSpl0it
badcd27b93
Merge pull request #6485 from Habetdin/fix/update-legacy-links
Update legacy admin dashboard links
2025-05-08 14:16:44 +02:00
FreddleSpl0it
7d3ef3d67f
Merge pull request #6487 from mailcow/fix/6469
[Web] Fix force password update at next login
2025-05-08 14:04:56 +02:00
FreddleSpl0it
5b89e253a6
Merge remote-tracking branch 'origin/staging' into fix/6469 2025-05-08 13:50:50 +02:00
FreddleSpl0it
a90f4c2a2e
Merge pull request #6495 from Kuehn-Andreas/fix/redirection-after-login-with-skip-SOGo
Check if skip_sogo is not set before redirecting to SOGo
2025-05-08 11:53:47 +02:00
FreddleSpl0it
db7b917944
Merge pull request #6488 from mailcow/fix/6470
[Dovecot] Fix EAS login issue with app passwords and improve auth cache handling in Dovecot
2025-05-08 11:49:55 +02:00
FreddleSpl0it
401b744808
[Dovecot] return PASSDB_RESULT_PASSWORD_MISMATCH instead of PASSDB_RESULT_INTERNAL_FAILURE 2025-05-08 11:38:29 +02:00
milkmaker
0c83255573 update postscreen_access.cidr 2025-05-01 00:21:11 +00:00
Marcel Schuster
d55f0fc366
Update functions.quarantine.inc.php
Fix regex for quarantine release functions
2025-04-29 22:14:43 +02:00
milkmaker
06b3ba91a0
[Web] Updated lang.zh-cn.json (#6502)
Co-authored-by: Easton Man <me@eastonman.com>
2025-04-27 18:47:08 +02:00
DerLinkman
aa4125fe62
sogo: enabled SOGoEnableMailCleaning per default 2025-04-23 15:13:52 +02:00
Andreas Kühn
d8c6ed9191 Check if skip_sogo is not set before redirecting to SOGo 2025-04-22 14:23:33 +02:00
FreddleSpl0it
cb47fa406f
[Web] Fix force password update at next login 2025-04-15 13:48:13 +02:00
FreddleSpl0it
c4d0f35008
[Dovecot] Fix EAS login and improve logging 2025-04-15 10:49:56 +02:00
Habetdin
0d3e8dd738 Update legacy admin dashboard links 2025-04-13 16:09:47 +03:00
PseudoResonance
692355a08a
Allow additional domains in OAuth2 redirect URLs 2025-04-12 06:24:37 -07:00
renovate[bot]
a370499aaa
Update dependency Imagick/imagick to v3.8.0 (#6480) 2025-04-11 08:30:54 +02:00
milkmaker
84f67d6608
Translations update from Weblate (#6478)
* [Web] Updated lang.de-de.json

Co-authored-by: Jonas Leiner <jonas.leiner.123@gmail.com>

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

Co-authored-by: Neuronnexion <support@nnx.com>

---------

Co-authored-by: Jonas Leiner <jonas.leiner.123@gmail.com>
Co-authored-by: Neuronnexion <support@nnx.com>
2025-04-10 17:16:44 +02:00
milkmaker
4ac839cf49
Translations update from Weblate (#6463)
* [Web] Updated lang.hu-hu.json

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

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

[Web] Updated lang.lv-lv.json

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

* [Web] Added lang.bg-bg.json

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

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

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

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

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

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

Co-authored-by: Easton Man <me@eastonman.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
Co-authored-by: Peter <magic@kthx.at>
Co-authored-by: Easton Man <me@eastonman.com>
2025-04-10 17:11:30 +02:00
FreddleSpl0it
b96a5b1efd
[Web] Fix AJAX urls to absolute path 2025-04-09 08:07:46 +02:00
FreddleSpl0it
766c5e8580
[Dovecot] Ignore app passwords protocol access on SOGo request 2025-04-09 08:02:30 +02:00
FreddleSpl0it
3f493e043d
Merge pull request #6468 from mailcow/staging
Update 2025-03b
2025-04-07 09:09:39 +02:00
FreddleSpl0it
3ddad9dee8
Merge pull request #6460 from mailcow/ui/improve-ldap-ssl-labels
[Web] Improve clarity of LDAP SSL/TLS settings
2025-04-07 08:58:19 +02:00
FreddleSpl0it
2c10c39bc4
[Web] Update 2FA Info tooltip 2025-04-07 08:06:43 +02:00
FreddleSpl0it
0eb8f38792
[Web] Update LDAP SSL/TLS tooltips 2025-04-07 07:59:43 +02:00
FreddleSpl0it
402bf53a5c
[Web] Improve clarity of LDAP SSL/TLS settings 2025-04-04 13:18:42 +02:00
FreddleSpl0it
428a59dd3f
Merge branch 'fix/dovecot-lua-timeout' into staging 2025-04-03 14:18:33 +02:00
FreddleSpl0it
153890b283
Merge pull request #6439 from mailcow/fix/6430
[SOGo] Use JS for mailcow logout
2025-04-03 12:57:24 +02:00
FreddleSpl0it
a741c2ba4a
Merge pull request #6426 from sardaukar/fix/typo-on-backup-and-restore-script
Fix tiny typo
2025-04-03 12:41:46 +02:00
FreddleSpl0it
741e5c719f
Merge pull request #6438 from mailcow/fix/6405
[Netfilter] Downgrade to 1.61
2025-04-03 12:39:48 +02:00
FreddleSpl0it
34e4f93db9
Merge pull request #6451 from mailcow/fix/6437
[Web] Fix transport routing test
2025-04-03 12:38:43 +02:00
FreddleSpl0it
3758135dc3
Merge pull request #6450 from mailcow/fix/sasl_logs
Fix sasl_logs
2025-04-03 12:38:13 +02:00
FreddleSpl0it
6794e6ff43
[Dovecot] Add service for authentication cache_key 2025-04-03 12:31:43 +02:00
FreddleSpl0it
62f816e64a
[Web] Check app password before user password on web login 2025-04-03 12:19:04 +02:00
FreddleSpl0it
e65478076b
[Web] Prevent user sync for mismatched authsource 2025-04-03 11:58:35 +02:00
FreddleSpl0it
ceeabded73
[Web] Fix transport routing test 2025-04-03 10:29:47 +02:00
FreddleSpl0it
805634f9a9
Fix sasl_logs 2025-04-03 10:19:30 +02:00
DerLinkman
a92832d115
update README.md to include first 50 and 100$ monthly sponsors 2025-04-02 14:39:24 +02:00
milkmaker
4c5f485587
update postscreen_access.cidr (#6443) 2025-04-01 22:00:11 +02:00
FreddleSpl0it
db3a577ae3
[Web] Fix password reset 2025-04-01 16:39:15 +02:00
FreddleSpl0it
e452917de9
[SOGo] Show mailcow Settings Button to SOGoSuperUsers 2025-03-31 12:14:43 +02:00
FreddleSpl0it
f37961b7d0
[SOGo] Use JS for mailcow logout 2025-03-31 11:32:01 +02:00
FreddleSpl0it
0157cbddaf
[Netfilter] Downgrade to 1.61 2025-03-31 10:36:20 +02:00
Bruno Antunes
65d872cc14
Fix tiny typo 2025-03-27 20:21:25 +00:00
FreddleSpl0it
4ad2422810
[Dovecot] Increase Timeout for HTTP Login Request 2025-03-27 16:52:15 +01:00
FreddleSpl0it
2c47145dee
Merge pull request #6419 from mailcow/staging
Update 2025-03a
2025-03-27 09:19:29 +01:00
FreddleSpl0it
9b41b24522
Merge pull request #6402 from marvinruder/fix/long-dropdown-label
fix(ui): Swap translations for oversized dropdown
2025-03-27 08:07:51 +01:00
FreddleSpl0it
1c9d80f554
Merge pull request #6406 from mailcow/fix/6392
[Web] Fix SOGo access after Passwordless auth
2025-03-27 07:42:07 +01:00
FreddleSpl0it
7172cad257
Merge pull request #6407 from mailcow/fix/6396
[Web] Fix oauth2 redirect after user login
2025-03-27 07:41:08 +01:00
FreddleSpl0it
b550c6f88e
Merge pull request #6408 from mailcow/fix/6373
[Swagger] Fix type property for /api/v1/add/bcc endpoint
2025-03-27 07:40:19 +01:00
FreddleSpl0it
5baf9eb375
Merge pull request #6409 from mailcow/fix/6372
[Web] Check if mailbox is active before renaming
2025-03-27 07:40:03 +01:00
FreddleSpl0it
4eb89f67ed
Merge pull request #6410 from mailcow/fix/6395
[Web] Use absolute paths for flag SVGs
2025-03-27 07:39:34 +01:00
FreddleSpl0it
efdc798238
Merge pull request #6411 from mailcow/fix/6340
[Nginx] Move conf.d include before SNI vhosts
2025-03-27 07:39:06 +01:00
Marvin A. Ruder
8408b82e9c
Add new option with description to existing configuration files during next update
* Remove Olefy settings file from rspamd configuration
* Have rspamd container generate Olefy settings file at startup if not disabled

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>
2025-03-26 17:17:13 +01:00
FreddleSpl0it
65fb4c2aa8
[Nginx] Move conf.d include before SNI vhosts 2025-03-26 13:04:43 +01:00
FreddleSpl0it
a5ca3353da
[Web] Use absolute paths for flag SVGs 2025-03-26 10:59:56 +01:00
FreddleSpl0it
95aa35e133
[Web] Check if mailbox is active before renaming 2025-03-26 10:10:22 +01:00
FreddleSpl0it
21b11ed999
[Swagger] Fix type property for /api/v1/add/bcc endpoint 2025-03-26 09:24:03 +01:00
FreddleSpl0it
348107dae8
[Web] Fix oauth2 redirect after user login 2025-03-26 09:13:05 +01:00
FreddleSpl0it
fcb1b29c89
[Web] Fix SOGo access after Passwordless auth 2025-03-26 08:32:34 +01:00
Marvin A. Ruder
05fc4f7aba
fix(ui): Swap translations for oversized dropdown
* Fix other typos
* Fixes #6400

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>
2025-03-25 21:24:22 +01:00
Marvin A. Ruder
cd3b1ab828
Allow disabling Olefy
* Fixes #6389

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>
2025-03-25 20:24:33 +01:00
FreddleSpl0it
c3c68360dc
Merge pull request #6391 from mailcow/staging
Update 2025-03
2025-03-25 08:10:50 +01:00
FreddleSpl0it
d584dd387e
Merge pull request #6390 from mailcow/nightly
Nightly 2025-03 to staging
2025-03-25 07:36:20 +01:00
FreddleSpl0it
986b0afbfa
ldap-sync: Fix template selection 2025-03-24 15:33:42 +01:00
FreddleSpl0it
59d139bc63
Merge branch 'nightly' into staging 2025-03-24 13:39:43 +01:00
FreddleSpl0it
ad5f07f077
update.sh: add 2025-03 as major version 2025-03-24 11:47:27 +01:00
FreddleSpl0it
cf2d3c1b4e
Merge branch 'staging' into nightly 2025-03-24 11:38:59 +01:00
FreddleSpl0it
91c82e8a67
Merge pull request #6384 from mailcow/feat/update-components-alp-3.21
os: updated alpine containers to 3.21
2025-03-24 11:30:58 +01:00
FreddleSpl0it
ba7437a8f3
Merge pull request #6380 from mailcow/feat/legacy-switch
Add Legacy Updates
2025-03-20 14:25:13 +01:00
FreddleSpl0it
684256b66e
update.sh: Fix legacy typo 2025-03-20 14:23:28 +01:00
FreddleSpl0it
70ba361583
update.sh: Fix text in legacy update prompt 2025-03-20 14:15:15 +01:00
FreddleSpl0it
94d4817ecb
[Web] Add default_template parameter to edit/identity-provider documentation 2025-03-20 13:38:27 +01:00
FreddleSpl0it
72ced70e33
[Web] Fix mailbox authsource selection 2025-03-20 13:08:42 +01:00
FreddleSpl0it
887b7114a8
Add default template for IdP attribute mapping 2025-03-19 14:35:32 +01:00
Nick Bouwhuis
ceebc56e62 feat/replace bgp.he.net with bgp.tools 2025-03-18 10:08:08 +00:00
FreddleSpl0it
8910135f02
[Web] Add edit/identity-provider Api Documentation 2025-03-17 13:21:28 +01:00
DerLinkman
463e3ab78c
rspamd: update rspamd to 3.11.1 (#6374) 2025-03-14 12:18:59 +01:00
FreddleSpl0it
2a15914324
Fix major update prompt 2025-03-14 11:22:57 +01:00
FreddleSpl0it
e21696ff27
Add error message when mailbox creation fails 2025-03-14 09:36:40 +01:00
FreddleSpl0it
5a7275843a
Add error message when mailbox creation fails 2025-03-14 09:30:33 +01:00
FreddleSpl0it
c93106f9d6
[Web] Fix redirect after renaming mailbox 2025-03-14 09:29:02 +01:00
FreddleSpl0it
43c1597051
[Web] Check if authsource is configured before adding or updating a mailbox 2025-03-14 09:19:39 +01:00
FreddleSpl0it
c3aa4f7418
[Web] Add authsource property to mailbox API Documentation 2025-03-14 09:17:51 +01:00
FreddleSpl0it
cb08132a74
[Web] Fix authentication when mailbox or domain is deactivated 2025-03-13 14:39:03 +01:00
FreddleSpl0it
2596b9d386
[Web] Improve auth logging and language strings 2025-03-12 11:42:14 +01:00
Marvin A. Ruder
062539b7d7
dkim: Add support for 3072 and 4096 bit RSA keys (#6365)
* dkim: Add support for 3072 and 4096 bit RSA keys

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>

* php: added missing ; in dkim function

* php: make 4096 DKIM default

* db: update schema to set dkim 4096 as default

* Revert "db: update schema to set dkim 4096 as default"

This reverts commit 790b40a695.

* Revert "php: make 4096 DKIM default"

This reverts commit 7e643376c7.

---------

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>
Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-03-11 15:30:46 +01:00
DerLinkman
18acbc7a4c
cold-standby: changed texts + removed --no-parallel for pull 2025-03-11 12:35:13 +01:00
DerLinkman
2f93f1d0c5
os: fixes for newer mariadb-client versions (especially on alpine 3.21) 2025-03-10 16:45:57 +01:00
DerLinkman
0860a7503e
os: updated alpine containers to 3.21 2025-03-10 11:56:12 +01:00
renovate[bot]
86df78255d
chore(deps): update dependency composer/composer to v2.8.6 (#5719)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 11:39:19 +01:00
FreddleSpl0it
aac0a900ce
[Web] Fix JSON parsing issue for api requests 2025-03-10 10:49:27 +01:00
milkmaker
03565df48d
[Web] Updated lang.ko-kr.json (#6356)
Co-authored-by: dongsu8142 <dongsu8142@naver.com>
2025-03-07 21:37:31 +01:00
FreddleSpl0it
5f15475b55
[Rspamd] Remove redis.conf from tracking 2025-03-07 15:18:42 +01:00
FreddleSpl0it
25d34b5acf
[Web] Remove default ui help text 2025-03-07 14:52:08 +01:00
FreddleSpl0it
6b165887d8
Merge branch 'staging' into nightly 2025-03-07 13:21:57 +01:00
FreddleSpl0it
82eb3c64cd
[Web] Use SQL password only when authsource is mailcow 2025-03-07 13:15:27 +01:00
FreddleSpl0it
bc21e7fe50
[Web] Separate FIDO2 logins 2025-03-07 13:12:48 +01:00
FreddleSpl0it
6f9c8deab7
[Web] Support old style app links 2025-03-07 09:56:20 +01:00
FreddleSpl0it
8761d8fc47
[Web] Fix app layout issue 2025-03-07 09:54:35 +01:00
milkmaker
0435766c17
[Web] Updated lang.ko-kr.json (#6353)
Co-authored-by: dongsu8142 <dongsu8142@naver.com>
2025-03-05 17:43:37 +01:00
renovate[bot]
79f4cf4021
chore(deps): update docker/build-push-action action to v6 (#6334) 2025-03-05 16:35:46 +01:00
milkmaker
81803836f0
[Web] Updated lang.ko-kr.json (#6350)
Co-authored-by: dongsu8142 <dongsu8142@naver.com>
2025-03-03 22:49:23 +01:00
milkmaker
4bd267515a
update postscreen_access.cidr (#6345) 2025-03-01 13:32:21 +01:00
DerLinkman
70190e5230
rspamd: remove .info from fishy tlds (default) 2025-02-28 15:38:05 +01:00
FreddleSpl0it
a632980871
Merge pull request #6336 from mailcow/staging
Update 2025-02
2025-02-27 11:48:57 +01:00
DerLinkman
5296085189
update.sh: corrected typos inside update.sh 2025-02-27 11:47:08 +01:00
DerLinkman
a4c2cf4c67
scripts: adapted new docker image names to docker_garbage function + removed dup 2025-02-27 11:44:52 +01:00
FreddleSpl0it
2d1ef41d32
Merge pull request #6335 from mailcow/staging
Update 2025-02
2025-02-27 11:05:55 +01:00
Peter
3c9d0c9d57
use ghcr.io for backupimage (#6333)
* use ghcr.io for backup image

* backup script: use renamed script + improved build of image

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-02-27 10:58:23 +01:00
FreddleSpl0it
35a6f81d0d
[Redis] use 7.4.2-alpine image 2025-02-27 09:28:52 +01:00
FreddleSpl0it
4b31c04e3e
Merge pull request #6330 from mailcow/feat/major-update-prompt
Prompt user before applying major updates
2025-02-27 08:15:21 +01:00
FreddleSpl0it
3d9cc2f6dd
add 2025-02 to major versions 2025-02-27 08:14:34 +01:00
DerLinkman
704dd50262
compose: use ghcr.io for new/current mailcow docker images instead of docker hub (#6332) 2025-02-26 15:20:57 +01:00
FreddleSpl0it
8d0c03b2fc
small adjustment for legacy version 2025-02-26 10:39:41 +01:00
FreddleSpl0it
c4a0e370b7
Merge pull request #6155 from PseudoResonance/fix2752
Fix #2752 - Allow domain recipients for address rewrite
2025-02-26 10:01:03 +01:00
FreddleSpl0it
b77ff2f51c
Add switch to legacy version 2025-02-26 09:47:59 +01:00
FreddleSpl0it
787fa49d0c
prompt user before applying major updates 2025-02-25 12:08:21 +01:00
DerLinkman
a6c38590ca
rspamd: upgraded rspamd to 3.11.0-2 (incl. NIXSPAM Removal) (#6328) 2025-02-25 09:23:10 +01:00
PseudoResonance
e52323bf1d
Fix @ prefixing domain rewrite and update localization 2025-02-24 22:36:17 -08:00
PseudoResonance
f15ee39b63
Fix #2752: Domain recipient for address rewrite
(cherry picked from commit 40f6d691d8774d6f813153974f8fe462a8db9ab3)
2025-02-24 22:07:23 -08:00
FreddleSpl0it
fcebe98557
Merge branch 'staging' into nightly 2025-02-24 15:09:36 +01:00
FreddleSpl0it
6ec5e88793
Merge pull request #6309 from mailcow/fix/6308
[Dovecot][Netfilter] Fix dovecot failed login regex
2025-02-24 11:26:06 +01:00
FreddleSpl0it
7d35646342
[Netfilter] adjust dovecot failed login regex 2025-02-24 09:20:41 +01:00
FreddleSpl0it
321965adee
[Netfilter] Fix dovecot password mismatch regex 2025-02-18 15:05:59 +01:00
Peter
7bce5d836b
Move sed cmd to remove discontinued DNSBLs (#6315)
* Move sed cmd to remove discontinued DNSBLs

* compose: bump postfix version

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-02-18 11:20:03 +01:00
FreddleSpl0it
351f4ce787
[Redis] Add support for masterauth via env var 2025-02-18 11:16:06 +01:00
FreddleSpl0it
a567d5dc31
[Nginx] Add support for trusted proxies via env var 2025-02-18 11:03:34 +01:00
DerLinkman
4ac541f671
[Mariadb] Update to 10.11 (LTS) (#5152)
* [Mariadb] Update to 10.11 (LTS)

* mysql: set default collation to general_ci
2025-02-17 15:48:25 +01:00
Dmitriy Alekseev
f6dc0b463f
Update Rspamd to 3.11.0 and enable SMTPUTF8 for outgoing mail (#6216)
* Update Rspamd to 3.11

* Enable SMTPUTF8 and hide it from SMTPD greeting

* Update options.inc

* compose: increased rspamd tag
2025-02-17 14:41:39 +01:00
DerLinkman
16e22e23dc
sogo: switched apt source to sogo again (supports aarch64 now) 2025-02-17 14:31:50 +01:00
FreddleSpl0it
d8afa6f393
[Dovecot][Netfilter] Fix dovecot failed login regex 2025-02-14 13:12:12 +01:00
milkmaker
836e3f15b7
[Web] Updated lang.es-es.json (#6307)
Co-authored-by: Julie GINESTIERE <julien.ginestiere+git@gmail.com>
2025-02-13 19:32:39 +01:00
FreddleSpl0it
aaa7e4a184
[Web] Fix incorrect session lifetime in sogo-auth.php 2025-02-13 11:54:55 +01:00
FreddleSpl0it
3912341b32
[SOGo] rename custom logo 2025-02-12 11:31:14 +01:00
FreddleSpl0it
735d5f0e56
Merge pull request #6220 from Babybatrick/staging
Adding lines to docker-compose.yml to allow for simpler SOGo web client UI customisation
2025-02-12 10:54:16 +01:00
FreddleSpl0it
f375794fb7
Merge pull request #6223 from mailcow/ffdhe2048
Ffdhe2048
2025-02-12 10:48:22 +01:00
renovate[bot]
4ed3017a02
chore(deps): update devops-infra/action-pull-request action to v0.6.0 (#6302) 2025-02-12 06:56:10 +01:00
FreddleSpl0it
ef2f5f7be0
[Dovecot] Use Redis ACL user quota_notify with restricted access 2025-02-11 16:59:18 +01:00
FreddleSpl0it
54728bf780
[Dovecot] Fix create sogo-sso.conf 2025-02-11 14:40:38 +01:00
Henry Williams
743e88fd67
Update generate_config.sh version checking for wider compatibility (#6270)
* Update generate_config.sh version checking for wider compatibility 

fix: replace `grep -oP` with `grep -oE` for broader compatibility

The `-P` option (Perl-compatible regex) is not supported in all versions of `grep`, particularly the default BSD `grep` on macOS. This change replaces `-P` with `-E` (extended regex), which is more widely available and ensures compatibility across different environments.

Tested on macOS and Linux.

* Update generate_config.sh to remove use of platform dependent grep

Replaced version checking using free-form text. Instead, uses Docker’s built-in templating instead of parsing free-form text. This gives cross-platform consistency without dependency on particular versions of grep.
2025-02-11 13:55:03 +01:00
DerLinkman
ac2f0c7db1
Merge pull request #6286 from mailcow/fix-workflow-staging
Fix check_prs_if_on_staging workflow
2025-02-11 13:52:44 +01:00
FreddleSpl0it
f64c6aa1d4
Merge pull request #6269 from mailcow/staging
Automatic PR to nightly from 2025-01-27T10:00:26Z
2025-02-07 15:10:10 +01:00
FreddleSpl0it
e2cf22ff9e
Merge pull request #6268 from mailcow/feat/nightly-separated-login
[Web] Separate Login pages
2025-02-07 15:09:39 +01:00
FreddleSpl0it
55dcae4a01
[Web] Fix Generic-OIDC connection test 2025-02-07 15:05:43 +01:00
FreddleSpl0it
f0016eeecd
[Web] Add german translation for idp settings 2025-02-07 14:19:20 +01:00
Peter
83fc2c6387
It's github-token now 2025-01-31 17:20:28 +01:00
FreddleSpl0it
cb5cae3e44
Merge branch 'nightly' into feat/nightly-separated-login 2025-01-27 16:37:09 +01:00
FreddleSpl0it
aca01c8aa2
[Web] Separate Login pages 2025-01-27 15:59:50 +01:00
FreddleSpl0it
de6bd222fc
[Web] increase db_version 2025-01-24 09:25:19 +01:00
FreddleSpl0it
36d4fcbf39
Merge pull request #6255 from mailcow/staging
Automatic PR to nightly from 2025-01-23T11:01:42Z
2025-01-23 15:21:39 +01:00
FreddleSpl0it
9d791d0c4f
Merge branch 'staging' into nightly 2025-01-23 12:06:47 +01:00
FreddleSpl0it
1e77f8d8a1
Merge pull request #6250 from mailcow/staging
Automatic PR to nightly from 2025-01-22T19:10:32Z
2025-01-23 11:01:55 +01:00
FreddleSpl0it
5f45f8ae34
[Web] Fix mailbox datatable search 2025-01-23 09:18:45 +01:00
FreddleSpl0it
d430b595c1
Merge branch 'staging' into nightly 2025-01-23 08:11:45 +01:00
FreddleSpl0it
1e70a20188
[SOGo] Add mailcow Buttons to SOGo navbar 2025-01-15 16:15:25 +01:00
FreddleSpl0it
8048e0a53c
[Web] Fix permission exception in IdP actions 2025-01-15 12:48:10 +01:00
Dmitriy Alekseev
d92aa4b15d
Update dhparams.pem
Use https://ssl-config.mozilla.org/ffdhe2048.txt due to better security of the key
2024-12-20 15:39:41 +01:00
Amin
ade20d79d4
Uploading of the necessary files, after new volumes were added to docker-compose.yml (sogo-mailcow container)
After new volumes were added to docker-compose.yml in the sogo-mailcow container, it is necessary to include the specified files in the path, in order for docker to correctly start after running `docker compose up` command, otherwise error will appear, as necessary files would be missing.
The files uploaded are original SOGo UI elements, obtained from the sogo-mailcow container. Whenever users will need to change the UI elements, they would just need to change these files. Hence simplifying the process.
2024-12-19 22:13:27 +08:00
Amin
65bc8f0972
Update docker-compose.yml (sogo-mailcow)
This commit includes the addition of 3 lines, in the volumes part of the sogo-mailcow container, to allow for better customisation of the user interface on the web client page.
2024-12-19 21:59:05 +08:00
FreddleSpl0it
69b03791a2
Add missing Redis authentication 2024-12-09 13:54:44 +01:00
FreddleSpl0it
c9dd102741
[Dovecot] use auth_cache 2024-12-06 12:55:44 +01:00
FreddleSpl0it
bbddfc3eab
[Web] rearrange login buttons 2024-12-05 15:21:07 +01:00
FreddleSpl0it
a41bb55c83
Merge remote-tracking branch 'origin/staging' into nightly 2024-12-05 14:33:41 +01:00
FreddleSpl0it
b6174fae23
Merge pull request #6194 from mailcow/feat/nightly-enhancements
[Nightly] Enhancements
2024-12-05 13:18:39 +01:00
FreddleSpl0it
1d6513ffba
[Web] fix idp login alerts and updates 2024-12-04 14:49:31 +01:00
FreddleSpl0it
896a9638d6
Fix mailcowauth 2024-12-02 14:16:43 +01:00
FreddleSpl0it
83e53eb524
[Web] fix incomplete session on broken logins 2024-12-02 11:55:17 +01:00
FreddleSpl0it
f36184df64
[Web] update mailbox on idp login 2024-12-02 10:35:45 +01:00
FreddleSpl0it
6fa1c9f63d
[Web] protect /get/identity-provider 2024-12-02 10:24:15 +01:00
FreddleSpl0it
ccc8595665
[SOGo] redirect to /user if unauthenticated 2024-12-01 16:51:56 +01:00
FreddleSpl0it
45c13c687b
[Web] update user based on template after login 2024-12-01 16:36:16 +01:00
FreddleSpl0it
d61a08c2a9
[Web] hide auth heading for external managed users 2024-11-30 14:39:05 +01:00
FreddleSpl0it
c8c4cfd939
[Web] add ignore ssl option for keycloak and generic-oidc provider 2024-11-30 14:37:07 +01:00
FreddleSpl0it
ec4b9b088c
[Web] support multiple ldap hosts separated by comma 2024-11-29 18:59:07 +01:00
FreddleSpl0it
b2db8e6b31
[Dovecot] init identity provider before user login 2024-11-29 16:52:34 +01:00
FreddleSpl0it
05e4bd7602
[Web] use global vars for iam_provider and iam_settings 2024-11-29 15:50:35 +01:00
FreddleSpl0it
dc379267a9
Merge remote-tracking branch 'origin/staging' into nightly 2024-11-12 16:07:55 +01:00
Niklas Meyer
b90375b6e5
php: use correct php image + workaround of #6149 (#6159)
* compose: bump php-fpm container to correctly use patched c-ares

* [Web] check $containers_info contains required fields

---------

Co-authored-by: FreddleSpl0it <patschul@posteo.de>
2024-11-12 15:56:23 +01:00
FreddleSpl0it
9542698e95
Merge remote-tracking branch 'origin/staging' into nightly 2024-11-12 15:10:03 +01:00
Niklas Meyer
afe0ba74d2
compose: bump sogo version to include 5.11.2 (#6156) 2024-11-12 11:11:34 +01:00
milkmaker
dc5a28111d
[Web] Updated lang.zh-cn.json (#6151)
[Web] Updated lang.zh-cn.json

Co-authored-by: Easton Man <me@eastonman.com>
2024-11-11 21:39:15 +01:00
Niklas Meyer
52f3f93aee
update.sh: precaution ask for deletion of dns_blocklists.cf if old format (#6154) 2024-11-11 16:50:14 +01:00
FreddleSpl0it
f9304dcd9b
[Web] check if $iam_provider is null on ldap_mbox_login 2024-10-09 12:34:39 +02:00
FreddleSpl0it
0b9b8c9060
[Web] Ensure correct SOGo SSO password is used after Dovecot restart 2024-09-06 10:05:00 +02:00
FreddleSpl0it
0d2046baeb
Merge branch 'staging' into nightly 2024-09-05 14:53:37 +02:00
FreddleSpl0it
82fcddb177
[Web] Fix catch block in LDAP connection test 2024-09-02 10:12:51 +02:00
FreddleSpl0it
320bd31d37
[Web] fix LDAP "ignore ssl errors" option 2024-09-02 10:02:10 +02:00
FreddleSpl0it
b307e0a0d5
[PHP-FPM] Add missing space in log message 2024-09-02 09:57:33 +02:00
FreddleSpl0it
ef238e5332
[LDAP] skip sync user if username_field in LDAP is empty 2024-08-28 11:28:37 +02:00
FreddleSpl0it
dbf87e99fc
[Web] Convert LDAP username_field and attribute_field to lowercase 2024-08-21 10:48:04 +02:00
milkmaker
aeeac63e1f
Fix: Escape a ' character in update.sh (#6034) (#6035)
Co-authored-by: Hassan A Hashim <h3ssan@protonmail.com>
2024-08-20 14:22:51 +02:00
Niklas Meyer
ffcd242048
Merge pull request #6027 from mailcow/staging
Automatic PR to nightly from 2024-08-19T12:28:50Z
2024-08-20 13:41:54 +02:00
DerLinkman
e21157c10d
Merge branch 'staging' into nightly 2024-08-19 11:42:12 +02:00
FreddleSpl0it
fa3c453d6e
Use DN instead of DistinguishedName for LDAP login 2024-08-15 12:49:57 +02:00
FreddleSpl0it
962ac39e4a
Merge remote-tracking branch 'origin/staging' into nightly 2024-08-15 12:45:52 +02:00
Niklas Meyer
ebc8e6b838
Merge pull request #6008 from mailcow/staging
Automatic PR to nightly from 2024-08-15T07:42:17Z
2024-08-15 09:53:17 +02:00
DerLinkman
1fc964d72e
compose: bump dovecot image to newest nightly (20240814) 2024-08-14 10:12:10 +02:00
DerLinkman
5571d80ae6
Merge branch 'staging' into nightly 2024-08-14 10:10:34 +02:00
DerLinkman
3396e1b427
Merge branch 'staging' into nightly 2024-08-13 16:03:30 +02:00
FreddleSpl0it
58a5a4578c
[Web] use cn as fallback ldap login 2024-08-13 12:14:05 +02:00
FreddleSpl0it
519d95cb8b
[Web] extend ldap auth logging 2024-08-13 09:30:54 +02:00
FreddleSpl0it
092d3cd80b
[Web] extend ldap auth logging 2024-08-12 10:14:53 +02:00
FreddleSpl0it
c034f4bd27
[Web] fix wrong log type on PDOException 2024-08-12 10:10:33 +02:00
FreddleSpl0it
73d60eb085
Merge pull request #5997 from mailcow/fix/nightly-tfa
[Web] fix incorrect user role assignment after TFA verification
2024-08-08 17:11:57 +02:00
FreddleSpl0it
b39b7c24a5
[Web] fix incorrect user role assignment after TFA verification 2024-08-08 17:04:56 +02:00
DerLinkman
d0ecb72e08
Merge branch 'staging' into nightly 2024-08-08 08:44:12 +02:00
DerLinkman
772d5c51fd
Merge branch 'staging' into nightly 2024-08-07 14:21:23 +02:00
FreddleSpl0it
9b86ff764e
Merge pull request #5975 from mailcow/staging
Automatic PR to nightly from 2024-08-01T03:13:55Z
2024-08-01 11:07:55 +02:00
FreddleSpl0it
57bc03b878
Merge remote-tracking branch 'origin/staging' into nightly 2024-07-31 10:35:44 +02:00
FreddleSpl0it
f7ae2a6162
[Nightly][SOGo] Update 5.10.0 2024-06-06 12:12:30 +02:00
FreddleSpl0it
3080a70287
[Nightly][SOGo] Update to 5.10.0 2024-06-03 09:20:54 +02:00
Patrick Schult
caee770e36
Merge pull request #5850 from mailcow/fix/nightly-autodiscover
[Web] Fix autodiscover fails with external IdP
2024-04-19 20:38:56 +02:00
FreddleSpl0it
95d6eeb37a
[Web] revert include prerequisites in autodiscover - include autoload 2024-04-19 20:32:44 +02:00
FreddleSpl0it
eadf70d809
[Web] include prerequisites in autodiscover 2024-04-18 14:23:35 +02:00
Patrick Schult
6e9c3e2687
Merge pull request #5821 from mailcow/fix/web-nightly
Fix/web nightly
2024-04-04 09:33:22 +02:00
FreddleSpl0it
cf2fda66e2
[Web] escape html of alert messages 2024-04-04 09:31:20 +02:00
FreddleSpl0it
cd24057f1a
[Web] use SEC_FETCH_DEST header to block api requests 2024-04-04 09:31:03 +02:00
FreddleSpl0it
c68a436a22
[Web] fix invalid rspamd map check 2024-04-04 09:30:30 +02:00
FreddleSpl0it
0807c122f6
[Web] set default LDAP options on get 2024-03-08 15:11:49 +01:00
FreddleSpl0it
e0bda6ca6a
[Web] prevent multiple dual-logins 2024-03-08 14:05:37 +01:00
FreddleSpl0it
2ba64e93f9
[Web] allow SSL / TLS connections for LDAP 2024-03-08 13:50:20 +01:00
FreddleSpl0it
e1c3ad9fe8
[Web] return idp instance after init 2024-03-08 13:15:35 +01:00
FreddleSpl0it
ffbf1758e0
[Web] fix identity_provider ArgumentCountError 2024-02-26 13:40:34 +01:00
Patrick Schult
a3af2d8392
Merge pull request #5764 from mailcow/fix/nightly-issues
Fix nightly issues with new ldap provider
2024-02-26 13:33:44 +01:00
FreddleSpl0it
39a4b115ed
[SOGo] fix plist_ldap.sh example 2024-02-26 13:14:08 +01:00
FreddleSpl0it
881c2d6e02
[SOGo] remove custom logout from toolbar 2024-02-26 13:13:50 +01:00
FreddleSpl0it
d237157c0b
init identity_provider only after all conditions are met 2024-02-26 13:12:44 +01:00
FreddleSpl0it
6928eb632e
[Dovecot] move sogo sso to mailcowauth.php 2024-02-26 13:10:08 +01:00
FreddleSpl0it
010d898786
[Web] apply LDAP filter 2024-02-23 10:01:56 +01:00
FreddleSpl0it
766c270b1f
[SOGo] fix custom html elements and wrong redirection 2024-02-23 09:12:17 +01:00
FreddleSpl0it
916d0fd46a
[Web] catch all exceptions on ldap connect 2024-02-23 08:12:06 +01:00
Patrick Schult
9561526f33
Merge pull request #5754 from mailcow/feat/idp-ldap2
[Nightly] add LDAP as direct IdP
2024-02-20 15:33:58 +01:00
FreddleSpl0it
45811bc2dc
[Web] fix mailbox datatable search 2024-02-20 15:00:06 +01:00
FreddleSpl0it
132e37bfec
[SOGo] use bash script for ldap plist template 2024-02-20 12:42:37 +01:00
FreddleSpl0it
b3e26e14ef
[Ofelia] add ldap sync cronjob 2024-02-20 12:01:08 +01:00
FreddleSpl0it
a3bb889def
[Web] fix set_tfa for ldap users 2024-02-20 11:41:02 +01:00
FreddleSpl0it
3a1dcb3aaf
[Web] fix set_tfa for ldap users 2024-02-20 11:34:01 +01:00
FreddleSpl0it
d22cafacc8
[Web] fix ldap filter if empty 2024-02-20 11:21:25 +01:00
FreddleSpl0it
78e7266368
[Web] add LDAP query filter 2024-02-20 10:46:23 +01:00
FreddleSpl0it
a06c78362a
[Web] add ldap idp 2024-02-20 10:31:14 +01:00
FreddleSpl0it
d479d18507
[Web] update directorytree/ldaprecord 2024-02-20 10:30:11 +01:00
DerLinkman
40146839ef
docker-compose: bumped versions 2024-02-08 12:42:36 +01:00
DerLinkman
448f85abe8
Remove unused files 2024-02-08 12:42:36 +01:00
FreddleSpl0it
9a4b79a629
[Web] fix idp mailbox login 2024-02-08 12:42:35 +01:00
DerLinkman
058b79ed5c
dovecot: corrected dockerfile inside nightly 2024-02-08 12:42:35 +01:00
FreddleSpl0it
216398355b
fix functions.inc.php, update sogo and dovecot nightly image 2024-02-08 12:42:35 +01:00
Geert Hauwaerts
1cda16523d
Fixed SQL query for retrieving SSO users. 2024-02-08 12:42:34 +01:00
FreddleSpl0it
2f1e1438e9
[Web] add log messages to verify-sso function 2024-02-08 12:42:34 +01:00
FreddleSpl0it
9039ab4e12
[Web] add missing file to autodiscover.php 2024-02-08 12:42:34 +01:00
DerLinkman
db47696ba7
Updated Dovecot Image to use OpenSSL 3.0 fix 2024-02-08 12:42:33 +01:00
FreddleSpl0it
eb9e3b8391
[Web] add configurable client scopes for generic-oidc 2024-02-08 12:42:33 +01:00
FreddleSpl0it
ba32f1131e
[Web] dont rtrim generic-oidc urls 2024-02-08 12:42:32 +01:00
DerLinkman
27ef04baa0
Update Dovecot to reuse lz4 compression 2024-02-08 12:42:32 +01:00
DerLinkman
b3a94e79e3
Use dedicated nightly images on nightly branch + updates of images itself 2024-02-08 12:42:32 +01:00
FreddleSpl0it
3a4c0c84a3
fix keycloak mailpassword flow 2024-02-08 12:42:31 +01:00
Mirko Ceroni
73a044ec14
Update sogo-auth.php 2024-02-08 12:42:31 +01:00
Mirko Ceroni
389eb99c10
Update sogo-auth.php
initialize db before validating credentials
2024-02-08 12:42:31 +01:00
FreddleSpl0it
597d98e1d7
Fixes #5408 2024-02-08 12:42:30 +01:00
FreddleSpl0it
981307a1c6
[Web] add missing break 2024-02-08 12:42:30 +01:00
FreddleSpl0it
2d51881ae3
[Web] fix user protocol badges 2024-02-08 12:42:30 +01:00
FreddleSpl0it
788f03e993
[Dovecot] remove passwd-verify.lua generation 2024-02-08 12:42:29 +01:00
DerLinkman
81024b8c12
Clamd using Alpine Packages instead self compile 2024-02-08 12:42:29 +01:00
DerLinkman
89c5064213
Rebased Dovecot on Alpine + fixed logging 2024-02-08 12:42:29 +01:00
DerLinkman
4b18a99e55
Small fixes for CLAMD Health Check 2024-02-08 12:42:28 +01:00
DerLinkman
92d2cca7c3
Added missing Labels to Dockerfiles 2024-02-08 12:42:28 +01:00
DerLinkman
466e36ecbb
Optimized Build Process for Dovecot 2024-02-08 12:42:28 +01:00
DerLinkman
7ec7bd21cb
Changed Dovecot Base to Bullseye again (Self compile) 2024-02-08 12:42:27 +01:00
DerLinkman
38db7226a8
Optimized CLAMAV Builds to match exact version instead of Repo 2024-02-08 12:42:27 +01:00
DerLinkman
60f9412bb8
Switched to Alpine Edge (for IMAPSYNC Deps) 2024-02-08 12:42:26 +01:00
DerLinkman
737c0502ac
Rebased Dovecot on Alpine 3.17 instead Bullseye (ARM64 Support) 2024-02-08 12:42:26 +01:00
DerLinkman
6da41b1027
Removed Test self compiled SOGo Dockerfile 2024-02-08 12:42:26 +01:00
DerLinkman
2bd46ae0fd
Changed Maintainer to tinc within Dockerfiles 2024-02-08 12:42:25 +01:00
DerLinkman
c15ab10b1b
Updated Clamd Building to be x86 and ARM Compatible 2024-02-08 12:42:25 +01:00
DerLinkman
ddaeebc822
[Rspamd] Update to 3.6 (Ratelimit fix) 2024-02-08 12:42:25 +01:00
FreddleSpl0it
e64293c82f
[Web] minor fixes 2024-02-08 12:42:24 +01:00
FreddleSpl0it
ccc17e4a20
[SOGo] deny direct login on external users 2024-02-08 12:42:24 +01:00
FreddleSpl0it
a53ef2ed7a
[SOGo] remove sogo_view and triggers 2024-02-08 12:42:24 +01:00
FreddleSpl0it
185c36cdfe
[Web] catch update_sogo exceptions 2024-02-08 12:42:23 +01:00
FreddleSpl0it
9beb47c067
[Web] fix malformed_username check 2024-02-08 12:42:23 +01:00
FreddleSpl0it
3d486678ae
[Web] remove keycloak sync disabled warning 2024-02-08 12:42:23 +01:00
FreddleSpl0it
04e2494af8
deny changes on identity provider if it's in use 2024-02-08 12:42:22 +01:00
FreddleSpl0it
7b47159478
rework auth - move dovecot sasl log to php 2024-02-08 12:42:22 +01:00
FreddleSpl0it
17b6ac3313
[Web] allow mailbox authsource to be switchable 2024-02-08 12:42:22 +01:00
FreddleSpl0it
43600cd127
[Web] fix identity-provider settings layout 2024-02-08 12:42:21 +01:00
FreddleSpl0it
6d3a32c1d9
[Web] trim CRON_LOG 2024-02-08 12:42:21 +01:00
FreddleSpl0it
21fa3c8458
[Web] remove unnecessary if block 2024-02-08 12:42:21 +01:00
FreddleSpl0it
6df663825a
[Web] add curl timeouts to oidc requests 2024-02-08 12:42:20 +01:00
FreddleSpl0it
8ce4600562
[Web] update lang files 2024-02-08 12:42:20 +01:00
FreddleSpl0it
3179c0e712
[Dovecot] mailcowauth minor fixes 2024-02-08 12:42:19 +01:00
FreddleSpl0it
37254738e2
[Web] improve identity-provider template 2024-02-08 12:42:19 +01:00
FreddleSpl0it
a4cce147aa
[Web] improve attribute sync performance & make authsource editable 2024-02-08 12:42:19 +01:00
FreddleSpl0it
b176585a9c
[Web] add crontasks logs 2024-02-08 12:42:18 +01:00
FreddleSpl0it
f8647bb15e
[Web] add keycloak sync crontask 2024-02-08 12:42:18 +01:00
FreddleSpl0it
85368971fd
[Web] handle fatal errors on getAccessToken 2024-02-08 12:42:18 +01:00
FreddleSpl0it
e4284b8e19
[Web] fix attribute mapping list 2024-02-08 12:42:17 +01:00
FreddleSpl0it
5545d8a56c
[Web] hide auth settings for external users 2024-02-08 12:42:17 +01:00
FreddleSpl0it
4dc3222f03
[Web] fix bug on mailbox login 2024-02-08 12:42:17 +01:00
FreddleSpl0it
7cf6a9d808
[Web] update lang.en-gb.json 2024-02-08 12:42:16 +01:00
FreddleSpl0it
95a15d18a7
[Web] update guzzlehttp/psr7 2024-02-08 12:42:16 +01:00
FreddleSpl0it
cee771a3fb
[Web] update stevenmaguire/oauth2-keycloak and firebase/php-jwt 2024-02-08 12:42:16 +01:00
FreddleSpl0it
a805d3b2e3
[Web] add league/oauth2-client 2024-02-08 12:42:15 +01:00
FreddleSpl0it
b251c58b23
update gitignore 2024-02-08 12:42:15 +01:00
FreddleSpl0it
cc7516685f
[Web] functions.auth.inc.php corrections 2024-02-08 12:42:15 +01:00
FreddleSpl0it
ad19ff5429
[Web] remove ropc flow 2024-02-08 12:42:14 +01:00
FreddleSpl0it
e784c98a5a
[Web] add "add mailbox_from_template" function 2024-02-08 12:42:14 +01:00
FreddleSpl0it
28679eb916
[Web] add generic-oidc provider 2024-02-08 12:42:13 +01:00
FreddleSpl0it
c8fec24da3
[Web] add "edit mailbox_from_template" function 2024-02-08 12:42:13 +01:00
FreddleSpl0it
0c1e2ed6f2
[Web] revert configurable authsource 2024-02-08 12:42:13 +01:00
FreddleSpl0it
90476ae057
[Web] rename var for tab-config-identity-provider.twig 2024-02-08 12:42:12 +01:00
FreddleSpl0it
3b6a1d50bd
[Web] add generic-oidc provider 2024-02-08 12:42:12 +01:00
FreddleSpl0it
1ab1505c88
[Web] remove sso login alertbox 2024-02-08 12:42:12 +01:00
FreddleSpl0it
593e581cf3
[Web] move iam sso functions 2024-02-08 12:42:11 +01:00
FreddleSpl0it
e202d00beb
[Dovecot] group auth files 2024-02-08 12:42:11 +01:00
FreddleSpl0it
dca5f1baab
[Web] move /process/login to internal endpoint 2024-02-08 12:42:11 +01:00
FreddleSpl0it
f0689e08d9
[Web] iam - add switch for direct login flow 2024-02-08 12:42:10 +01:00
FreddleSpl0it
5bbb12b53e
[Dovecot] fix wrong lua syntax 2024-02-08 12:42:10 +01:00
FreddleSpl0it
c6a56e0748
[Web] add IAM delete button & fix add mbox modal 2024-02-08 12:42:10 +01:00
FreddleSpl0it
3c62a7fd9f
[Web] IAM - add delete option & fix test connection 2024-02-08 12:42:09 +01:00
FreddleSpl0it
61ab17d8a1
[Web] fix iam attribute mapping ui 2024-02-08 12:42:09 +01:00
FreddleSpl0it
d4ae616460
replace ropc flow with keycloak rest api flow 2024-02-08 12:42:09 +01:00
FreddleSpl0it
b7a18255fe
[Web] rename role mapping to attribute mapping 2024-02-08 12:42:08 +01:00
FreddleSpl0it
1c73a16ca0
new dovecout lua auth - use https 2024-02-08 12:42:08 +01:00
FreddleSpl0it
1aeb36d40e
[Web] create ratelimit acl on iam mbox creation 2 2024-02-08 12:42:07 +01:00
FreddleSpl0it
f251c9826e
[Web] create ratelimit acl on iam mbox creation 2024-02-08 12:42:07 +01:00
FreddleSpl0it
204063819c
[Web] fix broken sogo-sso 2024-02-08 12:42:07 +01:00
FreddleSpl0it
13f8882616
[Web] fix app_pass ignore_access 2024-02-08 12:42:06 +01:00
FreddleSpl0it
eba1d469c8
[Web] keycloak auth functions 2024-02-08 12:42:06 +01:00
FreddleSpl0it
6e9980bf0f
[Web] add manage identity provider 2024-02-08 12:42:06 +01:00
FreddleSpl0it
67c9c5b8ed
[Web] remove u2f lib from prerequisites 2024-02-08 12:42:05 +01:00
FreddleSpl0it
cd3660a96d
[Web] add oauth2-keycloak lib 2024-02-08 12:42:05 +01:00
FreddleSpl0it
9d8c1a01ac
[Web] remove u2f lib 2024-02-08 12:42:05 +01:00
FreddleSpl0it
0a77cad2dd
[Web] limit identity_provider function better 2024-02-08 12:42:04 +01:00
FreddleSpl0it
f6869da3a0
[Web] manage keycloak identity provider 2024-02-08 12:42:04 +01:00
FreddleSpl0it
6adad79e5c
[Web] organize auth functions+api auth w/ dovecot 2024-02-08 12:42:04 +01:00
FreddleSpl0it
50d4d59626
[Web] update de-de + en-gb lang 2024-02-08 12:42:03 +01:00
FreddleSpl0it
56a9f1a411
[Web] organize user landing 2024-02-08 12:42:03 +01:00
FreddleSpl0it
84ff6ff2c5
[Web] fix user login history 2024-02-08 12:42:03 +01:00
FreddleSpl0it
6e35574c72
[Web] add app hide option 2024-02-08 12:42:02 +01:00
FreddleSpl0it
415c1d0574
[Web] add seperate link for logged in users 2024-02-08 12:42:02 +01:00
FreddleSpl0it
cfce7086a5
[Web] few style changes 2024-02-08 12:42:01 +01:00
FreddleSpl0it
c90d637a48
[Web] redirect to sogo after failed sogo-auth 2024-02-08 12:42:01 +01:00
886 changed files with 59244 additions and 12176 deletions

View file

@ -11,22 +11,35 @@ body:
required: true
- type: checkboxes
attributes:
label: I've found a bug and checked that ...
description: Prior to placing the issue, please check following:** *(fill out each checkbox with an `X` once done)*
label: Checklist prior issue creation
description: Prior to creating the issue...
options:
- label: ... I understand that not following the below instructions will result in immediate closure and/or deletion of my issue.
- label: I understand that failure to follow below instructions may cause this issue to be closed.
required: true
- label: ... I have understood that this bug report is dedicated for bugs, and not for support-related inquiries.
- label: I understand that vague, incomplete or inaccurate information may cause this issue to be closed.
required: true
- label: ... I have understood that answers are voluntary and community-driven, and not commercial support.
- label: I understand that this form is intended solely for reporting software bugs and not for support-related inquiries.
required: true
- label: ... I have verified that my issue has not been already answered in the past. I also checked previous [issues](https://github.com/mailcow/mailcow-dockerized/issues).
- label: I understand that all responses are voluntary and community-driven, and do not constitute commercial support.
required: true
- label: I confirm that I have reviewed previous [issues](https://github.com/mailcow/mailcow-dockerized/issues) to ensure this matter has not already been addressed.
required: true
- label: I confirm that my environment meets all [prerequisite requirements](https://docs.mailcow.email/getstarted/prerequisite-system/) as specified in the official documentation.
required: true
- type: textarea
attributes:
label: Description
description: Please provide a brief description of the bug in 1-2 sentences. If applicable, add screenshots to help explain your problem. Very useful for bugs in mailcow UI.
render: plain text
description: Please provide a brief description of the bug. If applicable, add screenshots to help explain your problem. (Very useful for bugs in mailcow UI.)
validations:
required: true
- type: textarea
attributes:
label: "Steps to reproduce:"
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
placeholder: |-
1. ...
2. ...
3. ...
validations:
required: true
- type: textarea
@ -36,45 +49,36 @@ body:
render: plain text
validations:
required: true
- type: textarea
attributes:
label: "Steps to reproduce:"
description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful."
render: plain text
placeholder: |-
1. ...
2. ...
3. ...
validations:
required: true
- type: markdown
attributes:
value: |
## System information
### In this stage we would kindly ask you to attach general system information about your setup.
In this stage we would kindly ask you to attach general system information about your setup.
- type: dropdown
attributes:
label: "Which branch are you using?"
description: "#### `git rev-parse --abbrev-ref HEAD`"
description: "#### Run: `git rev-parse --abbrev-ref HEAD`"
multiple: false
options:
- master
- master (stable)
- staging
- nightly
validations:
required: true
- type: dropdown
attributes:
label: "Which architecture are you using?"
description: "#### `uname -m`"
description: "#### Run: `uname -m`"
multiple: false
options:
- x86
- x86_64
- ARM64 (aarch64)
validations:
required: true
- type: input
attributes:
label: "Operating System:"
description: "#### Run: `lsb_release -ds`"
placeholder: "e.g. Ubuntu 22.04 LTS"
validations:
required: true
@ -93,43 +97,44 @@ body:
- type: input
attributes:
label: "Virtualization technology:"
placeholder: "KVM, VMware, Xen, etc - **LXC and OpenVZ are not supported**"
description: "LXC and OpenVZ are not supported!"
placeholder: "KVM, VMware ESXi, Xen, etc"
validations:
required: true
- type: input
attributes:
label: "Docker version:"
description: "#### `docker version`"
description: "#### Run: `docker version`"
placeholder: "20.10.21"
validations:
required: true
- type: input
attributes:
label: "docker-compose version or docker compose version:"
description: "#### `docker-compose version` or `docker compose version`"
description: "#### Run: `docker-compose version` or `docker compose version`"
placeholder: "v2.12.2"
validations:
required: true
- type: input
attributes:
label: "mailcow version:"
description: "#### ```git describe --tags `git rev-list --tags --max-count=1` ```"
placeholder: "2022-08"
description: "#### Run: ```git describe --tags `git rev-list --tags --max-count=1` ```"
placeholder: "2022-08x"
validations:
required: true
- type: input
attributes:
label: "Reverse proxy:"
placeholder: "e.g. Nginx/Traefik"
placeholder: "e.g. nginx/Traefik, or none"
validations:
required: true
- type: textarea
attributes:
label: "Logs of git diff:"
description: "#### Output of `git diff origin/master`, any other changes to the code? If so, **please post them**:"
description: "#### Output of `git diff origin/master`, any other changes to the code? Sanitize if needed. If so, **please post them**:"
render: plain text
validations:
required: true
required: false
- type: textarea
attributes:
label: "Logs of iptables -L -vn:"

View file

@ -12,7 +12,7 @@ jobs:
- name: Send message
uses: thollander/actions-comment-pull-request@v3.0.1
with:
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
github-token: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
message: |
Thanks for contributing!

View file

@ -14,7 +14,7 @@ jobs:
pull-requests: write
steps:
- name: Mark/Close Stale Issues and Pull Requests 🗑️
uses: actions/stale@v9.1.0
uses: actions/stale@v10.1.1
with:
repo-token: ${{ secrets.STALE_ACTION_PAT }}
days-before-stale: 60

View file

@ -27,7 +27,7 @@ jobs:
- "watchdog-mailcow"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Docker
run: |
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh

View file

@ -8,11 +8,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Run the Action
uses: devops-infra/action-pull-request@v0.5.5
uses: devops-infra/action-pull-request@v1.0.2
with:
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}

View file

@ -9,9 +9,11 @@ on:
jobs:
docker_image_build:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@ -19,11 +21,13 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_USERNAME }}
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
@ -32,4 +36,4 @@ jobs:
platforms: linux/amd64,linux/arm64
file: data/Dockerfiles/backup/Dockerfile
push: true
tags: mailcow/backup:latest
tags: ghcr.io/mailcow/backup:latest

View file

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Generate postscreen_access.cidr
run: |

7
.gitignore vendored
View file

@ -45,9 +45,12 @@ data/conf/rspamd/local.d/*
data/conf/rspamd/override.d/*
data/conf/sogo/custom-theme.js
data/conf/sogo/plist_ldap
data/conf/sogo/plist_ldap.sh
data/conf/sogo/sieve.creds
data/conf/sogo/cron.creds
data/conf/sogo/sogo-full.svg
data/conf/sogo/custom-fulllogo.svg
data/conf/sogo/custom-shortlogo.svg
data/conf/sogo/custom-fulllogo.png
data/gitea/
data/gogs/
data/hooks/dovecot/*
@ -71,3 +74,5 @@ rebuild-images.sh
refresh_images.sh
update_diffs/
create_cold_standby.sh
!data/conf/nginx/mailcow_auth.conf
data/conf/postfix/postfix-tlspol

View file

@ -1,11 +1,11 @@
# Contribution Guidelines
**_Last modified on 15th August 2024_**
**_Last modified on 12th November 2025_**
First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow!
As we want to keep mailcow's development structured we setup these Guidelines which helps you to create your issue/pull request accordingly.
**PLEASE NOTE, THAT WE MIGHT CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULLFIL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request.
**PLEASE NOTE, THAT WE WILL CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULFILL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request.
## Topics
@ -27,14 +27,18 @@ However, please note the following regarding pull requests:
6. Please **ALWAYS** create the actual pull request against the staging branch and **NEVER** directly against the master branch. *If you forget to do this, our moobot will remind you to switch the branch to staging.*
7. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project.
8. If you are planning larger and therefore more complex pull requests, it would be advisable to first announce this in a separate issue and then start implementing it after the idea has been accepted in order to avoid unnecessary frustration and effort!
9. If your PR requires a Docker image rebuild (changes to Dockerfiles or files in data/Dockerfiles/), update the image tag in docker-compose.yml. Use the base-image versioning (e.g. ghcr.io/mailcow/sogo:5.12.4 → :5.12.5 for version bumps; append a letter for patch fixes, e.g. :5.12.4a). Follow this scheme.
---
## Issue Reporting
**_Last modified on 15th August 2024_**
**_Last modified on 12th November 2025_**
If you plan to report a issue within mailcow please read and understand the following rules:
### Security disclosures / Security-related fixes
- Security vulnerabilities and security fixes must always be reported confidentially first to the contact address specified in SECURITY.md before they are integrated, published, or publicly disclosed in issues/PRs. Please wait for a response from the specified contact to ensure coordinated and responsible disclosure.
### Issue Reporting Guidelines
1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support).

View file

@ -13,6 +13,22 @@ You can also [get a SAL](https://www.servercow.de/mailcow?lang=en#sal) which is
Or just spread the word: moo.
## Many thanks to our GitHub Sponsors ❤️
A big thank you to everyone supporting us on GitHub Sponsors—your contributions mean the world to us! Special thanks to the following amazing supporters:
### 100$/Month Sponsors
<a href="https://www.colba.net/" target=_blank><img
src="https://avatars.githubusercontent.com/u/204464723" height="58"
/></a>
<a href="https://www.maehdros.com/" target=_blank><img
src="https://avatars.githubusercontent.com/u/173894712" height="58"
/></a>
### 50$/Month Sponsors
<a href="https://github.com/vnukhr" target=_blank><img
src="https://avatars.githubusercontent.com/u/7805987?s=52&v=4" height="58"
/></a>
## Info, documentation and support
Please see [the official documentation](https://docs.mailcow.email/) for installation and support instructions. 🐄

230
_modules/scripts/core.sh Normal file
View file

@ -0,0 +1,230 @@
#!/usr/bin/env bash
# _modules/scripts/core.sh
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
# ANSI color for red errors
RED='\e[31m'
GREEN='\e[32m'
YELLOW='\e[33m'
BLUE='\e[34m'
MAGENTA='\e[35m'
LIGHT_RED='\e[91m'
LIGHT_GREEN='\e[92m'
NC='\e[0m'
caller="${BASH_SOURCE[1]##*/}"
get_installed_tools(){
for bin in openssl curl docker git awk sha1sum grep cut jq; do
if [[ -z $(command -v ${bin}) ]]; then
echo "Error: Cannot find command '${bin}'. Cannot proceed."
echo "Solution: Please review system requirements and install requirements. Then, re-run the script."
echo "See System Requirements: https://docs.mailcow.email/getstarted/install/"
echo "Exiting..."
exit 1
fi
done
if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"${NC}"; exit 1; fi
# This will also cover sort
if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\"${NC}"; exit 1; fi
if sed --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox sed detected, please install gnu sed, \"apk add --no-cache --upgrade sed\"${NC}"; exit 1; fi
}
get_docker_version(){
# Check Docker Version (need at least 24.X)
docker_version=$(docker version --format '{{.Server.Version}}' | cut -d '.' -f 1)
}
get_compose_type(){
if docker compose > /dev/null 2>&1; then
if docker compose version --short | grep -e "^[2-9]\." -e "^v[2-9]\." -e "^[1-9][0-9]\." -e "^v[1-9][0-9]\." > /dev/null 2>&1; then
COMPOSE_VERSION=native
COMPOSE_COMMAND="docker compose"
if [[ "$caller" == "update.sh" ]]; then
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf"
fi
echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
sleep 2
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
else
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
exit 1
fi
elif docker-compose > /dev/null 2>&1; then
if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
if docker-compose version --short | grep -e "^[2-9]\." -e "^[1-9][0-9]\." > /dev/null 2>&1; then
COMPOSE_VERSION=standalone
COMPOSE_COMMAND="docker-compose"
if [[ "$caller" == "update.sh" ]]; then
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' "$SCRIPT_DIR/mailcow.conf"
fi
echo -e "\e[33mFound Docker Compose Standalone.\e[0m"
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
sleep 2
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
else
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
exit 1
fi
fi
else
echo -e "\e[31mCannot find Docker Compose.\e[0m"
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
exit 1
fi
}
detect_bad_asn() {
echo -e "\e[33mDetecting if your IP is listed on Spamhaus Bad ASN List...\e[0m"
response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email")
if [ "$response" -eq 503 ]; then
if [ -z "$SPAMHAUS_DQS_KEY" ]; then
echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
echo -e "\e[33mmailcow did not detected a value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf!\e[0m"
sleep 2
echo ""
echo -e "\e[33mTo use the Spamhaus DNS Blocklists again, you will need to create a FREE account for their Data Query Service (DQS) at: https://www.spamhaus.com/free-trial/sign-up-for-a-free-data-query-service-account\e[0m"
echo -e "\e[33mOnce done, enter your DQS API key in mailcow.conf and mailcow will do the rest for you!\e[0m"
echo ""
sleep 2
else
echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m"
echo -e "\e[32mmailcow detected a Value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf. Postfix will use DQS with the given API key...\e[0m"
fi
elif [ "$response" -eq 200 ]; then
echo -e "\e[33mCheck completed! Your IP is \e[32mclean\e[0m"
elif [ "$response" -eq 429 ]; then
echo -e "\e[33mCheck completed! \e[31mYour IP seems to be rate limited on the ASN Check service... please try again later!\e[0m"
else
echo -e "\e[31mCheck failed! \e[0mMaybe a DNS or Network problem?\e[0m"
fi
}
check_online_status() {
CHECK_ONLINE_DOMAINS=('https://github.com' 'https://hub.docker.com')
for domain in "${CHECK_ONLINE_DOMAINS[@]}"; do
if timeout 6 curl --head --silent --output /dev/null ${domain}; then
return 0
fi
done
return 1
}
prefetch_images() {
[[ -z ${BRANCH} ]] && { echo -e "\e[33m\nUnknown branch...\e[0m"; exit 1; }
git fetch origin #${BRANCH}
while read image; do
RET_C=0
until docker pull "${image}"; do
RET_C=$((RET_C + 1))
echo -e "\e[33m\nError pulling $image, retrying...\e[0m"
[ ${RET_C} -gt 3 ] && { echo -e "\e[31m\nToo many failed retries, exiting\e[0m"; exit 1; }
sleep 1
done
done < <(git show "origin/${BRANCH}:docker-compose.yml" | grep "image:" | awk '{ gsub("image:","", $3); print $2 }')
}
docker_garbage() {
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
IMGS_TO_DELETE=()
declare -A IMAGES_INFO
COMPOSE_IMAGES=($(grep -oP "image: \K(ghcr\.io/)?mailcow.+" "${SCRIPT_DIR}/docker-compose.yml"))
for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep -E '(mailcow/|ghcr\.io/mailcow/)'); do
ID=$(echo "$existing_image" | cut -d ':' -f 1)
REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2)
TAG=$(echo "$existing_image" | cut -d ':' -f 3)
if [[ "$REPOSITORY" == "mailcow/backup" || "$REPOSITORY" == "ghcr.io/mailcow/backup" ]]; then
if [[ "$TAG" != "<none>" ]]; then
continue
fi
fi
if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then
continue
else
IMGS_TO_DELETE+=("$ID")
IMAGES_INFO["$ID"]="$REPOSITORY:$TAG"
fi
done
if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then
echo "The following unused mailcow images were found:"
for id in "${IMGS_TO_DELETE[@]}"; do
echo " ${IMAGES_INFO[$id]} ($id)"
done
if [ -z "$FORCE" ]; then
read -r -p "Do you want to delete them to free up some space? [y/N] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
docker rmi ${IMGS_TO_DELETE[*]}
else
echo "OK, skipped."
fi
else
echo "Running in forced mode! Force removing old mailcow images..."
docker rmi ${IMGS_TO_DELETE[*]}
fi
echo -e "\e[32mFurther cleanup...\e[0m"
echo "If you want to cleanup further garbage collected by Docker, please make sure all containers are up and running before cleaning your system by executing \"docker system prune\""
fi
}
in_array() {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
detect_major_update() {
if [ ${BRANCH} == "master" ]; then
# Array with major versions
# Add major versions here
MAJOR_VERSIONS=(
"2025-02"
"2025-03"
"2025-09"
)
current_version=""
if [[ -f "${SCRIPT_DIR}/data/web/inc/app_info.inc.php" ]]; then
current_version=$(grep 'MAILCOW_GIT_VERSION' ${SCRIPT_DIR}/data/web/inc/app_info.inc.php | sed -E 's/.*MAILCOW_GIT_VERSION="([^"]+)".*/\1/')
fi
if [[ -z "$current_version" ]]; then
return 1
fi
release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag"
updates_to_apply=()
for version in "${MAJOR_VERSIONS[@]}"; do
if [[ "$current_version" < "$version" ]]; then
updates_to_apply+=("$version")
fi
done
if [[ ${#updates_to_apply[@]} -gt 0 ]]; then
echo -e "\e[33m\nMAJOR UPDATES to be applied:\e[0m"
for update in "${updates_to_apply[@]}"; do
echo "$update - $release_url/$update"
done
echo -e "\nPlease read the release notes before proceeding."
read -p "Do you want to proceed with the update? [y/n] " response
if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "Proceeding with the update..."
else
echo "Update canceled. Exiting."
exit 1
fi
fi
fi
}

View file

@ -0,0 +1,239 @@
#!/usr/bin/env bash
# _modules/scripts/ipv6_controller.sh
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
# 1) Check if the host supports IPv6
get_ipv6_support() {
# ---- helper: probe external IPv6 connectivity without DNS ----
_probe_ipv6_connectivity() {
# Use literal, always-on IPv6 echo responders (no DNS required)
local PROBE_IPS=("2001:4860:4860::8888" "2606:4700:4700::1111")
local ip rc=1
for ip in "${PROBE_IPS[@]}"; do
if command -v ping6 &>/dev/null; then
ping6 -c1 -W2 "$ip" &>/dev/null || ping6 -c1 -w2 "$ip" &>/dev/null
rc=$?
elif command -v ping &>/dev/null; then
ping -6 -c1 -W2 "$ip" &>/dev/null || ping -6 -c1 -w2 "$ip" &>/dev/null
rc=$?
else
rc=1
fi
[[ $rc -eq 0 ]] && return 0
done
return 1
}
if [[ ! -f /proc/net/if_inet6 ]] || grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null; then
DETECTED_IPV6=false
echo -e "${YELLOW}IPv6 not detected on host ${LIGHT_RED}IPv6 is administratively disabled${YELLOW}.${NC}"
return
fi
if ip -6 route show default 2>/dev/null | grep -qE '^default'; then
echo -e "${YELLOW}Default IPv6 route found testing external IPv6 connectivity...${NC}"
if _probe_ipv6_connectivity; then
DETECTED_IPV6=true
echo -e "IPv6 detected on host ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
else
DETECTED_IPV6=false
echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
fi
return
fi
if ip -6 addr show scope global 2>/dev/null | grep -q 'inet6'; then
DETECTED_IPV6=false
echo -e "${YELLOW}Global IPv6 address present but no default route ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
return
fi
if ip -6 addr show scope link 2>/dev/null | grep -q 'inet6'; then
echo -e "${YELLOW}Only link-local IPv6 addresses found testing external IPv6 connectivity...${NC}"
if _probe_ipv6_connectivity; then
DETECTED_IPV6=true
echo -e "External IPv6 connectivity available ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}"
else
DETECTED_IPV6=false
echo -e "${YELLOW}Only link-local IPv6 present and no external connectivity ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
fi
return
fi
DETECTED_IPV6=false
echo -e "${YELLOW}IPv6 not detected on host ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}"
}
# 2) Ensure Docker daemon.json has (or create) the required IPv6 settings
docker_daemon_edit(){
DOCKER_DAEMON_CONFIG="/etc/docker/daemon.json"
DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1)
MISSING=()
_has_kv() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; }
if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then
# reject empty or whitespace-only file immediately
if [[ ! -s "$DOCKER_DAEMON_CONFIG" ]] || ! grep -Eq '[{}]' "$DOCKER_DAEMON_CONFIG"; then
echo -e "${RED}ERROR: $DOCKER_DAEMON_CONFIG exists but is empty or contains no JSON braces please initialize it with valid JSON (e.g. {}).${NC}"
exit 1
fi
# Validate JSON if jq is present
if command -v jq &>/dev/null && ! jq empty "$DOCKER_DAEMON_CONFIG" &>/dev/null; then
echo -e "${RED}ERROR: Invalid JSON in $DOCKER_DAEMON_CONFIG please correct manually.${NC}"
exit 1
fi
# Gather missing keys
! _has_kv ipv6 true && MISSING+=("ipv6: true")
# For Docker < 28, keep requiring fixed-cidr-v6 (default bridge needs it on old engines)
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
! grep -Eq '"fixed-cidr-v6"[[:space:]]*:[[:space:]]*".+"' "$DOCKER_DAEMON_CONFIG" \
&& MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"')
fi
# For Docker < 27, ip6tables needed and was tied to experimental in older releases
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
_has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true")
! _has_kv experimental true && MISSING+=("experimental: true")
fi
# Fix if needed
if ((${#MISSING[@]}>0)); then
echo -e "${MAGENTA}Your daemon.json is missing: ${YELLOW}${MISSING[*]}${NC}"
if [[ -n "$FORCE" ]]; then
ans=Y
else
read -p "Would you like to update $DOCKER_DAEMON_CONFIG now? [Y/n] " ans
ans=${ans:-Y}
fi
if [[ $ans =~ ^[Yy]$ ]]; then
cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak"
if command -v jq &>/dev/null; then
TMP=$(mktemp)
# Base filter: ensure ipv6 = true
JQ_FILTER='.ipv6 = true'
# Add fixed-cidr-v6 only for Docker < 28
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
JQ_FILTER+=' | .["fixed-cidr-v6"] = (.["fixed-cidr-v6"] // "fd00:dead:beef:c0::/80")'
fi
# Add ip6tables/experimental only for Docker < 27
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
JQ_FILTER+=' | .ip6tables = true | .experimental = true'
fi
jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG"
echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}"
(command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
echo -e "${YELLOW}Docker restarted.${NC}"
else
echo -e "${RED}Please install jq or manually update daemon.json and restart Docker.${NC}"
exit 1
fi
else
echo -e "${YELLOW}User declined Docker update please insert these changes manually:${NC}"
echo "${MISSING[*]}"
exit 1
fi
fi
else
# Create new daemon.json if missing
if [[ -n "$FORCE" ]]; then
ans=Y
else
read -p "$DOCKER_DAEMON_CONFIG not found. Create it with IPv6 settings? [Y/n] " ans
ans=${ans:-Y}
fi
if [[ $ans =~ ^[Yy]$ ]]; then
mkdir -p "$(dirname "$DOCKER_DAEMON_CONFIG")"
if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
{
"ipv6": true,
"fixed-cidr-v6": "fd00:dead:beef:c0::/80",
"ip6tables": true,
"experimental": true
}
EOF
elif [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
{
"ipv6": true,
"fixed-cidr-v6": "fd00:dead:beef:c0::/80"
}
EOF
else
# Docker 28+: ipv6 works without fixed-cidr-v6
cat > "$DOCKER_DAEMON_CONFIG" <<EOF
{
"ipv6": true
}
EOF
fi
echo -e "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}"
echo "Restarting Docker..."
(command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
echo "Docker restarted."
else
echo "User declined to create daemon.json please manually merge the docker daemon with these configs:"
echo "${MISSING[*]}"
exit 1
fi
fi
}
# 3) Main wrapper for generate_config.sh and update.sh
configure_ipv6() {
# detect manual override if mailcow.conf is present
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2)
elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then
MANUAL_SETTING="$ENABLE_IPV6"
else
MANUAL_SETTING=""
fi
get_ipv6_support
# if user manually set it, check for mismatch
if [[ "$DETECTED_IPV6" != "true" ]]; then
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF"
else
echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF"
fi
else
export IPV6_BOOL=false
fi
echo "Skipping Docker IPv6 configuration because host does not support IPv6."
echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6."
echo "IPv6 configuration complete: ENABLE_IPV6=false"
sleep 2
return
fi
docker_daemon_edit
if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then
if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then
sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF"
else
echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF"
fi
else
export IPV6_BOOL=true
fi
echo "IPv6 configuration complete: ENABLE_IPV6=true"
}

View file

@ -0,0 +1,96 @@
#!/usr/bin/env bash
# _modules/scripts/migrate_options.sh
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
migrate_config_options() {
sed -i --follow-symlinks '$a\' mailcow.conf
KEYS=(
SOLR_HEAP
SKIP_SOLR
SOLR_PORT
FLATCURVE_EXPERIMENTAL
DISABLE_IPv6
ACME_CONTACT
)
for key in "${KEYS[@]}"; do
if grep -q "${key}" mailcow.conf; then
case "${key}" in
SOLR_HEAP)
echo "Removing ${key} in mailcow.conf"
sed -i '/# Solr heap size in MB\b/d' mailcow.conf
sed -i '/# Solr is a prone to run\b/d' mailcow.conf
sed -i '/SOLR_HEAP\b/d' mailcow.conf
;;
SKIP_SOLR)
echo "Removing ${key} in mailcow.conf"
sed -i '/\bSkip Solr on low-memory\b/d' mailcow.conf
sed -i '/\bSolr is disabled by default\b/d' mailcow.conf
sed -i '/\bDisable Solr or\b/d' mailcow.conf
sed -i '/\bSKIP_SOLR\b/d' mailcow.conf
;;
SOLR_PORT)
echo "Removing ${key} in mailcow.conf"
sed -i '/\bSOLR_PORT\b/d' mailcow.conf
;;
FLATCURVE_EXPERIMENTAL)
echo "Removing ${key} in mailcow.conf"
sed -i '/\bFLATCURVE_EXPERIMENTAL\b/d' mailcow.conf
;;
DISABLE_IPv6)
echo "Migrating ${key} to ENABLE_IPv6 in mailcow.conf"
local old=$(grep '^DISABLE_IPv6=' "mailcow.conf" | cut -d'=' -f2)
local new
if [[ "$old" == "y" ]]; then
new="false"
else
new="true"
fi
sed -i '/^DISABLE_IPv6=/d' "mailcow.conf"
echo "ENABLE_IPV6=$new" >> "mailcow.conf"
;;
ACME_CONTACT)
echo "Deleting obsoleted ${key} in mailcow.conf"
sed -i '/^# Lets Encrypt registration contact information/d' mailcow.conf
sed -i '/^# Optional: Leave empty for none/d' mailcow.conf
sed -i '/^# This value is only used on first order!/d' mailcow.conf
sed -i '/^# Setting it at a later point will require the following steps:/d' mailcow.conf
sed -i '/^# https:\/\/docs.mailcow.email\/troubleshooting\/debug-reset_tls\//d' mailcow.conf
sed -i '/^ACME_CONTACT=.*/d' mailcow.conf
sed -i '/^#ACME_CONTACT=.*/d' mailcow.conf
;;
esac
fi
done
solr_volume=$(docker volume ls -qf name=^${COMPOSE_PROJECT_NAME}_solr-vol-1)
if [[ -n $solr_volume ]]; then
echo -e "\e[34mSolr has been replaced within mailcow since 2025-01.\nThe volume $solr_volume is unused.\e[0m"
sleep 1
if [ ! "$FORCE" ]; then
read -r -p "Remove $solr_volume? [y/N] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo -e "\e[33mRemoving $solr_volume...\e[0m"
docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m"
echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m"
else
echo -e "Not removing $solr_volume. Run \`docker volume rm $solr_volume\` manually if needed."
fi
else
echo -e "\e[33mForce removing $solr_volume...\e[0m"
docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m"
echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m"
fi
fi
# Delete old fts.conf before forced switch to flatcurve to ensure update is working properly
FTS_CONF_PATH="${SCRIPT_DIR}/data/conf/dovecot/conf.d/fts.conf"
if [[ -f "$FTS_CONF_PATH" ]]; then
if grep -q "Autogenerated by mailcow" "$FTS_CONF_PATH"; then
rm -rf $FTS_CONF_PATH
fi
fi
}

View file

@ -0,0 +1,300 @@
#!/usr/bin/env bash
# _modules/scripts/new_options.sh
# THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY!
# DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!!
adapt_new_options() {
CONFIG_ARRAY=(
"AUTODISCOVER_SAN"
"SKIP_LETS_ENCRYPT"
"SKIP_SOGO"
"USE_WATCHDOG"
"WATCHDOG_NOTIFY_EMAIL"
"WATCHDOG_NOTIFY_WEBHOOK"
"WATCHDOG_NOTIFY_WEBHOOK_BODY"
"WATCHDOG_NOTIFY_BAN"
"WATCHDOG_NOTIFY_START"
"WATCHDOG_EXTERNAL_CHECKS"
"WATCHDOG_SUBJECT"
"SKIP_CLAMD"
"SKIP_OLEFY"
"SKIP_IP_CHECK"
"ADDITIONAL_SAN"
"DOVEADM_PORT"
"IPV4_NETWORK"
"IPV6_NETWORK"
"LOG_LINES"
"SNAT_TO_SOURCE"
"SNAT6_TO_SOURCE"
"COMPOSE_PROJECT_NAME"
"DOCKER_COMPOSE_VERSION"
"SQL_PORT"
"API_KEY"
"API_KEY_READ_ONLY"
"API_ALLOW_FROM"
"MAILDIR_GC_TIME"
"MAILDIR_SUB"
"ACL_ANYONE"
"FTS_HEAP"
"FTS_PROCS"
"SKIP_FTS"
"ENABLE_SSL_SNI"
"ALLOW_ADMIN_EMAIL_LOGIN"
"SKIP_HTTP_VERIFICATION"
"SOGO_EXPIRE_SESSION"
"SOGO_URL_ENCRYPTION_KEY"
"REDIS_PORT"
"REDISPASS"
"DOVECOT_MASTER_USER"
"DOVECOT_MASTER_PASS"
"MAILCOW_PASS_SCHEME"
"ADDITIONAL_SERVER_NAMES"
"WATCHDOG_VERBOSE"
"WEBAUTHN_ONLY_TRUSTED_VENDORS"
"SPAMHAUS_DQS_KEY"
"SKIP_UNBOUND_HEALTHCHECK"
"DISABLE_NETFILTER_ISOLATION_RULE"
"HTTP_REDIRECT"
"ENABLE_IPV6"
)
sed -i --follow-symlinks '$a\' mailcow.conf
for option in ${CONFIG_ARRAY[@]}; do
if grep -q "${option}" mailcow.conf; then
continue
fi
echo "Adding new option \"${option}\" to mailcow.conf"
case "${option}" in
AUTODISCOVER_SAN)
echo '# Obtain certificates for autodiscover.* and autoconfig.* domains.' >> mailcow.conf
echo '# This can be useful to switch off in case you are in a scenario where a reverse proxy already handles those.' >> mailcow.conf
echo '# There are mixed scenarios where ports 80,443 are occupied and you do not want to share certs' >> mailcow.conf
echo '# between services. So acme-mailcow obtains for maildomains and all web-things get handled' >> mailcow.conf
echo '# in the reverse proxy.' >> mailcow.conf
echo 'AUTODISCOVER_SAN=y' >> mailcow.conf
;;
DOCKER_COMPOSE_VERSION)
echo "# Used Docker Compose version" >> mailcow.conf
echo "# Switch here between native (compose plugin) and standalone" >> mailcow.conf
echo "# For more informations take a look at the mailcow docs regarding the configuration options." >> mailcow.conf
echo "# Normally this should be untouched but if you decided to use either of those you can switch it manually here." >> mailcow.conf
echo "# Please be aware that at least one of those variants should be installed on your machine or mailcow will fail." >> mailcow.conf
echo "" >> mailcow.conf
echo "DOCKER_COMPOSE_VERSION=${DOCKER_COMPOSE_VERSION}" >> mailcow.conf
;;
DOVEADM_PORT)
echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf
;;
LOG_LINES)
echo '# Max log lines per service to keep in Redis logs' >> mailcow.conf
echo "LOG_LINES=9999" >> mailcow.conf
;;
IPV4_NETWORK)
echo '# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)' >> mailcow.conf
echo "IPV4_NETWORK=172.22.1" >> mailcow.conf
;;
IPV6_NETWORK)
echo '# Internal IPv6 subnet in fc00::/7' >> mailcow.conf
echo "IPV6_NETWORK=fd4d:6169:6c63:6f77::/64" >> mailcow.conf
;;
SQL_PORT)
echo '# Bind SQL to 127.0.0.1 on port 13306' >> mailcow.conf
echo "SQL_PORT=127.0.0.1:13306" >> mailcow.conf
;;
API_KEY)
echo '# Create or override API key for web UI' >> mailcow.conf
echo "#API_KEY=" >> mailcow.conf
;;
API_KEY_READ_ONLY)
echo '# Create or override read-only API key for web UI' >> mailcow.conf
echo "#API_KEY_READ_ONLY=" >> mailcow.conf
;;
API_ALLOW_FROM)
echo '# Must be set for API_KEY to be active' >> mailcow.conf
echo '# IPs only, no networks (networks can be set via UI)' >> mailcow.conf
echo "#API_ALLOW_FROM=" >> mailcow.conf
;;
SNAT_TO_SOURCE)
echo '# Use this IPv4 for outgoing connections (SNAT)' >> mailcow.conf
echo "#SNAT_TO_SOURCE=" >> mailcow.conf
;;
SNAT6_TO_SOURCE)
echo '# Use this IPv6 for outgoing connections (SNAT)' >> mailcow.conf
echo "#SNAT6_TO_SOURCE=" >> mailcow.conf
;;
MAILDIR_GC_TIME)
echo '# Garbage collector cleanup' >> mailcow.conf
echo '# Deleted domains and mailboxes are moved to /var/vmail/_garbage/timestamp_sanitizedstring' >> mailcow.conf
echo '# How long should objects remain in the garbage until they are being deleted? (value in minutes)' >> mailcow.conf
echo '# Check interval is hourly' >> mailcow.conf
echo 'MAILDIR_GC_TIME=1440' >> mailcow.conf
;;
ACL_ANYONE)
echo '# Set this to "allow" to enable the anyone pseudo user. Disabled by default.' >> mailcow.conf
echo '# When enabled, ACL can be created, that apply to "All authenticated users"' >> mailcow.conf
echo '# This should probably only be activated on mail hosts, that are used exclusively by one organisation.' >> mailcow.conf
echo '# Otherwise a user might share data with too many other users.' >> mailcow.conf
echo 'ACL_ANYONE=disallow' >> mailcow.conf
;;
FTS_HEAP)
echo '# Dovecot Indexing (FTS) Process maximum heap size in MB, there is no recommendation, please see Dovecot docs.' >> mailcow.conf
echo '# Flatcurve is used as FTS Engine. It is supposed to be pretty efficient in CPU and RAM consumption.' >> mailcow.conf
echo '# Please always monitor your Resource consumption!' >> mailcow.conf
echo "FTS_HEAP=128" >> mailcow.conf
;;
SKIP_FTS)
echo '# Skip FTS (Fulltext Search) for Dovecot on low-memory, low-threaded systems or if you simply want to disable it.' >> mailcow.conf
echo "# Dovecot inside mailcow use Flatcurve as FTS Backend." >> mailcow.conf
echo "SKIP_FTS=y" >> mailcow.conf
;;
FTS_PROCS)
echo '# Controls how many processes the Dovecot indexing process can spawn at max.' >> mailcow.conf
echo '# Too many indexing processes can use a lot of CPU and Disk I/O' >> mailcow.conf
echo '# Please visit: https://doc.dovecot.org/configuration_manual/service_configuration/#indexer-worker for more informations' >> mailcow.conf
echo "FTS_PROCS=1" >> mailcow.conf
;;
ENABLE_SSL_SNI)
echo '# Create seperate certificates for all domains - y/n' >> mailcow.conf
echo '# this will allow adding more than 100 domains, but some email clients will not be able to connect with alternative hostnames' >> mailcow.conf
echo '# see https://wiki.dovecot.org/SSL/SNIClientSupport' >> mailcow.conf
echo "ENABLE_SSL_SNI=n" >> mailcow.conf
;;
SKIP_SOGO)
echo '# Skip SOGo: Will disable SOGo integration and therefore webmail, DAV protocols and ActiveSync support (experimental, unsupported, not fully implemented) - y/n' >> mailcow.conf
echo "SKIP_SOGO=n" >> mailcow.conf
;;
MAILDIR_SUB)
echo '# MAILDIR_SUB defines a path in a users virtual home to keep the maildir in. Leave empty for updated setups.' >> mailcow.conf
echo "#MAILDIR_SUB=Maildir" >> mailcow.conf
echo "MAILDIR_SUB=" >> mailcow.conf
;;
WATCHDOG_NOTIFY_WEBHOOK)
echo '# Send notifications to a webhook URL that receives a POST request with the content type "application/json".' >> mailcow.conf
echo '# You can use this to send notifications to services like Discord, Slack and others.' >> mailcow.conf
echo '#WATCHDOG_NOTIFY_WEBHOOK=https://discord.com/api/webhooks/XXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' >> mailcow.conf
;;
WATCHDOG_NOTIFY_WEBHOOK_BODY)
echo '# JSON body included in the webhook POST request. Needs to be in single quotes.' >> mailcow.conf
echo '# Following variables are available: SUBJECT, BODY' >> mailcow.conf
WEBHOOK_BODY='{"username": "mailcow Watchdog", "content": "**${SUBJECT}**\n${BODY}"}'
echo "#WATCHDOG_NOTIFY_WEBHOOK_BODY='${WEBHOOK_BODY}'" >> mailcow.conf
;;
WATCHDOG_NOTIFY_BAN)
echo '# Notify about banned IP. Includes whois lookup.' >> mailcow.conf
echo "WATCHDOG_NOTIFY_BAN=y" >> mailcow.conf
;;
WATCHDOG_NOTIFY_START)
echo '# Send a notification when the watchdog is started.' >> mailcow.conf
echo "WATCHDOG_NOTIFY_START=y" >> mailcow.conf
;;
WATCHDOG_SUBJECT)
echo '# Subject for watchdog mails. Defaults to "Watchdog ALERT" followed by the error message.' >> mailcow.conf
echo "#WATCHDOG_SUBJECT=" >> mailcow.conf
;;
WATCHDOG_EXTERNAL_CHECKS)
echo '# Checks if mailcow is an open relay. Requires a SAL. More checks will follow.' >> mailcow.conf
echo '# No data is collected. Opt-in and anonymous.' >> mailcow.conf
echo '# Will only work with unmodified mailcow setups.' >> mailcow.conf
echo "WATCHDOG_EXTERNAL_CHECKS=n" >> mailcow.conf
;;
SOGO_EXPIRE_SESSION)
echo '# SOGo session timeout in minutes' >> mailcow.conf
echo "SOGO_EXPIRE_SESSION=480" >> mailcow.conf
;;
REDIS_PORT)
echo "REDIS_PORT=127.0.0.1:7654" >> mailcow.conf
;;
DOVECOT_MASTER_USER)
echo '# DOVECOT_MASTER_USER and _PASS must _both_ be provided. No special chars.' >> mailcow.conf
echo '# Empty by default to auto-generate master user and password on start.' >> mailcow.conf
echo '# User expands to DOVECOT_MASTER_USER@mailcow.local' >> mailcow.conf
echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf
echo "DOVECOT_MASTER_USER=" >> mailcow.conf
;;
DOVECOT_MASTER_PASS)
echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf
echo "DOVECOT_MASTER_PASS=" >> mailcow.conf
;;
MAILCOW_PASS_SCHEME)
echo '# Password hash algorithm' >> mailcow.conf
echo '# Only certain password hash algorithm are supported. For a fully list of supported schemes,' >> mailcow.conf
echo '# see https://docs.mailcow.email/models/model-passwd/' >> mailcow.conf
echo "MAILCOW_PASS_SCHEME=BLF-CRYPT" >> mailcow.conf
;;
ADDITIONAL_SERVER_NAMES)
echo '# Additional server names for mailcow UI' >> mailcow.conf
echo '#' >> mailcow.conf
echo '# Specify alternative addresses for the mailcow UI to respond to' >> mailcow.conf
echo '# This is useful when you set mail.* as ADDITIONAL_SAN and want to make sure mail.maildomain.com will always point to the mailcow UI.' >> mailcow.conf
echo '# If the server name does not match a known site, Nginx decides by best-guess and may redirect users to the wrong web root.' >> mailcow.conf
echo '# You can understand this as server_name directive in Nginx.' >> mailcow.conf
echo '# Comma separated list without spaces! Example: ADDITIONAL_SERVER_NAMES=a.b.c,d.e.f' >> mailcow.conf
echo 'ADDITIONAL_SERVER_NAMES=' >> mailcow.conf
;;
WEBAUTHN_ONLY_TRUSTED_VENDORS)
echo "# WebAuthn device manufacturer verification" >> mailcow.conf
echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf
echo '# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates' >> mailcow.conf
echo 'WEBAUTHN_ONLY_TRUSTED_VENDORS=n' >> mailcow.conf
;;
SPAMHAUS_DQS_KEY)
echo "# Spamhaus Data Query Service Key" >> mailcow.conf
echo '# Optional: Leave empty for none' >> mailcow.conf
echo '# Enter your key here if you are using a blocked ASN (OVH, AWS, Cloudflare e.g) for the unregistered Spamhaus Blocklist.' >> mailcow.conf
echo '# If empty, it will completely disable Spamhaus blocklists if it detects that you are running on a server using a blocked AS.' >> mailcow.conf
echo '# Otherwise it will work as usual.' >> mailcow.conf
echo 'SPAMHAUS_DQS_KEY=' >> mailcow.conf
;;
WATCHDOG_VERBOSE)
echo '# Enable watchdog verbose logging' >> mailcow.conf
echo 'WATCHDOG_VERBOSE=n' >> mailcow.conf
;;
SKIP_UNBOUND_HEALTHCHECK)
echo '# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) - y/n' >> mailcow.conf
echo 'SKIP_UNBOUND_HEALTHCHECK=n' >> mailcow.conf
;;
DISABLE_NETFILTER_ISOLATION_RULE)
echo '# Prevent netfilter from setting an iptables/nftables rule to isolate the mailcow docker network - y/n' >> mailcow.conf
echo '# CAUTION: Disabling this may expose container ports to other neighbors on the same subnet, even if the ports are bound to localhost' >> mailcow.conf
echo 'DISABLE_NETFILTER_ISOLATION_RULE=n' >> mailcow.conf
;;
HTTP_REDIRECT)
echo '# Redirect HTTP connections to HTTPS - y/n' >> mailcow.conf
echo 'HTTP_REDIRECT=n' >> mailcow.conf
;;
ENABLE_IPV6)
echo '# IPv6 Controller Section' >> mailcow.conf
echo '# This variable controls the usage of IPv6 within mailcow.' >> mailcow.conf
echo '# Can either be true or false | Defaults to true' >> mailcow.conf
echo '# WARNING: MAKE SURE TO PROPERLY CONFIGURE IPv6 ON YOUR HOST FIRST BEFORE ENABLING THIS AS FAULTY CONFIGURATIONS CAN LEAD TO OPEN RELAYS!' >> mailcow.conf
echo '# A COMPLETE DOCKER STACK REBUILD (compose down && compose up -d) IS NEEDED TO APPLY THIS.' >> mailcow.conf
echo ENABLE_IPV6=${IPV6_BOOL} >> mailcow.conf
;;
SKIP_CLAMD)
echo '# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n' >> mailcow.conf
echo 'SKIP_CLAMD=n' >> mailcow.conf
;;
SKIP_OLEFY)
echo '# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n' >> mailcow.conf
echo 'SKIP_OLEFY=n' >> mailcow.conf
;;
REDISPASS)
echo "REDISPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 28)" >> mailcow.conf
;;
SOGO_URL_ENCRYPTION_KEY)
echo '# SOGo URL encryption key (exactly 16 characters, limited to AZ, az, 09)' >> mailcow.conf
echo '# This key is used to encrypt email addresses within SOGo URLs' >> mailcow.conf
echo "SOGO_URL_ENCRYPTION_KEY=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 16)" >> mailcow.conf
;;
*)
echo "${option}=" >> mailcow.conf
;;
esac
done
}

View file

@ -1,8 +1,7 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
RUN apk upgrade --no-cache \
&& apk add --update --no-cache \
bash \
@ -15,7 +14,7 @@ RUN apk upgrade --no-cache \
tini \
tzdata \
python3 \
acme-tiny --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/
acme-tiny
COPY acme.sh /srv/acme.sh
COPY functions.sh /srv/functions.sh

View file

@ -138,7 +138,7 @@ log_f "Resolver OK"
log_f "Waiting for domain table..."
while [[ -z ${DOMAIN_TABLE} ]]; do
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)
DOMAIN_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
[[ -z ${DOMAIN_TABLE} ]] && sleep 10
done
log_f "OK" no_date
@ -159,18 +159,6 @@ while true; do
fi
if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then
log_f "Generating missing Lets Encrypt account key..."
if [[ ! -z ${ACME_CONTACT} ]]; then
if ! verify_email "${ACME_CONTACT}"; then
log_f "Invalid email address, will not start registration!"
sleep 365d
exec $(readlink -f "$0")
else
ACME_CONTACT_PARAMETER="--contact mailto:${ACME_CONTACT}"
log_f "Valid email address, using ${ACME_CONTACT} for registration"
fi
else
ACME_CONTACT_PARAMETER=""
fi
openssl genrsa 4096 > ${ACME_BASE}/acme/account.pem
else
log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem"
@ -218,7 +206,7 @@ while true; do
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
# Fetch certs for autoconfig and autodiscover subdomains
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig')
ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig' 'mta-sts')
fi
if [[ ${SKIP_IP_CHECK} != "y" ]]; then
@ -231,7 +219,7 @@ while true; do
#########################################
# IP and webroot challenge verification #
SQL_DOMAINS=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs)
SQL_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs)
if [[ ! $? -eq 0 ]]; then
log_f "Failed to read SQL domains, retrying in 1 minute..."
sleep 1m
@ -299,7 +287,7 @@ while true; do
VALIDATED_CERTIFICATES+=("${CERT_NAME}")
# obtain server certificate if required
ACME_CONTACT_PARAMETER=${ACME_CONTACT_PARAMETER} DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa
DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa
RETURN="$?"
if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully
CERT_AMOUNT_CHANGED=1

View file

@ -93,8 +93,8 @@ until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do
sleep 2
done
log_f "Resolver OK"
log_f "Using command acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/"
ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} \
log_f "Using command acme-tiny ${DIRECTORY_URL} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/"
ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} \
--account-key ${ACME_BASE}/acme/account.pem \
--disable-check \
--csr ${CSR} \

View file

@ -1,3 +1,3 @@
FROM debian:bookworm-slim
FROM debian:trixie-slim
RUN apt update && apt install pigz
RUN apt update && apt install pigz zstd -y --no-install-recommends

View file

@ -8,7 +8,7 @@ fi
# Cleaning up garbage
echo "Cleaning up tmp files..."
rm -rf /var/lib/clamav/clamav-*.tmp
rm -rf /var/lib/clamav/tmp.*
# Prepare whitelist

View file

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

View file

@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View file

@ -241,9 +241,9 @@ async def handle_pubsub_messages(channel: aioredis.client.PubSub):
else:
dockerapi.logger.error("api call: missing container_name, post_action or request")
else:
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
dockerapi.logger.error("Unknown PubSub received - %s" % json.dumps(data_json))
else:
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
dockerapi.logger.error("Unknown PubSub received - %s" % json.dumps(data_json))
await asyncio.sleep(0.0)
except asyncio.TimeoutError:

View file

@ -1,9 +1,9 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
ARG GOSU_VERSION=1.16
ARG GOSU_VERSION=1.17
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
@ -34,9 +34,13 @@ 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 +69,7 @@ RUN addgroup -g 5000 vmail \
perl-par-packer \
perl-parse-recdescent \
perl-lockfile-simple \
libproc \
libproc2 \
perl-readonly \
perl-regexp-common \
perl-sys-meminfo \

View file

@ -15,6 +15,6 @@ if ! [[ ${MAX_AGE} =~ ${NUM_REGEXP} ]] ; then
exit 1
fi
TO_DELETE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN)
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY"
TO_DELETE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN)
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY"
echo "Deleted ${TO_DELETE} items from quarantine table (max age is ${MAX_AGE//[!0-9]/} days)"

View file

@ -28,7 +28,7 @@ ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
# Create missing directories
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
[[ ! -d /etc/dovecot/lua/ ]] && mkdir -p /etc/dovecot/lua/
[[ ! -d /etc/dovecot/auth/ ]] && mkdir -p /etc/dovecot/auth/
[[ ! -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
@ -131,123 +131,6 @@ 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
# 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
@ -321,16 +204,17 @@ EOF
# Create random master Password for SOGo SSO
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
# Creating additional creds file for SOGo notify crons (calendars, etc)
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
cat <<EOF > /etc/dovecot/sogo-sso.conf
# Autogenerated by mailcow
passdb {
driver = static
args = allow_real_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
args = allow_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
}
EOF
# Creating additional creds file for SOGo notify crons (calendars, etc) (dummy user, sso password)
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then
# Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated
cat <<'EOF' > /usr/local/bin/quota_notify.py
@ -385,8 +269,8 @@ sievec /usr/lib/dovecot/sieve/report-ham.sieve
# Fix permissions
chown root:root /etc/dovecot/sql/*.conf
chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/lua/passwd-verify.lua
chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/lua/passwd-verify.lua
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 -R vmail:vmail /var/vmail/sieve
chown -R vmail:vmail /var/volatile
chown -R vmail:vmail /var/vmail_index
@ -414,15 +298,15 @@ printenv | sed 's/^\(.*\)$/export \1/g' > /source_env.sh
# Clean stopped imapsync jobs
rm -f /tmp/imapsync_busy.lock
IMAPSYNC_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs)
[[ ! -z ${IMAPSYNC_TABLE} ]] && mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'"
IMAPSYNC_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs)
[[ ! -z ${IMAPSYNC_TABLE} ]] && mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'"
# Envsubst maildir_gc
echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh
# GUID generation
while [[ ${VERSIONS_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_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then
VERSIONS_OK=OK
else
echo "Waiting for versions table to be created..."
@ -433,11 +317,11 @@ PUBKEY_MCRYPT=$(doveconf -P 2> /dev/null | grep -i mail_crypt_global_public_key
if [ -f ${PUBKEY_MCRYPT} ]; then
GUID=$(cat <(echo ${MAILCOW_HOSTNAME}) /mail_crypt/ecpubkey.pem | sha256sum | cut -d ' ' -f1 | tr -cd "[a-fA-F0-9.:/] ")
if [ ${#GUID} -eq 64 ]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "${GUID}");
EOF
else
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "INVALID");
EOF
fi
@ -456,7 +340,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/lua/passwd-verify.lua
touch /etc/dovecot/auth/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

@ -132,8 +132,8 @@ while ($row = $sth->fetchrow_arrayref()) {
"--tmpdir", "/tmp",
"--nofoldersizes",
"--addheader",
($timeout1 gt "0" ? () : ('--timeout1', $timeout1)),
($timeout2 gt "0" ? () : ('--timeout2', $timeout2)),
($timeout1 le "0" ? () : ('--timeout1', $timeout1)),
($timeout2 le "0" ? () : ('--timeout2', $timeout2)),
($exclude eq "" ? () : ("--exclude", $exclude)),
($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)),
($maxage eq "0" ? () : ('--maxage', $maxage)),

View file

@ -8,7 +8,8 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
import jinja2
from jinja2 import Template
from jinja2 import TemplateError
from jinja2.sandbox import SandboxedEnvironment
import json
import redis
import time
@ -75,22 +76,27 @@ try:
def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
if category == "add_header": category = "add header"
meta_query = query_mysql('SELECT SHA2(CONCAT(id, qid), 256) AS qhash, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
if len(meta_query) == 0:
return
msg_count = len(meta_query)
env = SandboxedEnvironment()
if r.get('Q_HTML'):
try:
template = Template(r.get('Q_HTML'))
except:
print("Error: Cannot parse quarantine template, falling back to default template.")
with open('/templates/quarantine.tpl') as file_:
template = Template(file_.read())
try:
template = env.from_string(r.get('Q_HTML'))
except Exception:
print("Error: Cannot parse quarantine template, falling back to default template.")
with open('/templates/quarantine.tpl') as file_:
template = env.from_string(file_.read())
else:
with open('/templates/quarantine.tpl') as file_:
template = Template(file_.read())
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
with open('/templates/quarantine.tpl') as file_:
template = env.from_string(file_.read())
try:
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
except (jinja2.exceptions.SecurityError, TemplateError) as ex:
print(f"SecurityError or TemplateError in template rendering: {ex}")
return
text = html2text.html2text(html)
count = 0
while count < 15:

View file

@ -6,7 +6,7 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
import jinja2
from jinja2 import Template
from jinja2.sandbox import SandboxedEnvironment
import redis
import time
import json
@ -23,7 +23,7 @@ else:
while True:
try:
r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, username='quota_notify', password='')
r.ping()
except Exception as ex:
print('%s - trying again...' % (ex))
@ -33,16 +33,24 @@ while True:
if r.get('QW_HTML'):
try:
template = Template(r.get('QW_HTML'))
except:
print("Error: Cannot parse quarantine template, falling back to default template.")
env = SandboxedEnvironment()
template = env.from_string(r.get('QW_HTML'))
except Exception:
print("Error: Cannot parse quota template, falling back to default template.")
with open('/templates/quota.tpl') as file_:
template = Template(file_.read())
env = SandboxedEnvironment()
template = env.from_string(file_.read())
else:
with open('/templates/quota.tpl') as file_:
template = Template(file_.read())
env = SandboxedEnvironment()
template = env.from_string(file_.read())
try:
html = template.render(username=username, percent=percent)
except (jinja2.exceptions.SecurityError, jinja2.TemplateError) as ex:
print(f"SecurityError or TemplateError in template rendering: {ex}")
sys.exit(1)
html = template.render(username=username, percent=percent)
text = html2text.html2text(html)
try:

View file

@ -38,8 +38,13 @@ filter f_replica {
not match("User has no mail_replica in userdb" value("MESSAGE"));
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
};
filter f_dovecot_auth_try {
not match("- trying the next passdb" value("MESSAGE")) and
not match("- trying the next userdb" value("MESSAGE"));
};
log {
source(s_dgram);
filter(f_dovecot_auth_try);
filter(f_replica);
destination(d_stdout);
filter(f_mail);

View file

@ -38,8 +38,13 @@ filter f_replica {
not match("User has no mail_replica in userdb" value("MESSAGE"));
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
};
filter f_dovecot_auth_try {
not match("- trying the next passdb" value("MESSAGE")) and
not match("- trying the next userdb" value("MESSAGE"));
};
log {
source(s_dgram);
filter(f_dovecot_auth_try);
filter(f_replica);
destination(d_stdout);
filter(f_mail);

View file

@ -23,3 +23,4 @@ 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,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View file

@ -1,6 +1,6 @@
#!/bin/sh
backend=iptables
backend=nftables
nft list table ip filter &>/dev/null
nftables_found=$?

View file

@ -1,5 +1,7 @@
#!/usr/bin/env python3
DEBUG = False
import re
import os
import sys
@ -20,10 +22,13 @@ from modules.Logger import Logger
from modules.IPTables import IPTables
from modules.NFTables import NFTables
def logdebug(msg):
if DEBUG:
logger.logInfo("DEBUG: %s" % msg)
# globals
# Globals
WHITELIST = []
BLACKLIST= []
BLACKLIST = []
bans = {}
quit_now = False
exit_code = 0
@ -33,12 +38,10 @@ r = None
pubsub = None
clear_before_quit = False
def refreshF2boptions():
global f2boptions
global quit_now
global exit_code
f2boptions = {}
if not r.get('F2B_OPTIONS'):
@ -52,8 +55,9 @@ def refreshF2boptions():
else:
try:
f2boptions = json.loads(r.get('F2B_OPTIONS'))
except ValueError:
logger.logCrit('Error loading F2B options: F2B_OPTIONS is not json')
except ValueError as e:
logger.logCrit(
'Error loading F2B options: F2B_OPTIONS is not json. Exception: %s' % e)
quit_now = True
exit_code = 2
@ -61,15 +65,15 @@ def refreshF2boptions():
r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False))
def verifyF2boptions(f2boptions):
verifyF2boption(f2boptions,'ban_time', 1800)
verifyF2boption(f2boptions,'max_ban_time', 10000)
verifyF2boption(f2boptions,'ban_time_increment', True)
verifyF2boption(f2boptions,'max_attempts', 10)
verifyF2boption(f2boptions,'retry_window', 600)
verifyF2boption(f2boptions,'netban_ipv4', 32)
verifyF2boption(f2boptions,'netban_ipv6', 128)
verifyF2boption(f2boptions,'banlist_id', str(uuid.uuid4()))
verifyF2boption(f2boptions,'manage_external', 0)
verifyF2boption(f2boptions, 'ban_time', 1800)
verifyF2boption(f2boptions, 'max_ban_time', 10000)
verifyF2boption(f2boptions, 'ban_time_increment', True)
verifyF2boption(f2boptions, 'max_attempts', 10)
verifyF2boption(f2boptions, 'retry_window', 600)
verifyF2boption(f2boptions, 'netban_ipv4', 32)
verifyF2boption(f2boptions, 'netban_ipv6', 128)
verifyF2boption(f2boptions, 'banlist_id', str(uuid.uuid4()))
verifyF2boption(f2boptions, 'manage_external', 0)
def verifyF2boption(f2boptions, f2boption, f2bdefault):
f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault
@ -85,11 +89,10 @@ def refreshF2bregex():
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 .+'
f2bregex[6] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): Password mismatch \(SHA1 of given password: [a-f0-9]+\)'
f2bregex[7] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): unknown user \(SHA1 of given password: [a-f0-9]+\)'
f2bregex[8] = r'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
f2bregex[9] = r'([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False))
else:
try:
@ -112,7 +115,7 @@ def get_ip(address):
def ban(address):
global f2boptions
global lock
logdebug("ban() called with address=%s" % address)
refreshF2boptions()
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
RETRY_WINDOW = int(f2boptions['retry_window'])
@ -120,31 +123,43 @@ def ban(address):
NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6'])
ip = get_ip(address)
if not ip: return
if not ip:
logdebug("No valid IP -- skipping ban()")
return
address = str(ip)
self_network = ipaddress.ip_network(address)
with lock:
temp_whitelist = set(WHITELIST)
if temp_whitelist:
for wl_key in temp_whitelist:
wl_net = ipaddress.ip_network(wl_key, False)
if wl_net.overlaps(self_network):
logger.logInfo('Address %s is whitelisted by rule %s' % (self_network, wl_net))
return
logdebug("Checking if %s overlaps with any WHITELIST entries" % self_network)
if temp_whitelist:
for wl_key in temp_whitelist:
wl_net = ipaddress.ip_network(wl_key, False)
logdebug("Checking overlap between %s and %s" % (self_network, wl_net))
if wl_net.overlaps(self_network):
logger.logInfo(
'Address %s is allowlisted by rule %s' % (self_network, wl_net))
return
net = ipaddress.ip_network((address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
net = ipaddress.ip_network(
(address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False)
net = str(net)
logdebug("Ban net: %s" % net)
if not net in bans:
bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0}
logdebug("Initing new ban counter for %s" % net)
current_attempt = time.time()
logdebug("Current attempt ts=%s, previous: %s, retry_window: %s" %
(current_attempt, bans[net]['last_attempt'], RETRY_WINDOW))
if current_attempt - bans[net]['last_attempt'] > RETRY_WINDOW:
bans[net]['attempts'] = 0
logdebug("Ban counter for %s reset as window expired" % net)
bans[net]['attempts'] += 1
bans[net]['last_attempt'] = current_attempt
logdebug("%s attempts now %d" % (net, bans[net]['attempts']))
if bans[net]['attempts'] >= MAX_ATTEMPTS:
cur_time = int(round(time.time()))
@ -152,34 +167,41 @@ def ban(address):
logger.logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 ))
if type(ip) is ipaddress.IPv4Address and int(f2boptions['manage_external']) != 1:
with lock:
logdebug("Calling tables.banIPv4(%s)" % net)
tables.banIPv4(net)
elif int(f2boptions['manage_external']) != 1:
with lock:
logdebug("Calling tables.banIPv6(%s)" % net)
tables.banIPv6(net)
logdebug("Updating F2B_ACTIVE_BANS[%s]=%d" %
(net, cur_time + NET_BAN_TIME))
r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME)
else:
logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % (
MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net))
def unban(net):
global lock
logdebug("Calling unban() with net=%s" % net)
if not net in bans:
logger.logInfo('%s is not banned, skipping unban and deleting from queue (if any)' % net)
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
return
logger.logInfo(
'%s is not banned, skipping unban and deleting from queue (if any)' % net)
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
return
logger.logInfo('Unbanning %s' % net)
if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network:
with lock:
logdebug("Calling tables.unbanIPv4(%s)" % net)
tables.unbanIPv4(net)
else:
with lock:
logdebug("Calling tables.unbanIPv6(%s)" % net)
tables.unbanIPv6(net)
r.hdel('F2B_ACTIVE_BANS', '%s' % net)
r.hdel('F2B_QUEUE_UNBAN', '%s' % net)
if net in bans:
logdebug("Unban for %s, setting attempts=0, ban_counter+=1" % net)
bans[net]['attempts'] = 0
bans[net]['ban_counter'] += 1
@ -205,17 +227,19 @@ def permBan(net, unban=False):
if is_unbanned:
r.hdel('F2B_PERM_BANS', '%s' % net)
logger.logCrit('Removed host/network %s from blacklist' % net)
logger.logCrit('Removed host/network %s from denylist' % net)
elif is_banned:
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
logger.logCrit('Added host/network %s to blacklist' % net)
logger.logCrit('Added host/network %s to denylist' % net)
def clear():
global lock
logger.logInfo('Clearing all bans')
for net in bans.copy():
logdebug("Unbanning net: %s" % net)
unban(net)
with lock:
logdebug("Clearing IPv4/IPv6 table")
tables.clearIPv4Table()
tables.clearIPv6Table()
try:
@ -276,21 +300,35 @@ def snat6(snat_target):
def autopurge():
global f2boptions
logdebug("autopurge thread started")
while not quit_now:
logdebug("autopurge tick")
time.sleep(10)
refreshF2boptions()
MAX_ATTEMPTS = int(f2boptions['max_attempts'])
QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN')
logdebug("QUEUE_UNBAN: %s" % QUEUE_UNBAN)
if QUEUE_UNBAN:
for net in QUEUE_UNBAN:
logdebug("Autopurge: unbanning queued net: %s" % net)
unban(str(net))
for net in bans.copy():
if bans[net]['attempts'] >= MAX_ATTEMPTS:
NET_BAN_TIME = calcNetBanTime(bans[net]['ban_counter'])
TIME_SINCE_LAST_ATTEMPT = time.time() - bans[net]['last_attempt']
if TIME_SINCE_LAST_ATTEMPT > NET_BAN_TIME:
unban(net)
# Only check expiry for actively banned IPs:
active_bans = r.hgetall('F2B_ACTIVE_BANS')
now = time.time()
for net_str, expire_str in active_bans.items():
logdebug("Checking ban expiry for (actively banned): %s" % net_str)
# Defensive: always process if timer missing or expired
try:
expire = float(expire_str)
except Exception:
logdebug("Invalid expire time for %s; unbanning" % net_str)
unban(net_str)
continue
time_left = expire - now
logdebug("Time left for %s: %.1f seconds" % (net_str, time_left))
if time_left <= 0:
logdebug("Ban expired for %s" % net_str)
unban(net_str)
def mailcowChainOrder():
global lock
@ -360,7 +398,7 @@ def whitelistUpdate():
with lock:
if Counter(new_whitelist) != Counter(WHITELIST):
WHITELIST = new_whitelist
logger.logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST))
logger.logInfo('Allowlist was changed, it has %s entries' % len(WHITELIST))
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
def blacklistUpdate():
@ -376,7 +414,7 @@ def blacklistUpdate():
addban = set(new_blacklist).difference(BLACKLIST)
delban = set(BLACKLIST).difference(new_blacklist)
BLACKLIST = new_blacklist
logger.logInfo('Blacklist was changed, it has %s entries' % len(BLACKLIST))
logger.logInfo('Denylist was changed, it has %s entries' % len(BLACKLIST))
if addban:
for net in addban:
permBan(net=net)
@ -387,42 +425,43 @@ def blacklistUpdate():
def sigterm_quit(signum, frame):
global clear_before_quit
logdebug("SIGTERM received, setting clear_before_quit to True and exiting")
clear_before_quit = True
sys.exit(exit_code)
def berfore_quit():
def before_quit():
logdebug("before_quit called, clear_before_quit=%s" % clear_before_quit)
if clear_before_quit:
clear()
if pubsub is not None:
pubsub.unsubscribe()
if __name__ == '__main__':
atexit.register(berfore_quit)
logger = Logger()
logdebug("Sys.argv: %s" % sys.argv)
atexit.register(before_quit)
signal.signal(signal.SIGTERM, sigterm_quit)
# init Logger
logger = Logger()
# init backend
backend = sys.argv[1]
logdebug("Backend: %s" % backend)
if backend == "nftables":
logger.logInfo('Using NFTables backend')
tables = NFTables(chain_name, logger)
else:
logger.logInfo('Using IPTables backend')
logger.logWarn(
"DEPRECATION: iptables-legacy is deprecated and will be removed in future releases. "
"Please switch to nftables on your host to ensure complete compatibility."
)
time.sleep(5)
tables = IPTables(chain_name, logger)
# In case a previous session was killed without cleanup
clear()
# Reinit MAILCOW chain
# Is called before threads start, no locking
logger.logInfo("Initializing mailcow netfilter chain")
tables.initChainIPv4()
tables.initChainIPv6()
if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE").lower() in ("y", "yes"):
if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE", "").lower() in ("y", "yes"):
logger.logInfo(f"Skipping {chain_name} isolation")
else:
logger.logInfo(f"Setting {chain_name} isolation")
@ -433,23 +472,28 @@ if __name__ == '__main__':
try:
redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '')
redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '')
logdebug(
"Connecting redis (SLAVEOF_IP:%s, PORT:%s)" % (redis_slaveof_ip, redis_slaveof_port))
if "".__eq__(redis_slaveof_ip):
r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
r = redis.StrictRedis(
host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
else:
r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS'])
r = redis.StrictRedis(
host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS'])
r.ping()
pubsub = r.pubsub()
except Exception as ex:
print('%s - trying again in 3 seconds' % (ex))
logdebug(
'Redis connection failed: %s - trying again in 3 seconds' % (ex))
time.sleep(3)
else:
break
logger.set_redis(r)
logdebug("Redis connection established, setting up F2B keys")
# rename fail2ban to netfilter
if r.exists('F2B_LOG'):
logdebug("Renaming F2B_LOG to NETFILTER_LOG")
r.rename('F2B_LOG', 'NETFILTER_LOG')
# clear bans in redis
r.delete('F2B_ACTIVE_BANS')
r.delete('F2B_PERM_BANS')
@ -464,7 +508,7 @@ if __name__ == '__main__':
snat_ip = os.getenv('SNAT_TO_SOURCE')
snat_ipo = ipaddress.ip_address(snat_ip)
if type(snat_ipo) is ipaddress.IPv4Address:
snat4_thread = Thread(target=snat4,args=(snat_ip,))
snat4_thread = Thread(target=snat4, args=(snat_ip,))
snat4_thread.daemon = True
snat4_thread.start()
except ValueError:
@ -500,4 +544,5 @@ if __name__ == '__main__':
while not quit_now:
time.sleep(0.5)
sys.exit(exit_code)
logdebug("Exiting with code %s" % exit_code)
sys.exit(exit_code)

View file

@ -1,5 +1,6 @@
import time
import json
import datetime
class Logger:
def __init__(self):
@ -8,17 +9,28 @@ class Logger:
def set_redis(self, redis):
self.r = redis
def _format_timestamp(self):
# Local time with milliseconds
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def log(self, priority, message):
tolog = {}
tolog['time'] = int(round(time.time()))
tolog['priority'] = priority
tolog['message'] = message
print(message)
# build redis-friendly dict
tolog = {
'time': int(round(time.time())), # keep raw timestamp for Redis
'priority': priority,
'message': message
}
# print human-readable message with timestamp
ts = self._format_timestamp()
print(f"{ts} {priority.upper()}: {message}", flush=True)
# also push JSON to Redis if connected
if self.r is not None:
try:
self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False))
except Exception as ex:
print('Failed logging to redis: %s' % (ex))
print(f'{ts} WARN: Failed logging to redis: {ex}', flush=True)
def logWarn(self, message):
self.log('warn', message)
@ -27,4 +39,4 @@ class Logger:
self.log('crit', message)
def logInfo(self, message):
self.log('info', message)
self.log('info', message)

View file

@ -10,7 +10,7 @@ def includes_conf(env, template_vars):
server_name_config = f"server_name {template_vars['MAILCOW_HOSTNAME']} autodiscover.* autoconfig.* {' '.join(template_vars['ADDITIONAL_SERVER_NAMES'])};"
listen_plain_config = f"listen {template_vars['HTTP_PORT']};"
listen_ssl_config = f"listen {template_vars['HTTPS_PORT']};"
if not template_vars['DISABLE_IPv6']:
if template_vars['ENABLE_IPV6']:
listen_plain_config += f"\nlisten [::]:{template_vars['HTTP_PORT']};"
listen_ssl_config += f"\nlisten [::]:{template_vars['HTTPS_PORT']} ssl;"
listen_ssl_config += "\nhttp2 on;"
@ -43,10 +43,11 @@ def nginx_conf(env, template_vars):
def prepare_template_vars():
ipv4_network = os.getenv("IPV4_NETWORK", "172.22.1")
additional_server_names = os.getenv("ADDITIONAL_SERVER_NAMES", "")
trusted_proxies = os.getenv("TRUSTED_PROXIES", "")
template_vars = {
'IPV4_NETWORK': ipv4_network,
'TRUSTED_NETWORK': os.getenv("TRUSTED_NETWORK", False),
'TRUSTED_PROXIES': [item.strip() for item in trusted_proxies.split(",") if item.strip()],
'SKIP_RSPAMD': os.getenv("SKIP_RSPAMD", "n").lower() in ("y", "yes"),
'SKIP_SOGO': os.getenv("SKIP_SOGO", "n").lower() in ("y", "yes"),
'NGINX_USE_PROXY_PROTOCOL': os.getenv("NGINX_USE_PROXY_PROTOCOL", "n").lower() in ("y", "yes"),
@ -57,7 +58,7 @@ def prepare_template_vars():
'SOGOHOST': os.getenv("SOGOHOST", ipv4_network + ".248"),
'RSPAMDHOST': os.getenv("RSPAMDHOST", "rspamd-mailcow"),
'PHPFPMHOST': os.getenv("PHPFPMHOST", "php-fpm-mailcow"),
'DISABLE_IPv6': os.getenv("DISABLE_IPv6", "n").lower() in ("y", "yes"),
'ENABLE_IPV6': os.getenv("ENABLE_IPV6", "true").lower() != "false",
'HTTP_REDIRECT': os.getenv("HTTP_REDIRECT", "n").lower() in ("y", "yes"),
}

View file

@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View file

@ -32,6 +32,13 @@ import time
import magic
import re
skip_olefy = os.getenv('SKIP_OLEFY', '')
if skip_olefy.lower() in ['yes', 'y']:
print("SKIP_OLEFY=y, skipping Olefy...")
time.sleep(365 * 24 * 60 * 60)
sys.exit(0)
# merge variables from /etc/olefy.conf and the defaults
olefy_listen_addr_string = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1,::1')
olefy_listen_port = int(os.getenv('OLEFY_BINDPORT', '10050'))
@ -113,7 +120,7 @@ def oletools( stream, tmp_file_name, lid ):
out = bytes(out.decode('utf-8', 'ignore').replace(' ', ' ').replace('\t', '').replace('\n', '').replace('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)', ''), encoding="utf-8")
failed = False
if out.__len__() < 30:
logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode,
logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode,
out.decode('utf-8', 'ignore'), err.decode('utf-8', 'ignore')))
out = b'[ { "error": "Unhandled error - too short olevba response" } ]'
failed = True

View file

@ -1,19 +1,19 @@
FROM php:8.2-fpm-alpine3.20
FROM php:8.2-fpm-alpine3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG APCU_PECL_VERSION=5.1.24
ARG APCU_PECL_VERSION=5.1.27
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
ARG IMAGICK_PECL_VERSION=3.7.0
ARG IMAGICK_PECL_VERSION=3.8.0
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG MAILPARSE_PECL_VERSION=3.1.8
ARG MAILPARSE_PECL_VERSION=3.1.9
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG MEMCACHED_PECL_VERSION=3.2.0
ARG MEMCACHED_PECL_VERSION=3.3.0
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
ARG REDIS_PECL_VERSION=6.1.0
ARG REDIS_PECL_VERSION=6.2.0
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
ARG COMPOSER_VERSION=2.6.6
ARG COMPOSER_VERSION=2.8.6
RUN apk add -U --no-cache autoconf \
aspell-dev \

View file

@ -81,7 +81,7 @@ if [ ${SQL_CHANGED} -eq 1 ]; then
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)
TZ_CHECK=$(mariadb --skip-ssl --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.${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:"
@ -120,11 +120,11 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
while read line
do
DOMAIN_ARR+=("$line")
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
while read line
do
DOMAIN_ARR+=("$line")
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
if [[ ! -z ${DOMAIN_ARR} ]]; then
for domain in "${DOMAIN_ARR[@]}"; do
@ -146,13 +146,13 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]})
if [[ ! -z ${VALIDATED_IPS} ]]; then
if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELETE FROM api WHERE access = 'rw';
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw");
EOF
fi
if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELETE FROM api WHERE access = 'ro';
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro");
EOF
@ -161,13 +161,13 @@ EOF
fi
# Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED)
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DROP EVENT IF EXISTS clean_spamalias;
DELIMITER //
CREATE EVENT clean_spamalias
ON SCHEDULE EVERY 1 DAY DO
BEGIN
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP();
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0;
END;
//
DELIMITER ;

View file

@ -0,0 +1,50 @@
FROM golang:1.25-bookworm AS builder
WORKDIR /src
ENV CGO_ENABLED=0 \
GO111MODULE=on \
NOOPT=1 \
VERSION=1.8.22
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
cd /src/postfix-tlspol && \
scripts/build.sh build-only
FROM debian:bookworm-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ENV LC_ALL=C
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
dirmngr \
dnsutils \
iputils-ping \
sudo \
supervisor \
redis-tools \
syslog-ng \
syslog-ng-core \
syslog-ng-mod-redis \
tzdata \
&& rm -rf /var/lib/apt/lists/* \
&& touch /etc/default/locale
COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
COPY postfix-tlspol.sh /opt/postfix-tlspol.sh
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
COPY docker-entrypoint.sh /docker-entrypoint.sh
COPY --from=builder /src/postfix-tlspol/build/postfix-tlspol /usr/local/bin/postfix-tlspol
RUN chmod +x /opt/postfix-tlspol.sh \
/usr/local/sbin/stop-supervisor.sh \
/docker-entrypoint.sh
RUN rm -rf /tmp/* /var/tmp/*
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

View file

@ -0,0 +1,7 @@
#!/bin/bash
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
fi
exec "$@"

View file

@ -0,0 +1,52 @@
#!/bin/bash
LOGLVL=info
if [ ${DEV_MODE} != "n" ]; then
echo -e "\e[31mEnabling debug mode\e[0m"
set -x
LOGLVL=debug
fi
[[ ! -d /etc/postfix-tlspol ]] && mkdir -p /etc/postfix-tlspol
[[ ! -d /var/lib/postfix-tlspol ]] && mkdir -p /var/lib/postfix-tlspol
until dig +short mailcow.email > /dev/null; do
echo "Waiting for DNS..."
sleep 1
done
# Do not attempt to write to slave
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning"
else
export REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning"
fi
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
echo "Waiting for Redis..."
sleep 2
done
echo "Waiting for Postfix..."
until ping postfix -c1 > /dev/null; do
sleep 1
done
echo "Postfix OK"
cat <<EOF > /etc/postfix-tlspol/config.yaml
server:
address: 0.0.0.0:8642
log-level: ${LOGLVL}
prefetch: true
cache-file: /var/lib/postfix-tlspol/cache.db
dns:
# must support DNSSEC
address: 127.0.0.11:53
EOF
/usr/local/bin/postfix-tlspol -config /etc/postfix-tlspol/config.yaml

View file

@ -0,0 +1,8 @@
#!/bin/bash
printf "READY\n";
while read line; do
echo "Processing Event: $line" >&2;
kill -3 $(cat "/var/run/supervisord.pid")
done < /dev/stdin

View file

@ -0,0 +1,25 @@
[supervisord]
pidfile=/var/run/supervisord.pid
nodaemon=true
user=root
[program:syslog-ng]
command=/usr/sbin/syslog-ng --foreground --no-caps
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autostart=true
[program:postfix-tlspol]
startsecs=10
autorestart=true
command=/opt/postfix-tlspol.sh
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[eventlistener:processes]
command=/usr/local/sbin/stop-supervisor.sh
events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL

View file

@ -0,0 +1,45 @@
@version: 3.38
@include "scl.conf"
options {
chain_hostnames(off);
flush_lines(0);
use_dns(no);
dns_cache(no);
use_fqdn(no);
owner("root"); group("adm"); perm(0640);
stats_freq(0);
bad_hostname("^gconfd$");
};
source s_src {
unix-stream("/dev/log");
internal();
};
destination d_stdout { pipe("/dev/stdout"); };
destination d_redis_ui_log {
redis(
host("`REDIS_SLAVEOF_IP`")
persist-name("redis1")
port(`REDIS_SLAVEOF_PORT`)
auth("`REDISPASS`")
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
);
};
filter f_mail { facility(mail); };
# start
# overriding warnings are still displayed when the entrypoint runs its initial check
# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs
# Some other warnings are ignored
filter f_ignore {
not match("overriding earlier entry" value("MESSAGE"));
not match("TLS SNI from checks.mailcow.email" value("MESSAGE"));
not match("no SASL support" value("MESSAGE"));
not facility (local0, local1, local2, local3, local4, local5, local6, local7);
};
# end
log {
source(s_src);
filter(f_ignore);
destination(d_stdout);
filter(f_mail);
destination(d_redis_ui_log);
};

View file

@ -0,0 +1,45 @@
@version: 3.38
@include "scl.conf"
options {
chain_hostnames(off);
flush_lines(0);
use_dns(no);
dns_cache(no);
use_fqdn(no);
owner("root"); group("adm"); perm(0640);
stats_freq(0);
bad_hostname("^gconfd$");
};
source s_src {
unix-stream("/dev/log");
internal();
};
destination d_stdout { pipe("/dev/stdout"); };
destination d_redis_ui_log {
redis(
host("redis-mailcow")
persist-name("redis1")
port(6379)
auth("`REDISPASS`")
command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n")
);
};
filter f_mail { facility(mail); };
# start
# overriding warnings are still displayed when the entrypoint runs its initial check
# warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs
# Some other warnings are ignored
filter f_ignore {
not match("overriding earlier entry" value("MESSAGE"));
not match("TLS SNI from checks.mailcow.email" value("MESSAGE"));
not match("no SASL support" value("MESSAGE"));
not facility (local0, local1, local2, local3, local4, local5, local6, local7);
};
# end
log {
source(s_src);
filter(f_ignore);
destination(d_stdout);
filter(f_mail);
destination(d_redis_ui_log);
};

View file

@ -1,9 +1,9 @@
FROM debian:bookworm-slim
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ENV LC_ALL C
ENV LC_ALL=C
RUN dpkg-divert --local --rename --add /sbin/initctl \
&& ln -sf /bin/true /sbin/initctl \

View file

@ -390,7 +390,7 @@ hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT goto FROM spamalias
WHERE address='%s'
AND validity >= UNIX_TIMESTAMP()
AND (validity >= UNIX_TIMESTAMP() OR permanent != 0)
EOF
if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then
@ -416,10 +416,11 @@ postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
bl.mailspike.net=127.0.0.[10;11;12]*4
EOF
fi
DNSBL_CONFIG=$(grep -v '^#' /opt/postfix/conf/dns_blocklists.cf | grep '\S')
# Remove discontinued Nixspam DNSBL from existing dns_blocklists.cf
sed -i '/ix\.dnsbl\.manitu\.net\*2/d' /opt/postfix/conf/dns_blocklists.cf
# Remove discontinued DNSBLs from existing dns_blocklists.cf
sed -i '/ix\.dnsbl\.manitu\.net\*2/d' /opt/postfix/conf/dns_blocklists.cf # Nixspam
DNSBL_CONFIG=$(grep -v '^#' /opt/postfix/conf/dns_blocklists.cf | grep '\S')
if [ ! -z "$DNSBL_CONFIG" ]; then
echo -e "\e[33mChecking if ASN for your IP is listed for Spamhaus Bad ASN List...\e[0m"

View file

@ -2,11 +2,11 @@ FROM debian:bookworm-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ARG RSPAMD_VER=rspamd_3.10.2-1~b8a232043
ARG RSPAMD_VER=rspamd_3.13.2-1~8bf602278
ARG CODENAME=bookworm
ENV LC_ALL=C
RUN apt-get update && apt-get install -y \
RUN apt-get update && apt-get install -y --no-install-recommends \
tzdata \
ca-certificates \
gnupg2 \
@ -14,8 +14,8 @@ RUN apt-get update && apt-get install -y \
dnsutils \
netcat-traditional \
wget \
redis-tools \
procps \
redis-tools \
procps \
nano \
lua-cjson \
&& arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \

View file

@ -81,6 +81,29 @@ EOF
redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF NO ONE
fi
if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
if [[ -f /etc/rspamd/local.d/external_services.conf ]]; then
rm /etc/rspamd/local.d/external_services.conf
fi
else
if [[ ! -f /etc/rspamd/local.d/external_services.conf ]]; then
cat <<EOF > /etc/rspamd/local.d/external_services.conf
oletools {
# default olefy settings
servers = "olefy:10055";
# needs to be set explicitly for Rspamd < 1.9.5
scan_mime_parts = true;
# mime-part regex matching in content-type or filename
# block all macros
extended = true;
max_size = 3145728;
timeout = 20.0;
retransmits = 1;
}
EOF
fi
fi
# Provide additional lua modules
ln -s /usr/lib/$(uname -m)-linux-gnu/liblua5.1-cjson.so.0.0.0 /usr/lib/rspamd/cjson.so

View file

@ -4,7 +4,7 @@ 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
ARG SOGO_DEBIAN_REPOSITORY=https://packagingv2.sogo.nu/sogo-nightly-debian/
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
ARG GOSU_VERSION=1.17
ENV LC_ALL=C
@ -33,9 +33,8 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
&& gosu nobody true \
&& mkdir /usr/share/doc/sogo \
&& touch /usr/share/doc/sogo/empty.sh \
&& wget http://www.axis.cz/linux/debian/axis-archive-keyring.deb -O /tmp/axis-archive-keyring.deb \
&& apt install -y /tmp/axis-archive-keyring.deb \
&& echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} sogo-v5" > /etc/apt/sources.list.d/sogo.list \
&& wget -O- https://keys.openpgp.org/vks/v1/by-fingerprint/74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 | gpg --dearmor | apt-key add - \
&& echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} main" > /etc/apt/sources.list.d/sogo.list \
&& apt-get update && apt-get install -y --no-install-recommends \
sogo \
sogo-activesync \
@ -48,6 +47,7 @@ COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY acl.diff /acl.diff
COPY navMailcowBtns.diff /navMailcowBtns.diff
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
COPY docker-entrypoint.sh /

View file

@ -14,118 +14,18 @@ do
done
# Wait for updated schema
DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
echo "Waiting for schema update..."
DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
sleep 5
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
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
fi
# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
@ -213,10 +113,10 @@ while read -r line gal
</dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
# Generate alternative LDAP authentication dict, when SQL authentication fails
# This will nevertheless read attributes from LDAP
line=${line} envsubst < /etc/sogo/plist_ldap >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
/etc/sogo/plist_ldap.sh ${line} ${gal} >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
echo " </array>
</dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N)
done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N)
# Generate footer
echo ' </dict>
@ -240,8 +140,12 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
# fi
#fi
# Copy logo, if any
[[ -f /etc/sogo/sogo-full.svg ]] && cp /etc/sogo/sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg
if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then
patch -R /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff;
fi
# Rename custom logo, if any
[[ -f /etc/sogo/sogo-full.svg ]] && mv /etc/sogo/sogo-full.svg /etc/sogo/custom-fulllogo.svg
# Rsync web content
echo "Syncing web content with named volume"

View file

@ -0,0 +1,15 @@
60,65d58
< var:ng-click="navButtonClick"
< ng-href="/user">
< <md-icon>build</md-icon>
< <md-tooltip>mailcow <var:string label:value="Preferences"/></md-tooltip>
< </md-button>
< <md-button class="md-icon-button"
83c76
< onclick="mc_logout();"
---
> ng-show="::activeUser.path.logoff.length"
85c78
< ng-href="#">
---
> ng-href="{{::activeUser.path.logoff}}">

View file

@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View file

@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
@ -16,7 +16,6 @@ RUN apk add --update \
fcgi \
openssl \
nagios-plugins-mysql \
nagios-plugins-dns \
nagios-plugins-disk \
bind-tools \
redis \
@ -32,9 +31,11 @@ RUN apk add --update \
tzdata \
whois \
&& curl https://raw.githubusercontent.com/mludvig/smtp-cli/v3.10/smtp-cli -o /smtp-cli \
&& chmod +x smtp-cli
&& chmod +x smtp-cli \
&& mkdir /usr/lib/mailcow
COPY watchdog.sh /watchdog.sh
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
COPY check_dns.sh /usr/lib/mailcow/check_dns.sh
CMD ["/watchdog.sh"]

View file

@ -0,0 +1,39 @@
#!/bin/sh
while getopts "H:s:" opt; do
case "$opt" in
H) HOST="$OPTARG" ;;
s) SERVER="$OPTARG" ;;
*) echo "Usage: $0 -H host -s server"; exit 3 ;;
esac
done
if [ -z "$SERVER" ]; then
echo "No DNS Server provided"
exit 3
fi
if [ -z "$HOST" ]; then
echo "No host to test provided"
exit 3
fi
# run dig and measure the time it takes to run
START_TIME=$(date +%s%3N)
dig_output=$(dig +short +timeout=2 +tries=1 "$HOST" @"$SERVER" 2>/dev/null)
dig_rc=$?
dig_output_ips=$(echo "$dig_output" | grep -E '^[0-9.]+$' | sort | paste -sd ',' -)
END_TIME=$(date +%s%3N)
ELAPSED_TIME=$((END_TIME - START_TIME))
# validate and perform nagios like output and exit codes
if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then
echo "Domain $HOST was not found by the server"
exit 2
elif [ $dig_rc -eq 0 ]; then
echo "DNS OK: $ELAPSED_TIME ms response time. $HOST returns $dig_output_ips"
exit 0
else
echo "Unknown error"
exit 3
fi

View file

@ -49,7 +49,7 @@
# 2013101601 Optical clean up #
# 2013101602 Rewrite help output #
# 2013101700 Handle Slave IO in 'Connecting' state #
# 2013101701 Minor changes in output, handling UNKWNON situations now #
# 2013101701 Minor changes in output, handling UNKNOWN situations now #
# 2013101702 Exit CRITICAL when Slave IO in Connecting state #
# 2013123000 Slave_SQL_Running also matched Slave_SQL_Running_State #
# 2015011600 Added 'moving' check to catch possible connection issues #
@ -131,10 +131,10 @@ elif [[ -n "${socket}" && (-z "${user}" || -z "${password}") ]]; then
fi
# Connect to the DB server and store output in vars
if [[ -n $socket ]]; then
ConnectionResult=$(mysql ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1)
if [[ -n $socket ]]; then
ConnectionResult=$(mariadb --skip-ssl ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1)
else
ConnectionResult=$(mysql ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1)
ConnectionResult=$(mariadb --skip-ssl ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1)
fi
if [ -z "`echo "${ConnectionResult}" |grep Slave_IO_State`" ]; then
@ -178,33 +178,33 @@ if [ ${check} = ${ok} ] && [ ${checkio} = ${ok} ]; then
then echo "CRITICAL: Slave is ${delayinfo} seconds behind Master | delay=${delayinfo}s"; exit ${STATE_CRITICAL}
elif [[ ${delayinfo} -ge ${warn_delay} ]]
then echo "WARNING: Slave is ${delayinfo} seconds behind Master | delay=${delayinfo}s"; exit ${STATE_WARNING}
else
else
# Everything looks OK here but now let us check if the replication is moving
if [[ -n ${moving} ]] && [[ -n ${tmpfile} ]] && [[ $readpos -eq $execpos ]]
then
#echo "Debug: Read pos is $readpos - Exec pos is $execpos"
then
#echo "Debug: Read pos is $readpos - Exec pos is $execpos"
# Check if tmp file exists
curtime=`date +%s`
if [[ -w $tmpfile ]]
then
if [[ -w $tmpfile ]]
then
tmpfiletime=`date +%s -r $tmpfile`
if [[ `expr $curtime - $tmpfiletime` -gt ${moving} ]]
then
exectmp=`cat $tmpfile`
#echo "Debug: Exec pos in tmpfile is $exectmp"
if [[ $exectmp -eq $execpos ]]
then
then
# The value read from the tmp file and from db are the same. Replication hasnt moved!
echo "WARNING: Slave replication has not moved in ${moving} seconds. Manual check required."; exit ${STATE_WARNING}
else
else
# Replication has moved since the tmp file was written. Delete tmp file and output OK.
rm $tmpfile
echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK};
fi
else
else
echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK};
fi
else
else
echo "$execpos" > $tmpfile
echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK};
fi

View file

@ -1,5 +1,10 @@
#!/bin/bash
if [ "${DEV_MODE}" != "n" ]; then
echo -e "\e[31mEnabled Debug Mode\e[0m"
set -x
fi
trap "exit" INT TERM
trap "kill 0" EXIT
@ -234,7 +239,7 @@ external_checks() {
diff_c=0
THRESHOLD=${EXTERNAL_CHECKS_THRESHOLD}
# Reduce error count by 2 after restarting an unhealthy container
GUID=$(mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN)
GUID=$(mariadb --skip-ssl -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN)
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do
err_c_cur=${err_count}
@ -297,7 +302,7 @@ unbound_checks() {
touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow
host_ip=$(get_container_ip unbound-mailcow)
err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_dns -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/mailcow/check_dns.sh -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
DNSSEC=$(dig com +dnssec | egrep 'flags:.+ad')
if [[ -z ${DNSSEC} ]]; then
echo "DNSSEC failure" 2>> /tmp/unbound-mailcow 1>&2
@ -445,6 +450,31 @@ postfix_checks() {
return 1
}
postfix-tlspol_checks() {
err_count=0
diff_c=0
THRESHOLD=${POSTFIX_TLSPOL_THRESHOLD}
# Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do
touch /tmp/postfix-tlspol-mailcow; echo "$(tail -50 /tmp/postfix-tlspol-mailcow)" > /tmp/postfix-tlspol-mailcow
host_ip=$(get_container_ip postfix-tlspol-mailcow)
err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 8642 2>> /tmp/postfix-tlspol-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "Postfix TLS Policy companion" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
if [[ $? == 10 ]]; then
diff_c=0
sleep 1
else
diff_c=0
sleep $(( ( RANDOM % 60 ) + 20 ))
fi
done
return 1
}
clamd_checks() {
err_count=0
diff_c=0
@ -922,6 +952,18 @@ PID=$!
echo "Spawned mailq_checks with PID ${PID}"
BACKGROUND_TASKS+=(${PID})
(
while true; do
if ! postfix-tlspol_checks; then
log_msg "Postfix TLS Policy hit error limit"
echo postfix-tlspol-mailcow > /tmp/com_pipe
fi
done
) &
PID=$!
echo "Spawned postfix-tlspol_checks with PID ${PID}"
BACKGROUND_TASKS+=(${PID})
(
while true; do
if ! dovecot_checks; then
@ -994,6 +1036,7 @@ PID=$!
echo "Spawned cert_checks with PID ${PID}"
BACKGROUND_TASKS+=(${PID})
if [[ "${SKIP_OLEFY}" =~ ^([nN][oO]|[nN])+$ ]]; then
(
while true; do
if ! olefy_checks; then
@ -1005,6 +1048,7 @@ done
PID=$!
echo "Spawned olefy_checks with PID ${PID}"
BACKGROUND_TASKS+=(${PID})
fi
(
while true; do

View file

@ -1,8 +1,8 @@
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA9iHB0CRDhV8wfBgqnmvuJpl0fzL3qL75R4ZvQHlfMNLrxuIz2x9D
9zcDhPcBTVzV5Ay0AAkke4wP6r6wDQqXqBP4Y8IOkYAyLh3jM40jfHQzQt+5JdQl
ond3kiscBsFOch/vMfSLMu3lAb0YhPNTvrxhMz7LcVAWYl82swASupdiKR+MgaQr
XsugpmDKsHW60VmIM9B7K9Y+rNHwvMWkmISd0KxA8oOy1WJvsVEissMALZDE3c4w
2xHmO2lXxgEx3aez28736t4m/KW3g9Zr31a1M0KusmfY//fGkPk4NUrLBOS2xrgp
Y/rG1qSBdcVyerM0Ki93qCyHKYu4ene0OwIBAg==
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
-----END DH PARAMETERS-----

View file

@ -0,0 +1,115 @@
<?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 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);
}
$redis->auth(getenv("REDISPASS"));
}
catch (Exception $e) {
error_log("MAILCOWAUTH: " . $e . PHP_EOL);
http_response_code(500); // Internal Server Error
echo json_encode($return);
exit;
}
// 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';
require_once 'functions.ratelimit.inc.php';
require_once 'functions.acl.inc.php';
$isSOGoRequest = $post['real_rip'] == getenv('IPV4_NETWORK') . '.248';
$result = false;
if ($isSOGoRequest) {
// This is a SOGo Auth request. First check for SSO password.
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
if ($sogo_sso_pass === $post['password']){
error_log('MAILCOWAUTH: SOGo SSO auth for user ' . $post['username']);
set_sasl_log($post['username'], $post['real_rip'], "SOGO");
$result = true;
}
}
if ($result === false){
// If it's a SOGo Request, don't check for protocol access
$service = ($isSOGoRequest) ? false : array($post['service'] => true);
$result = apppass_login($post['username'], $post['password'], $service, array(
'is_internal' => true,
'remote_addr' => $post['real_rip']
));
if ($result) {
error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
set_sasl_log($post['username'], $post['real_rip'], $post['service']);
}
}
if ($result === false){
// Init Identity Provider
$iam_provider = identity_provider('init');
$iam_settings = identity_provider('get');
$result = user_login($post['username'], $post['password'], array('is_internal' => true, 'service' => $post['service']));
if ($result) {
error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
set_sasl_log($post['username'], $post['real_rip'], $post['service']);
}
}
if ($result) {
http_response_code(200); // OK
$return['success'] = true;
} else {
error_log("MAILCOWAUTH: Login failed for user " . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']);
http_response_code(401); // Unauthorized
}
echo json_encode($return);
session_destroy();
exit;

View file

@ -0,0 +1,57 @@
function auth_password_verify(request, password)
if request.domain == nil then
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
end
local json = require "cjson"
local ltn12 = require "ltn12"
local https = require "ssl.https"
https.TIMEOUT = 30
local req = {
username = request.user,
password = password,
real_rip = request.real_rip,
service = request.service
}
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
}
-- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry.
-- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry,
-- even if the TTL has expired. Useful to avoid cache eviction during backend issues.
if c ~= 200 and c ~= 401 then
dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user)
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error"
end
local response_str = table.concat(res)
local is_response_valid, response_json = pcall(json.decode, response_str)
if not is_response_valid then
dovecot.i_info("Invalid JSON received: " .. response_str)
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Invalid response format"
end
if response_json.success == true then
return dovecot.auth.PASSDB_RESULT_OK, ""
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

@ -53,7 +53,7 @@ mail_shared_explicit_inbox = yes
mail_prefetch_count = 30
passdb {
driver = lua
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes cache_key=%s:%u:%w
result_success = return-ok
result_failure = continue
result_internalfail = continue
@ -69,7 +69,7 @@ passdb {
# a return of the following passdb is mandatory
passdb {
driver = lua
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes
}
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
service doveadm {
@ -125,6 +125,7 @@ service managesieve-login {
}
service imap-login {
service_count = 1
process_min_avail = 2
process_limit = 10000
vsz_limit = 1G
user = dovenull
@ -140,6 +141,7 @@ service imap-login {
}
service pop3-login {
service_count = 1
process_min_avail = 1
vsz_limit = 1G
inet_listener pop3_haproxy {
port = 10110
@ -239,7 +241,7 @@ plugin {
mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
mail_crypt_save_version = 2
# Enable compression while saving, lz4 Dovecot v2.2.11+
# Enable compression while saving, lz4 Dovecot v2.3.17+
zlib_save = lz4
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
@ -274,10 +276,11 @@ service stats {
}
}
imap_max_line_length = 2 M
#auth_cache_verify_password_with_worker = yes
#auth_cache_negative_ttl = 0
#auth_cache_ttl = 30 s
#auth_cache_size = 2 M
auth_cache_verify_password_with_worker = yes
auth_cache_negative_ttl = 60s
auth_cache_ttl = 300s
auth_cache_size = 10M
auth_verbose_passwords = sha1:6
service replicator {
process_min_avail = 1
}
@ -301,7 +304,6 @@ replication_dsync_parameters = -d -l 30 -U -n INBOX
!include_try /etc/dovecot/sni.conf
!include_try /etc/dovecot/sogo_trusted_ip.conf
!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>

View file

@ -1,7 +1,7 @@
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
collation-server = utf8mb4_general_ci
#innodb_file_per_table = TRUE
#innodb_file_format = barracuda
#innodb_large_prefix = TRUE
@ -20,7 +20,7 @@ thread_cache_size = 8
query_cache_type = 0
query_cache_size = 0
max_heap_table_size = 48M
thread_stack = 192K
thread_stack = 256K
skip-host-cache
skip-name-resolve
log-warnings = 0

View file

@ -13,6 +13,7 @@ events {
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
@ -48,13 +49,21 @@ http {
listen {{ HTTP_PORT }} default_server;
listen [::]:{{ HTTP_PORT }} default_server;
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }};
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }};
if ( $request_uri ~* "%0A|%0D" ) { return 403; }
location ^~ /.well-known/acme-challenge/ {
allow all;
default_type "text/plain";
}
location ^~ /.well-known/mta-sts.txt {
allow all;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass {{ PHPFPMHOST }}:9002;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location / {
return 301 https://$host$uri$is_args$args;
}
@ -70,7 +79,7 @@ http {
{%endif%}
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
{% if not DISABLE_IPv6 %}
{% if ENABLE_IPV6 %}
{% if not HTTP_REDIRECT %}
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
{%endif%}
@ -82,7 +91,7 @@ http {
ssl_certificate /etc/ssl/mail/cert.pem;
ssl_certificate_key /etc/ssl/mail/key.pem;
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.*;
server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.*;
include /etc/nginx/includes/sites-default.conf;
}
@ -97,7 +106,7 @@ http {
{%endif%}
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
{% if not DISABLE_IPv6 %}
{% if ENABLE_IPV6 %}
{% if not HTTP_REDIRECT %}
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
{%endif%}
@ -118,7 +127,7 @@ http {
# rspamd dynmaps:
server {
listen 8081;
{% if not DISABLE_IPv6 %}
{% if ENABLE_IPV6 %}
listen [::]:8081;
{%endif%}
index index.php index.html;
@ -159,6 +168,31 @@ http {
}
}
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;
}
}
include /etc/nginx/conf.d/*.conf;
{% for cert in valid_cert_dirs %}
server {
{% if not HTTP_REDIRECT %}
@ -166,7 +200,7 @@ http {
{%endif%}
listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl;
{% if not DISABLE_IPv6 %}
{% if ENABLE_IPV6 %}
{% if not HTTP_REDIRECT %}
listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%};
{%endif%}
@ -183,6 +217,4 @@ http {
include /etc/nginx/includes/sites-default.conf;
}
{% endfor %}
include /etc/nginx/conf.d/*.conf;
}

View file

@ -14,7 +14,6 @@ ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=15768000;";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Frame-Options "SAMEORIGIN" always;
@ -52,10 +51,12 @@ set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
set_real_ip_from fc00::/7;
{% if not TRUSTED_NETWORK %}
{% for TRUSTED_PROXY in TRUSTED_PROXIES %}
set_real_ip_from {{ TRUSTED_PROXY }};
{% endfor %}
{% if not NGINX_USE_PROXY_PROTOCOL %}
real_ip_header X-Forwarded-For;
{% else %}
set_real_ip_from {{ TRUSTED_NETWORK }};
real_ip_header proxy_protocol;
{% endif %}
real_ip_recursive on;
@ -74,6 +75,14 @@ location ^~ /.well-known/acme-challenge/ {
allow all;
default_type "text/plain";
}
location ^~ /.well-known/mta-sts.txt {
allow all;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass {{ PHPFPMHOST }}:9002;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent;
rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent;

View file

@ -0,0 +1,231 @@
<?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("err", $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);
}
$redis->auth(getenv("REDISPASS"));
}
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";
$_SESSION['acl']['unlimited_quota'] = "1";
$iam_settings = identity_provider('get');
if ($iam_settings['authsource'] != "keycloak" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) {
session_destroy();
exit;
}
// Set pagination variables
$start = 0;
$max = 100;
// 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);
// Init Keycloak Provider
$iam_provider = identity_provider('init');
// 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("err", "Received HTTP {$code}");
session_destroy();
exit;
}
try {
$response = json_decode($response, true);
} catch (Exception $e) {
logMsg("err", $e->getMessage());
break;
}
if (!is_array($response)){
logMsg("err", "Received 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;
}
// try get mailbox user
$stmt = $pdo->prepare("SELECT
mailbox.*,
domain.active AS d_active
FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $user['email']));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// check if matching attribute mapping exists
$user_template = $user['attributes']['mailcow_template'][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
$_SESSION['access_all_exception'] = '1';
if (!$row && intval($iam_settings['import_users']) == 1){
if ($mapper_key === false){
if (!empty($iam_settings['default_template'])) {
$mbox_template = $iam_settings['default_template'];
logMsg("warning", "Using default template for user " . $user['email']);
} else {
logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
continue;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// mailbox user does not exist, create...
logMsg("info", "Creating user " . $user['email']);
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user['email'])[1],
'local_part' => explode('@', $user['email'])[0],
'name' => $user['firstName'] . " " . $user['lastName'],
'authsource' => 'keycloak',
'template' => $mbox_template
));
if (!$create_res){
logMsg("err", "Could not create user " . $user['email']);
continue;
}
} else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "keycloak") {
if ($mapper_key === false){
logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
continue;
}
$mbox_template = $iam_settings['templates'][$mapper_key];
// mailbox user does exist, sync attribtues...
logMsg("info", "Syncing attributes for user " . $user['email']);
mailbox('edit', 'mailbox_from_template', array(
'username' => $user['email'],
'name' => $user['firstName'] . " " . $user['lastName'],
'template' => $mbox_template
));
} else {
// skip mailbox user
logMsg("info", "Skipping user " . $user['email']);
}
$_SESSION['access_all_exception'] = '0';
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

@ -0,0 +1,198 @@
<?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("err", $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);
}
$redis->auth(getenv("REDISPASS"));
}
catch (Exception $e) {
echo "Exiting: " . $e->getMessage();
session_destroy();
exit;
}
function logMsg($priority, $message, $task = "LDAP 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";
$_SESSION['acl']['unlimited_quota'] = "1";
$iam_settings = identity_provider('get');
if ($iam_settings['authsource'] != "ldap" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) {
session_destroy();
exit;
}
// Set pagination variables
$start = 0;
$max = 100;
// 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);
// Init Provider
$iam_provider = identity_provider('init');
// Get ldap users
$ldap_query = $iam_provider->query();
if (!empty($iam_settings['filter'])) {
$ldap_query = $ldap_query->rawFilter($iam_settings['filter']);
}
$response = $ldap_query->where($iam_settings['username_field'], "*")
->where($iam_settings['attribute_field'], "*")
->select([$iam_settings['username_field'], $iam_settings['attribute_field'], 'displayname'])
->paginate($max);
// Process the users
foreach ($response as $user) {
// try get mailbox user
$stmt = $pdo->prepare("SELECT
mailbox.*,
domain.active AS d_active
FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $user[$iam_settings['username_field']][0]));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// check if matching attribute mapping exists
$user_template = $user[$iam_settings['attribute_field']][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
if (empty($user[$iam_settings['username_field']][0])){
logMsg("warning", "Skipping user " . $user['displayname'][0] . " due to empty LDAP ". $iam_settings['username_field'] . " property.");
continue;
}
$_SESSION['access_all_exception'] = '1';
if (!$row && intval($iam_settings['import_users']) == 1){
if ($mapper_key === false){
if (!empty($iam_settings['default_template'])) {
$mbox_template = $iam_settings['default_template'];
} else {
logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
continue;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// mailbox user does not exist, create...
logMsg("info", "Creating user " . $user[$iam_settings['username_field']][0]);
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user[$iam_settings['username_field']][0])[1],
'local_part' => explode('@', $user[$iam_settings['username_field']][0])[0],
'name' => $user['displayname'][0],
'authsource' => 'ldap',
'template' => $mbox_template
));
if (!$create_res){
logMsg("err", "Could not create user " . $user[$iam_settings['username_field']][0]);
continue;
}
} else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "ldap") {
if ($mapper_key === false){
logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
continue;
}
$mbox_template = $iam_settings['templates'][$mapper_key];
// mailbox user does exist, sync attribtues...
logMsg("info", "Syncing attributes for user " . $user[$iam_settings['username_field']][0]);
mailbox('edit', 'mailbox_from_template', array(
'username' => $user[$iam_settings['username_field']][0],
'name' => $user['displayname'][0],
'template' => $mbox_template
));
} else {
// skip mailbox user
logMsg("info", "Skipping user " . $user[$iam_settings['username_field']][0]);
}
$_SESSION['access_all_exception'] = '0';
sleep(0.025);
}
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,7 +1,16 @@
; NOTE: Restart phpfpm on ANY manual changes to PHP files!
; opcache
opcache.enable=1
opcache.enable_cli=1
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1
opcache.validate_timestamps=0
; JIT
; Disabled for now due to some PHP segmentation faults observed
; in certain environments. Possibly some PHP or PHP extension bug.
opcache.jit=disable
opcache.jit_buffer_size=0

View file

@ -152,7 +152,7 @@ smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf
smtp_sasl_security_options =
smtp_sasl_mechanism_filter = plain, login
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf
smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
mail_name = Postcow
# local_transport map catches local destinations and prevents routing local dests when the next map would route "*"
@ -162,10 +162,9 @@ transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf
smtp_sasl_auth_soft_bounce = no
postscreen_discard_ehlo_keywords = silent-discard, dsn, chunking
smtpd_discard_ehlo_keywords = chunking, silent-discard
postscreen_discard_ehlo_keywords = chunking, silent-discard, smtputf8, dsn
smtpd_discard_ehlo_keywords = chunking, silent-discard, smtputf8
compatibility_level = 3.7
smtputf8_enable = no
# Define protocols for SMTPS and submission service
submission_smtpd_tls_mandatory_protocols = >=TLSv1.2
smtps_smtpd_tls_mandatory_protocols = >=TLSv1.2

View file

@ -1,32 +1,71 @@
# Whitelist generated by Postwhite v3.4 on Sat Feb 1 00:18:03 UTC 2025
# Whitelist generated by Postwhite v3.4 on Mon Dec 1 00:24:43 UTC 2025
# https://github.com/stevejenkins/postwhite/
# 1984 total rules
# 2186 total rules
2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit
2a01:111:f403:8000::/50 permit
2a01:111:f403:2800::/53 permit
2a01:111:f403:8000::/51 permit
2a01:111:f403::/49 permit
2a01:111:f403:c000::/51 permit
2a01:111:f403:d000::/53 permit
2a01:111:f403:f000::/52 permit
2a01:238:20a:202:5370::1 permit
2a01:238:20a:202:5372::1 permit
2a01:238:20a:202:5373::1 permit
2a01:238:400:101:53::1 permit
2a01:238:400:102:53::1 permit
2a01:238:400:103:53::1 permit
2a01:238:400:301:53::1 permit
2a01:238:400:302:53::1 permit
2a01:238:400:303:53::1 permit
2a01:238:400:470:53::1 permit
2a01:238:400:471:53::1 permit
2a01:238:400:472:53::1 permit
2a01:b747:3000:200::/56 permit
2a01:b747:3001:200::/56 permit
2a01:b747:3002:200::/56 permit
2a01:b747:3003:200::/56 permit
2a01:b747:3004:200::/56 permit
2a01:b747:3005:200::/56 permit
2a01:b747:3006:200::/56 permit
2a02:a60:0:5::/64 permit
2a0f:f640::/56 permit
2c0f:fb50:4000::/36 permit
2.207.151.53 permit
2.207.217.30 permit
3.64.237.68 permit
3.65.3.180 permit
3.70.123.177 permit
3.72.182.33 permit
3.74.81.189 permit
3.74.125.228 permit
3.75.33.185 permit
3.93.157.0/24 permit
3.94.40.108 permit
3.121.107.214 permit
3.129.120.190 permit
3.210.190.0/24 permit
3.211.80.218 permit
3.216.221.67 permit
3.221.209.22 permit
8.20.114.31 permit
8.25.194.0/23 permit
8.25.196.0/23 permit
10.162.0.0/16 permit
8.36.116.0/24 permit
8.39.144.0/24 permit
12.130.86.238 permit
13.107.213.69 permit
13.107.246.69 permit
13.108.16.0/20 permit
13.110.208.0/21 permit
13.110.209.0/24 permit
13.110.216.0/22 permit
13.110.224.0/20 permit
13.111.0.0/16 permit
13.111.191.0/24 permit
13.216.7.111 permit
13.216.54.180 permit
13.247.164.219 permit
15.200.21.50 permit
15.200.44.248 permit
15.200.201.185 permit
@ -35,25 +74,30 @@
17.57.156.0/24 permit
17.58.0.0/16 permit
17.142.0.0/15 permit
17.143.234.140/30 permit
18.97.0.8/30 permit
18.97.1.184/29 permit
18.97.2.64/26 permit
18.156.89.250 permit
18.156.205.64 permit
18.157.70.148 permit
18.157.114.255 permit
18.157.243.190 permit
18.158.153.154 permit
18.194.95.56 permit
18.197.217.180 permit
18.198.96.88 permit
18.199.210.3 permit
18.207.52.234 permit
18.208.124.128/25 permit
18.216.232.154 permit
18.235.27.253 permit
18.236.40.242 permit
18.236.56.161 permit
20.51.6.32/30 permit
20.51.98.61 permit
20.52.52.2 permit
20.52.128.133 permit
20.59.80.4/30 permit
20.63.210.192/28 permit
20.69.8.108/30 permit
20.70.246.20 permit
20.76.201.171 permit
20.83.222.104/30 permit
20.88.157.184/30 permit
20.94.180.64/28 permit
@ -62,14 +106,11 @@
20.98.194.68/30 permit
20.105.209.76/30 permit
20.107.239.64/30 permit
20.112.250.133 permit
20.118.139.208/30 permit
20.141.10.196 permit
20.185.214.0/27 permit
20.185.214.32/27 permit
20.185.214.64/27 permit
20.231.239.246 permit
20.236.44.162 permit
23.103.224.0/19 permit
23.249.208.0/20 permit
23.251.224.0/19 permit
@ -81,6 +122,7 @@
23.253.183.147 permit
23.253.183.148 permit
23.253.183.150 permit
24.110.64.0/18 permit
27.123.204.128/30 permit
27.123.204.132/31 permit
27.123.204.148/30 permit
@ -93,19 +135,50 @@
27.123.206.56/29 permit
27.123.206.76/30 permit
27.123.206.80/28 permit
31.25.48.222 permit
31.47.251.17 permit
31.186.239.0/24 permit
34.2.64.0/22 permit
34.2.68.0/23 permit
34.2.70.0/23 permit
34.2.71.64/26 permit
34.2.72.0/22 permit
34.2.75.0/26 permit
34.2.78.0/23 permit
34.2.80.0/23 permit
34.2.82.0/23 permit
34.2.84.0/24 permit
34.2.84.64/26 permit
34.2.85.0/24 permit
34.2.85.64/26 permit
34.2.86.0/23 permit
34.2.88.0/23 permit
34.2.90.0/23 permit
34.2.92.0/23 permit
34.2.94.0/23 permit
34.70.158.162 permit
34.74.74.140 permit
34.83.159.189 permit
34.141.160.224 permit
34.193.58.168 permit
34.195.217.107 permit
34.197.10.50 permit
34.197.254.9 permit
34.198.94.229 permit
34.198.218.121 permit
34.212.163.75 permit
34.215.104.144 permit
34.218.116.3 permit
34.218.115.239 permit
34.225.212.172 permit
35.83.148.184 permit
35.155.198.111 permit
35.158.23.94 permit
35.161.32.253 permit
35.162.73.231 permit
35.167.93.243 permit
35.174.145.124 permit
35.176.132.251 permit
35.190.247.0/24 permit
35.191.0.0/16 permit
35.205.92.9 permit
35.228.216.85 permit
35.242.169.159 permit
37.188.97.188 permit
37.218.248.47 permit
@ -120,11 +193,19 @@
40.233.83.78 permit
40.233.88.28 permit
44.206.138.57 permit
44.210.169.44 permit
44.217.45.156 permit
44.236.56.93 permit
44.238.220.251 permit
44.245.243.92 permit
44.246.1.125 permit
44.246.68.102 permit
44.246.77.92 permit
45.14.148.0/22 permit
46.19.170.16 permit
45.143.132.0/24 permit
45.143.133.0/24 permit
45.143.134.0/24 permit
45.143.135.0/24 permit
46.226.48.0/21 permit
46.228.36.37 permit
46.228.36.38/31 permit
@ -175,6 +256,7 @@
46.243.88.177 permit
46.243.95.179 permit
46.243.95.180 permit
50.16.246.183 permit
50.18.45.249 permit
50.18.121.236 permit
50.18.121.248 permit
@ -188,14 +270,23 @@
50.56.130.220 permit
50.56.130.221 permit
50.56.130.222 permit
50.112.246.219 permit
52.1.14.157 permit
52.5.230.59 permit
52.12.53.23 permit
52.13.214.179 permit
52.26.1.71 permit
52.27.5.72 permit
52.27.28.47 permit
52.28.63.81 permit
52.28.197.132 permit
52.34.181.151 permit
52.35.192.45 permit
52.36.138.31 permit
52.37.142.146 permit
52.42.203.116 permit
52.50.24.208 permit
52.57.120.243 permit
52.58.216.183 permit
52.59.143.3 permit
52.60.41.5 permit
@ -208,7 +299,6 @@
52.96.91.34 permit
52.96.111.82 permit
52.96.172.98 permit
52.96.214.50 permit
52.96.222.194 permit
52.96.222.226 permit
52.96.223.2 permit
@ -238,23 +328,23 @@
54.174.63.0/24 permit
54.186.193.102 permit
54.191.223.56 permit
54.211.126.101 permit
54.213.20.246 permit
54.214.39.184 permit
54.240.0.0/18 permit
54.240.64.0/19 permit
54.240.96.0/19 permit
54.240.64.0/18 permit
54.241.16.209 permit
54.244.54.130 permit
54.244.242.0/24 permit
54.255.61.23 permit
57.103.64.0/18 permit
57.129.93.249 permit
62.13.128.0/24 permit
62.13.129.128/25 permit
62.13.136.0/21 permit
62.13.144.0/21 permit
62.13.152.0/21 permit
62.17.146.128/26 permit
62.179.121.0/24 permit
62.201.172.0/27 permit
62.201.172.32/27 permit
62.253.227.114 permit
@ -262,6 +352,9 @@
63.128.21.0/24 permit
63.143.57.128/25 permit
63.143.59.128/25 permit
63.176.194.123 permit
63.178.132.221 permit
63.178.143.178 permit
64.18.0.0/20 permit
64.20.241.45 permit
64.69.212.0/24 permit
@ -274,6 +367,7 @@
64.127.115.252 permit
64.132.88.0/23 permit
64.132.92.0/24 permit
64.181.194.190 permit
64.207.219.7 permit
64.207.219.8 permit
64.207.219.9 permit
@ -303,23 +397,15 @@
64.207.219.143 permit
64.233.160.0/19 permit
65.52.80.137 permit
65.54.51.64/26 permit
65.54.61.64/26 permit
65.54.121.120/29 permit
65.54.190.0/24 permit
65.54.241.0/24 permit
65.55.29.77 permit
65.55.33.64/28 permit
65.55.34.0/24 permit
65.55.42.224/28 permit
65.55.52.224/27 permit
65.55.78.128/25 permit
65.55.81.48/28 permit
65.55.90.0/24 permit
65.55.94.0/25 permit
65.55.111.0/24 permit
65.55.113.64/26 permit
65.55.116.0/25 permit
65.55.126.0/25 permit
65.55.174.0/25 permit
65.55.178.128/27 permit
@ -330,7 +416,6 @@
65.212.180.36 permit
66.102.0.0/20 permit
66.119.150.192/26 permit
66.162.193.226/31 permit
66.163.184.0/24 permit
66.163.185.0/24 permit
66.163.186.0/24 permit
@ -536,13 +621,11 @@
74.86.241.250/31 permit
74.112.67.243 permit
74.125.0.0/16 permit
74.202.227.40 permit
74.208.4.200 permit
74.208.4.201 permit
74.208.4.220 permit
74.208.4.221 permit
74.209.250.0/24 permit
75.2.70.75 permit
76.223.128.0/19 permit
76.223.176.0/20 permit
77.238.176.0/24 permit
@ -565,25 +648,31 @@
77.238.189.142 permit
77.238.189.146/31 permit
77.238.189.148/30 permit
79.135.106.0/24 permit
79.135.107.0/24 permit
81.169.146.243 permit
81.169.146.245 permit
81.169.146.246 permit
81.223.46.0/27 permit
82.165.159.2 permit
82.165.159.3 permit
82.165.159.4 permit
82.165.159.12 permit
82.165.159.13 permit
82.165.159.14 permit
82.165.159.34 permit
82.165.159.35 permit
82.165.159.40 permit
82.165.159.41 permit
82.165.159.42 permit
82.165.159.45 permit
82.165.159.130 permit
82.165.159.131 permit
84.116.6.0/23 permit
84.116.36.0/24 permit
84.116.50.0/23 permit
85.9.206.169 permit
85.9.210.45 permit
85.158.136.0/21 permit
85.215.255.39 permit
85.215.255.40 permit
85.215.255.41 permit
85.215.255.45 permit
85.215.255.46 permit
85.215.255.47 permit
85.215.255.48 permit
85.215.255.49 permit
86.61.88.25 permit
87.238.80.0/21 permit
87.248.103.12 permit
@ -623,12 +712,13 @@
87.248.117.205 permit
87.253.232.0/21 permit
89.22.108.0/24 permit
91.198.2.0/24 permit
91.211.240.0/22 permit
94.169.2.0/23 permit
94.236.119.0/26 permit
94.245.112.0/27 permit
94.245.112.10/31 permit
95.131.104.0/21 permit
95.217.114.154 permit
96.43.144.0/20 permit
96.43.144.64/28 permit
96.43.144.64/31 permit
@ -1119,7 +1209,6 @@
98.139.245.208/30 permit
98.139.245.212/31 permit
99.78.197.208/28 permit
99.83.190.102 permit
103.9.96.0/22 permit
103.28.42.0/24 permit
103.151.192.0/23 permit
@ -1128,9 +1217,6 @@
104.43.243.237 permit
104.44.112.128/25 permit
104.47.0.0/17 permit
104.47.20.0/23 permit
104.47.75.0/24 permit
104.47.108.0/23 permit
104.130.96.0/28 permit
104.130.122.0/23 permit
106.10.144.64/27 permit
@ -1256,6 +1342,7 @@
106.50.16.0/28 permit
107.20.18.111 permit
107.20.210.250 permit
107.22.191.150 permit
108.174.0.0/24 permit
108.174.0.215 permit
108.174.3.0/24 permit
@ -1264,9 +1351,8 @@
108.174.6.215 permit
108.175.18.45 permit
108.175.30.45 permit
108.177.8.0/21 permit
108.177.96.0/19 permit
108.179.144.0/20 permit
109.224.244.0/24 permit
109.237.142.0/24 permit
111.221.23.128/25 permit
111.221.26.0/27 permit
@ -1317,6 +1403,7 @@
128.245.248.0/21 permit
129.41.77.70 permit
129.41.169.249 permit
129.77.16.0/20 permit
129.80.5.164 permit
129.80.64.36 permit
129.80.67.121 permit
@ -1338,7 +1425,6 @@
129.213.195.191 permit
130.61.9.72 permit
130.162.39.83 permit
130.211.0.0/22 permit
130.248.172.0/24 permit
130.248.173.0/24 permit
131.253.30.0/24 permit
@ -1347,12 +1433,25 @@
132.226.26.225 permit
132.226.49.32 permit
132.226.56.24 permit
134.128.64.0/19 permit
134.128.96.0/19 permit
134.170.27.8 permit
134.170.113.0/26 permit
134.170.141.64/26 permit
134.170.143.0/24 permit
134.170.174.0/24 permit
135.84.216.0/22 permit
136.143.160.0/24 permit
136.143.161.0/24 permit
136.143.162.0/24 permit
136.143.176.0/24 permit
136.143.177.0/24 permit
136.143.178.49 permit
136.143.182.0/23 permit
136.143.184.0/24 permit
136.143.188.0/24 permit
136.143.190.0/23 permit
136.146.128.0/20 permit
136.147.128.0/20 permit
136.147.135.0/24 permit
136.147.176.0/20 permit
@ -1401,6 +1500,7 @@
146.20.215.0/24 permit
146.20.215.182 permit
146.88.28.0/24 permit
146.148.116.76 permit
147.154.32.0/25 permit
147.243.1.47 permit
147.243.1.48 permit
@ -1410,7 +1510,7 @@
148.105.0.0/16 permit
148.105.8.0/21 permit
149.72.0.0/16 permit
149.72.223.204 permit
149.72.234.184 permit
149.72.248.236 permit
149.97.173.180 permit
150.230.98.160 permit
@ -1422,9 +1522,6 @@
155.248.220.138 permit
155.248.234.149 permit
155.248.237.141 permit
157.55.0.192/26 permit
157.55.1.128/26 permit
157.55.2.0/25 permit
157.55.9.128/25 permit
157.55.11.0/25 permit
157.55.49.0/25 permit
@ -1464,6 +1561,9 @@
159.135.224.0/20 permit
159.135.228.10 permit
159.183.0.0/16 permit
159.183.68.71 permit
159.183.79.38 permit
159.183.129.172 permit
160.1.62.192 permit
161.38.192.0/20 permit
161.38.204.0/22 permit
@ -1480,7 +1580,10 @@
163.114.132.120 permit
163.114.134.16 permit
163.114.135.16 permit
163.116.128.0/17 permit
163.192.116.87 permit
164.152.23.32 permit
164.152.25.241 permit
164.177.132.168/30 permit
166.78.68.0/22 permit
166.78.68.221 permit
@ -1506,6 +1609,7 @@
168.138.5.36 permit
168.138.73.51 permit
168.138.77.31 permit
168.138.237.153 permit
168.245.0.0/17 permit
168.245.12.252 permit
168.245.46.9 permit
@ -1515,17 +1619,11 @@
170.10.132.56/29 permit
170.10.132.64/29 permit
170.10.133.0/24 permit
172.217.0.0/19 permit
172.217.32.0/20 permit
172.217.128.0/19 permit
172.217.160.0/20 permit
172.217.192.0/19 permit
172.253.56.0/21 permit
172.253.112.0/20 permit
173.0.84.0/29 permit
173.0.84.224/27 permit
173.0.94.244/30 permit
173.194.0.0/16 permit
173.194.0.0/17 permit
173.203.79.182 permit
173.203.81.39 permit
173.224.161.128/25 permit
@ -1549,9 +1647,14 @@
182.50.78.64/28 permit
183.240.219.64/29 permit
185.4.120.0/22 permit
185.11.253.128/27 permit
185.11.255.0/24 permit
185.12.80.0/22 permit
185.28.196.0/22 permit
185.58.84.93 permit
185.70.40.0/24 permit
185.70.41.0/24 permit
185.70.43.0/24 permit
185.80.93.204 permit
185.80.93.227 permit
185.80.95.31 permit
@ -1559,6 +1662,8 @@
185.138.56.128/25 permit
185.189.236.0/22 permit
185.211.120.0/22 permit
185.233.188.0/23 permit
185.233.190.0/23 permit
185.250.236.0/22 permit
185.250.239.148 permit
185.250.239.168 permit
@ -1611,6 +1716,7 @@
188.125.85.234/31 permit
188.125.85.236/31 permit
188.125.85.238 permit
188.165.51.139 permit
188.172.128.0/20 permit
192.0.64.0/18 permit
192.18.139.154 permit
@ -1633,10 +1739,31 @@
193.109.254.0/23 permit
193.122.128.100 permit
193.123.56.63 permit
193.142.157.0/24 permit
193.142.157.191 permit
193.142.157.198 permit
194.19.134.0/25 permit
194.25.134.16/28 permit
194.25.134.80/28 permit
194.64.234.129 permit
194.97.196.0/24 permit
194.97.196.3 permit
194.97.196.4 permit
194.97.196.11 permit
194.97.196.12 permit
194.97.204.0/24 permit
194.97.204.3 permit
194.97.204.4 permit
194.97.204.11 permit
194.97.204.12 permit
194.97.212.0/24 permit
194.97.212.3 permit
194.97.212.4 permit
194.97.212.11 permit
194.97.212.12 permit
194.106.220.0/23 permit
194.113.24.0/22 permit
194.113.42.0/26 permit
194.154.193.192/27 permit
195.4.92.0/23 permit
195.54.172.0/23 permit
@ -1650,7 +1777,16 @@
198.61.254.21 permit
198.61.254.231 permit
198.178.234.57 permit
198.202.211.1 permit
198.244.48.0/20 permit
198.244.56.107 permit
198.244.56.108 permit
198.244.56.109 permit
198.244.56.111 permit
198.244.56.112 permit
198.244.56.113 permit
198.244.56.114 permit
198.244.56.115 permit
198.244.59.30 permit
198.244.59.33 permit
198.244.59.35 permit
@ -1720,9 +1856,11 @@
204.92.114.187 permit
204.92.114.203 permit
204.92.114.204/31 permit
204.216.164.202 permit
204.220.160.0/21 permit
204.220.168.0/21 permit
204.220.176.0/20 permit
204.220.181.105 permit
204.232.168.0/24 permit
205.139.110.0/24 permit
205.201.128.0/20 permit
@ -1750,7 +1888,6 @@
207.46.52.79 permit
207.46.58.128/25 permit
207.46.116.128/29 permit
207.46.117.0/24 permit
207.46.132.128/27 permit
207.46.198.0/25 permit
207.46.200.0/27 permit
@ -1797,8 +1934,6 @@
208.71.42.212/31 permit
208.71.42.214 permit
208.72.249.240/29 permit
208.74.204.5 permit
208.74.204.9 permit
208.75.120.0/22 permit
208.76.62.0/24 permit
208.76.63.0/24 permit
@ -1858,17 +1993,11 @@
212.82.111.228/31 permit
212.82.111.230 permit
212.123.28.40 permit
212.227.15.3 permit
212.227.15.4 permit
212.227.15.5 permit
212.227.15.6 permit
212.227.15.14 permit
212.227.15.7 permit
212.227.15.8 permit
212.227.15.15 permit
212.227.15.18 permit
212.227.15.19 permit
212.227.15.25 permit
212.227.15.26 permit
212.227.15.29 permit
212.227.15.44 permit
212.227.15.45 permit
212.227.15.46 permit
@ -1876,23 +2005,32 @@
212.227.15.50 permit
212.227.15.52 permit
212.227.15.53 permit
212.227.15.54 permit
212.227.15.55 permit
212.227.17.11 permit
212.227.17.12 permit
212.227.17.18 permit
212.227.17.19 permit
212.227.17.1 permit
212.227.17.2 permit
212.227.17.7 permit
212.227.17.16 permit
212.227.17.17 permit
212.227.17.20 permit
212.227.17.21 permit
212.227.17.22 permit
212.227.17.26 permit
212.227.17.27 permit
212.227.17.28 permit
212.227.17.29 permit
212.227.126.206 permit
212.227.126.207 permit
212.227.126.208 permit
212.227.126.209 permit
212.227.126.220 permit
212.227.126.221 permit
212.227.126.222 permit
212.227.126.223 permit
212.227.126.224 permit
212.227.126.225 permit
212.227.126.226 permit
212.227.126.227 permit
213.46.255.0/24 permit
213.95.19.64/27 permit
213.95.135.4 permit
213.199.128.139 permit
213.199.128.145 permit
213.199.138.181 permit
@ -1902,6 +2040,7 @@
216.17.150.242 permit
216.17.150.251 permit
216.24.224.0/20 permit
216.27.86.152/31 permit
216.39.60.154/31 permit
216.39.60.156/30 permit
216.39.60.160/30 permit
@ -1939,6 +2078,8 @@
216.99.5.68 permit
216.109.114.32/27 permit
216.109.114.64/29 permit
216.113.162.65 permit
216.113.163.65 permit
216.128.126.97 permit
216.136.162.65 permit
216.136.162.120/29 permit
@ -1953,8 +2094,6 @@
216.205.24.0/24 permit
216.221.160.0/19 permit
216.239.32.0/19 permit
217.72.192.77 permit
217.72.192.78 permit
217.77.141.52 permit
217.77.141.59 permit
217.175.194.0/24 permit
@ -1966,6 +2105,21 @@
2001:0868:0100:0600::/64 permit
2001:4860:4000::/36 permit
2001:748:100:40::2:0/112 permit
2001:748:400:1300::3 permit
2001:748:400:1300::4 permit
2001:748:400:1301::0/64 permit
2001:748:400:1301::3 permit
2001:748:400:1301::4 permit
2001:748:400:2300::3 permit
2001:748:400:2300::4 permit
2001:748:400:2301::0/64 permit
2001:748:400:2301::3 permit
2001:748:400:2301::4 permit
2001:748:400:3300::3 permit
2001:748:400:3300::4 permit
2001:748:400:3301::0/64 permit
2001:748:400:3301::3 permit
2001:748:400:3301::4 permit
2404:6800:4000::/36 permit
2603:1010:3:3::5b permit
2603:1020:201:10::10f permit
@ -1985,4 +2139,5 @@
2620:119:50c0:207::/64 permit
2620:119:50c0:207::215 permit
2800:3f0:4000::/36 permit
194.25.134.0/24 permit # t-online.de
49.12.4.251 permit # checks.mailcow.email
2a01:4f8:c17:7906::10 permit # checks.mailcow.email

View file

@ -2,6 +2,11 @@
cat <<EOF > /redis.conf
requirepass $REDISPASS
user quota_notify on nopass ~QW_* -@all +get +hget +ping
EOF
if [ -n "$REDISMASTERPASS" ]; then
echo "masterauth $REDISMASTERPASS" >> /redis.conf
fi
exec redis-server /redis.conf

View file

@ -24,7 +24,6 @@
/.+\.guru$/i
/.+\.icu$/i
/.+\.id$/i
/.+\.info$/i
/.+\.in.net$/i
/.+\.ir$/i
/.+\.jetzt$/i

View file

@ -133,7 +133,7 @@ try {
error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = explode(',', $goto_branch);
} else {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
$stmt->execute(array(':domain' => $parsed_goto['domain']));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
if ($goto_branch) {

View file

@ -56,7 +56,7 @@ function normalize_email($email) {
$email = explode('@', $email);
$email[0] = str_replace('.', '', $email[0]);
$email = implode('@', $email);
}
}
$gm_alt = "@googlemail.com";
if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) {
$email = explode('@', $email);
@ -114,7 +114,7 @@ function ucl_rcpts($object, $type) {
$rcpt[] = str_replace('/', '\/', $row['address']);
}
// Aliases by alias domains
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
WHERE `mailbox`.`username` = :object");
$stmt->execute(array(
@ -184,7 +184,7 @@ while ($row = array_shift($rows)) {
rcpt = <?=json_encode($rcpt, JSON_UNESCAPED_SLASHES);?>;
<?php
}
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
AND `object`= :object");
$stmt->execute(array(':object' => $row['object']));
@ -468,4 +468,36 @@ while ($row = array_shift($rows)) {
<?php
}
?>
<?php
// Start internal aliases
$stmt = $pdo->query("SELECT `id`, `address`, `domain` FROM `alias` WHERE `active` = '1' AND `internal` = '1'");
$aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($alias = array_shift($aliases)) {
// build allowed_domains regex and add target domain and alias domains
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `active` = '1' AND `target_domain` = :target_domain");
$stmt->execute(array(':target_domain' => $alias['domain']));
$allowed_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
$allowed_domains = array_map(function($item) {
return str_replace('.', '\.', $item['alias_domain']);
}, $allowed_domains);
$allowed_domains[] = str_replace('.', '\.', $alias['domain']);
$allowed_domains = implode('|', $allowed_domains);
?>
internal_alias_<?=$alias['id'];?> {
priority = 10;
rcpt = "<?=$alias['address'];?>";
from = "/^((?!.*@(<?=$allowed_domains;?>)).)*$/";
apply "default" {
MAILCOW_INTERNAL_ALIAS = 9999.0;
}
symbols [
"MAILCOW_INTERNAL_ALIAS"
]
}
<?php
}
?>
}

View file

@ -8,7 +8,7 @@ VIRUS_FOUND {
}
# Bad policy from free mail providers
FREEMAIL_POLICY_FAILURE {
expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST& !WHITELISTED_FWD_HOST & -g+:policies";
expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST & !WHITELISTED_FWD_HOST & -g+:policies";
score = 16.0;
}
# Applies to freemail with undisclosed recipients

View file

@ -1,12 +0,0 @@
oletools {
# default olefy settings
servers = "olefy:10055";
# needs to be set explicitly for Rspamd < 1.9.5
scan_mime_parts = true;
# mime-part regex matching in content-type or filename
# block all macros
extended = true;
max_size = 3145728;
timeout = 20.0;
retransmits = 1;
}

View file

@ -3,6 +3,7 @@ dns {
}
map_watch_interval = 30s;
task_timeout = 30s;
enable_mime_utf = true;
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.

View file

@ -1,2 +0,0 @@
servers = "redis:6379";
timeout = 10;

View file

@ -102,7 +102,7 @@ rspamd_config:register_symbol({
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
if #rcpt_split == 2 then
if rcpt_split[1] == 'postmaster' then
task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt')
task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt', 'postmaster')
return
end
end
@ -167,7 +167,7 @@ rspamd_config:register_symbol({
for k,v in pairs(data) do
if (v and v ~= userdata and v == '1') then
rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result")
task:set_pre_result('accept', 'ip matched with forward hosts')
task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam')
end
end
end
@ -454,12 +454,18 @@ rspamd_config:register_symbol({
local redis_params = rspamd_parse_redis_server('dyn_rl')
local rspamd_logger = require "rspamd_logger"
local envfrom = task:get_from(1)
local envrcpt = task:get_recipients(1) or {}
local uname = task:get_user()
if not envfrom or not uname then
return false
end
local uname = uname:lower()
if #envrcpt == 1 and envrcpt[1].addr:lower() == uname then
return false
end
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
local function redis_cb_user(err, data)
@ -544,13 +550,13 @@ rspamd_config:register_symbol({
-- determine newline type
local function newline(task)
local t = task:get_newlines_type()
if t == 'cr' then
return '\r'
elseif t == 'lf' then
return '\n'
end
return '\r\n'
end
-- retrieve footer
@ -558,7 +564,7 @@ rspamd_config:register_symbol({
if err or type(data) ~= 'string' then
rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
else
-- parse json string
local footer = cjson.decode(data)
if not footer then
@ -607,26 +613,30 @@ rspamd_config:register_symbol({
if footer.plain and footer.plain ~= "" then
footer.plain = lua_util.jinja_template(footer.plain, replacements, true)
end
-- add footer
local out = {}
local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
local seen_cte
local newline_s = newline(task)
local function rewrite_ct_cb(name, hdr)
if rewrite.need_rewrite_ct then
if name:lower() == 'content-type' then
local nct = string.format('%s: %s/%s; charset=utf-8',
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
-- include boundary if present
local boundary_part = rewrite.new_ct.boundary and
string.format('; boundary="%s"', rewrite.new_ct.boundary) or ''
local nct = string.format('%s: %s/%s; charset=utf-8%s',
'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part)
out[#out + 1] = nct
-- update Content-Type header
-- update Content-Type header (include boundary if present)
task:set_milter_reply({
remove_headers = {['Content-Type'] = 0},
})
task:set_milter_reply({
add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8', rewrite.new_ct.type, rewrite.new_ct.subtype)}
add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8%s',
rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part)}
})
return
elseif name:lower() == 'content-transfer-encoding' then
@ -645,16 +655,16 @@ rspamd_config:register_symbol({
end
out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
end
task:headers_foreach(rewrite_ct_cb, {full = true})
if not seen_cte and rewrite.need_rewrite_ct then
out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
end
-- End of headers
out[#out + 1] = newline_s
if rewrite.out then
for _,o in ipairs(rewrite.out) do
out[#out + 1] = o

View file

@ -182,7 +182,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = explode(',', $goto_branch);
} else {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
$stmt->execute(array(':domain' => $parsed_goto['domain']));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
if ($goto_branch) {
@ -236,6 +236,9 @@ foreach ($rcpt_final_mailboxes as $rcpt_final) {
':action' => $action,
':fuzzy_hashes' => $fuzzy
));
$lastId = $pdo->lastInsertId();
$stmt_update = $pdo->prepare("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE `id` = :id");
$stmt_update->execute(array(':id' => $lastId));
$stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN (
SELECT `id`
FROM (

View file

@ -167,7 +167,7 @@ foreach (json_decode($rcpts, true) as $rcpt) {
error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL);
$goto_branch_array = explode(',', $goto_branch);
} else {
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` AND '1'");
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'");
$stmt->execute(array(':domain' => $parsed_goto['domain']));
$goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain'];
if ($goto_branch) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY st0 "fill:#50BD37;">
]>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="640px" height="350px" viewBox="78.712 58.488 640 350" style="enable-background:new 78.712 58.488 640 350;"
xml:space="preserve">
<path style="&st0;" d="M648.541,145.679c-9.947,0-17.009-7.278-17.009-17.048c0-9.777,7.062-17.057,17.009-17.057
c10.024,0,17.086,7.279,17.086,17.057C665.627,138.401,658.565,145.679,648.541,145.679z M648.511,94.893
c-19.693,0-33.679,14.4-33.679,33.738c0,19.33,13.985,33.729,33.679,33.729c19.822,0,33.808-14.4,33.808-33.729
C682.318,109.293,668.333,94.893,648.511,94.893z M648.482,179.843c-29.889,0-51.123-21.868-51.123-51.212
c0-29.353,21.234-51.209,51.123-51.209c30.082,0,51.307,21.856,51.307,51.209C699.789,157.975,678.564,179.843,648.482,179.843z
M648.442,58.488c-40.929,0-69.995,29.946-69.995,70.143c0,40.189,29.066,70.125,69.995,70.125c41.194,0,70.27-29.937,70.27-70.125
C718.712,88.434,689.637,58.488,648.442,58.488z M158.166,183.902l-21.018-5.008c-19.131-4.396-28.849-9.413-28.849-23.21
c0-15.684,15.99-21.965,30.419-21.965c14.667,0,25.382,7.329,31.693,18.737c0.02,0.048,0.051,0.097,0.09,0.157
c0.127,0.247,0.276,0.484,0.403,0.731l0.03-0.02c1.985,3.002,5.323,5.008,8.919,5.008c6.122,0,10.558-4.425,10.558-10.547
c0-2.341-0.504-4.82-1.601-6.688c-10.764-18.302-28.513-26.192-48.838-26.192c-27.594,0-54.262,13.797-54.262,44.218
c0,27.921,27.605,36.079,37.64,38.578l20.069,4.71c15.368,3.763,27.912,8.791,27.912,23.517c0,16.938-17.561,23.943-34.499,23.943
c-17.245,0-30.015-9.37-38.814-22.37h-0.01c-1.956-3-4.988-4.328-8.702-4.328c-5.984,0-10.805,5.185-10.587,11.162
c0.098,2.438,0.909,4.637,2.153,6.405c13.787,20.633,33.728,28.41,55.96,28.41c28.543,0,57.085-13.143,57.085-45.132
C193.918,203.325,178.551,188.613,158.166,183.902z M298.479,250.312c-33.866,0-55.199-25.403-55.199-58.331
c0-32.939,21.333-58.343,55.199-58.343c34.192,0,55.516,25.403,55.516,58.343C353.996,224.91,332.672,250.312,298.479,250.312z
M298.479,114.823c-45.471,0-77.777,32.93-77.777,77.158c0,44.217,32.306,77.146,77.777,77.146
c45.786,0,78.093-32.929,78.093-77.146C376.572,147.753,344.266,114.823,298.479,114.823z M518.715,234.312
c-0.771,0.74-1.549,1.472-2.399,2.175c-1.106,1.014-2.391,2.112-3.854,3.208c-8.829,6.391-19.979,10.094-33.017,10.094
c-33.876,0-55.198-25.402-55.198-58.332c0-32.939,21.322-58.342,55.198-58.342c34.183,0,55.506,25.403,55.506,58.342
C534.951,208.653,529.135,223.774,518.715,234.312z M468.097,317.938c2.528,0,5.146-0.168,7.863-0.504
c5.018-0.631,9.588-0.909,13.729-0.909c19.24,0.109,29.036,5.7,34.943,12.158c5.895,6.499,8.168,15.311,8.158,22.796
c0.01,3.586-0.555,6.795-1.177,8.721c-2.944,8.93-8.888,15.002-17.996,19.576c-9.035,4.484-21.095,6.777-33.707,6.757
c-4.514,0-9.105-0.288-13.639-0.831c-8.573-0.987-19.911-4.671-28.13-11.093c-4.138-3.199-6.458-6.991-8.858-11.485
c-2.379-4.514-2.783-9.748-2.783-16.442v-0.742c0-12.346,4.84-20.544,11.051-26.5c3.07-2.904,5.69-5.064,7.99-6.438
c0.366-0.218,0.438-0.416,0.755-0.593C452.39,316.014,459.684,317.968,468.097,317.938z M479.445,114.301
c-45.471,0-77.786,32.929-77.786,77.157c0,29.887,14.765,54.598,38.378,67.489c-0.314,0.314-0.621,0.641-0.916,0.966
c-6.104,6.687-9.226,15.25-9.236,23.913c-0.008,3.821,0.624,7.741,1.977,11.494c-3.062,1.956-6.717,4.634-10.46,8.147
c-9.026,8.408-18.734,22.541-19.021,42.097c-0.01,0.454-0.01,0.829-0.01,1.118c-0.01,10.071,2.379,19.157,6.459,26.774
c6.133,11.466,15.683,19.445,25.539,24.77c9.917,5.334,20.257,8.166,29.273,9.274c5.373,0.643,10.826,0.988,16.268,0.988
c15.151-0.02,30.261-2.578,43.409-9.019c13.085-6.34,24.333-17.253,29.192-32.562c1.443-4.553,2.212-9.719,2.231-15.428
c-0.02-11.595-3.349-25.759-13.767-37.452c-10.421-11.734-27.654-19.566-51.288-19.459c-5.138,0-10.606,0.356-16.426,1.078
c-1.877,0.227-3.596,0.334-5.166,0.334c-7.239-0.048-10.872-2.053-13.036-4.098c-2.133-2.084-3.2-4.839-3.229-8.058
c-0.01-3.28,1.284-6.727,3.467-9.078c2.231-2.332,5.008-3.91,9.846-3.97c0.436,0,0.9,0.01,1.374,0.05
c3.101,0.216,6.112,0.325,9.037,0.325c24.188,0.047,42.38-7.448,54.756-17.759c12.415-10.312,18.971-22.854,22.071-32.76l-0.04-0.01
c3.37-8.899,5.197-18.715,5.197-29.166C557.539,147.229,525.234,114.301,479.445,114.301z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY st0 "fill:#50BD37;">
]>
<svg version="1.1" id="SOGo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="140.263px" height="140.269px" viewBox="499.737 0 140.263 140.269"
style="enable-background:new 499.737 0 140.263 140.269;" xml:space="preserve">
<path style="&st0;" d="M569.697,87.024c-9.928,0-16.975-7.264-16.975-17.017c0-9.757,7.047-17.022,16.975-17.022
c10.006,0,17.054,7.265,17.054,17.022C586.751,79.76,579.703,87.024,569.697,87.024z M569.667,36.335
c-19.657,0-33.614,14.372-33.614,33.673c0,19.294,13.955,33.667,33.614,33.667c19.787,0,33.745-14.372,33.745-33.667
C603.411,50.707,589.454,36.335,569.667,36.335z M569.639,121.123c-29.833,0-51.025-21.825-51.025-51.115
c0-29.296,21.192-51.111,51.025-51.111c30.025,0,51.213,21.815,51.213,51.111C620.852,99.298,599.664,121.123,569.639,121.123z
M569.602,0c-40.854,0-69.864,29.889-69.864,70.007c0,40.112,29.01,69.993,69.864,69.993c41.116,0,70.136-29.88,70.136-69.993
C639.737,29.889,610.719,0,569.602,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,3 +1,21 @@
// redirect to mailcow login form
document.addEventListener('DOMContentLoaded', function () {
var loginForm = document.forms.namedItem("loginForm");
if (loginForm) {
window.location.href = '/user';
}
});
// logout function
function mc_logout() {
fetch("/", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "logout=1"
}).then(() => window.location.href = '/');
}
// Custom SOGo JS
// Change the visible font-size in the editor, this does not change the font of a html message by default
@ -5,3 +23,4 @@ CKEDITOR.addCss("body {font-size: 16px !important}");
// Enable scayt by default
//CKEDITOR.config.scayt_autoStartup = true;

View file

@ -1,28 +1,34 @@
<!--
<example>
<key>canAuthenticate</key>
<string>YES</string>
<key>id</key>
<string>${line}_ldap</string>
<key>isAddressBook</key>
<string>NO</string>
<key>IDFieldName</key>
<string>mail</string>
<key>UIDFieldName</key>
<string>uid</string>
<key>bindFields</key>
<array>
<string>mail</string>
</array>
<key>type</key>
<string>ldap</string>
<key>bindDN</key>
<string>cn=admin,dc=example,dc=local</string>
<key>bindPassword</key>
<string>password</string>
<key>baseDN</key>
<string>ou=People,dc=example,dc=local</string>
<key>hostname</key>
<string>ldap://1.2.3.4:389</string>
</example>
-->
#!/bin/bash
domain="$1"
gal_status="$2"
echo "
<!--
<example>
<key>canAuthenticate</key>
<string>YES</string>
<key>id</key>
<string>"${domain}"_ldap</string>
<key>isAddressBook</key>
<string>"${gal_status}"</string>
<key>IDFieldName</key>
<string>mail</string>
<key>UIDFieldName</key>
<string>uid</string>
<key>bindFields</key>
<array>
<string>mail</string>
</array>
<key>type</key>
<string>ldap</string>
<key>bindDN</key>
<string>cn=admin,dc=example,dc=local</string>
<key>bindPassword</key>
<string>password</string>
<key>baseDN</key>
<string>ou=People,dc=example,dc=local</string>
<key>hostname</key>
<string>ldap://1.2.3.4:389</string>
</example>
-->"

View file

@ -16,6 +16,9 @@
SOGoFoldersSendEMailNotifications = YES;
SOGoForwardEnabled = YES;
// Added with SOGo 5.12 - Allows users to cleanup there maildirectories by deleting mails oder than X
SOGoEnableMailCleaning = YES;
// Fixes "MODIFICATION_FAILED" error (HTTP 412) in Clients when accepting invitations from external services
SOGoDisableOrganizerEventCheck = YES;
@ -83,6 +86,12 @@
SOGoMaximumFailedLoginInterval = 900;
SOGoFailedLoginBlockInterval = 900;
// Enable SOGo URL Description for GDPR compliance, this may cause some issues with calendars and contacts. Also uncomment the encryption key below to use it.
//SOGoURLEncryptionEnabled = NO;
// Set a 16 character encryption key for SOGo URL Description, change this to your own value
//SOGoURLPathEncryptionKey = "SOGoSuperSecret0";
GCSChannelCollectionTimer = 60;
GCSChannelExpireAge = 60;
@ -91,7 +100,7 @@
//SoDebugBaseURL = YES;
//ImapDebugEnabled = YES;
//SOGoEASDebugEnabled = YES;
SOGoEASSearchInBody = YES; // Experimental. Enabled since 2023-10
SOGoEASSearchInBody = YES;
//LDAPDebugEnabled = YES;
//PGDebugEnabled = YES;
//MySQL4DebugEnabled = YES;

View file

@ -1,21 +1,31 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /');
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$clamd_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_CLAMD"])) ? false : true;
$olefy_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_OLEFY"])) ? false : true;
if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) {
$_SESSION['gal'] = json_decode($license_cache, true);
}
$js_minifier->add('/web/js/site/debug.js');
$js_minifier->add('/web/js/site/dashboard.js');
// vmail df
$exec_fields = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail');
@ -24,6 +34,7 @@ $vmail_df = explode(',', (string)json_decode(docker('post', 'dovecot-mailcow', '
// containers
$containers_info = (array) docker('info');
if ($clamd_status === false) unset($containers_info['clamd-mailcow']);
if ($olefy_status === false) unset($containers_info['olefy-mailcow']);
ksort($containers_info);
$containers = array();
foreach ($containers_info as $container => $container_info) {
@ -59,7 +70,7 @@ foreach ($containers_info as $container => $container_info) {
$hostname = getenv('MAILCOW_HOSTNAME');
$timezone = getenv('TZ');
$template = 'debug.twig';
$template = 'dashboard.twig';
$template_data = [
'log_lines' => getenv('LOG_LINES'),
'vmail_df' => $vmail_df,
@ -68,6 +79,7 @@ $template_data = [
'gal' => @$_SESSION['gal'],
'license_guid' => license('guid'),
'clamd_status' => $clamd_status,
'olefy_status' => $olefy_status,
'containers' => $containers,
'ip_check' => customize('get', 'ip_check'),
'lang_admin' => json_encode($lang['admin']),

30
data/web/admin/index.php Normal file
View file

@ -0,0 +1,30 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin/dashboard');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
$template = 'admin_index.twig';
$template_data = [
'login_delay' => @$_SESSION['ldelay'],
'custom_login' => customize('get', 'custom_login'),
];
$js_minifier->add('/web/js/site/index.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View file

@ -1,10 +1,20 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
header('Location: /');
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
@ -14,7 +24,7 @@ $js_minifier->add('/web/js/site/mailbox.js');
$js_minifier->add('/web/js/presets/sieveMailbox.js');
$js_minifier->add('/web/js/site/pwgen.js');
$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
$role = "admin";
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false';
$allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false';

View file

@ -1,8 +1,17 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /');
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit();
}
@ -11,7 +20,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$js_minifier->add('/web/js/site/queue.js');
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
$role = "admin";
$template = 'queue.twig';
$template_data = [

View file

@ -1,8 +1,17 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /');
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit();
}
@ -86,6 +95,8 @@ $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allo
$cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
$f2b_data = fail2ban('get');
// mbox templates
$mbox_templates = mailbox('get', 'mailbox_templates');
$template = 'admin.twig';
$template_data = [
@ -114,10 +125,13 @@ $template_data = [
'logo_specs' => customize('get', 'main_logo_specs'),
'logo_dark_specs' => customize('get', 'main_logo_dark_specs'),
'ip_check' => customize('get', 'ip_check'),
'custom_login' => customize('get', 'custom_login'),
'password_complexity' => password_complexity('get'),
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'cors_settings' => $cors_settings,
'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

@ -346,7 +346,8 @@ paths:
description: the domain which emails should be forwarded
type: string
type:
description: the type of bcc map can be `sender` or `recipient`
description: the type of bcc map can be `sender` or `rcpt`
enum: [sender, rcpt]
type: string
type: object
summary: Create BCC Map
@ -409,7 +410,7 @@ paths:
description: a list of domains for which a dkim key should be generated
type: string
key_size:
description: the key size (1024 or 2048)
description: the key size (1024, 2048, 3072 or 4096)
type: number
type: object
summary: Generate DKIM Key
@ -1112,6 +1113,7 @@ paths:
domain: domain.tld
local_part: info
name: Full name
authsource: mailcow
password: atedismonsin
password2: atedismonsin
quota: "3072"
@ -1132,11 +1134,16 @@ paths:
name:
description: Full name of the mailbox user
type: string
authsource:
description: Specifies the authentication source for the mailbox.
type: string
enum: [mailcow, ldap, keycloak, generic-oidc]
default: mailcow
password2:
description: mailbox password for confirmation
type: string
password:
description: mailbox password
description: mailbox password when using `mailcow` as the authentication source.
type: string
quota:
description: mailbox quota
@ -3374,6 +3381,7 @@ paths:
active: "1"
force_pw_update: "0"
name: Full name
authsource: mailcow
password: ""
password2: ""
quota: "3072"
@ -3398,11 +3406,15 @@ paths:
name:
description: Full name of the mailbox user
type: string
authsource:
description: Specifies the authentication source for the mailbox.
type: string
enum: [mailcow, ldap, keycloak, generic-oidc]
password2:
description: new mailbox password for confirmation
type: string
password:
description: new mailbox password
description: new mailbox password when using `mailcow` as the authentication source.
type: string
quota:
description: mailbox quota
@ -5687,7 +5699,7 @@ paths:
- description: name of domain
in: path
name: domain
required: false
required: true
schema:
type: string
- description: e.g. api-key-string
@ -5755,8 +5767,8 @@ paths:
tags:
- Cross-Origin Resource Sharing (CORS)
description: >-
This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API.
CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests.
This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API.
CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests.
By editing the CORS settings, you can specify which domains and which methods are permitted to access the API resources from outside the mailcow domain.
operationId: Edit Cross-Origin Resource Sharing (CORS) settings
requestBody:
@ -5814,6 +5826,226 @@ paths:
Using this endpoint you can get the global spam filter score or the spam filter score of a certain mailbox.
operationId: Get mailbox or global spam filter score
summary: Get mailbox or global spam filter score
/api/v1/edit/identity-provider:
post:
responses:
"401":
$ref: "#/components/responses/Unauthorized"
"200":
content:
application/json:
examples:
response:
value:
- type: "success"
log:
- "identity_provider"
- "edit"
- authsource: "keycloak"
server_url: "https://auth.mailcow.tld"
realm: "mailcow"
client_id: "mailcow_client"
client_secret: "*"
redirect_url: "https://mail.mailcow.tld"
redirect_url_extra: ["https://extramail.mailcow.tld"]
version: "26.1.3"
default_template: "Default"
mappers:
- "small_mbox"
- "medium_mbox"
templates:
- "small"
- "medium"
ignore_ssl_error: true
mailpassword_flow: true
periodic_sync: true
import_users: true
sync_interval: 30
msg:
- "object_modified"
- ""
description: OK
headers: { }
tags:
- Identity Provider
description: >-
Configure an external Identity Provider to use as user authentication
operationId: Edit external Identity Provider settings
requestBody:
content:
application/json:
schema:
properties:
items:
type: array
default: ["identity-provider"]
attr:
type: object
properties:
authsource:
description: Specifies the type of the Identity Provider
type: string
enum: [ldap, keycloak, generic-oidc]
server_url:
description: The base URL of your Keycloak server. Required if `authsource` is keycloak.
type: string
realm:
description: The Keycloak realm where the mailcow client is configured. Required if `authsource` is keycloak.
type: string
client_id:
description: The Client ID assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc.
type: string
client_secret:
description: The Client Secret assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc.
type: string
redirect_url:
description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc.
type: string
redirect_url_extra:
description: Additional redirect URLs that OIDC Provider can use after authentication if valid.
type: array
version:
description: Specifies the Keycloak version. Required if `authsource` is keycloak.
type: string
default_template:
description: (Optional) If no matching Attribute Mapping exists for a User, the default template will be used for creating the mailbox, but not for updating the mailbox.
type: string
mappers:
description: (Optional) Attribute values used to match a mailbox template. Each element corresponds to the respective index in the templates array (i.e., the first element matches the first element of templates, the second matches the second, and so on).
type: array
templates:
description: (Optional) Defines the mailbox templates to be assigned. Each element corresponds to the respective index in the `mappers` array.
type: array
ignore_ssl_error:
description: If enabled, SSL certificate validation is bypassed
type: boolean
default: false
mailpassword_flow:
description: If enabled, mailcow will attempt to validate user credentials using the Keycloak Admin REST API instead of relying solely on the Authorization Code Flow.
type: boolean
default: false
periodic_sync:
description: If enabled, mailcow periodically performs a full sync of all users from Keycloak or LDAP.
type: boolean
default: false
import_users:
description: If enabled, new users are automatically imported from Keycloak or LDAP into mailcow.
type: boolean
default: false
sync_interval:
description: Defines the time interval (in minutes) for periodic synchronization and user imports.
type: number
default: 15
host:
description: The address of your LDAP server. You can provide a single hostname or a comma-separated list of hosts for fallback in case the primary server is unreachable. Required if `authsource` is ldap.
type: string
port:
description: The port used to connect to the LDAP server. Required if `authsource` is ldap.
type: string
use_ssl:
description: enable LDAPS connection. If Port is set to 389 it will be overriden to 636.
type: boolean
default: false
use_tls:
description: enable TLS connection. TLS is recommended over SSL. SSL Ports cannot be used.
type: boolean
default: false
basedn:
description: The Distinguished Name (DN) from which searches will be performed. Required if `authsource` is ldap.
type: string
username_field:
description: The LDAP attribute used to identify users during authentication. Required if `authsource` is ldap.
type: string
default: mail
filter:
description: An optional LDAP search filter to refine which users can authenticate.
type: string
attribute_field:
description: Specifies an LDAP attribute that holds a specific value which can be mapped to a mailbox template using the Attribute Mapping section. Required if `authsource` is ldap.
type: string
binddn:
description: The Distinguished Name (DN) of the LDAP user that will be used to authenticate and perform LDAP searches. This account should have sufficient permissions to read the required attributes. Required if `authsource` is ldap.
type: string
bindpass:
description: The password for the Bind DN user. It is required for authentication when connecting to the LDAP server. Required if `authsource` is ldap.
type: string
authorize_url:
description: The OIDC provider's authorization server URL. Required if `authsource` is generic-oidc.
type: string
token_url:
description: The OIDC provider's token server URL. Required if `authsource` is generic-oidc.
type: string
userinfo_url:
description: The OIDC provider's user info server URL. Required if `authsource` is generic-oidc.
type: string
client_scopes:
description: Specifies the OIDC scopes requested during authentication.
type: string
default: "openid profile email mailcow_template"
examples:
keycloak:
value:
items:
- "identity-provider"
attr:
authsource: "keycloak"
server_url: "https://auth.mailcow.tld"
realm: "mailcow"
client_id: "mailcow_client"
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
redirect_url: "https://mail.mailcow.tld"
redirect_url_extra: ["https://extramail.mailcow.tld"]
version: "26.1.3"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
templates: ["small", "medium"]
ignore_ssl_error: true
mailpassword_flow: true
periodic_sync: true
import_users: true
sync_interval: 30
ldap:
value:
items:
- "identity-provider"
attr:
authsource: "ldap"
host: "127.0.0.1"
port: "389"
use_ssl: false
use_tls: false
ignore_ssl_error: false
basedn: "DC=mailcow,DC=local"
username_field: "mail"
filter: "(memberOf:1.2.840.113556.1.4.1941:=DC=mailcow,DC=local)"
attribute_field: "othermailbox"
binddn: "CN=LDAP Read Only,CN=Users,DC=mailcow,DC=local"
bindpass: "moohoo"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
templates: ["small", "medium"]
periodic_sync: true
import_users: true
sync_interval: 30
generic-oidc:
value:
items:
- "identity-provider"
attr:
authsource: "generic-oidc"
authorize_url: "https://auth.mailcow.tld/application/o/authorize/"
token_url: "https://auth.mailcow.tld/application/o/token/"
userinfo_url: "https://auth.mailcow.tld/application/o/userinfo/"
client_id: "mailcow_client"
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
redirect_url: "https://mail.mailcow.tld"
redirect_url_extra: ["https://extramail.mailcow.tld"]
client_scopes: "openid profile email mailcow_template"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
templates: ["small", "medium"]
ignore_ssl_error: true
summary: Edit external Identity Provider
tags:
- name: Domains
@ -5860,3 +6092,5 @@ tags:
description: Edit domain ratelimits
- name: Cross-Origin Resource Sharing (CORS)
description: Manage Cross-Origin Resource Sharing (CORS) settings
- name: Identity Provider
description: Manage external Identity Provider settings

View file

@ -85,7 +85,7 @@ if (count($records) == 0 || $records[0]['target'] != '') { ?>
<authentication>password-cleartext</authentication>
</outgoingServer>
<enable visiturl="https://<?=$mailcow_hostname; ?><?php if ($port != 443) echo ':'.$port; ?>/admin.php">
<enable visiturl="https://<?=$mailcow_hostname; ?><?php if ($port != 443) echo ':'.$port; ?>/admin">
<instruction>If you didn't change the password given to you by the administrator or if you didn't change it in a long time, please consider doing that now.</instruction>
<instruction lang="de">Sollten Sie das Ihnen durch den Administrator vergebene Passwort noch nicht geändert haben, empfehlen wir dies nun zu tun. Auch ein altes Passwort sollte aus Sicherheitsgründen geändert werden.</instruction>
</enable>

View file

@ -1,10 +1,15 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/vendor/autoload.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
$default_autodiscover_config = $autodiscover_config;
if(file_exists('inc/vars.local.inc.php')) {
include_once 'inc/vars.local.inc.php';
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
$default_autodiscover_config = $autodiscover_config;
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
// Redis
@ -50,6 +55,11 @@ $opt = [
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
// Init Identity Provider
$iam_provider = identity_provider('init');
$iam_settings = identity_provider('get');
$login_user = strtolower(trim($_SERVER['PHP_AUTH_USER']));
$login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW']));

File diff suppressed because one or more lines are too long

View file

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

View file

@ -33,6 +33,13 @@
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;
@ -74,10 +81,23 @@
align-items: center;
padding: 0 10px !important;
}
.navbar-fixed-bottom .navbar-collapse,
.navbar-fixed-bottom .navbar-collapse,
.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;
@ -123,18 +143,18 @@
}
}
@keyframes blink {
50% {
color: transparent
50% {
color: transparent
}
}
.loader-dot {
animation: 1s blink infinite
.loader-dot {
animation: 1s blink infinite
}
.loader-dot:nth-child(2) {
animation-delay: 250ms
.loader-dot:nth-child(2) {
animation-delay: 250ms
}
.loader-dot:nth-child(3) {
animation-delay: 500ms
.loader-dot:nth-child(3) {
animation-delay: 500ms
}
pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;}
@ -200,13 +220,13 @@ legend {
}
.haveibeenpwned {
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.full-width-select {
width: 100%!important;
width: 100%!important;
}
.tooltip {
font-family: inherit;
@ -330,7 +350,7 @@ code {
.caret {
transform: rotate(0deg);
}
a[aria-expanded='true'] > .caret,
a[aria-expanded='true'] > .caret,
button[aria-expanded='true'] > .caret {
transform: rotate(-180deg);
}
@ -340,7 +360,7 @@ button[aria-expanded='true'] > .caret {
}
.list-group-header {
background: #f7f7f7;
}
}
.bg-primary, .alert-primary, .btn-primary {
@ -366,12 +386,13 @@ button[aria-expanded='true'] > .caret {
background-color: #f0f0f0;
}
.btn.btn-outline-secondary {
border-color: #cfcfcf !important;
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 {
background-color: #f0f0f0 !important;
}
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
color: #fff;
background-color: #555;
background-image: none;
@ -389,4 +410,26 @@ button[aria-expanded='true'] > .caret {
.badge.bg-danger > a {
color: #fff !important;
text-decoration: none;
}
.hr-title {
display: flex;
align-items: center;
text-align: center;
margin: 20px 0;
}
.hr-title::before,
.hr-title::after {
content: "";
flex: 1;
border-bottom: 1px solid #ccc;
}
.hr-title:not(:empty)::before {
margin-right: 10px;
}
.hr-title:not(:empty)::after {
margin-left: 10px;
}

View file

@ -6,15 +6,9 @@
max-width: 350px;
}
.card-login .apps .btn {
width: auto;
float: left;
margin-right: 10px;
margin-top: auto;
}
.card-login .apps .btn:hover {
margin-top: 1px !important;
border-bottom-width: 3px;
.card .apps {
display: flex;
flex-wrap: wrap;
}
.responsive-tabs .nav-tabs {
@ -43,16 +37,6 @@
opacity: 1;
}
.card-login .apps .btn {
width: 100%;
float: none;
margin-bottom: 10px;
}
.card-login .apps .btn {
border-bottom-width: 4px;
}
.xs-show {
display: block !important;
}
@ -113,9 +97,6 @@
.btn-group.nowrap .dropdown-menu {
width: 100%;
}
.card-login .btn-group {
display: block;
}
.mass-actions-user .btn-group {
float: none;
}
@ -191,9 +172,6 @@
.btn-group .btn i {
margin-right: 5px;
}
.card-login .btn-group .btn {
display: block !important;
}
.dt-sm-head-hidden .dtr-title {
display: none !important;
@ -206,7 +184,7 @@
.senders-mw220 {
max-width: 100% !important;
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before,
table.dataTable td.dt-control:before {
@ -215,7 +193,7 @@
line-height: 2rem;
margin-top: -15px;
}
li .dtr-data {
padding: 0;
}

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