1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2026-06-13 01:50:34 +00:00

Compare commits

..

144 Commits

Author SHA1 Message Date
FreddleSpl0it 0a58aa293a Merge pull request #6141 from mailcow/staging
2024-11
2024-11-07 11:41:45 +01:00
milkmaker be79f320d2 Translations update from Weblate (#6140)
* [Web] Updated lang.lv-lv.json

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

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

Co-authored-by: Furkan <furkan43500@gmail.com>

---------

Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
Co-authored-by: Furkan <furkan43500@gmail.com>
2024-11-06 19:08:53 +01:00
Niklas Meyer 6ec1e357c3 fix: broken sogo cron notifications (for appointments etc.) (#6128) 2024-11-05 16:21:14 +01:00
milkmaker 8b2f71f97e update postscreen_access.cidr (#6129) 2024-11-05 16:20:57 +01:00
renovate[bot] 93cf99cc9e chore(deps): update thollander/actions-comment-pull-request action to v3.0.1 (#6130)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-02 20:38:18 +01:00
FreddleSpl0it d8c8e4ab1b [DockerApi] Fix IMAP ACL migration issue when renaming mailbox 2024-10-31 11:00:03 +01:00
FreddleSpl0it 2d76ffc88c Merge pull request #6045 from mailcow/feat/rename-mbox
[Web][DockerApi] Add Feature to Rename Email Addresses
2024-10-25 10:49:58 +02:00
FreddleSpl0it 672bb345fd Fix mailbox_rename de-de translation 2024-10-25 10:47:53 +02:00
milkmaker 5c88030b5a Translations update from Weblate (#6123)
* [Web] Updated lang.lv-lv.json

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

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

[Web] Updated lang.zh-tw.json

Co-authored-by: SamWang8891 <g348.8891@gmail.com>
Co-authored-by: milkmaker <milkmaker@mailcow.de>

---------

Co-authored-by: Edgars Andersons <Edgars+Mailcow+Weblate@gaitenis.id.lv>
Co-authored-by: SamWang8891 <g348.8891@gmail.com>
2024-10-22 21:52:42 +02:00
Niklas Meyer b106945c73 Feat/rspamd 3.10.2 (#6122)
* rspamd: update to 3.10.2

* rspamd: fix broken archive_extension gz
2024-10-21 16:03:51 +02:00
milkmaker 502a7100ca [Web] Updated lang.zh-cn.json (#6120)
Co-authored-by: SamWang8891 <g348.8891@gmail.com>
2024-10-19 22:24:45 +02:00
Niklas Meyer ee2791d93a rspamd: update to 3.10.1 (#6115)
* rspamd: upgrade to 3.10.1

* rspamd: adapt 30s task timeout per default now
2024-10-18 15:50:45 +02:00
SamWang8891 399630cf34 Update lang.zh-tw.json (#6114) 2024-10-17 14:50:05 +02:00
Patrik Kernstock fce93609dd Update mime_types.conf configuration (#6013)
In the last months and years, the default `mime_types.conf` of rspamd has changed and it might be also useful to make some adjustments to the weight of certain file extensions.

This PR is removing all file extensions from `mime_types.conf` which are already in rspamd's default configuration at [rspamd/src/plugins/lua/mime_types.lua](https://github.com/rspamd/rspamd/blob/master/src/plugins/lua/mime_types.lua). If file extension is not present or has a different score compared to rspamd default, it is still in the list.

There are also a few major differences to certain file extensions, which might be useful to discuss and carefully adjust. For example, `.exe` files are rated very 'badly' due to high chance of being malicious, so are other extensions like `bat`, `cmd`, etc.

Current suggestion:
```lua
# Extensions that are treated as 'bad'
# Number is score multiply factor
bad_extensions = {
  apk = 4,
  appx = 4,
  appxbundle = 4,
  bat = 8,
  cab = 20,
  cmd = 8,
  com = 20,
  diagcfg = 4,
  diagpack = 4,
  dmg = 8,
  ex = 20,
  ex_ = 20,
  exe = 20,
  img = 4,
  jar = 8,
  jnlp = 8,
  js = 8,
  jse = 8,
  lnk = 20,
  mjs = 8,
  msi = 4,
  msix = 4,
  msixbundle = 4,
  ps1 = 8,
  scr = 20,
  sct = 20,
  vb = 20,
  vbe = 20,
  vbs = 20,
  vhd = 4,
  py = 4,
  reg = 8,
  scf = 8,
  vhdx = 4,
};

# Extensions that are particularly penalized for archives
bad_archive_extensions = {
  pptx = 0.5,
  docx = 0.5,
  xlsx = 0.5,
  pdf = 1.0,
  jar = 12,
  jnlp = 12,
  bat = 12,
  cmd = 12,
};

# Used to detect another archive in archive
archive_extensions = {
  tar = 1,
  ['tar.gz'] = 1,
};
```

**As a important reminder**: For all remaining and additional file extensions and score weights, please check above default rspamd configuration!
2024-10-17 09:11:55 +02:00
Niklas Meyer 38907b5032 dovecot: activate lazy_expunge plugin per default (unconfigured) (#6112) 2024-10-16 15:56:40 +02:00
Peter 5a0f20b9ea Update dependency twig/twig to v3.14.0 (#6071) 2024-10-16 15:29:16 +02:00
Niklas Meyer 8dcaffe925 php: upgrade to alpine 3.20 (base os) (#6106) 2024-10-16 10:35:54 +02:00
Niklas Meyer c53bf85480 postfix: add X-Original-To header per default (#6110) 2024-10-16 10:35:39 +02:00
Niklas Meyer 982e823c71 sogo: upgrade to 5.11.1 (#6109) 2024-10-15 16:13:51 +02:00
renovate[bot] 382056ec18 chore(deps): update dependency krakjoe/apcu to v5.1.24 (#6087)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-15 11:24:26 +02:00
renovate[bot] 4c9690e87c chore(deps): update dependency php/pecl-mail-mailparse to v3.1.8 (#6096)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-15 11:09:23 +02:00
renovate[bot] 9a58e5e35a chore(deps): update dependency phpredis/phpredis to v6.1.0 (#6098)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
2024-10-15 10:45:32 +02:00
renovate[bot] 932cf453de chore(deps): update dependency nextcloud/server to v28.0.11 (#6101)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-15 10:34:57 +02:00
milkmaker 1538fda71c update postscreen_access.cidr (#6093) 2024-10-15 10:34:39 +02:00
renovate[bot] 54a0d53deb chore(deps): update thollander/actions-comment-pull-request action to v3 (#6102)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-15 10:34:19 +02:00
Niklas Meyer fda95301ba fix: added tls1.0/1.1 patch for openssl when using older tls versions in override (#6105) 2024-10-15 10:32:08 +02:00
FreddleSpl0it 1528e8766a [DockerApi] correctly escape user input 2024-09-06 15:59:52 +02:00
Hassan A Hashim 220fdbb168 Add missing Russian translation (#6065) 2024-09-06 07:14:34 +02:00
milkmaker fe3d08515e [Web] Language file updated by 'Cleanup translation files' addon (#6064) 2024-09-06 07:13:59 +02:00
airon-assustadus 22f7f61ac9 feat/brazilian-translations (#6048)
# What
- Adding some brazilian translations that were missing

Co-authored-by: Airon Teixeira <airon@ymail.com>
2024-09-05 15:09:49 +02:00
FreddleSpl0it 29d8cfe2ba [Web] Set min-width and text-align for last login badges 2024-09-05 14:02:04 +02:00
FreddleSpl0it f2e35dff68 [Web] rename user in sender_acl table 2024-09-05 12:40:30 +02:00
FreddleSpl0it b1368d29d1 Merge pull request #5724 from q16marvin/master
show last sso login in mailbox table
2024-09-05 12:02:16 +02:00
FreddleSpl0it 0d704a57f5 Merge pull request #6057 from mailcow/fix/sogo-auto-reply
[SOGo] Fix vacation auto reply date shifting
2024-09-05 11:19:40 +02:00
FreddleSpl0it 462137ede7 Merge pull request #6044 from mailcow/feat/redis-session-store
[PHP-FPM] Use redis as session store
2024-09-05 10:55:07 +02:00
Niklas Meyer bb6f405841 compose: added clamd as depends_on to rspamd (#6062) 2024-09-04 14:42:30 +02:00
renovate[bot] 8b2d67169b chore(deps): update peter-evans/create-pull-request action to v7 (#6059)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-03 19:42:10 +02:00
Finn Hoffhenke 710cec996c feat: Added check for newer version tags on remote (#6054) 2024-09-02 15:40:29 +02:00
Niklas Meyer 0129f84a32 Merge pull request #6056 from mailcow/update/postscreen_access.cidr
[Postfix] update postscreen_access.cidr
2024-09-02 15:37:24 +02:00
FreddleSpl0it ae3653a925 [SOGo] vacation auto reply date shifting #5394 2024-09-02 10:22:51 +02:00
milkmaker af0c61b90a update postscreen_access.cidr 2024-09-01 00:19:09 +00:00
milkmaker 7203735532 [Web] Updated lang.it-it.json (#6053)
Co-authored-by: Stefano <stefano.vassena@gmail.com>
2024-08-29 20:27:23 +02:00
FreddleSpl0it 4f9e37c0c3 [Web] rename user in bcc_maps, recipient_maps and imapsync table 2024-08-28 11:16:29 +02:00
FreddleSpl0it d21c1bfa72 [Web] add error handling for get_acl call 2024-08-28 10:48:44 +02:00
FreddleSpl0it 822d9a7de6 [Web] rename goto in alias table 2024-08-27 10:07:07 +02:00
DerLinkman 37beed6ad9 update FUNDING.yml 2024-08-26 09:56:49 +02:00
milkmaker 0066040bdc Translations update from Weblate (#6049)
* [Web] Updated lang.cs-cz.json

Co-authored-by: Kristian Feldsam <feldsam@gmail.com>

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

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

---------

Co-authored-by: Kristian Feldsam <feldsam@gmail.com>
Co-authored-by: Samuel F <20537389+samuelfranzini@users.noreply.github.com>
2024-08-24 14:09:28 +02:00
DerLinkman 75f18df143 Revert "Before update on 2024-08-20_14_22_10"
This reverts commit 89398c4726.
2024-08-23 09:54:10 +02:00
FreddleSpl0it 8e7b27aae4 [DockerApi] rework doveadm__get_acl function 2024-08-23 09:30:23 +02:00
FreddleSpl0it c62b467ac4 [PHP-FPM] Use redis as session store 2024-08-22 11:16:01 +02:00
FreddleSpl0it be5a181be5 [Web][DockerApi] migrate imap acl on mbox rename 2024-08-22 10:10:05 +02:00
FreddleSpl0it 10dfd0a443 [Web][DockerApi] Add the ability to rename the local part of a mailbox 2024-08-21 10:10:34 +02:00
milkmaker cc5138da13 Translations update from Weblate (#6039)
* [Web] Updated lang.fr-fr.json

[Web] Updated lang.fr-fr.json

Co-authored-by: GeistFighter <lorentzjohan1@gmail.com>
Co-authored-by: Samuel F <20537389+samuelfranzini@users.noreply.github.com>

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

Co-authored-by: Berttas <mika@tarh.fi>

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

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

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

Co-authored-by: DRago_Angel <dragoangel@users.noreply.translate.mailcow.email>

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

Co-authored-by: xmacaba <lixo@macaba.com.br>

---------

Co-authored-by: GeistFighter <lorentzjohan1@gmail.com>
Co-authored-by: Samuel F <20537389+samuelfranzini@users.noreply.github.com>
Co-authored-by: Berttas <mika@tarh.fi>
Co-authored-by: Habetdin <15926758+Habetdin@users.noreply.github.com>
Co-authored-by: DRago_Angel <dragoangel@users.noreply.translate.mailcow.email>
Co-authored-by: xmacaba <lixo@macaba.com.br>
2024-08-20 21:34:04 +02:00
DerLinkman 89398c4726 Before update on 2024-08-20_14_22_10 2024-08-20 14:22:55 +02:00
DerLinkman 8971b11c49 Merge branch 'staging' 2024-08-20 14:08:57 +02:00
Hassan A Hashim bb7fd483f7 Fix: Escape a ' character in update.sh (#6034) 2024-08-20 14:08:08 +02:00
Niklas Meyer 439a936fd8 Merge pull request #6033 from mailcow/staging
2024-08a
2024-08-20 13:44:51 +02:00
Délano 567ebbc324 Pushover/Quarantine utf 8 fix - fixes #6028 (#6031)
* Decode rspamd-subject for pushover notifications

Fixes #6028

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

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

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

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

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

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

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

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

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

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

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

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

* compose: bump unbound tag

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

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

Add hint about classifier name, and add missing learn_condition

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

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

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

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

---------

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

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

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

This reverts commit 9b478b3859.

* Revert all before "the storm" in php world
2024-07-12 16:21:53 +02:00
Niklas Meyer a689109f44 Merge pull request #5955 from mailcow/revert-5875-staging_cml
Revert "Update debug.twig to include a link to the git project URL for the mailcow version tag"
2024-07-12 16:05:01 +02:00
Niklas Meyer 58c0a46459 Revert "Update debug.twig to include a link to the git project URL for the mailcow version tag" 2024-07-12 16:04:19 +02:00
Julian Raufelder 384e5a2e64 Don't expose SMTP/IMAP if announced "not provided" via SRV
Fixes #5944
2024-07-09 19:57:32 +02:00
FreddleSpl0it f33d82ffc1 [Web] use correct user to fetch TFA authenticators 2024-07-03 15:50:17 +02:00
Daniel Muehlbachler-Pietrzykowski ffeeb179e1 restore: remove tty requirement from restore process to allow for automated restores 2024-07-03 10:53:37 +02:00
Ayowel 5c851f2935 Allow prompt-less install on low-resource systems 2024-03-26 08:19:24 +01:00
Marcel Schuster 914a8204d4 Watchdog: escape subject and body for webhooks 2024-03-01 23:07:05 +01:00
q16marvin 19deda31bc Update functions.mailbox.inc.php 2024-02-09 11:23:47 +01:00
q16marvin 4f47534824 Update mailbox.js 2024-02-09 11:23:09 +01:00
283 changed files with 10274 additions and 4302 deletions
+1
View File
@@ -1 +1,2 @@
github: mailcow
custom: ["https://www.servercow.de/mailcow?lang=en#sal"] custom: ["https://www.servercow.de/mailcow?lang=en#sal"]
@@ -1,13 +1,3 @@
## :memo: Brief description
<!-- Diff summary - START -->
<!-- Diff summary - END -->
## :computer: Commits
<!-- Diff commits - START -->
<!-- Diff commits - END -->
## :file_folder: Modified files ## :file_folder: Modified files
<!-- Diff files - START --> <!-- Diff files - START -->
<!-- Diff files - END --> <!-- Diff files - END -->
+38
View File
@@ -0,0 +1,38 @@
<!-- _Please make sure to review and check all of these items, otherwise we might refuse your PR:_ -->
## Contribution Guidelines
* [ ] I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree them
<!-- _NOTE: this tickbox is needed to fullfil on order to get your PR reviewed._ -->
## What does this PR include?
### Short Description
<!-- Please write a short description, what your PR does here. -->
### Affected Containers
<!-- Please list all affected Docker containers here, which you commited changes to -->
<!--
Please list them like this:
- container1
- container2
- container3
etc.
-->
## Did you run tests?
### What did you tested?
<!-- Please write shortly, what you've tested (which components etc.). -->
### What were the final results? (Awaited, got)
<!-- Please write shortly, what your final tests results were. What did you awaited? Was the outcome the awaited one? -->
@@ -10,7 +10,7 @@ jobs:
if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging
steps: steps:
- name: Send message - name: Send message
uses: thollander/actions-comment-pull-request@v2.5.0 uses: thollander/actions-comment-pull-request@v3.0.1
with: with:
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }} GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
message: | message: |
@@ -22,7 +22,7 @@ jobs:
bash helper-scripts/update_postscreen_whitelist.sh bash helper-scripts/update_postscreen_whitelist.sh
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@v6 uses: peter-evans/create-pull-request@v7
with: with:
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }} token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
commit-message: update postscreen_access.cidr commit-message: update postscreen_access.cidr
+1
View File
@@ -45,6 +45,7 @@ data/conf/rspamd/override.d/*
data/conf/sogo/custom-theme.js data/conf/sogo/custom-theme.js
data/conf/sogo/plist_ldap data/conf/sogo/plist_ldap
data/conf/sogo/sieve.creds data/conf/sogo/sieve.creds
data/conf/sogo/cron.creds
data/conf/sogo/sogo-full.svg data/conf/sogo/sogo-full.svg
data/gitea/ data/gitea/
data/gogs/ data/gogs/
+25 -8
View File
@@ -1,25 +1,42 @@
# Contribution Guidelines (Last modified on 27th June 2024) # Contribution Guidelines
**_Last modified on 15th August 2024_**
First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow! First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow!
## Pull Requests (Last modified on 27th June 2024) As we want to keep mailcow's development structured we setup these Guidelines which helps you to create your issue/pull request accordingly.
**PLEASE NOTE, THAT WE MIGHT CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULLFIL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request.
## Topics
- [Pull Requests](#pull-requests)
- [Issue Reporting](#issue-reporting)
- [Guidelines](#issue-reporting-guidelines)
- [Issue Report Guide](#issue-report-guide)
## Pull Requests
**_Last modified on 15th August 2024_**
However, please note the following regarding pull requests: However, please note the following regarding pull requests:
1. **ALWAYS** create your PR using the staging branch of your locally cloned mailcow instance, as the pull request will end up in said staging branch of mailcow once approved. Ideally, you should simply create a new branch for your pull request that is named after the type of your PR (e.g. `feat/` for function updates or `fix/` for bug fixes) and the actual content (e.g. `sogo-6.0.0` for an update from SOGo to version 6 or `html-escape` for a fix that includes escaping HTML in mailcow). 1. **ALWAYS** create your PR using the staging branch of your locally cloned mailcow instance, as the pull request will end up in said staging branch of mailcow once approved. Ideally, you should simply create a new branch for your pull request that is named after the type of your PR (e.g. `feat/` for function updates or `fix/` for bug fixes) and the actual content (e.g. `sogo-6.0.0` for an update from SOGo to version 6 or `html-escape` for a fix that includes escaping HTML in mailcow).
2. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english. 2. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english.
3. Please **keep** this pull request branch **clean** and free of commits that have nothing to do with the changes you have made (e.g. commits from other users from other branches). *If you make changes to the `update.sh` script or other scripts that trigger a commit, there is usually a developer mode for clean working in this case. 3. Please **keep** this pull request branch **clean** and free of commits that have nothing to do with the changes you have made (e.g. commits from other users from other branches). *If you make changes to the `update.sh` script or other scripts that trigger a commit, there is usually a developer mode for clean working in this case.*
4. **Test your changes before you commit them as a pull request.** <ins>If possible</ins>, write a small **test log** or demonstrate the functionality with a **screenshot or GIF**. *We will of course also test your pull request ourselves, but proof from you will save us the question of whether you have tested your own changes yourself.* 4. **Test your changes before you commit them as a pull request.** <ins>If possible</ins>, write a small **test log** or demonstrate the functionality with a **screenshot or GIF**. *We will of course also test your pull request ourselves, but proof from you will save us the question of whether you have tested your own changes yourself.*
5. Please **ALWAYS** create the actual pull request against the staging branch and **NEVER** directly against the master branch. *If you forget to do this, our moobot will remind you to switch the branch to staging.* 5. **Please use** the pull request template we provide once creating a pull request. *HINT: During editing you encounter comments which looks like: `<!-- CONTENT -->`. These can be removed or kept, as they will not rendered later on GitHub! Please only create actual content without the said comments.*
6. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project. 6. 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. 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! 7. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project.
8. If you are planning larger and therefore more complex pull requests, it would be advisable to first announce this in a separate issue and then start implementing it after the idea has been accepted in order to avoid unnecessary frustration and effort!
--- ---
## Issue Reporting (Last modified on 27th June 2024) ## Issue Reporting
**_Last modified on 15th August 2024_**
If you plan to report a issue within mailcow please read and understand the following rules: If you plan to report a issue within mailcow please read and understand the following rules:
### Issue Reporting Guidelines
1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support). 1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support).
2. **ONLY** report an error if you have the **necessary know-how (at least the basics)** for the administration of an e-mail server and the usage of Docker. mailcow is a complex and fully-fledged e-mail server including groupware components on a Docker basement and it requires a bit of technical know-how for debugging and operating. 2. **ONLY** report an error if you have the **necessary know-how (at least the basics)** for the administration of an e-mail server and the usage of Docker. mailcow is a complex and fully-fledged e-mail server including groupware components on a Docker basement and it requires a bit of technical know-how for debugging and operating.
3. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english. 3. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english.
@@ -29,7 +46,7 @@ If you plan to report a issue within mailcow please read and understand the foll
7. When you create a issue/feature request: Please note that the creation does <ins>**not guarantee an instant implementation or fix by the mailcow team or the community**</ins>. 7. When you create a issue/feature request: Please note that the creation does <ins>**not guarantee an instant implementation or fix by the mailcow team or the community**</ins>.
8. Please **ALWAYS** anonymize any sensitive information in your bug report or feature request before submitting it. 8. Please **ALWAYS** anonymize any sensitive information in your bug report or feature request before submitting it.
### Quick guide to reporting problems: ### Issue Report Guide
1. Read your logs; follow them to see what the reason for your problem is. 1. Read your logs; follow them to see what the reason for your problem is.
2. Follow the leads given to you in your logfiles and start investigating. 2. Follow the leads given to you in your logfiles and start investigating.
3. Restarting the troubled service or the whole stack to see if the problem persists. 3. Restarting the troubled service or the whole stack to see if the problem persists.
+1 -1
View File
@@ -1,6 +1,6 @@
FROM alpine:3.20 FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
RUN apk upgrade --no-cache \ RUN apk upgrade --no-cache \
+3 -3
View File
@@ -117,13 +117,13 @@ fi
chmod 600 ${ACME_BASE}/key.pem chmod 600 ${ACME_BASE}/key.pem
log_f "Waiting for database..." log_f "Waiting for database..."
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent > /dev/null; do while ! /usr/bin/mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent > /dev/null; do
sleep 2 sleep 2
done done
log_f "Database OK" log_f "Database OK"
log_f "Waiting for Nginx..." log_f "Waiting for Nginx..."
until $(curl --output /dev/null --silent --head --fail http://nginx:8081); do until $(curl --output /dev/null --silent --head --fail http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network:8081); do
sleep 2 sleep 2
done done
log_f "Nginx OK" log_f "Nginx OK"
@@ -137,7 +137,7 @@ log_f "Resolver OK"
# Waiting for domain table # Waiting for domain table
log_f "Waiting for domain table..." log_f "Waiting for domain table..."
while [[ -z ${DOMAIN_TABLE} ]]; do while [[ -z ${DOMAIN_TABLE} ]]; do
curl --silent http://nginx/ >/dev/null 2>&1 curl --silent http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network/ >/dev/null 2>&1
DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs) DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
[[ -z ${DOMAIN_TABLE} ]] && sleep 10 [[ -z ${DOMAIN_TABLE} ]] && sleep 10
done done
@@ -2,32 +2,32 @@
# Reading container IDs # Reading container IDs
# Wrapping as array to ensure trimmed content when calling $NGINX etc. # Wrapping as array to ensure trimmed content when calling $NGINX etc.
NGINX=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"nginx-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " ")) NGINX=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"nginx-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " "))
DOVECOT=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"dovecot-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " ")) DOVECOT=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"dovecot-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " "))
POSTFIX=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " ")) POSTFIX=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " "))
reload_nginx(){ reload_nginx(){
echo "Reloading Nginx..." echo "Reloading Nginx..."
NGINX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi/containers/${NGINX}/exec -d '{"cmd":"reload", "task":"nginx"}' --silent -H 'Content-type: application/json' | jq -r .type) NGINX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${NGINX}/exec -d '{"cmd":"reload", "task":"nginx"}' --silent -H 'Content-type: application/json' | jq -r .type)
[[ ${NGINX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Nginx, restarting container..."; restart_container ${NGINX} ; } [[ ${NGINX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Nginx, restarting container..."; restart_container ${NGINX} ; }
} }
reload_dovecot(){ reload_dovecot(){
echo "Reloading Dovecot..." echo "Reloading Dovecot..."
DOVECOT_RELOAD_RET=$(curl -X POST --insecure https://dockerapi/containers/${DOVECOT}/exec -d '{"cmd":"reload", "task":"dovecot"}' --silent -H 'Content-type: application/json' | jq -r .type) DOVECOT_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${DOVECOT}/exec -d '{"cmd":"reload", "task":"dovecot"}' --silent -H 'Content-type: application/json' | jq -r .type)
[[ ${DOVECOT_RELOAD_RET} != 'success' ]] && { echo "Could not reload Dovecot, restarting container..."; restart_container ${DOVECOT} ; } [[ ${DOVECOT_RELOAD_RET} != 'success' ]] && { echo "Could not reload Dovecot, restarting container..."; restart_container ${DOVECOT} ; }
} }
reload_postfix(){ reload_postfix(){
echo "Reloading Postfix..." echo "Reloading Postfix..."
POSTFIX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi/containers/${POSTFIX}/exec -d '{"cmd":"reload", "task":"postfix"}' --silent -H 'Content-type: application/json' | jq -r .type) POSTFIX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/exec -d '{"cmd":"reload", "task":"postfix"}' --silent -H 'Content-type: application/json' | jq -r .type)
[[ ${POSTFIX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Postfix, restarting container..."; restart_container ${POSTFIX} ; } [[ ${POSTFIX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Postfix, restarting container..."; restart_container ${POSTFIX} ; }
} }
restart_container(){ restart_container(){
for container in $*; do for container in $*; do
echo "Restarting ${container}..." echo "Restarting ${container}..."
C_REST_OUT=$(curl -X POST --insecure https://dockerapi/containers/${container}/restart --silent | jq -r '.msg') C_REST_OUT=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${container}/restart --silent | jq -r '.msg')
echo "${C_REST_OUT}" echo "${C_REST_OUT}"
done done
} }
+1 -1
View File
@@ -1,6 +1,6 @@
FROM alpine:3.20 FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
RUN apk upgrade --no-cache \ RUN apk upgrade --no-cache \
&& apk add --update --no-cache \ && apk add --update --no-cache \
+2 -2
View File
@@ -1,6 +1,6 @@
FROM alpine:3.20 FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
ARG PIP_BREAK_SYSTEM_PACKAGES=1 ARG PIP_BREAK_SYSTEM_PACKAGES=1
WORKDIR /app WORKDIR /app
@@ -24,4 +24,4 @@ COPY main.py /app/main.py
COPY modules/ /app/modules/ COPY modules/ /app/modules/
ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"] ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"]
CMD exec python main.py CMD ["python", "main.py"]
+4 -4
View File
@@ -90,7 +90,7 @@ async def get_container(container_id : str):
if container._id == container_id: if container._id == container_id:
container_info = await container.show() container_info = await container.show()
return Response(content=json.dumps(container_info, indent=4), media_type="application/json") return Response(content=json.dumps(container_info, indent=4), media_type="application/json")
res = { res = {
"type": "danger", "type": "danger",
"msg": "no container found" "msg": "no container found"
@@ -130,7 +130,7 @@ async def get_containers():
async def post_containers(container_id : str, post_action : str, request: Request): async def post_containers(container_id : str, post_action : str, request: Request):
global dockerapi global dockerapi
try : try:
request_json = await request.json() request_json = await request.json()
except Exception as err: except Exception as err:
request_json = {} request_json = {}
@@ -191,7 +191,7 @@ async def post_container_update_stats(container_id : str):
stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats')) stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats'))
return Response(content=json.dumps(stats, indent=4), media_type="application/json") return Response(content=json.dumps(stats, indent=4), media_type="application/json")
# PubSub Handler # PubSub Handler
async def handle_pubsub_messages(channel: aioredis.client.PubSub): async def handle_pubsub_messages(channel: aioredis.client.PubSub):
@@ -244,7 +244,7 @@ async def handle_pubsub_messages(channel: aioredis.client.PubSub):
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json)) dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
else: else:
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json)) dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
await asyncio.sleep(0.0) await asyncio.sleep(0.0)
except asyncio.TimeoutError: except asyncio.TimeoutError:
pass pass
+142 -3
View File
@@ -159,7 +159,7 @@ class DockerApi:
postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
# todo: check each exit code # todo: check each exit code
res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'} res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'}
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: mailq - task: list # api call: container_post - post_action: exec - cmd: mailq - task: list
def container_post__exec__mailq__list(self, request_json, **kwargs): def container_post__exec__mailq__list(self, request_json, **kwargs):
if 'container_id' in kwargs: if 'container_id' in kwargs:
@@ -318,7 +318,7 @@ class DockerApi:
if 'username' in request_json and 'script_name' in request_json: if 'username' in request_json and 'script_name' in request_json:
for container in self.sync_docker_client.containers.list(filters=filters): for container in self.sync_docker_client.containers.list(filters=filters):
cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"] cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
sieve_return = container.exec_run(cmd) sieve_return = container.exec_run(cmd)
return self.exec_run_handler('utf8_text_only', sieve_return) return self.exec_run_handler('utf8_text_only', sieve_return)
# api call: container_post - post_action: exec - cmd: maildir - task: cleanup # api call: container_post - post_action: exec - cmd: maildir - task: cleanup
@@ -342,6 +342,30 @@ class DockerApi:
cmd = ["/bin/bash", "-c", cmd_vmail] cmd = ["/bin/bash", "-c", cmd_vmail]
maildir_cleanup = container.exec_run(cmd, user='vmail') maildir_cleanup = container.exec_run(cmd, user='vmail')
return self.exec_run_handler('generic', maildir_cleanup) return self.exec_run_handler('generic', maildir_cleanup)
# api call: container_post - post_action: exec - cmd: maildir - task: move
def container_post__exec__maildir__move(self, request_json, **kwargs):
if 'container_id' in kwargs:
filters = {"id": kwargs['container_id']}
elif 'container_name' in kwargs:
filters = {"name": kwargs['container_name']}
if 'old_maildir' in request_json and 'new_maildir' in request_json:
for container in self.sync_docker_client.containers.list(filters=filters):
vmail_name = request_json['old_maildir'].replace("'", "'\\''")
new_vmail_name = request_json['new_maildir'].replace("'", "'\\''")
cmd_vmail = f"if [[ -d '/var/vmail/{vmail_name}' ]]; then /bin/mv '/var/vmail/{vmail_name}' '/var/vmail/{new_vmail_name}'; fi"
index_name = request_json['old_maildir'].split("/")
new_index_name = request_json['new_maildir'].split("/")
if len(index_name) > 1 and len(new_index_name) > 1:
index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''")
new_index_name = new_index_name[1].replace("'", "'\\''") + "@" + new_index_name[0].replace("'", "'\\''")
cmd_vmail_index = f"if [[ -d '/var/vmail_index/{index_name}' ]]; then /bin/mv '/var/vmail_index/{index_name}' '/var/vmail_index/{new_index_name}_index'; fi"
cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index]
else:
cmd = ["/bin/bash", "-c", cmd_vmail]
maildir_move = container.exec_run(cmd, user='vmail')
return self.exec_run_handler('generic', maildir_move)
# api call: container_post - post_action: exec - cmd: rspamd - task: worker_password # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
def container_post__exec__rspamd__worker_password(self, request_json, **kwargs): def container_post__exec__rspamd__worker_password(self, request_json, **kwargs):
if 'container_id' in kwargs: if 'container_id' in kwargs:
@@ -374,6 +398,121 @@ class DockerApi:
self.logger.error('failed changing Rspamd password') self.logger.error('failed changing Rspamd password')
res = { 'type': 'danger', 'msg': 'command did not complete' } res = { 'type': 'danger', 'msg': 'command did not complete' }
return Response(content=json.dumps(res, indent=4), media_type="application/json") return Response(content=json.dumps(res, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: sogo - task: rename
def container_post__exec__sogo__rename_user(self, request_json, **kwargs):
if 'container_id' in kwargs:
filters = {"id": kwargs['container_id']}
elif 'container_name' in kwargs:
filters = {"name": kwargs['container_name']}
if 'old_username' in request_json and 'new_username' in request_json:
for container in self.sync_docker_client.containers.list(filters=filters):
old_username = request_json['old_username'].replace("'", "'\\''")
new_username = request_json['new_username'].replace("'", "'\\''")
sogo_return = container.exec_run(["/bin/bash", "-c", f"sogo-tool rename-user '{old_username}' '{new_username}'"], user='sogo')
return self.exec_run_handler('generic', sogo_return)
# api call: container_post - post_action: exec - cmd: doveadm - task: get_acl
def container_post__exec__doveadm__get_acl(self, request_json, **kwargs):
if 'container_id' in kwargs:
filters = {"id": kwargs['container_id']}
elif 'container_name' in kwargs:
filters = {"name": kwargs['container_name']}
for container in self.sync_docker_client.containers.list(filters=filters):
id = request_json['id'].replace("'", "'\\''")
shared_folders = container.exec_run(["/bin/bash", "-c", f"doveadm mailbox list -u '{id}'"])
shared_folders = shared_folders.output.decode('utf-8')
shared_folders = shared_folders.splitlines()
formatted_acls = []
mailbox_seen = []
for shared_folder in shared_folders:
if "Shared" not in shared_folder:
mailbox = shared_folder.replace("'", "'\\''")
if mailbox in mailbox_seen:
continue
acls = container.exec_run(["/bin/bash", "-c", f"doveadm acl get -u '{id}' '{mailbox}'"])
acls = acls.output.decode('utf-8').strip().splitlines()
if len(acls) >= 2:
for acl in acls[1:]:
user_id, rights = acl.split(maxsplit=1)
user_id = user_id.split('=')[1]
mailbox_seen.append(mailbox)
formatted_acls.append({ 'user': id, 'id': user_id, 'mailbox': mailbox, 'rights': rights.split() })
elif "Shared" in shared_folder and "/" in shared_folder:
shared_folder = shared_folder.split("/")
if len(shared_folder) < 3:
continue
user = shared_folder[1].replace("'", "'\\''")
mailbox = '/'.join(shared_folder[2:]).replace("'", "'\\''")
if mailbox in mailbox_seen:
continue
acls = container.exec_run(["/bin/bash", "-c", f"doveadm acl get -u '{user}' '{mailbox}'"])
acls = acls.output.decode('utf-8').strip().splitlines()
if len(acls) >= 2:
for acl in acls[1:]:
user_id, rights = acl.split(maxsplit=1)
user_id = user_id.split('=')[1].replace("'", "'\\''")
if user_id == id and mailbox not in mailbox_seen:
mailbox_seen.append(mailbox)
formatted_acls.append({ 'user': user, 'id': id, 'mailbox': mailbox, 'rights': rights.split() })
return Response(content=json.dumps(formatted_acls, indent=4), media_type="application/json")
# api call: container_post - post_action: exec - cmd: doveadm - task: delete_acl
def container_post__exec__doveadm__delete_acl(self, request_json, **kwargs):
if 'container_id' in kwargs:
filters = {"id": kwargs['container_id']}
elif 'container_name' in kwargs:
filters = {"name": kwargs['container_name']}
for container in self.sync_docker_client.containers.list(filters=filters):
user = request_json['user'].replace("'", "'\\''")
mailbox = request_json['mailbox'].replace("'", "'\\''")
id = request_json['id'].replace("'", "'\\''")
if user and mailbox and id:
acl_delete_return = container.exec_run(["/bin/bash", "-c", f"doveadm acl delete -u '{user}' '{mailbox}' 'user={id}'"])
return self.exec_run_handler('generic', acl_delete_return)
# api call: container_post - post_action: exec - cmd: doveadm - task: set_acl
def container_post__exec__doveadm__set_acl(self, request_json, **kwargs):
if 'container_id' in kwargs:
filters = {"id": kwargs['container_id']}
elif 'container_name' in kwargs:
filters = {"name": kwargs['container_name']}
for container in self.sync_docker_client.containers.list(filters=filters):
user = request_json['user'].replace("'", "'\\''")
mailbox = request_json['mailbox'].replace("'", "'\\''")
id = request_json['id'].replace("'", "'\\''")
rights = ""
available_rights = [
"admin",
"create",
"delete",
"expunge",
"insert",
"lookup",
"post",
"read",
"write",
"write-deleted",
"write-seen"
]
for right in request_json['rights']:
right = right.replace("'", "'\\''").lower()
if right in available_rights:
rights += right + " "
if user and mailbox and id and rights:
acl_set_return = container.exec_run(["/bin/bash", "-c", f"doveadm acl set -u '{user}' '{mailbox}' 'user={id}' {rights}"])
return self.exec_run_handler('generic', acl_set_return)
# Collect host stats # Collect host stats
async def get_host_stats(self, wait=5): async def get_host_stats(self, wait=5):
@@ -462,7 +601,7 @@ class DockerApi:
except: except:
pass pass
return ''.join(total_data) return ''.join(total_data)
try : try :
socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock
if not cmd.endswith("\n"): if not cmd.endswith("\n"):
+5 -4
View File
@@ -1,11 +1,12 @@
FROM alpine:3.20 FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$ # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
ARG GOSU_VERSION=1.16 ARG GOSU_VERSION=1.16
ENV LANG C.UTF-8 ENV LANG=C.UTF-8
ENV LC_ALL C.UTF-8 ENV LC_ALL=C.UTF-8
# Add groups and users before installing Dovecot to not break compatibility # Add groups and users before installing Dovecot to not break compatibility
RUN addgroup -g 5000 vmail \ RUN addgroup -g 5000 vmail \
@@ -132,4 +133,4 @@ COPY repl_health.sh /usr/local/bin/repl_health.sh
COPY optimize-fts.sh /usr/local/bin/optimize-fts.sh COPY optimize-fts.sh /usr/local/bin/optimize-fts.sh
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
+22 -13
View File
@@ -2,7 +2,7 @@
set -e set -e
# Wait for MySQL to warm-up # Wait for MySQL to warm-up
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for database to come up..." echo "Waiting for database to come up..."
sleep 2 sleep 2
done done
@@ -114,15 +114,15 @@ if [[ "${FLATCURVE_EXPERIMENTAL}" =~ ^([yY][eE][sS]|[yY]) ]]; then
echo -e "\e[33mActivating Flatcurve as FTS Backend...\e[0m" echo -e "\e[33mActivating Flatcurve as FTS Backend...\e[0m"
echo -e "\e[33mDepending on your previous setup a full reindex might be needed... \e[0m" echo -e "\e[33mDepending on your previous setup a full reindex might be needed... \e[0m"
echo -e "\e[34mVisit https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-fts/#fts-related-dovecot-commands to learn how to reindex\e[0m" echo -e "\e[34mVisit https://docs.mailcow.email/manual-guides/Dovecot/u_e-dovecot-fts/#fts-related-dovecot-commands to learn how to reindex\e[0m"
echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication lazy_expunge' > /etc/dovecot/mail_plugins
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins_imap echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins_imap
echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_flatcurve notify listescape replication' > /etc/dovecot/mail_plugins_lmtp echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_flatcurve notify listescape replication' > /etc/dovecot/mail_plugins_lmtp
elif [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then elif [[ "${SKIP_SOLR}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication' > /etc/dovecot/mail_plugins echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication lazy_expunge' > /etc/dovecot/mail_plugins
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify listescape replication mail_log' > /etc/dovecot/mail_plugins_imap echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify listescape replication mail_log' > /etc/dovecot/mail_plugins_imap
echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl notify listescape replication' > /etc/dovecot/mail_plugins_lmtp echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl notify listescape replication' > /etc/dovecot/mail_plugins_lmtp
else else
echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_solr listescape replication' > /etc/dovecot/mail_plugins echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_solr listescape replication lazy_expunge' > /etc/dovecot/mail_plugins
echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_solr listescape replication' > /etc/dovecot/mail_plugins_imap echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_solr listescape replication' > /etc/dovecot/mail_plugins_imap
echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_solr notify listescape replication' > /etc/dovecot/mail_plugins_lmtp echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_solr notify listescape replication' > /etc/dovecot/mail_plugins_lmtp
fi fi
@@ -257,10 +257,14 @@ plugin {
fts_autoindex_exclude2 = \Trash fts_autoindex_exclude2 = \Trash
fts = flatcurve fts = flatcurve
# Maximum term length can be set via the 'maxlen' argument (maxlen is
# specified in bytes, not number of UTF-8 characters)
fts_tokenizer_email_address = maxlen=100
fts_tokenizer_generic = algorithm=simple maxlen=30
# These are not flatcurve settings, but required for Dovecot FTS. See # These are not flatcurve settings, but required for Dovecot FTS. See
# Dovecot FTS Configuration link above for further information. # Dovecot FTS Configuration link above for further information.
fts_languages = en es de fts_languages = en es de
fts_tokenizer_generic = algorithm=simple
fts_tokenizers = generic email-address fts_tokenizers = generic email-address
# OPTIONAL: Recommended default FTS core configuration # OPTIONAL: Recommended default FTS core configuration
@@ -367,6 +371,8 @@ EOF
# Create random master Password for SOGo SSO # Create random master Password for SOGo SSO
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1) RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
# Creating additional creds file for SOGo notify crons (calendars, etc)
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
cat <<EOF > /etc/dovecot/sogo-sso.conf cat <<EOF > /etc/dovecot/sogo-sso.conf
# Autogenerated by mailcow # Autogenerated by mailcow
passdb { passdb {
@@ -401,20 +407,23 @@ else
chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem
fi fi
# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20)
if grep -qE 'ssl_min_protocol\s*=\s*(TLSv1|TLSv1\.1)\s*$' /etc/dovecot/dovecot.conf /etc/dovecot/extra.conf; then
sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf
echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf
echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf
echo "[tls_system_default]" >> /etc/ssl/openssl.cnf
echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf
echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf
fi
# Compile sieve scripts # Compile sieve scripts
sievec /var/vmail/sieve/global_sieve_before.sieve sievec /var/vmail/sieve/global_sieve_before.sieve
sievec /var/vmail/sieve/global_sieve_after.sieve sievec /var/vmail/sieve/global_sieve_after.sieve
sievec /usr/lib/dovecot/sieve/report-spam.sieve sievec /usr/lib/dovecot/sieve/report-spam.sieve
sievec /usr/lib/dovecot/sieve/report-ham.sieve sievec /usr/lib/dovecot/sieve/report-ham.sieve
for file in /var/vmail/*/*/sieve/*.sieve ; do
if [[ "$file" == "/var/vmail/*/*/sieve/*.sieve" ]]; then
continue
fi
sievec "$file" "$(dirname "$file")/../.dovecot.svbin"
chown vmail:vmail "$(dirname "$file")/../.dovecot.svbin"
done
# Fix permissions # Fix permissions
chown root:root /etc/dovecot/sql/*.conf 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 chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/lua/passwd-verify.lua
+3 -3
View File
@@ -3,8 +3,8 @@ FILE=/tmp/mail$$
cat > $FILE cat > $FILE
trap "/bin/rm -f $FILE" 0 1 2 3 13 15 trap "/bin/rm -f $FILE" 0 1 2 3 13 15
cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzydel cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzydel
cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnham cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/learnham
cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzyadd
exit 0 exit 0
+3 -3
View File
@@ -3,8 +3,8 @@ FILE=/tmp/mail$$
cat > $FILE cat > $FILE
trap "/bin/rm -f $FILE" 0 1 2 3 13 15 trap "/bin/rm -f $FILE" 0 1 2 3 13 15
cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzydel cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzydel
cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnspam cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/learnspam
cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzyadd
exit 0 exit 0
+10 -6
View File
@@ -11,21 +11,25 @@ else
fi fi
# Deploy # Deploy
curl --connect-timeout 15 --retry 10 --max-time 30 https://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"' | tr -dc '0-9').tar.gz --output /tmp/sa-rules-heinlein.tar.gz if curl --connect-timeout 15 --retry 10 --max-time 30 https://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"' | tr -dc '0-9').tar.gz --output /tmp/sa-rules-heinlein.tar.gz; then
if gzip -t /tmp/sa-rules-heinlein.tar.gz; then if gzip -t /tmp/sa-rules-heinlein.tar.gz; then
tar xfvz /tmp/sa-rules-heinlein.tar.gz -C /tmp/sa-rules-heinlein tar xfvz /tmp/sa-rules-heinlein.tar.gz -C /tmp/sa-rules-heinlein
cat /tmp/sa-rules-heinlein/*cf > /etc/rspamd/custom/sa-rules cat /tmp/sa-rules-heinlein/*cf > /etc/rspamd/custom/sa-rules
fi
else
echo "Failed to download SA rules. Exiting."
exit 0 # Must be 0 otherwise dovecot would not start at all
fi fi
sed -i -e 's/\([^\\]\)\$\([^\/]\)/\1\\$\2/g' /etc/rspamd/custom/sa-rules sed -i -e 's/\([^\\]\)\$\([^\/]\)/\1\\$\2/g' /etc/rspamd/custom/sa-rules
if [[ "$(cat /etc/rspamd/custom/sa-rules | md5sum | cut -d' ' -f1)" != "${HASH_SA_RULES}" ]]; then if [[ "$(cat /etc/rspamd/custom/sa-rules | md5sum | cut -d' ' -f1)" != "${HASH_SA_RULES}" ]]; then
CONTAINER_NAME=rspamd-mailcow CONTAINER_NAME=rspamd-mailcow
CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | \ CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | \
jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | \ jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | \
jq -rc "select( .name | tostring | contains(\"${CONTAINER_NAME}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id") jq -rc "select( .name | tostring | contains(\"${CONTAINER_NAME}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id")
if [[ ! -z ${CONTAINER_ID} ]]; then if [[ ! -z ${CONTAINER_ID} ]]; then
curl --silent --insecure -XPOST --connect-timeout 15 --max-time 120 https://dockerapi/containers/${CONTAINER_ID}/restart curl --silent --insecure -XPOST --connect-timeout 15 --max-time 120 https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/restart
fi fi
fi fi
+2 -1
View File
@@ -1,5 +1,6 @@
FROM alpine:3.20 FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
WORKDIR /app WORKDIR /app
+2 -1
View File
@@ -1,5 +1,6 @@
FROM alpine:3.20 FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
ARG PIP_BREAK_SYSTEM_PACKAGES=1 ARG PIP_BREAK_SYSTEM_PACKAGES=1
WORKDIR /app WORKDIR /app
+61 -54
View File
@@ -1,64 +1,68 @@
FROM php:8.2-fpm-bookworm FROM php:8.2-fpm-alpine3.20
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
# renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$ # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG APCU_PECL_VERSION=5.1.23 ARG APCU_PECL_VERSION=5.1.24
# renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
ARG IMAGICK_PECL_VERSION=3.7.0 ARG IMAGICK_PECL_VERSION=3.7.0
# renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$ # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG MAILPARSE_PECL_VERSION=3.1.6 ARG MAILPARSE_PECL_VERSION=3.1.8
# renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$ # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
ARG MEMCACHED_PECL_VERSION=3.2.0 ARG MEMCACHED_PECL_VERSION=3.2.0
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
ARG REDIS_PECL_VERSION=6.0.2 ARG REDIS_PECL_VERSION=6.1.0
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$ # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
ARG COMPOSER_VERSION=2.6.6 ARG COMPOSER_VERSION=2.6.6
RUN apt-get update && apt-get install --no-install-recommends -y \ RUN apk add -U --no-cache autoconf \
aspell \ aspell-dev \
aspell-en \ aspell-libs \
autoconf \
bash \ bash \
default-mysql-client \ c-client \
dnsutils \ cyrus-sasl-dev \
freetype \
freetype-dev \
g++ \ g++ \
gettext \
git \ git \
gettext \
gettext-dev \
gmp-dev \
gnupg \ gnupg \
icu-dev \
icu-libs \
imagemagick \ imagemagick \
imagemagick-dev \
imap-dev \
jq \ jq \
libc-client-dev \ libavif \
libc-client2007e \ libavif-dev \
libfreetype6-dev \ libjpeg-turbo \
libgettextpo-dev \ libjpeg-turbo-dev \
libgmp-dev \ libmemcached \
libicu-dev \
libjpeg62-turbo-dev \
libkrb5-3 \
libkrb5-dev \
libldap2-dev \
libmagickcore-dev \
libmagickwand-dev \
libmemcached-dev \ libmemcached-dev \
libmemcached11 \ libpng \
libpcre3-dev \
libpng-dev \ libpng-dev \
libpspell-dev \ libressl \
librsvg2-dev \ libressl-dev \
libsasl2-dev \ librsvg \
libssl-dev \ libtool \
libwebp-dev \ libwebp-dev \
libxml2-dev \ libxml2-dev \
libxpm \
libxpm-dev \ libxpm-dev \
libxpm4 \ libzip \
libzip-dev \ libzip-dev \
libzip4 \ linux-headers \
make \ make \
mysql-client \
openldap-dev \
pcre-dev \
re2c \ re2c \
redis-tools \ redis \
smbclient \ samba-client \
zlib-dev \
tzdata \ tzdata \
zlib1g-dev \
&& pecl install APCu-${APCU_PECL_VERSION} \ && pecl install APCu-${APCU_PECL_VERSION} \
&& pecl install imagick-${IMAGICK_PECL_VERSION} \ && pecl install imagick-${IMAGICK_PECL_VERSION} \
&& pecl install mailparse-${MAILPARSE_PECL_VERSION} \ && pecl install mailparse-${MAILPARSE_PECL_VERSION} \
@@ -68,37 +72,40 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
&& pecl clear-cache \ && pecl clear-cache \
&& docker-php-ext-configure intl \ && docker-php-ext-configure intl \
&& docker-php-ext-configure exif \ && docker-php-ext-configure exif \
&& docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp --with-xpm \ && docker-php-ext-configure gd --with-freetype=/usr/include/ \
--with-jpeg=/usr/include/ \
--with-webp \
--with-xpm \
--with-avif \
&& docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets sysvsem zip bcmath gmp \ && docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets sysvsem zip bcmath gmp \
&& docker-php-ext-configure imap --with-imap --with-imap-ssl --with-kerberos \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \
&& docker-php-ext-install -j 4 imap \ && docker-php-ext-install -j 4 imap \
&& curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \ && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \
&& mv composer.phar /usr/local/bin/composer \ && mv composer.phar /usr/local/bin/composer \
&& chmod +x /usr/local/bin/composer \ && chmod +x /usr/local/bin/composer \
&& apt-get purge -y --auto-remove \ && apk del --purge autoconf \
autoconf \ aspell-dev \
cyrus-sasl-dev \
freetype-dev \
g++ \ g++ \
libc-client-dev \ gettext-dev \
libfreetype6-dev \ icu-dev \
libgettextpo-dev \ imagemagick-dev \
libicu-dev \ imap-dev \
libjpeg62-turbo-dev \ libavif-dev \
libkrb5-dev \ libjpeg-turbo-dev \
libldap2-dev \
libmagickcore-dev \
libmagickwand-dev \
libmemcached-dev \ libmemcached-dev \
libpcre3-dev \
libpng-dev \ libpng-dev \
libpspell-dev \ libressl-dev \
libsasl2-dev \
libssl-dev \
libwebp-dev \ libwebp-dev \
libxml2-dev \ libxml2-dev \
libxpm-dev \ libxpm-dev \
libzip-dev \ libzip-dev \
linux-headers \
make \ make \
zlib1g-dev openldap-dev \
pcre-dev \
zlib-dev
COPY ./docker-entrypoint.sh / COPY ./docker-entrypoint.sh /
+20 -11
View File
@@ -3,27 +3,36 @@
function array_by_comma { local IFS=","; echo "$*"; } function array_by_comma { local IFS=","; echo "$*"; }
# Wait for containers # Wait for containers
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for SQL..." echo "Waiting for SQL..."
sleep 2 sleep 2
done done
# Do not attempt to write to slave # Do not attempt to write to slave
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT}" REDIS_HOST=$REDIS_SLAVEOF_IP
REDIS_PORT=$REDIS_SLAVEOF_PORT
else else
REDIS_CMDLINE="redis-cli -h redis -p 6379" REDIS_HOST="redis"
REDIS_PORT="6379"
fi fi
REDIS_CMDLINE="redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT}"
until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do
echo "Waiting for Redis..." echo "Waiting for Redis..."
sleep 2 sleep 2
done done
# Set redis session store
echo -n '
session.save_handler = redis
session.save_path = "tcp://'${REDIS_HOST}':'${REDIS_PORT}'"
' > /usr/local/etc/php/conf.d/session_store.ini
# Check mysql_upgrade (master and slave) # Check mysql_upgrade (master and slave)
CONTAINER_ID= CONTAINER_ID=
until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do
CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null) CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null)
echo "Could not get mysql-mailcow container id... trying again" echo "Could not get mysql-mailcow container id... trying again"
sleep 2 sleep 2
done done
@@ -35,7 +44,7 @@ until [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; do
echo "Tried to upgrade MySQL and failed, giving up after ${SQL_LOOP_C} retries and starting container (oops, not good)" echo "Tried to upgrade MySQL and failed, giving up after ${SQL_LOOP_C} retries and starting container (oops, not good)"
break break
fi fi
SQL_FULL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json') SQL_FULL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json')
SQL_UPGRADE_STATUS=$(echo ${SQL_FULL_UPGRADE_RETURN} | jq -r .type) SQL_UPGRADE_STATUS=$(echo ${SQL_FULL_UPGRADE_RETURN} | jq -r .type)
SQL_LOOP_C=$((SQL_LOOP_C+1)) SQL_LOOP_C=$((SQL_LOOP_C+1))
echo "SQL upgrade iteration #${SQL_LOOP_C}" echo "SQL upgrade iteration #${SQL_LOOP_C}"
@@ -44,7 +53,7 @@ until [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; do
echo "MySQL applied an upgrade, debug output:" echo "MySQL applied an upgrade, debug output:"
echo ${SQL_FULL_UPGRADE_RETURN} echo ${SQL_FULL_UPGRADE_RETURN}
sleep 3 sleep 3
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for SQL to return, please wait" echo "Waiting for SQL to return, please wait"
sleep 2 sleep 2
done done
@@ -60,12 +69,12 @@ done
# doing post-installation stuff, if SQL was upgraded (master and slave) # doing post-installation stuff, if SQL was upgraded (master and slave)
if [ ${SQL_CHANGED} -eq 1 ]; then if [ ${SQL_CHANGED} -eq 1 ]; then
POSTFIX=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null) POSTFIX=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null)
if [[ -z "${POSTFIX}" ]] || ! [[ "${POSTFIX}" =~ ^[[:alnum:]]*$ ]]; then if [[ -z "${POSTFIX}" ]] || ! [[ "${POSTFIX}" =~ ^[[:alnum:]]*$ ]]; then
echo "Could not determine Postfix container ID, skipping Postfix restart." echo "Could not determine Postfix container ID, skipping Postfix restart."
else else
echo "Restarting Postfix" echo "Restarting Postfix"
curl -X POST --silent --insecure https://dockerapi/containers/${POSTFIX}/restart | jq -r '.msg' curl -X POST --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/restart | jq -r '.msg'
echo "Sleeping 5 seconds..." echo "Sleeping 5 seconds..."
sleep 5 sleep 5
fi fi
@@ -74,7 +83,7 @@ fi
# Check mysql tz import (master and slave) # 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=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null)
if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then
SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json') SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json')
echo "MySQL mysql_tzinfo_to_sql - debug output:" echo "MySQL mysql_tzinfo_to_sql - debug output:"
echo ${SQL_FULL_TZINFO_IMPORT_RETURN} echo ${SQL_FULL_TZINFO_IMPORT_RETURN}
fi fi
@@ -198,10 +207,10 @@ fi
[[ ! -f /web/css/build/0081-custom-mailcow.css ]] && echo '/* Autogenerated by mailcow */' > /web/css/build/0081-custom-mailcow.css [[ ! -f /web/css/build/0081-custom-mailcow.css ]] && echo '/* Autogenerated by mailcow */' > /web/css/build/0081-custom-mailcow.css
# Fix permissions for global filters # Fix permissions for global filters
chown -R 33:33 /global_sieve/* chown -R 82:82 /global_sieve/*
# Fix permissions on twig cache folder # Fix permissions on twig cache folder
chown -R 33:33 /web/templates/cache chown -R 82:82 /web/templates/cache
# Clear cache # Clear cache
find /web/templates/cache/* -not -name '.gitkeep' -delete find /web/templates/cache/* -not -name '.gitkeep' -delete
+3 -2
View File
@@ -1,5 +1,6 @@
FROM debian:bookworm-slim FROM debian:bookworm-slim
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ENV LC_ALL C ENV LC_ALL C
@@ -59,4 +60,4 @@ EXPOSE 588
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
@@ -12,4 +12,15 @@ if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
fi fi
# Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20)
if grep -qE '\!SSLv2|\!SSLv3|>=TLSv1(\.[0-1])?$' /opt/postfix/conf/main.cf /opt/postfix/conf/extra.cf; then
sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf
echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf
echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf
echo "[tls_system_default]" >> /etc/ssl/openssl.cnf
echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf
echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf
fi
exec "$@" exec "$@"
+1 -1
View File
@@ -5,7 +5,7 @@ trap "postfix stop" EXIT
[[ ! -d /opt/postfix/conf/sql/ ]] && mkdir -p /opt/postfix/conf/sql/ [[ ! -d /opt/postfix/conf/sql/ ]] && mkdir -p /opt/postfix/conf/sql/
# Wait for MySQL to warm-up # Wait for MySQL to warm-up
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for database to come up..." echo "Waiting for database to come up..."
sleep 2 sleep 2
done done
+7 -6
View File
@@ -1,10 +1,10 @@
FROM debian:bullseye-slim FROM debian:bookworm-slim
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG RSPAMD_VER=rspamd_3.7.5-2~8c86c1676 ARG RSPAMD_VER=rspamd_3.10.2-1~b8a232043
ARG CODENAME=bullseye ARG CODENAME=bookworm
ENV LC_ALL C ENV LC_ALL=C
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
tzdata \ tzdata \
@@ -12,11 +12,12 @@ RUN apt-get update && apt-get install -y \
gnupg2 \ gnupg2 \
apt-transport-https \ apt-transport-https \
dnsutils \ dnsutils \
netcat \ netcat-traditional \
wget \ wget \
redis-tools \ redis-tools \
procps \ procps \
nano \ nano \
lua-cjson \
&& arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ && arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \
&& wget -P /tmp https://rspamd.com/apt-stable/pool/main/r/rspamd/${RSPAMD_VER}~${CODENAME}_${arch}.deb\ && wget -P /tmp https://rspamd.com/apt-stable/pool/main/r/rspamd/${RSPAMD_VER}~${CODENAME}_${arch}.deb\
&& apt install -y /tmp/${RSPAMD_VER}~${CODENAME}_${arch}.deb \ && apt install -y /tmp/${RSPAMD_VER}~${CODENAME}_${arch}.deb \
@@ -124,4 +124,190 @@ for file in /hooks/*; do
fi fi
done done
# If DQS KEY is set in mailcow.conf add Spamhaus DQS RBLs
if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then
cat <<EOF > /etc/rspamd/custom/dqs-rbl.conf
# Autogenerated by mailcow. DO NOT TOUCH!
spamhaus {
rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
from = false;
}
spamhaus_from {
from = true;
received = false;
rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
returncodes {
SPAMHAUS_ZEN = [ "127.0.0.2", "127.0.0.3", "127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7", "127.0.0.9", "127.0.0.10", "127.0.0.11" ];
}
}
spamhaus_authbl_received {
# Check if the sender client is listed in AuthBL (AuthBL is *not* part of ZEN)
rbl = "${SPAMHAUS_DQS_KEY}.authbl.dq.spamhaus.net";
from = false;
received = true;
ipv6 = true;
returncodes {
SH_AUTHBL_RECEIVED = "127.0.0.20"
}
}
spamhaus_dbl {
# Add checks on the HELO string
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
helo = true;
rdns = true;
dkim = true;
disable_monitoring = true;
returncodes {
RBL_DBL_SPAM = "127.0.1.2";
RBL_DBL_PHISH = "127.0.1.4";
RBL_DBL_MALWARE = "127.0.1.5";
RBL_DBL_BOTNET = "127.0.1.6";
RBL_DBL_ABUSED_SPAM = "127.0.1.102";
RBL_DBL_ABUSED_PHISH = "127.0.1.104";
RBL_DBL_ABUSED_MALWARE = "127.0.1.105";
RBL_DBL_ABUSED_BOTNET = "127.0.1.106";
RBL_DBL_DONT_QUERY_IPS = "127.0.1.255";
}
}
spamhaus_dbl_fullurls {
ignore_defaults = true;
no_ip = true;
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
selector = 'urls:get_host'
disable_monitoring = true;
returncodes {
DBLABUSED_SPAM_FULLURLS = "127.0.1.102";
DBLABUSED_PHISH_FULLURLS = "127.0.1.104";
DBLABUSED_MALWARE_FULLURLS = "127.0.1.105";
DBLABUSED_BOTNET_FULLURLS = "127.0.1.106";
}
}
spamhaus_zrd {
# Add checks on the HELO string also for DQS
rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
helo = true;
rdns = true;
dkim = true;
disable_monitoring = true;
returncodes {
RBL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
RBL_ZRD_FRESH_DOMAIN = [
"127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
];
RBL_ZRD_DONT_QUERY_IPS = "127.0.2.255";
}
}
"SPAMHAUS_ZEN_URIBL" {
enabled = true;
rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net";
resolve_ip = true;
checks = ['urls'];
replyto = true;
emails = true;
ipv4 = true;
ipv6 = true;
emails_domainonly = true;
returncodes {
URIBL_SBL = "127.0.0.2";
URIBL_SBL_CSS = "127.0.0.3";
URIBL_XBL = ["127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7"];
URIBL_PBL = ["127.0.0.10", "127.0.0.11"];
URIBL_DROP = "127.0.0.9";
}
}
SH_EMAIL_DBL {
ignore_defaults = true;
replyto = true;
emails_domainonly = true;
disable_monitoring = true;
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
returncodes = {
SH_EMAIL_DBL = [
"127.0.1.2",
"127.0.1.4",
"127.0.1.5",
"127.0.1.6"
];
SH_EMAIL_DBL_ABUSED = [
"127.0.1.102",
"127.0.1.104",
"127.0.1.105",
"127.0.1.106"
];
SH_EMAIL_DBL_DONT_QUERY_IPS = [ "127.0.1.255" ];
}
}
SH_EMAIL_ZRD {
ignore_defaults = true;
replyto = true;
emails_domainonly = true;
disable_monitoring = true;
rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
returncodes = {
SH_EMAIL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
SH_EMAIL_ZRD_FRESH_DOMAIN = [
"127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"
];
SH_EMAIL_ZRD_DONT_QUERY_IPS = [ "127.0.2.255" ];
}
}
"DBL" {
# override the defaults for DBL defined in modules.d/rbl.conf
rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net";
disable_monitoring = true;
}
"ZRD" {
ignore_defaults = true;
rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net";
no_ip = true;
dkim = true;
emails = true;
emails_domainonly = true;
urls = true;
returncodes = {
ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"];
ZRD_FRESH_DOMAIN = ["127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"];
}
}
spamhaus_sbl_url {
ignore_defaults = true
rbl = "${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net";
checks = ['urls'];
disable_monitoring = true;
returncodes {
SPAMHAUS_SBL_URL = "127.0.0.2";
}
}
SH_HBL_EMAIL {
ignore_defaults = true;
rbl = "_email.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net";
emails_domainonly = false;
selector = "from('smtp').lower;from('mime').lower";
ignore_whitelist = true;
checks = ['emails', 'replyto'];
hash = "sha1";
returncodes = {
SH_HBL_EMAIL = [
"127.0.3.2"
];
}
}
spamhaus_dqs_hbl {
symbol = "HBL_FILE_UNKNOWN";
rbl = "_file.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net.";
selector = "attachments('rbase32', 'sha256')";
ignore_whitelist = true;
ignore_defaults = true;
returncodes {
SH_HBL_FILE_MALICIOUS = "127.0.3.10";
SH_HBL_FILE_SUSPICIOUS = "127.0.3.15";
}
}
EOF
else
rm -rf /etc/rspamd/custom/dqs-rbl.conf
fi
exec "$@" exec "$@"
+9 -7
View File
@@ -1,12 +1,13 @@
FROM debian:bullseye-slim FROM debian:bookworm-slim
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG DEBIAN_VERSION=bullseye ARG DEBIAN_VERSION=bookworm
ARG SOGO_DEBIAN_REPOSITORY=http://www.axis.cz/linux/debian ARG SOGO_DEBIAN_REPOSITORY=http://www.axis.cz/linux/debian
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$ # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
ARG GOSU_VERSION=1.17 ARG GOSU_VERSION=1.17
ENV LC_ALL C ENV LC_ALL=C
# Prerequisites # Prerequisites
RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
@@ -32,13 +33,14 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
&& gosu nobody true \ && gosu nobody true \
&& mkdir /usr/share/doc/sogo \ && mkdir /usr/share/doc/sogo \
&& touch /usr/share/doc/sogo/empty.sh \ && touch /usr/share/doc/sogo/empty.sh \
&& apt-key adv --keyserver keys.openpgp.org --recv-key 74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 \ && 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 \ && echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} sogo-v5" > /etc/apt/sources.list.d/sogo.list \
&& apt-get update && apt-get install -y --no-install-recommends \ && apt-get update && apt-get install -y --no-install-recommends \
sogo \ sogo \
sogo-activesync \ sogo-activesync \
&& apt-get autoclean \ && apt-get autoclean \
&& rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/sogo.list \ && rm -rf /var/lib/apt/lists/* \
&& touch /etc/default/locale && touch /etc/default/locale
COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh
@@ -54,4 +56,4 @@ RUN chmod +x /bootstrap-sogo.sh \
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# Wait for MySQL to warm-up # Wait for MySQL to warm-up
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for database to come up..." echo "Waiting for database to come up..."
sleep 2 sleep 2
done done
@@ -10,6 +10,8 @@ if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
fi fi
echo "$TZ" > /etc/timezone
# Run hooks # Run hooks
for file in /hooks/*; do for file in /hooks/*; do
if [ -x "${file}" ]; then if [ -x "${file}" ]; then
@@ -1,4 +1,4 @@
@version: 3.28 @version: 3.38
@include "scl.conf" @include "scl.conf"
options { options {
chain_hostnames(off); chain_hostnames(off);
+1 -1
View File
@@ -1,4 +1,4 @@
@version: 3.28 @version: 3.38
@include "scl.conf" @include "scl.conf"
options { options {
chain_hostnames(off); chain_hostnames(off);
+12 -5
View File
@@ -1,18 +1,21 @@
FROM alpine:3.20 FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>" LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
RUN apk add --update --no-cache \ RUN apk add --update --no-cache \
curl \ curl \
bind-tools \ bind-tools \
coreutils \
unbound \ unbound \
bash \ bash \
openssl \ openssl \
drill \ drill \
tzdata \ tzdata \
syslog-ng \
supervisor \
&& curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \ && curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \
&& chown root:unbound /etc/unbound \ && chown root:unbound /etc/unbound \
&& adduser unbound tty \ && adduser unbound tty \
&& chmod 775 /etc/unbound && chmod 775 /etc/unbound
EXPOSE 53/udp 53/tcp EXPOSE 53/udp 53/tcp
@@ -21,9 +24,13 @@ COPY docker-entrypoint.sh /docker-entrypoint.sh
# healthcheck (dig, ping) # healthcheck (dig, ping)
COPY healthcheck.sh /healthcheck.sh COPY healthcheck.sh /healthcheck.sh
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
RUN chmod +x /healthcheck.sh RUN chmod +x /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=30s CMD [ "/healthcheck.sh" ] HEALTHCHECK --interval=30s --timeout=10s \
CMD sh -c '[ -f /tmp/healthcheck_status ] && [ "$(cat /tmp/healthcheck_status)" -eq 0 ] || exit 1'
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
CMD ["/usr/sbin/unbound"]
+77 -51
View File
@@ -1,76 +1,102 @@
#!/bin/bash #!/bin/bash
# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) STATUS_FILE="/tmp/healthcheck_status"
if [[ "${SKIP_UNBOUND_HEALTHCHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then RUNS=0
SKIP_UNBOUND_HEALTHCHECK=y
fi
# Reset logfile # Declare log function for logfile to stdout
echo "$(date +"%Y-%m-%d %H:%M:%S"): Starting health check - logs can be found in /var/log/healthcheck.log" function log_to_stdout() {
echo "$(date +"%Y-%m-%d %H:%M:%S"): Starting health check" > /var/log/healthcheck.log echo "$(date +"%Y-%m-%d %H:%M:%S"): $1"
# Declare log function for logfile inside container
function log_to_file() {
echo "$(date +"%Y-%m-%d %H:%M:%S"): $1" >> /var/log/healthcheck.log
} }
# General Ping function to check general pingability # General Ping function to check general pingability
function check_ping() { function check_ping() {
declare -a ipstoping=("1.1.1.1" "8.8.8.8" "9.9.9.9") declare -a ipstoping=("1.1.1.1" "8.8.8.8" "9.9.9.9")
local fail_tolerance=1
local failures=0
for ip in "${ipstoping[@]}" ; do for ip in "${ipstoping[@]}" ; do
ping -q -c 3 -w 5 "$ip" success=false
if [ $? -ne 0 ]; then for ((i=1; i<=3; i++)); do
log_to_file "Healthcheck: Couldn't ping $ip for 5 seconds... Gave up!" ping -q -c 3 -w 5 "$ip" > /dev/null
log_to_file "Please check your internet connection or firewall rules to fix this error, because a simple ping test should always go through from the unbound container!" if [ $? -eq 0 ]; then
return 1 success=true
fi break
else
log_to_stdout "Healthcheck: Failed to ping $ip on attempt $i. Trying again..."
fi
done done
if [ "$success" = false ]; then
log_to_stdout "Healthcheck: Couldn't ping $ip after 3 attempts. Marking this IP as failed."
((failures++))
fi
done
if [ $failures -gt $fail_tolerance ]; then
log_to_stdout "Healthcheck: Too many ping failures ($fail_tolerance failures allowed, you got $failures failures), marking Healthcheck as unhealthy..."
return 1
fi
return 0
log_to_file "Healthcheck: Ping Checks WORKING properly!"
return 0
} }
# General DNS Resolve Check against Unbound Resolver himself # General DNS Resolve Check against Unbound Resolver himself
function check_dns() { function check_dns() {
declare -a domains=("mailcow.email" "github.com" "hub.docker.com") declare -a domains=("fuzzy.mailcow.email" "github.com" "hub.docker.com")
local fail_tolerance=1
local failures=0
for domain in "${domains[@]}" ; do for domain in "${domains[@]}" ; do
for ((i=1; i<=3; i++)); do success=false
dig +short +timeout=2 +tries=1 "$domain" @127.0.0.1 > /dev/null for ((i=1; i<=3; i++)); do
if [ $? -ne 0 ]; then dig_output=$(dig +short +timeout=2 +tries=1 "$domain" @127.0.0.1 2>/dev/null)
log_to_file "Healthcheck: DNS Resolution Failed on $i attempt! Trying again..." dig_rc=$?
if [ $i -eq 3 ]; then
log_to_file "Healthcheck: DNS Resolution not possible after $i attempts... Gave up!" if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then
log_to_file "Maybe check your outbound firewall, as it needs to resolve DNS over TCP AND UDP!" log_to_stdout "Healthcheck: DNS Resolution Failed on attempt $i for $domain! Trying again..."
return 1 else
fi success=true
break
fi fi
done
done done
log_to_file "Healthcheck: DNS Resolver WORKING properly!"
return 0
if [ "$success" = false ]; then
log_to_stdout "Healthcheck: DNS Resolution not possible after 3 attempts for $domain... Gave up!"
((failures++))
fi
done
if [ $failures -gt $fail_tolerance ]; then
log_to_stdout "Healthcheck: Too many DNS failures ($fail_tolerance failures allowed, you got $failures failures), marking Healthcheck as unhealthy..."
return 1
fi
return 0
} }
if [[ ${SKIP_UNBOUND_HEALTHCHECK} == "y" ]]; then while true; do
log_to_file "Healthcheck: ALL CHECKS WERE SKIPPED! Unbound is healthy!"
exit 0
fi
# run checks, if check is not returning 0 (return value if check is ok), healthcheck will exit with 1 (marked in docker as unhealthy) if [[ ${SKIP_UNBOUND_HEALTHCHECK} == "y" ]]; then
check_ping log_to_stdout "Healthcheck: ALL CHECKS WERE SKIPPED! Unbound is healthy!"
echo "0" > $STATUS_FILE
sleep 365d
fi
if [ $? -ne 0 ]; then # run checks, if check is not returning 0 (return value if check is ok), healthcheck will exit with 1 (marked in docker as unhealthy)
exit 1 check_ping
fi PING_STATUS=$?
check_dns check_dns
DNS_STATUS=$?
if [ $? -ne 0 ]; then if [ $PING_STATUS -ne 0 ] || [ $DNS_STATUS -ne 0 ]; then
exit 1 echo "1" > $STATUS_FILE
fi
log_to_file "Healthcheck: ALL CHECKS WERE SUCCESSFUL! Unbound is healthy!" else
exit 0 echo "0" > $STATUS_FILE
fi
sleep 30
done
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
printf "READY\n";
while read line; do
echo "Processing Event: $line" >&2;
kill -3 $(cat "/var/run/supervisord.pid")
done < /dev/stdin
rm -rf /tmp/healthcheck_status
+32
View File
@@ -0,0 +1,32 @@
[supervisord]
nodaemon=true
user=root
pidfile=/var/run/supervisord.pid
[program:syslog-ng]
command=/usr/sbin/syslog-ng --foreground --no-caps
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autostart=true
[program:unbound]
command=/usr/sbin/unbound
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
[program:unbound-healthcheck]
command=/bin/bash /healthcheck.sh
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
autorestart=true
[eventlistener:processes]
command=/usr/local/sbin/stop-supervisor.sh
events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL
+21
View File
@@ -0,0 +1,21 @@
@version: 4.5
@include "scl.conf"
options {
chain_hostnames(off);
flush_lines(0);
use_dns(no);
use_fqdn(no);
owner("root"); group("adm"); perm(0640);
stats(freq(0));
keep_timestamp(no);
bad_hostname("^gconfd$");
};
source s_dgram {
unix-dgram("/dev/log");
internal();
};
destination d_stdout { pipe("/dev/stdout"); };
log {
source(s_dgram);
destination(d_stdout);
};
+3 -2
View File
@@ -1,5 +1,6 @@
FROM alpine:3.20 FROM alpine:3.20
LABEL maintainer "The Infrastructure Company GmbH <info@servercow.de>"
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
# Installation # Installation
RUN apk add --update \ RUN apk add --update \
@@ -36,4 +37,4 @@ RUN apk add --update \
COPY watchdog.sh /watchdog.sh COPY watchdog.sh /watchdog.sh
COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh
CMD /watchdog.sh CMD ["/watchdog.sh"]
+14 -10
View File
@@ -33,7 +33,7 @@ if [[ ! -p /tmp/com_pipe ]]; then
fi fi
# Wait for containers # Wait for containers
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for SQL..." echo "Waiting for SQL..."
sleep 2 sleep 2
done done
@@ -169,9 +169,13 @@ function notify_error() {
return 1 return 1
fi fi
# Escape subject and body (https://stackoverflow.com/a/2705678)
ESCAPED_SUBJECT=$(echo ${SUBJECT} | sed -e 's/[\/&]/\\&/g')
ESCAPED_BODY=$(echo ${BODY} | sed -e 's/[\/&]/\\&/g')
# Replace subject and body placeholders # Replace subject and body placeholders
WEBHOOK_BODY=$(echo ${WATCHDOG_NOTIFY_WEBHOOK_BODY} | sed "s/\$SUBJECT\|\${SUBJECT}/$SUBJECT/g" | sed "s/\$BODY\|\${BODY}/$BODY/g") WEBHOOK_BODY=$(echo ${WATCHDOG_NOTIFY_WEBHOOK_BODY} | sed -e "s/\$SUBJECT\|\${SUBJECT}/$ESCAPED_SUBJECT/g" -e "s/\$BODY\|\${BODY}/$ESCAPED_BODY/g")
# POST to webhook # POST to webhook
curl -X POST -H "Content-Type: application/json" ${CURL_VERBOSE} -d "${WEBHOOK_BODY}" ${WATCHDOG_NOTIFY_WEBHOOK} curl -X POST -H "Content-Type: application/json" ${CURL_VERBOSE} -d "${WEBHOOK_BODY}" ${WATCHDOG_NOTIFY_WEBHOOK}
@@ -191,12 +195,12 @@ get_container_ip() {
else else
sleep 0.5 sleep 0.5
# get long container id for exact match # get long container id for exact match
CONTAINER_ID=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id")) CONTAINER_ID=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id"))
# returned id can have multiple elements (if scaled), shuffle for random test # returned id can have multiple elements (if scaled), shuffle for random test
CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf)) CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf))
if [[ ! -z ${CONTAINER_ID} ]]; then if [[ ! -z ${CONTAINER_ID} ]]; then
for matched_container in "${CONTAINER_ID[@]}"; do for matched_container in "${CONTAINER_ID[@]}"; do
CONTAINER_IPS=($(curl --silent --insecure https://dockerapi/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress')) CONTAINER_IPS=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress'))
for ip_match in "${CONTAINER_IPS[@]}"; do for ip_match in "${CONTAINER_IPS[@]}"; do
# grep will do nothing if one of these vars is empty # grep will do nothing if one of these vars is empty
[[ -z ${ip_match} ]] && continue [[ -z ${ip_match} ]] && continue
@@ -716,7 +720,7 @@ rspamd_checks() {
From: watchdog@localhost From: watchdog@localhost
Empty Empty
' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan | jq -rc .default.required_score | sed 's/\..*//' ) ' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/scan | jq -rc .default.required_score | sed 's/\..*//' )
if [[ ${SCORE} -ne 9999 ]]; then if [[ ${SCORE} -ne 9999 ]]; then
echo "Rspamd settings check failed, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2 echo "Rspamd settings check failed, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2
err_count=$(( ${err_count} + 1)) err_count=$(( ${err_count} + 1))
@@ -1095,12 +1099,12 @@ while true; do
elif [[ ${com_pipe_answer} =~ .+-mailcow ]]; then elif [[ ${com_pipe_answer} =~ .+-mailcow ]]; then
kill -STOP ${BACKGROUND_TASKS[*]} kill -STOP ${BACKGROUND_TASKS[*]}
sleep 10 sleep 10
CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id") CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id")
if [[ ! -z ${CONTAINER_ID} ]]; then if [[ ! -z ${CONTAINER_ID} ]]; then
if [[ "${com_pipe_answer}" == "php-fpm-mailcow" ]]; then if [[ "${com_pipe_answer}" == "php-fpm-mailcow" ]]; then
HAS_INITDB=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/top | jq '.msg.Processes[] | contains(["php -c /usr/local/etc/php -f /web/inc/init_db.inc.php"])' | grep true) HAS_INITDB=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/top | jq '.msg.Processes[] | contains(["php -c /usr/local/etc/php -f /web/inc/init_db.inc.php"])' | grep true)
fi fi
S_RUNNING=$(($(date +%s) - $(curl --silent --insecure https://dockerapi/containers/${CONTAINER_ID}/json | jq .State.StartedAt | xargs -n1 date +%s -d))) S_RUNNING=$(($(date +%s) - $(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/json | jq .State.StartedAt | xargs -n1 date +%s -d)))
if [ ${S_RUNNING} -lt 360 ]; then if [ ${S_RUNNING} -lt 360 ]; then
log_msg "Container is running for less than 360 seconds, skipping action..." log_msg "Container is running for less than 360 seconds, skipping action..."
elif [[ ! -z ${HAS_INITDB} ]]; then elif [[ ! -z ${HAS_INITDB} ]]; then
@@ -1108,7 +1112,7 @@ while true; do
sleep 60 sleep 60
else else
log_msg "Sending restart command to ${CONTAINER_ID}..." log_msg "Sending restart command to ${CONTAINER_ID}..."
curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/restart curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/restart
notify_error "${com_pipe_answer}" notify_error "${com_pipe_answer}"
log_msg "Wait for restarted container to settle and continue watching..." log_msg "Wait for restarted container to settle and continue watching..."
sleep 35 sleep 35
+29
View File
@@ -0,0 +1,29 @@
<html>
<head>
<meta name="x-apple-disable-message-reformatting" />
<style>
body {
font-family: Helvetica, Arial, Sans-Serif;
}
/* mobile devices */
@media all and (max-width: 480px) {
.mob {
display: none;
}
}
</style>
</head>
<body>
Hello {{username2}},<br><br>
Somebody requested a new password for the {{hostname}} account associated with {{username}}.<br>
<small>Date of the password reset request: {{date}}</small><br><br>
You can reset your password by clicking the link below:<br>
<a href="{{link}}">{{link}}</a><br><br>
The link will be valid for the next {{token_lifetime}} minutes.<br><br>
If you did not request a new password, please ignore this email.<br>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
Hello {{username2}},
Somebody requested a new password for the {{hostname}} account associated with {{username}}.
Date of the password reset request: {{date}}
You can reset your password by clicking the link below:
{{link}}
The link will be valid for the next {{token_lifetime}} minutes.
If you did not request a new password, please ignore this email.
+16 -1
View File
@@ -289,5 +289,20 @@ namespace inbox {
mailbox "Kladde" { mailbox "Kladde" {
special_use = \Drafts special_use = \Drafts
} }
mailbox "Πρόχειρα" {
special_use = \Drafts
}
mailbox "Απεσταλμένα" {
special_use = \Sent
}
mailbox "Κάδος απορριμάτων" {
special_use = \Trash
}
mailbox "Ανεπιθύμητα" {
special_use = \Junk
}
mailbox "Αρχειοθετημένα" {
special_use = \Archive
}
prefix = prefix =
} }
+2
View File
@@ -170,6 +170,8 @@ smtputf8_enable = no
submission_smtpd_tls_mandatory_protocols = >=TLSv1.2 submission_smtpd_tls_mandatory_protocols = >=TLSv1.2
smtps_smtpd_tls_mandatory_protocols = >=TLSv1.2 smtps_smtpd_tls_mandatory_protocols = >=TLSv1.2
parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients
# This Option is added to correctly set the X-Original-To Header when mails are send to lmtp (dovecot)
lmtp_destination_recipient_limit=1
# DO NOT EDIT ANYTHING BELOW # # DO NOT EDIT ANYTHING BELOW #
# Overrides # # Overrides #
+1 -2
View File
@@ -4,7 +4,6 @@ smtp inet n - n - 1 postscreen
-o postscreen_upstream_proxy_protocol=haproxy -o postscreen_upstream_proxy_protocol=haproxy
-o syslog_name=haproxy -o syslog_name=haproxy
smtpd pass - - n - - smtpd smtpd pass - - n - - smtpd
-o smtpd_helo_restrictions=permit_mynetworks,reject_non_fqdn_helo_hostname
-o smtpd_sasl_auth_enable=no -o smtpd_sasl_auth_enable=no
-o smtpd_sender_restrictions=permit_mynetworks,reject_unlisted_sender,reject_unknown_sender_domain -o smtpd_sender_restrictions=permit_mynetworks,reject_unlisted_sender,reject_unknown_sender_domain
@@ -106,7 +105,7 @@ retry unix - - n - - error
discard unix - - n - - discard discard unix - - n - - discard
local unix - n n - - local local unix - n n - - local
virtual unix - n n - - virtual virtual unix - n n - - virtual
lmtp unix - - n - - lmtp lmtp unix - - n - - lmtp flags=O
anvil unix - - n - 1 anvil anvil unix - - n - 1 anvil
scache unix - - n - 1 scache scache unix - - n - 1 scache
maildrop unix - n n - - pipe flags=DRhu maildrop unix - n n - - pipe flags=DRhu
+51 -31
View File
@@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Mon Jul 1 00:16:55 UTC 2024 # Whitelist generated by Postwhite v3.4 on Fri Nov 1 00:18:49 UTC 2024
# https://github.com/stevejenkins/postwhite/ # https://github.com/stevejenkins/postwhite/
# 1993 total rules # 2013 total rules
2a00:1450:4000::/36 permit 2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit 2a01:111:f400::/48 permit
2a01:111:f403:8000::/50 permit 2a01:111:f403:8000::/50 permit
@@ -21,9 +21,7 @@
8.25.196.0/23 permit 8.25.196.0/23 permit
8.39.54.0/23 permit 8.39.54.0/23 permit
8.40.222.0/23 permit 8.40.222.0/23 permit
10.162.0.0/16 permit
12.130.86.238 permit 12.130.86.238 permit
13.72.50.45 permit
13.110.208.0/21 permit 13.110.208.0/21 permit
13.110.209.0/24 permit 13.110.209.0/24 permit
13.110.216.0/22 permit 13.110.216.0/22 permit
@@ -33,17 +31,17 @@
15.200.21.50 permit 15.200.21.50 permit
15.200.44.248 permit 15.200.44.248 permit
15.200.201.185 permit 15.200.201.185 permit
17.41.0.0/16 permit
17.57.155.0/24 permit 17.57.155.0/24 permit
17.57.156.0/24 permit 17.57.156.0/24 permit
17.58.0.0/16 permit 17.58.0.0/16 permit
17.142.0.0/15 permit 17.143.234.140/30 permit
18.156.89.250 permit 18.156.89.250 permit
18.157.243.190 permit 18.157.243.190 permit
18.194.95.56 permit 18.194.95.56 permit
18.198.96.88 permit 18.198.96.88 permit
18.208.124.128/25 permit 18.208.124.128/25 permit
18.216.232.154 permit 18.216.232.154 permit
18.235.27.253 permit
18.236.40.242 permit 18.236.40.242 permit
18.236.56.161 permit 18.236.56.161 permit
20.51.6.32/30 permit 20.51.6.32/30 permit
@@ -66,7 +64,6 @@
20.112.250.133 permit 20.112.250.133 permit
20.118.139.208/30 permit 20.118.139.208/30 permit
20.141.10.196 permit 20.141.10.196 permit
20.185.213.0/24 permit
20.185.214.0/27 permit 20.185.214.0/27 permit
20.185.214.32/27 permit 20.185.214.32/27 permit
20.185.214.64/27 permit 20.185.214.64/27 permit
@@ -112,15 +109,19 @@
37.218.249.47 permit 37.218.249.47 permit
37.218.251.62 permit 37.218.251.62 permit
39.156.163.64/29 permit 39.156.163.64/29 permit
40.71.187.0/24 permit
40.92.0.0/15 permit 40.92.0.0/15 permit
40.92.0.0/16 permit 40.92.0.0/16 permit
40.107.0.0/16 permit 40.107.0.0/16 permit
40.112.65.63 permit 40.112.65.63 permit
40.233.64.216 permit
40.233.83.78 permit
40.233.88.28 permit
43.228.184.0/22 permit 43.228.184.0/22 permit
44.206.138.57 permit 44.206.138.57 permit
44.217.45.156 permit
44.236.56.93 permit 44.236.56.93 permit
44.238.220.251 permit 44.238.220.251 permit
45.14.148.0/22 permit
46.19.170.16 permit 46.19.170.16 permit
46.226.48.0/21 permit 46.226.48.0/21 permit
46.228.36.37 permit 46.228.36.37 permit
@@ -181,7 +182,10 @@
50.18.125.237 permit 50.18.125.237 permit
50.18.126.162 permit 50.18.126.162 permit
50.31.32.0/19 permit 50.31.32.0/19 permit
50.56.130.220/30 permit 50.31.36.205 permit
50.56.130.220 permit
50.56.130.221 permit
50.56.130.222 permit
52.1.14.157 permit 52.1.14.157 permit
52.5.230.59 permit 52.5.230.59 permit
52.27.5.72 permit 52.27.5.72 permit
@@ -208,12 +212,12 @@
52.96.223.2 permit 52.96.223.2 permit
52.96.228.130 permit 52.96.228.130 permit
52.96.229.242 permit 52.96.229.242 permit
52.100.0.0/14 permit 52.100.0.0/15 permit
52.102.0.0/16 permit
52.103.0.0/17 permit 52.103.0.0/17 permit
52.119.213.144/28 permit 52.119.213.144/28 permit
52.185.106.240/28 permit 52.185.106.240/28 permit
52.200.59.0/24 permit 52.200.59.0/24 permit
52.205.61.79 permit
52.207.191.216 permit 52.207.191.216 permit
52.222.62.51 permit 52.222.62.51 permit
52.222.73.83 permit 52.222.73.83 permit
@@ -223,13 +227,8 @@
52.234.172.96/28 permit 52.234.172.96/28 permit
52.235.253.128 permit 52.235.253.128 permit
52.236.28.240/28 permit 52.236.28.240/28 permit
52.244.206.214 permit
52.247.53.144 permit
52.250.107.196 permit
52.250.126.174 permit
54.90.148.255 permit 54.90.148.255 permit
54.165.19.38 permit 54.165.19.38 permit
54.172.97.247 permit
54.174.52.0/24 permit 54.174.52.0/24 permit
54.174.57.0/24 permit 54.174.57.0/24 permit
54.174.59.0/24 permit 54.174.59.0/24 permit
@@ -246,16 +245,12 @@
54.244.54.130 permit 54.244.54.130 permit
54.244.242.0/24 permit 54.244.242.0/24 permit
54.255.61.23 permit 54.255.61.23 permit
57.103.64.0/18 permit
62.13.128.0/24 permit 62.13.128.0/24 permit
62.13.128.196 permit
62.13.129.128/25 permit 62.13.129.128/25 permit
62.13.136.0/22 permit 62.13.136.0/21 permit
62.13.140.0/22 permit 62.13.144.0/21 permit
62.13.144.0/22 permit 62.13.152.0/21 permit
62.13.148.0/23 permit
62.13.150.0/23 permit
62.13.152.0/23 permit
62.13.159.196 permit
62.17.146.128/26 permit 62.17.146.128/26 permit
62.179.121.0/24 permit 62.179.121.0/24 permit
62.201.172.0/27 permit 62.201.172.0/27 permit
@@ -277,7 +272,6 @@
64.127.115.252 permit 64.127.115.252 permit
64.132.88.0/23 permit 64.132.88.0/23 permit
64.132.92.0/24 permit 64.132.92.0/24 permit
64.147.123.128/27 permit
64.207.219.7 permit 64.207.219.7 permit
64.207.219.8 permit 64.207.219.8 permit
64.207.219.9 permit 64.207.219.9 permit
@@ -450,7 +444,6 @@
69.171.232.0/24 permit 69.171.232.0/24 permit
69.171.244.0/23 permit 69.171.244.0/23 permit
70.37.151.128/25 permit 70.37.151.128/25 permit
70.42.149.0/24 permit
70.42.149.35 permit 70.42.149.35 permit
72.14.192.0/18 permit 72.14.192.0/18 permit
72.21.192.0/19 permit 72.21.192.0/19 permit
@@ -567,7 +560,6 @@
77.238.189.142 permit 77.238.189.142 permit
77.238.189.146/31 permit 77.238.189.146/31 permit
77.238.189.148/30 permit 77.238.189.148/30 permit
81.7.169.128/25 permit
81.223.46.0/27 permit 81.223.46.0/27 permit
82.165.159.2 permit 82.165.159.2 permit
82.165.159.3 permit 82.165.159.3 permit
@@ -1257,6 +1249,7 @@
106.10.244.0/24 permit 106.10.244.0/24 permit
106.39.212.64/29 permit 106.39.212.64/29 permit
106.50.16.0/28 permit 106.50.16.0/28 permit
107.20.18.111 permit
107.20.210.250 permit 107.20.210.250 permit
108.174.0.0/24 permit 108.174.0.0/24 permit
108.174.0.215 permit 108.174.0.215 permit
@@ -1293,6 +1286,7 @@
119.42.242.52/31 permit 119.42.242.52/31 permit
119.42.242.156 permit 119.42.242.156 permit
121.244.91.48 permit 121.244.91.48 permit
121.244.91.52 permit
122.15.156.182 permit 122.15.156.182 permit
123.126.78.64/29 permit 123.126.78.64/29 permit
124.108.96.24/31 permit 124.108.96.24/31 permit
@@ -1322,7 +1316,9 @@
129.41.77.70 permit 129.41.77.70 permit
129.41.169.249 permit 129.41.169.249 permit
129.80.5.164 permit 129.80.5.164 permit
129.80.64.36 permit
129.80.67.121 permit 129.80.67.121 permit
129.80.145.156 permit
129.145.74.12 permit 129.145.74.12 permit
129.146.88.28 permit 129.146.88.28 permit
129.146.147.105 permit 129.146.147.105 permit
@@ -1333,6 +1329,9 @@
129.153.168.146 permit 129.153.168.146 permit
129.153.190.200 permit 129.153.190.200 permit
129.153.194.228 permit 129.153.194.228 permit
129.154.255.129 permit
129.158.56.255 permit
129.159.22.159 permit
129.159.87.137 permit 129.159.87.137 permit
129.213.195.191 permit 129.213.195.191 permit
130.61.9.72 permit 130.61.9.72 permit
@@ -1356,6 +1355,7 @@
135.84.216.0/22 permit 135.84.216.0/22 permit
136.143.160.0/24 permit 136.143.160.0/24 permit
136.143.161.0/24 permit 136.143.161.0/24 permit
136.143.162.0/24 permit
136.143.178.49 permit 136.143.178.49 permit
136.143.182.0/23 permit 136.143.182.0/23 permit
136.143.184.0/24 permit 136.143.184.0/24 permit
@@ -1368,7 +1368,6 @@
136.147.182.0/24 permit 136.147.182.0/24 permit
136.147.224.0/20 permit 136.147.224.0/20 permit
136.179.50.206 permit 136.179.50.206 permit
138.91.172.26 permit
139.60.152.0/22 permit 139.60.152.0/22 permit
139.138.35.44 permit 139.138.35.44 permit
139.138.46.121 permit 139.138.46.121 permit
@@ -1376,7 +1375,9 @@
139.138.46.219 permit 139.138.46.219 permit
139.138.57.55 permit 139.138.57.55 permit
139.138.58.119 permit 139.138.58.119 permit
139.167.79.86 permit
139.180.17.0/24 permit 139.180.17.0/24 permit
140.238.148.191 permit
141.148.159.229 permit 141.148.159.229 permit
141.193.32.0/23 permit 141.193.32.0/23 permit
141.193.184.32/27 permit 141.193.184.32/27 permit
@@ -1385,6 +1386,7 @@
141.193.185.32/27 permit 141.193.185.32/27 permit
141.193.185.64/26 permit 141.193.185.64/26 permit
141.193.185.128/25 permit 141.193.185.128/25 permit
143.47.120.152 permit
143.55.224.0/21 permit 143.55.224.0/21 permit
143.55.232.0/22 permit 143.55.232.0/22 permit
143.55.236.0/22 permit 143.55.236.0/22 permit
@@ -1398,7 +1400,10 @@
144.178.38.0/24 permit 144.178.38.0/24 permit
145.253.228.160/29 permit 145.253.228.160/29 permit
145.253.239.128/29 permit 145.253.239.128/29 permit
146.20.14.104/30 permit 146.20.14.104 permit
146.20.14.105 permit
146.20.14.106 permit
146.20.14.107 permit
146.20.112.0/26 permit 146.20.112.0/26 permit
146.20.113.0/24 permit 146.20.113.0/24 permit
146.20.191.0/24 permit 146.20.191.0/24 permit
@@ -1417,9 +1422,14 @@
149.72.248.236 permit 149.72.248.236 permit
149.97.173.180 permit 149.97.173.180 permit
150.230.98.160 permit 150.230.98.160 permit
151.145.38.14 permit
152.67.105.195 permit 152.67.105.195 permit
152.69.200.236 permit 152.69.200.236 permit
152.70.155.126 permit
155.248.208.51 permit 155.248.208.51 permit
155.248.220.138 permit
155.248.234.149 permit
155.248.237.141 permit
157.55.0.192/26 permit 157.55.0.192/26 permit
157.55.1.128/26 permit 157.55.1.128/26 permit
157.55.2.0/25 permit 157.55.2.0/25 permit
@@ -1474,6 +1484,7 @@
163.114.132.120 permit 163.114.132.120 permit
163.114.134.16 permit 163.114.134.16 permit
163.114.135.16 permit 163.114.135.16 permit
164.152.23.32 permit
164.177.132.168/30 permit 164.177.132.168/30 permit
165.173.128.0/24 permit 165.173.128.0/24 permit
166.78.68.0/22 permit 166.78.68.0/22 permit
@@ -1484,6 +1495,7 @@
167.89.0.0/17 permit 167.89.0.0/17 permit
167.89.46.159 permit 167.89.46.159 permit
167.89.54.103 permit 167.89.54.103 permit
167.89.60.95 permit
167.89.64.9 permit 167.89.64.9 permit
167.89.65.0 permit 167.89.65.0 permit
167.89.65.53 permit 167.89.65.53 permit
@@ -1498,6 +1510,7 @@
167.220.67.232/29 permit 167.220.67.232/29 permit
168.138.5.36 permit 168.138.5.36 permit
168.138.73.51 permit 168.138.73.51 permit
168.138.77.31 permit
168.245.0.0/17 permit 168.245.0.0/17 permit
168.245.12.252 permit 168.245.12.252 permit
168.245.46.9 permit 168.245.46.9 permit
@@ -1510,6 +1523,8 @@
170.10.68.0/22 permit 170.10.68.0/22 permit
170.10.128.0/24 permit 170.10.128.0/24 permit
170.10.129.0/24 permit 170.10.129.0/24 permit
170.10.132.56/29 permit
170.10.132.64/29 permit
170.10.133.0/24 permit 170.10.133.0/24 permit
172.217.0.0/19 permit 172.217.0.0/19 permit
172.217.32.0/20 permit 172.217.32.0/20 permit
@@ -1518,6 +1533,7 @@
172.217.192.0/19 permit 172.217.192.0/19 permit
172.253.56.0/21 permit 172.253.56.0/21 permit
172.253.112.0/20 permit 172.253.112.0/20 permit
173.0.84.0/29 permit
173.0.84.224/27 permit 173.0.84.224/27 permit
173.0.94.244/30 permit 173.0.94.244/30 permit
173.194.0.0/16 permit 173.194.0.0/16 permit
@@ -1536,7 +1552,6 @@
174.36.114.148/30 permit 174.36.114.148/30 permit
174.36.114.152/29 permit 174.36.114.152/29 permit
174.37.67.28/30 permit 174.37.67.28/30 permit
174.129.203.189 permit
175.41.215.51 permit 175.41.215.51 permit
176.32.105.0/24 permit 176.32.105.0/24 permit
176.32.127.0/24 permit 176.32.127.0/24 permit
@@ -1609,6 +1624,8 @@
188.172.128.0/20 permit 188.172.128.0/20 permit
192.0.64.0/18 permit 192.0.64.0/18 permit
192.18.139.154 permit 192.18.139.154 permit
192.18.145.36 permit
192.18.152.58 permit
192.30.252.0/22 permit 192.30.252.0/22 permit
192.161.144.0/20 permit 192.161.144.0/20 permit
192.162.87.0/24 permit 192.162.87.0/24 permit
@@ -1676,6 +1693,7 @@
199.122.123.0/24 permit 199.122.123.0/24 permit
199.127.232.0/22 permit 199.127.232.0/22 permit
199.255.192.0/22 permit 199.255.192.0/22 permit
202.12.124.128/27 permit
202.129.242.0/23 permit 202.129.242.0/23 permit
202.165.102.47 permit 202.165.102.47 permit
202.177.148.100 permit 202.177.148.100 permit
@@ -1728,7 +1746,9 @@
204.92.114.204/31 permit 204.92.114.204/31 permit
204.141.32.0/23 permit 204.141.32.0/23 permit
204.141.42.0/23 permit 204.141.42.0/23 permit
204.220.160.0/20 permit 204.220.160.0/21 permit
204.220.168.0/21 permit
204.220.176.0/20 permit
204.232.168.0/24 permit 204.232.168.0/24 permit
205.139.110.0/24 permit 205.139.110.0/24 permit
205.201.128.0/20 permit 205.201.128.0/20 permit
+5 -1
View File
@@ -21,6 +21,10 @@ FREEMAIL_TO_UNDISC_RCPT {
SOGO_CONTACT_EXCLUDE { SOGO_CONTACT_EXCLUDE {
expression = "(-WHITELISTED_FWD_HOST | -g+:policies) & ^SOGO_CONTACT & !DMARC_POLICY_ALLOW"; expression = "(-WHITELISTED_FWD_HOST | -g+:policies) & ^SOGO_CONTACT & !DMARC_POLICY_ALLOW";
} }
# Remove MAILCOW_WHITE symbol for senders with broken policy recieved not from fwd hosts
MAILCOW_WHITE_EXCLUDE {
expression = "^MAILCOW_WHITE & (-DMARC_POLICY_REJECT | -DMARC_POLICY_QUARANTINE | -R_SPF_PERMFAIL) & !WHITELISTED_FWD_HOST";
}
# Spoofed header from and broken policy (excluding sieve host, rspamd host, whitelisted senders, authenticated senders and forward hosts) # Spoofed header from and broken policy (excluding sieve host, rspamd host, whitelisted senders, authenticated senders and forward hosts)
SPOOFED_UNAUTH { SPOOFED_UNAUTH {
expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & !RSPAMD_HOST & !SIEVE_HOST & MAILCOW_DOMAIN_HEADER_FROM & !WHITELISTED_FWD_HOST & -g+:policies"; expression = "!MAILCOW_AUTH & !MAILCOW_WHITE & !RSPAMD_HOST & !SIEVE_HOST & MAILCOW_DOMAIN_HEADER_FROM & !WHITELISTED_FWD_HOST & -g+:policies";
@@ -103,4 +107,4 @@ CLAMD_JS_MALWARE {
expression = "CLAM_SECI_JS & !MAILCOW_WHITE"; expression = "CLAM_SECI_JS & !MAILCOW_WHITE";
description = "JS malware found, Securite JS malware Flag set through ClamAV"; description = "JS malware found, Securite JS malware Flag set through ClamAV";
score = 8; score = 8;
} }
+44 -30
View File
@@ -1,27 +1,45 @@
###############################################################################
# This list is added/merged with defined defaults in LUA module:
# https://github.com/rspamd/rspamd/blob/master/src/plugins/lua/mime_types.lua
###############################################################################
# Extensions that are treated as 'bad' # Extensions that are treated as 'bad'
# Number is score multiply factor # Number is score multiply factor
bad_extensions = { bad_extensions = {
scr = 20, apk = 4,
lnk = 20, appx = 4,
exe = 20, appxbundle = 4,
msi = 1, bat = 8,
msp = 1,
msu = 1,
jar = 2,
com = 20,
bat = 4,
cmd = 4,
ps1 = 4,
ace = 4,
arj = 4,
cab = 20, cab = 20,
cmd = 8,
com = 20,
diagcfg = 4,
diagpack = 4,
dmg = 8,
ex = 20,
ex_ = 20,
exe = 20,
img = 4,
jar = 8,
jnlp = 8,
js = 8,
jse = 8,
lnk = 20,
mjs = 8,
msi = 4,
msix = 4,
msixbundle = 4,
ps1 = 8,
scr = 20,
sct = 20,
vb = 20,
vbe = 20,
vbs = 20, vbs = 20,
hta = 4, vhd = 4,
shs = 4, py = 4,
wsc = 4, reg = 8,
wsf = 4, scf = 8,
iso = 8, vhdx = 4,
img = 8
}; };
# Extensions that are particularly penalized for archives # Extensions that are particularly penalized for archives
@@ -30,18 +48,14 @@ bad_archive_extensions = {
docx = 0.5, docx = 0.5,
xlsx = 0.5, xlsx = 0.5,
pdf = 1.0, pdf = 1.0,
jar = 3, jar = 12,
js = 0.5, jnlp = 12,
vbs = 20, bat = 12,
exe = 20 cmd = 12,
}; };
# Used to detect another archive in archive # Used to detect another archive in archive
archive_extensions = { archive_extensions = {
zip = 1, tar = 1,
arj = 1, gz = 1,
rar = 1, };
ace = 1,
7z = 1,
cab = 1
};
+1
View File
@@ -2,6 +2,7 @@ dns {
enable_dnssec = true; enable_dnssec = true;
} }
map_watch_interval = 30s; map_watch_interval = 30s;
task_timeout = 30s;
disable_monitoring = true; disable_monitoring = true;
# In case a task times out (like DNS lookup), soft reject the message # In case a task times out (like DNS lookup), soft reject the message
# instead of silently accepting the message without further processing. # instead of silently accepting the message without further processing.
+5 -1
View File
@@ -2,6 +2,7 @@ rbls {
interserver_ip { interserver_ip {
symbol = "RBL_INTERSERVER_IP"; symbol = "RBL_INTERSERVER_IP";
rbl = "rbl.interserver.net"; rbl = "rbl.interserver.net";
from = true;
ipv6 = false; ipv6 = false;
returncodes { returncodes {
RBL_INTERSERVER_BAD_IP = "127.0.0.2"; RBL_INTERSERVER_BAD_IP = "127.0.0.2";
@@ -19,4 +20,7 @@ rbls {
RBL_INTERSERVER_BAD_URI = "127.0.0.2"; RBL_INTERSERVER_BAD_URI = "127.0.0.2";
} }
} }
}
.include(try=true,override=true,priority=5) "$LOCAL_CONFDIR/custom/dqs-rbl.conf"
}
+257
View File
@@ -17,4 +17,261 @@ symbols = {
score = 4.0; score = 4.0;
description = "Listed on Interserver RBL"; description = "Listed on Interserver RBL";
} }
"SPAMHAUS_ZEN" {
weight = 7.0;
}
"SH_AUTHBL_RECEIVED" {
weight = 4.0;
}
"RBL_DBL_SPAM" {
weight = 7.0;
}
"RBL_DBL_PHISH" {
weight = 7.0;
}
"RBL_DBL_MALWARE" {
weight = 7.0;
}
"RBL_DBL_BOTNET" {
weight = 7.0;
}
"RBL_DBL_ABUSED_SPAM" {
weight = 3.0;
}
"RBL_DBL_ABUSED_PHISH" {
weight = 3.0;
}
"RBL_DBL_ABUSED_MALWARE" {
weight = 3.0;
}
"RBL_DBL_ABUSED_BOTNET" {
weight = 3.0;
}
"RBL_ZRD_VERY_FRESH_DOMAIN" {
weight = 7.0;
}
"RBL_ZRD_FRESH_DOMAIN" {
weight = 4.0;
}
"ZRD_VERY_FRESH_DOMAIN" {
weight = 7.0;
}
"ZRD_FRESH_DOMAIN" {
weight = 4.0;
}
"SH_EMAIL_DBL" {
weight = 7.0;
}
"SH_EMAIL_DBL_ABUSED" {
weight = 7.0;
}
"SH_EMAIL_ZRD_VERY_FRESH_DOMAIN" {
weight = 7.0;
}
"SH_EMAIL_ZRD_FRESH_DOMAIN" {
weight = 4.0;
}
"RBL_DBL_DONT_QUERY_IPS" {
weight = 0.0;
}
"RBL_ZRD_DONT_QUERY_IPS" {
weight = 0.0;
}
"SH_EMAIL_ZRD_DONT_QUERY_IPS" {
weight = 0.0;
}
"SH_EMAIL_DBL_DONT_QUERY_IPS" {
weight = 0.0;
}
"DBL" {
weight = 0.0;
description = "DBL unknown result";
groups = ["spamhaus"];
}
"DBL_SPAM" {
weight = 7;
description = "DBL uribl spam";
groups = ["spamhaus"];
}
"DBL_PHISH" {
weight = 7;
description = "DBL uribl phishing";
groups = ["spamhaus"];
}
"DBL_MALWARE" {
weight = 7;
description = "DBL uribl malware";
groups = ["spamhaus"];
}
"DBL_BOTNET" {
weight = 7;
description = "DBL uribl botnet C&C domain";
groups = ["spamhaus"];
}
"DBLABUSED_SPAM_FULLURLS" {
weight = 5.5;
description = "DBL uribl abused legit spam";
groups = ["spamhaus"];
}
"DBLABUSED_PHISH_FULLURLS" {
weight = 5.5;
description = "DBL uribl abused legit phish";
groups = ["spamhaus"];
}
"DBLABUSED_MALWARE_FULLURLS" {
weight = 5.5;
description = "DBL uribl abused legit malware";
groups = ["spamhaus"];
}
"DBLABUSED_BOTNET_FULLURLS" {
weight = 5.5;
description = "DBL uribl abused legit botnet";
groups = ["spamhaus"];
}
"DBL_ABUSE" {
weight = 5.5;
description = "DBL uribl abused legit spam";
groups = ["spamhaus"];
}
"DBL_ABUSE_REDIR" {
weight = 1.5;
description = "DBL uribl abused spammed redirector domain";
groups = ["spamhaus"];
}
"DBL_ABUSE_PHISH" {
weight = 5.5;
description = "DBL uribl abused legit phish";
groups = ["spamhaus"];
}
"DBL_ABUSE_MALWARE" {
weight = 5.5;
description = "DBL uribl abused legit malware";
groups = ["spamhaus"];
}
"DBL_ABUSE_BOTNET" {
weight = 5.5;
description = "DBL uribl abused legit botnet C&C";
groups = ["spamhaus"];
}
"DBL_PROHIBIT" {
weight = 0.0;
description = "DBL uribl IP queries prohibited!";
groups = ["spamhaus"];
}
"DBL_BLOCKED_OPENRESOLVER" {
weight = 0.0;
description = "You are querying Spamhaus from an open resolver, please see https://www.spamhaus.org/returnc/pub/";
groups = ["spamhaus"];
}
"DBL_BLOCKED" {
weight = 0.0;
description = "You are exceeding the query limit, please see https://www.spamhaus.org/returnc/vol/";
groups = ["spamhaus"];
}
"SPAMHAUS_ZEN_URIBL" {
weight = 0.0;
description = "Spamhaus ZEN URIBL: Filtered result";
groups = ["spamhaus"];
}
"URIBL_SBL" {
weight = 6.5;
description = "A domain in the message body resolves to an IP listed in Spamhaus SBL";
one_shot = true;
groups = ["spamhaus"];
}
"URIBL_SBL_CSS" {
weight = 6.5;
description = "A domain in the message body resolves to an IP listed in Spamhaus SBL CSS";
one_shot = true;
groups = ["spamhaus"];
}
"URIBL_PBL" {
weight = 0.01;
description = "A domain in the message body resolves to an IP listed in Spamhaus PBL";
one_shot = true;
groups = ["spamhaus"];
}
"URIBL_DROP" {
weight = 6.5;
description = "A domain in the message body resolves to an IP listed in Spamhaus DROP";
one_shot = true;
groups = ["spamhaus"];
}
"URIBL_XBL" {
weight = 5.0;
description = "A domain in the message body resolves to an IP listed in Spamhaus XBL";
one_shot = true;
groups = ["spamhaus"];
}
"SPAMHAUS_SBL_URL" {
weight = 6.5;
description = "A numeric URL in the message body is listed in Spamhaus SBL";
one_shot = true;
groups = ["spamhaus"];
}
"SH_HBL_EMAIL" {
weight = 7;
description = "Email listed in HBL";
groups = ["spamhaus"];
}
"SH_HBL_FILE_MALICIOUS" {
weight = 7;
description = "An attachment hash is listed in Spamhaus HBL as malicious";
groups = ["spamhaus"];
}
"SH_HBL_FILE_SUSPICIOUS" {
weight = 5;
description = "An attachment hash is listed in Spamhaus HBL as suspicious";
groups = ["spamhaus"];
}
"RBL_SPAMHAUS_CW_BTC" {
score = 7;
description = "Bitcoin found in Spamhaus cryptowallet list";
groups = ["spamhaus"];
}
"RBL_SPAMHAUS_CW_ETH" {
score = 7;
description = "Ethereum found in Spamhaus cryptowallet list";
groups = ["spamhaus"];
}
"RBL_SPAMHAUS_CW_BCH" {
score = 7;
description = "Bitcoinhash found in Spamhaus cryptowallet list";
groups = ["spamhaus"];
}
"RBL_SPAMHAUS_CW_XMR" {
score = 7;
description = "Monero found in Spamhaus cryptowallet list";
groups = ["spamhaus"];
}
"RBL_SPAMHAUS_CW_LTC" {
score = 7;
description = "Litecoin found in Spamhaus cryptowallet list";
groups = ["spamhaus"];
}
"RBL_SPAMHAUS_CW_XRP" {
score = 7;
description = "Ripple found in Spamhaus cryptowallet list";
groups = ["spamhaus"];
}
"RBL_SPAMHAUS_HBL_URL" {
score = 7;
description = "URL found in spamhaus HBL blocklist";
groups = ["spamhaus"];
}
} }
+4 -2
View File
@@ -1,12 +1,14 @@
classifier "bayes" { classifier "bayes" {
# name = "custom"; # 'name' parameter must be set if multiple classifiers are defined
learn_condition = 'return require("lua_bayes_learn").can_learn';
new_schema = true;
tokenizer { tokenizer {
name = "osb"; name = "osb";
} }
backend = "redis"; backend = "redis";
min_tokens = 11; min_tokens = 11;
min_learns = 5; min_learns = 5;
new_schema = true; expire = 7776000;
expire = 2592000;
statfile { statfile {
symbol = "BAYES_HAM"; symbol = "BAYES_HAM";
spam = false; spam = false;
+1 -1
View File
@@ -52,7 +52,7 @@ $headers = getallheaders();
$qid = $headers['X-Rspamd-Qid']; $qid = $headers['X-Rspamd-Qid'];
$fuzzy = $headers['X-Rspamd-Fuzzy']; $fuzzy = $headers['X-Rspamd-Fuzzy'];
$subject = $headers['X-Rspamd-Subject']; $subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
$score = $headers['X-Rspamd-Score']; $score = $headers['X-Rspamd-Score'];
$rcpts = $headers['X-Rspamd-Rcpt']; $rcpts = $headers['X-Rspamd-Rcpt'];
$user = $headers['X-Rspamd-User']; $user = $headers['X-Rspamd-User'];
+1 -1
View File
@@ -53,7 +53,7 @@ $qid = $headers['X-Rspamd-Qid'];
$rcpts = $headers['X-Rspamd-Rcpt']; $rcpts = $headers['X-Rspamd-Rcpt'];
$sender = $headers['X-Rspamd-From']; $sender = $headers['X-Rspamd-From'];
$ip = $headers['X-Rspamd-Ip']; $ip = $headers['X-Rspamd-Ip'];
$subject = $headers['X-Rspamd-Subject']; $subject = iconv_mime_decode($headers['X-Rspamd-Subject']);
$messageid= $json_body->message_id; $messageid= $json_body->message_id;
$priority = 0; $priority = 0;
+1
View File
@@ -107,6 +107,7 @@ $template_data = [
'f2b_banlist_url' => getBaseUrl() . "/api/v1/get/fail2ban/banlist/" . $f2b_data['banlist_id'], 'f2b_banlist_url' => getBaseUrl() . "/api/v1/get/fail2ban/banlist/" . $f2b_data['banlist_id'],
'q_data' => quarantine('settings'), 'q_data' => quarantine('settings'),
'qn_data' => quota_notification('get'), 'qn_data' => quota_notification('get'),
'pw_reset_data' => reset_password('get_notification'),
'rsettings_map' => file_get_contents('http://nginx:8081/settings.php'), 'rsettings_map' => file_get_contents('http://nginx:8081/settings.php'),
'rsettings' => $rsettings, 'rsettings' => $rsettings,
'rspamd_regex_maps' => $rspamd_regex_maps, 'rspamd_regex_maps' => $rspamd_regex_maps,
+446 -37
View File
@@ -939,10 +939,10 @@ function check_login($user, $pass, $app_passwd_data = false) {
$stmt->execute(array(':user' => $user)); $stmt->execute(array(':user' => $user));
$rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC)); $rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));
} }
foreach ($rows as $row) { foreach ($rows as $row) {
// verify password // verify password
if (verify_hash($row['password'], $pass) !== false) { if (verify_hash($row['password'], $pass) !== false) {
if (!array_key_exists("app_passwd_id", $row)){ if (!array_key_exists("app_passwd_id", $row)){
// password is not a app password // password is not a app password
// check for tfa authenticators // check for tfa authenticators
$authenticators = get_tfa($user); $authenticators = get_tfa($user);
@@ -953,11 +953,6 @@ function check_login($user, $pass, $app_passwd_data = false) {
$_SESSION['pending_mailcow_cc_role'] = "user"; $_SESSION['pending_mailcow_cc_role'] = "user";
$_SESSION['pending_tfa_methods'] = $authenticators['additional']; $_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']); unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "pending"; return "pending";
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) { } else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
// no authenticators found, login successfull // no authenticators found, login successfull
@@ -966,6 +961,11 @@ function check_login($user, $pass, $app_passwd_data = false) {
$stmt->execute(array(':user' => $user)); $stmt->execute(array(':user' => $user));
unset($_SESSION['ldelay']); unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "user"; return "user";
} }
} elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) { } elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {
@@ -1028,7 +1028,7 @@ function update_sogo_static_view($mailbox = null) {
// Check if the mailbox exists // Check if the mailbox exists
$stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'"); $stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'");
$stmt->execute(array(':mailbox' => $mailbox)); $stmt->execute(array(':mailbox' => $mailbox));
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row){ if ($row){
$mailbox_exists = true; $mailbox_exists = true;
} }
@@ -1056,7 +1056,7 @@ function update_sogo_static_view($mailbox = null) {
LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username
WHERE WHERE
mailbox.active = '1'"; mailbox.active = '1'";
if ($mailbox_exists) { if ($mailbox_exists) {
$query .= " AND mailbox.username = :mailbox"; $query .= " AND mailbox.username = :mailbox";
$stmt = $pdo->prepare($query); $stmt = $pdo->prepare($query);
@@ -1065,21 +1065,25 @@ function update_sogo_static_view($mailbox = null) {
$query .= " GROUP BY mailbox.username"; $query .= " GROUP BY mailbox.username";
$stmt = $pdo->query($query); $stmt = $pdo->query($query);
} }
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
flush_memcached(); flush_memcached();
} }
function edit_user_account($_data) { function edit_user_account($_data) {
global $lang; global $lang;
global $pdo; global $pdo;
$_data_log = $_data; $_data_log = $_data;
!isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*'; !isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*';
!isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*'; !isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*';
!isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*'; !isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*';
$username = $_SESSION['mailcow_cc_username']; $username = $_SESSION['mailcow_cc_username'];
$role = $_SESSION['mailcow_cc_role']; $role = $_SESSION['mailcow_cc_role'];
$password_old = $_data['user_old_pass']; $password_old = $_data['user_old_pass'];
$pw_recovery_email = $_data['pw_recovery_email'];
if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') { if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@@ -1088,20 +1092,24 @@ function edit_user_account($_data) {
); );
return false; return false;
} }
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group' // edit password
AND `username` = :user"); if (!empty($password_old) && !empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) {
$stmt->execute(array(':user' => $username)); $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
$row = $stmt->fetch(PDO::FETCH_ASSOC); WHERE `kind` NOT REGEXP 'location|thing|group'
if (!verify_hash($row['password'], $password_old)) { AND `username` = :user");
$_SESSION['return'][] = array( $stmt->execute(array(':user' => $username));
'type' => 'danger', $row = $stmt->fetch(PDO::FETCH_ASSOC);
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied' if (!verify_hash($row['password'], $password_old)) {
); $_SESSION['return'][] = array(
return false; 'type' => 'danger',
} 'log' => array(__FUNCTION__, $_data_log),
if (!empty($_data['user_new_pass']) && !empty($_data['user_new_pass2'])) { 'msg' => 'access_denied'
);
return false;
}
$password_new = $_data['user_new_pass']; $password_new = $_data['user_new_pass'];
$password_new2 = $_data['user_new_pass2']; $password_new2 = $_data['user_new_pass2'];
if (password_check($password_new, $password_new2) !== true) { if (password_check($password_new, $password_new2) !== true) {
@@ -1116,8 +1124,29 @@ function edit_user_account($_data) {
':password_hashed' => $password_hashed, ':password_hashed' => $password_hashed,
':username' => $username ':username' => $username
)); ));
update_sogo_static_view();
} }
update_sogo_static_view(); // edit password recovery email
elseif (isset($pw_recovery_email)) {
if (!isset($_SESSION['acl']['pw_reset']) || $_SESSION['acl']['pw_reset'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$pw_recovery_email = (!filter_var($pw_recovery_email, FILTER_VALIDATE_EMAIL)) ? '' : $pw_recovery_email;
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
WHERE `username` = :username");
$stmt->execute(array(
':recovery_email' => $pw_recovery_email,
':username' => $username
));
}
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_data_log), 'log' => array(__FUNCTION__, $_data_log),
@@ -1345,7 +1374,7 @@ function set_tfa($_data) {
$_data['registration']->certificate, $_data['registration']->certificate,
0 0
)); ));
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_data_log), 'log' => array(__FUNCTION__, $_data_log),
@@ -1515,7 +1544,7 @@ function unset_tfa_key($_data) {
try { try {
if (!is_numeric($id)) $access_denied = true; if (!is_numeric($id)) $access_denied = true;
// set access_denied error // set access_denied error
if ($access_denied){ if ($access_denied){
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -1524,7 +1553,7 @@ function unset_tfa_key($_data) {
'msg' => 'access_denied' 'msg' => 'access_denied'
); );
return false; return false;
} }
// check if it's last key // check if it's last key
$stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
@@ -1560,7 +1589,7 @@ function unset_tfa_key($_data) {
} }
function get_tfa($username = null, $id = null) { function get_tfa($username = null, $id = null) {
global $pdo; global $pdo;
if (isset($_SESSION['mailcow_cc_username'])) { if (empty($username) && isset($_SESSION['mailcow_cc_username'])) {
$username = $_SESSION['mailcow_cc_username']; $username = $_SESSION['mailcow_cc_username'];
} }
elseif (empty($username)) { elseif (empty($username)) {
@@ -1573,7 +1602,7 @@ function get_tfa($username = null, $id = null) {
WHERE `username` = :username AND `active` = '1'"); WHERE `username` = :username AND `active` = '1'");
$stmt->execute(array(':username' => $username)); $stmt->execute(array(':username' => $username));
$results = $stmt->fetchAll(PDO::FETCH_ASSOC); $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// no tfa methods found // no tfa methods found
if (count($results) == 0) { if (count($results) == 0) {
$data['name'] = 'none'; $data['name'] = 'none';
@@ -1781,8 +1810,8 @@ function verify_tfa_login($username, $_data) {
'msg' => array('webauthn_authenticator_failed') 'msg' => array('webauthn_authenticator_failed')
); );
return false; return false;
} }
if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) { if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@@ -2144,7 +2173,7 @@ function cors($action, $data = null) {
'msg' => 'access_denied' 'msg' => 'access_denied'
); );
return false; return false;
} }
$allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']); $allowed_origins = isset($data['allowed_origins']) ? $data['allowed_origins'] : array($_SERVER['SERVER_NAME']);
$allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins; $allowed_origins = !is_array($allowed_origins) ? array_filter(array_map('trim', explode("\n", $allowed_origins))) : $allowed_origins;
@@ -2177,7 +2206,7 @@ function cors($action, $data = null) {
$redis->hMSet('CORS_SETTINGS', array( $redis->hMSet('CORS_SETTINGS', array(
'allowed_origins' => implode(', ', $allowed_origins), 'allowed_origins' => implode(', ', $allowed_origins),
'allowed_methods' => implode(', ', $allowed_methods) 'allowed_methods' => implode(', ', $allowed_methods)
)); ));
} catch (RedisException $e) { } catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@@ -2229,10 +2258,10 @@ function cors($action, $data = null) {
header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin'); header('Access-Control-Allow-Headers: Accept, Content-Type, X-Api-Key, Origin');
// Access-Control settings requested, this is just a preflight request // Access-Control settings requested, this is just a preflight request
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' &&
isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) &&
isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) { isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
$allowed_methods = explode(', ', $cors_settings["allowed_methods"]); $allowed_methods = explode(', ', $cors_settings["allowed_methods"]);
if (in_array($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], $allowed_methods, true)) if (in_array($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], $allowed_methods, true))
// method allowed send 200 OK // method allowed send 200 OK
@@ -2261,6 +2290,386 @@ function uuid4() {
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
} }
function reset_password($action, $data = null) {
global $pdo;
global $redis;
global $mailcow_hostname;
global $PW_RESET_TOKEN_LIMIT;
global $PW_RESET_TOKEN_LIFETIME;
$_data_log = $data;
if (isset($_data_log['new_password'])) $_data_log['new_password'] = '*';
if (isset($_data_log['new_password2'])) $_data_log['new_password2'] = '*';
switch ($action) {
case 'check':
$token = $data;
$stmt = $pdo->prepare("SELECT `t1`.`username` FROM `reset_password` AS `t1` JOIN `mailbox` AS `t2` ON `t1`.`username` = `t2`.`username` WHERE `t1`.`token` = :token AND `t1`.`created` > DATE_SUB(NOW(), INTERVAL :lifetime MINUTE) AND `t2`.`active` = 1;");
$stmt->execute(array(
':token' => preg_replace('/[^a-zA-Z0-9-]/', '', $token),
':lifetime' => $PW_RESET_TOKEN_LIFETIME
));
$return = $stmt->fetch(PDO::FETCH_ASSOC);
return empty($return['username']) ? false : $return['username'];
break;
case 'issue':
$username = $data;
// perform cleanup
$stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE created < DATE_SUB(NOW(), INTERVAL :lifetime MINUTE);");
$stmt->execute(array(':lifetime' => $PW_RESET_TOKEN_LIFETIME));
if (filter_var($username, FILTER_VALIDATE_EMAIL) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$pw_reset_notification = reset_password('get_notification', 'raw');
if (!$pw_reset_notification) return false;
if (empty($pw_reset_notification['from']) || empty($pw_reset_notification['subject'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'password_reset_na'
);
return false;
}
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$mailbox_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($mailbox_data)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'password_reset_invalid_user'
);
return false;
}
$mailbox_attr = json_decode($mailbox_data['attributes'], true);
if (empty($mailbox_attr['recovery_email']) || filter_var($mailbox_attr['recovery_email'], FILTER_VALIDATE_EMAIL) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => "password_reset_invalid_user"
);
return false;
}
$stmt = $pdo->prepare("SELECT * FROM `reset_password`
WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$generated_token_count = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($generated_token_count >= $PW_RESET_TOKEN_LIMIT) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => "reset_token_limit_exceeded"
);
return false;
}
$token = implode('-', array(
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3))),
strtoupper(bin2hex(random_bytes(3)))
));
$stmt = $pdo->prepare("INSERT INTO `reset_password` (`username`, `token`)
VALUES (:username, :token)");
$stmt->execute(array(
':username' => $username,
':token' => $token
));
$reset_link = getBaseURL() . "/reset-password?token=" . $token;
$request_date = new DateTime();
$locale_date = locale_get_default();
$date_formatter = new IntlDateFormatter(
$locale_date,
IntlDateFormatter::FULL,
IntlDateFormatter::FULL
);
$formatted_request_date = $date_formatter->format($request_date);
// set template vars
// subject
$pw_reset_notification['subject'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{username}}', $username, $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['subject']);
$pw_reset_notification['subject'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['subject']);
// text
$pw_reset_notification['text_tmpl'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{username}}', $username, $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['text_tmpl']);
$pw_reset_notification['text_tmpl'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['text_tmpl']);
// html
$pw_reset_notification['html_tmpl'] = str_replace('{{hostname}}', $mailcow_hostname, $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{link}}', $reset_link, $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{username}}', $username, $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{username2}}', $mailbox_attr['recovery_email'], $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{date}}', $formatted_request_date, $pw_reset_notification['html_tmpl']);
$pw_reset_notification['html_tmpl'] = str_replace('{{token_lifetime}}', $PW_RESET_TOKEN_LIFETIME, $pw_reset_notification['html_tmpl']);
$email_sent = reset_password('send_mail', array(
"from" => $pw_reset_notification['from'],
"to" => $mailbox_attr['recovery_email'],
"subject" => $pw_reset_notification['subject'],
"text" => $pw_reset_notification['text_tmpl'],
"html" => $pw_reset_notification['html_tmpl']
));
if (!$email_sent){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => "recovery_email_failed"
);
return false;
}
list($localPart, $domainPart) = explode('@', $mailbox_attr['recovery_email']);
if (strlen($localPart) > 1) {
$maskedLocalPart = $localPart[0] . str_repeat('*', strlen($localPart) - 1);
} else {
$maskedLocalPart = "*";
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => array("recovery_email_sent", $maskedLocalPart . '@' . $domainPart)
);
return array(
"username" => $username,
"issue" => "success"
);
break;
case 'reset':
$token = $data['token'];
$new_password = $data['new_password'];
$new_password2 = $data['new_password2'];
$username = $data['username'];
$check_tfa = $data['check_tfa'];
if (!$username || !$token) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'invalid_reset_token'
);
return false;
}
# check new password
if (!password_check($new_password, $new_password2)) {
return false;
}
if ($check_tfa){
// check for tfa authenticators
$authenticators = get_tfa($username);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
$_SESSION['pending_mailcow_cc_username'] = $username;
$_SESSION['pending_pw_reset_token'] = $token;
$_SESSION['pending_pw_new_password'] = $new_password;
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return false;
}
}
# set new password
$password_hashed = hash_password($new_password);
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed,
`attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW())
WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
// perform cleanup
$stmt = $pdo->prepare("DELETE FROM `reset_password` WHERE `username` = :username;");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'password_changed_success'
);
return true;
break;
case 'get_notification':
$type = $data;
try {
$settings['from'] = $redis->Get('PW_RESET_FROM');
$settings['subject'] = $redis->Get('PW_RESET_SUBJ');
$settings['html_tmpl'] = $redis->Get('PW_RESET_HTML');
$settings['text_tmpl'] = $redis->Get('PW_RESET_TEXT');
if (empty($settings['html_tmpl']) && empty($settings['text_tmpl'])) {
$settings['html_tmpl'] = file_get_contents("/tpls/pw_reset_html.tpl");
$settings['text_tmpl'] = file_get_contents("/tpls/pw_reset_text.tpl");
}
if ($type != "raw") {
$settings['html_tmpl'] = htmlspecialchars($settings['html_tmpl']);
$settings['text_tmpl'] = htmlspecialchars($settings['text_tmpl']);
}
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => array('redis_error', $e)
);
return false;
}
return $settings;
break;
case 'send_mail':
$from = $data['from'];
$to = $data['to'];
$text = $data['text'];
$html = $data['html'];
$subject = $data['subject'];
if (!filter_var($from, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'from_invalid'
);
return false;
}
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'to_invalid'
);
return false;
}
if (empty($subject)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'subject_empty'
);
return false;
}
if (empty($text)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'text_empty'
);
return false;
}
ini_set('max_execution_time', 0);
ini_set('max_input_time', 0);
$mail = new PHPMailer;
$mail->Timeout = 10;
$mail->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
$mail->isSMTP();
$mail->Host = 'postfix-mailcow';
$mail->SMTPAuth = false;
$mail->Port = 25;
$mail->setFrom($from);
$mail->Subject = $subject;
$mail->CharSet ="UTF-8";
if (!empty($html)) {
$mail->Body = $html;
$mail->AltBody = $text;
}
else {
$mail->Body = $text;
}
$mail->XMailer = 'MooMail';
$mail->AddAddress($to);
if (!$mail->send()) {
return false;
}
$mail->ClearAllRecipients();
return true;
break;
}
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'access_denied'
);
return false;
}
switch ($action) {
case 'edit_notification':
$subject = $data['subject'];
$from = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $data['from']);
$from = (!filter_var($from, FILTER_VALIDATE_EMAIL)) ? "" : $from;
$subject = (empty($subject)) ? "" : $subject;
$text = (empty($data['text_tmpl'])) ? "" : $data['text_tmpl'];
$html = (empty($data['html_tmpl'])) ? "" : $data['html_tmpl'];
try {
$redis->Set('PW_RESET_FROM', $from);
$redis->Set('PW_RESET_SUBJ', $subject);
$redis->Set('PW_RESET_HTML', $html);
$redis->Set('PW_RESET_TEXT', $text);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => array('redis_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $action, $_data_log),
'msg' => 'saved_settings'
);
break;
}
}
function get_logs($application, $lines = false) { function get_logs($application, $lines = false) {
if ($lines === false) { if ($lines === false) {
+307 -90
View File
@@ -184,6 +184,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => 'global_filter_written' 'msg' => 'global_filter_written'
); );
return true; return true;
break;
case 'filter': case 'filter':
$sieve = new Sieve\SieveParser(); $sieve = new Sieve\SieveParser();
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) { if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
@@ -1232,7 +1233,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':active' => $active ':active' => $active
)); ));
if (isset($_data['acl'])) { if (isset($_data['acl'])) {
$_data['acl'] = (array)$_data['acl']; $_data['acl'] = (array)$_data['acl'];
$_data['spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; $_data['spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0;
@@ -1249,6 +1250,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; $_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; $_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; $_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
$_data['pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0;
} else { } else {
$_data['spam_alias'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_alias']); $_data['spam_alias'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_alias']);
$_data['tls_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_tls_policy']); $_data['tls_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_tls_policy']);
@@ -1263,15 +1265,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['quarantine_attachments'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_attachments']); $_data['quarantine_attachments'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_attachments']);
$_data['quarantine_notification'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_notification']); $_data['quarantine_notification'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_notification']);
$_data['quarantine_category'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_category']); $_data['quarantine_category'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_category']);
$_data['app_passwds'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_app_passwds']); $_data['app_passwds'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_app_passwds']);
$_data['pw_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pw_reset']);
} }
try { try {
$stmt = $pdo->prepare("INSERT INTO `user_acl` $stmt = $pdo->prepare("INSERT INTO `user_acl`
(`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`, (`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`,
`pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`) `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`, `pw_reset`)
VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset, VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset,
:pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) "); :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds, :pw_reset) ");
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
':spam_alias' => $_data['spam_alias'], ':spam_alias' => $_data['spam_alias'],
@@ -1287,7 +1290,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':quarantine_attachments' => $_data['quarantine_attachments'], ':quarantine_attachments' => $_data['quarantine_attachments'],
':quarantine_notification' => $_data['quarantine_notification'], ':quarantine_notification' => $_data['quarantine_notification'],
':quarantine_category' => $_data['quarantine_category'], ':quarantine_category' => $_data['quarantine_category'],
':app_passwds' => $_data['app_passwds'] ':app_passwds' => $_data['app_passwds'],
':pw_reset' => $_data['pw_reset']
)); ));
} }
catch (PDOException $e) { catch (PDOException $e) {
@@ -1463,7 +1467,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
return false; return false;
} }
// check attributes // check attributes
$attr = array(); $attr = array();
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
@@ -1553,7 +1557,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
} }
else { else {
$attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); $attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']);
$attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); $attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']);
@@ -1576,6 +1580,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
$attr['acl_pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0;
} else { } else {
$_data['acl'] = (array)$_data['acl']; $_data['acl'] = (array)$_data['acl'];
$attr['acl_spam_alias'] = 0; $attr['acl_spam_alias'] = 0;
@@ -2104,7 +2109,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
return false; return false;
} }
// check if param is whitelisted // check if param is whitelisted
if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){ if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){
// bad option // bad option
@@ -2797,11 +2802,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
// check name // check name
if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){
// keep template name of Default template // keep template name of Default template
$_data["template"] = $is_now["template"]; $_data["template"] = $is_now["template"];
} }
else { else {
$_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"];
} }
// check attributes // check attributes
$attr = array(); $attr = array();
$attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array(); $attr['tags'] = (isset($_data['tags'])) ? $_data['tags'] : array();
@@ -2828,10 +2833,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
":id" => $id , ":id" => $id ,
":template" => $_data["template"] , ":template" => $_data["template"] ,
":attributes" => json_encode($attr) ":attributes" => json_encode($attr)
)); ));
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -2865,21 +2870,22 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
} }
if (!empty($is_now)) { if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
(int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); (int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
(int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']); (int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
(int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']); (int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
(int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']); (int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
(int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']); (int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
(int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']); (int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
(int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576); (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name']; $name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
$domain = $is_now['domain']; $domain = $is_now['domain'];
$quota_b = $quota_m * 1048576; $quota_b = $quota_m * 1048576;
$password = (!empty($_data['password'])) ? $_data['password'] : null; $password = (!empty($_data['password'])) ? $_data['password'] : null;
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
$tags = (is_array($_data['tags']) ? $_data['tags'] : array()); $pw_recovery_email = (isset($_data['pw_recovery_email'])) ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
$tags = (is_array($_data['tags']) ? $_data['tags'] : array());
} }
else { else {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -3132,31 +3138,43 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':address' => $username, ':address' => $username,
':active' => $active ':active' => $active
)); ));
$stmt = $pdo->prepare("UPDATE `mailbox` SET try {
`active` = :active, $stmt = $pdo->prepare("UPDATE `mailbox` SET
`name`= :name, `active` = :active,
`quota` = :quota_b, `name`= :name,
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update), `quota` = :quota_b,
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access), `attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access), `attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),
`attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access), `attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
`attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access), `attributes` = JSON_SET(`attributes`, '$.sieve_access', :sieve_access),
`attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost), `attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
`attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access) `attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),
WHERE `username` = :username"); `attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access),
$stmt->execute(array( `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
':active' => $active, WHERE `username` = :username");
':name' => $name, $stmt->execute(array(
':quota_b' => $quota_b, ':active' => $active,
':force_pw_update' => $force_pw_update, ':name' => $name,
':sogo_access' => $sogo_access, ':quota_b' => $quota_b,
':imap_access' => $imap_access, ':force_pw_update' => $force_pw_update,
':pop3_access' => $pop3_access, ':sogo_access' => $sogo_access,
':sieve_access' => $sieve_access, ':imap_access' => $imap_access,
':smtp_access' => $smtp_access, ':pop3_access' => $pop3_access,
':relayhost' => $relayhost, ':sieve_access' => $sieve_access,
':username' => $username ':smtp_access' => $smtp_access,
)); ':recovery_email' => $pw_recovery_email,
':relayhost' => $relayhost,
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
return false;
}
// save tags // save tags
foreach($tags as $index => $tag){ foreach($tags as $index => $tag){
if (empty($tag)) continue; if (empty($tag)) continue;
@@ -3174,7 +3192,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':tag_name' => $tag, ':tag_name' => $tag,
)); ));
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -3185,6 +3203,197 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
return true; return true;
break; break;
case 'mailbox_rename':
$domain = $_data['domain'];
$old_local_part = $_data['old_local_part'];
$old_username = $old_local_part . "@" . $domain;
$new_local_part = $_data['new_local_part'];
$new_username = $new_local_part . "@" . $domain;
$create_alias = intval($_data['create_alias']);
if (!filter_var($old_username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('username_invalid', $old_username)
);
return false;
}
if (!filter_var($new_username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('username_invalid', $new_username)
);
return false;
}
$is_now = mailbox('get', 'mailbox_details', $old_username);
if (empty($is_now)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $is_now['domain'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
// get imap acls
try {
$exec_fields = array(
'cmd' => 'doveadm',
'task' => 'get_acl',
'id' => $old_username
);
$imap_acls = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
// delete imap acls
foreach ($imap_acls as $imap_acl) {
$exec_fields = array(
'cmd' => 'doveadm',
'task' => 'delete_acl',
'user' => $imap_acl['user'],
'mailbox' => $imap_acl['mailbox'],
'id' => $imap_acl['id']
);
docker('post', 'dovecot-mailcow', 'exec', $exec_fields);
}
} catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
return false;
}
// rename username in sql
try {
$pdo->beginTransaction();
$pdo->exec('SET FOREIGN_KEY_CHECKS = 0');
// Update username in mailbox table
$pdo->prepare('UPDATE mailbox SET username = :new_username, local_part = :new_local_part WHERE username = :old_username')
->execute([
':new_username' => $new_username,
':new_local_part' => $new_local_part,
':old_username' => $old_username
]);
$pdo->prepare("UPDATE alias SET address = :new_username, goto = :new_username2 WHERE address = :old_username")
->execute([
':new_username' => $new_username,
':new_username2' => $new_username,
':old_username' => $old_username
]);
// Update the username in all related tables
$tables = [
'tags_mailbox' => ['username'],
'sieve_filters' => ['username'],
'app_passwd' => ['mailbox'],
'user_acl' => ['username'],
'da_acl' => ['username'],
'quota2' => ['username'],
'quota2replica' => ['username'],
'pushover' => ['username'],
'alias' => ['goto'],
"imapsync" => ['user2'],
'bcc_maps' => ['local_dest', 'bcc_dest'],
'recipient_maps' => ['old_dest', 'new_dest'],
'sender_acl' => ['logged_in_as', 'send_as']
];
foreach ($tables as $table => $columns) {
foreach ($columns as $column) {
$stmt = $pdo->prepare("UPDATE $table SET $column = :new_username WHERE $column = :old_username")
->execute([
':new_username' => $new_username,
':old_username' => $old_username
]);
}
}
// Update c_uid, c_name and mail in _sogo_static_view table
$pdo->prepare("UPDATE _sogo_static_view SET c_uid = :new_username, c_name = :new_username2, mail = :new_username3 WHERE c_uid = :old_username")
->execute([
':new_username' => $new_username,
':new_username2' => $new_username,
':new_username3' => $new_username,
':old_username' => $old_username
]);
// Re-enable foreign key checks
$pdo->exec('SET FOREIGN_KEY_CHECKS = 1');
$pdo->commit();
} catch (PDOException $e) {
// Rollback the transaction if something goes wrong
$pdo->rollBack();
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
return false;
}
// move maildir
$exec_fields = array(
'cmd' => 'maildir',
'task' => 'move',
'old_maildir' => $domain . '/' . $old_local_part,
'new_maildir' => $domain . '/' . $new_local_part
);
docker('post', 'dovecot-mailcow', 'exec', $exec_fields);
// rename username in sogo
$exec_fields = array(
'cmd' => 'sogo',
'task' => 'rename_user',
'old_username' => $old_username,
'new_username' => $new_username
);
docker('post', 'sogo-mailcow', 'exec', $exec_fields);
// set imap acls
foreach ($imap_acls as $imap_acl) {
$user_id = ($imap_acl['id'] == $old_username) ? $new_username : $imap_acl['id'];
$user = ($imap_acl['user'] == $old_username) ? $new_username : $imap_acl['user'];
$exec_fields = array(
'cmd' => 'doveadm',
'task' => 'set_acl',
'user' => $user,
'mailbox' => $imap_acl['mailbox'],
'id' => $user_id,
'rights' => $imap_acl['rights']
);
docker('post', 'dovecot-mailcow', 'exec', $exec_fields);
}
// create alias
if ($create_alias == 1) {
mailbox("add", "alias", array(
"address" => $old_username,
"goto" => $new_username,
"active" => 1,
"sogo_visible" => 1,
"private_comment" => sprintf($lang['success']['mailbox_renamed'], $old_username, $new_username)
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_renamed', $old_username, $new_username)
);
break;
case 'mailbox_templates': case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") { if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -3217,11 +3426,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
// check name // check name
if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){ if ($is_now["template"] == "Default" && $is_now["template"] != $_data["template"]){
// keep template name of Default template // keep template name of Default template
$_data["template"] = $is_now["template"]; $_data["template"] = $is_now["template"];
} }
else { else {
$_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"]; $_data["template"] = (isset($_data["template"])) ? $_data["template"] : $is_now["template"];
} }
// check attributes // check attributes
$attr = array(); $attr = array();
$attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0; $attr["quota"] = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
@@ -3241,11 +3450,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0;
$attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0;
$attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0;
} }
else { else {
foreach ($is_now as $key => $value){ foreach ($is_now as $key => $value){
$attr[$key] = $is_now[$key]; $attr[$key] = $is_now[$key];
} }
} }
if (isset($_data['acl'])) { if (isset($_data['acl'])) {
$_data['acl'] = (array)$_data['acl']; $_data['acl'] = (array)$_data['acl'];
@@ -3263,10 +3472,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; $attr['acl_quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0;
$attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; $attr['acl_quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0;
$attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; $attr['acl_app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0;
} else { $attr['acl_pw_reset'] = (in_array('pw_reset', $_data['acl'])) ? 1 : 0;
} else {
foreach ($is_now as $key => $value){ foreach ($is_now as $key => $value){
$attr[$key] = $is_now[$key]; $attr[$key] = $is_now[$key];
} }
} }
@@ -3278,7 +3488,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
":id" => $id , ":id" => $id ,
":template" => $_data["template"] , ":template" => $_data["template"] ,
":attributes" => json_encode($attr) ":attributes" => json_encode($attr)
)); ));
} }
@@ -3307,7 +3517,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
$is_now = mailbox('get', 'mailbox_details', $mailbox); $is_now = mailbox('get', 'mailbox_details', $mailbox);
if(!empty($is_now)){ if(!empty($is_now)){
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $is_now['domain'])) { if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $is_now['domain'])) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -3334,15 +3544,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt->execute(array( $stmt->execute(array(
":username" => $mailbox, ":username" => $mailbox,
":custom_attributes" => json_encode($attributes) ":custom_attributes" => json_encode($attributes)
)); ));
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $mailbox) 'msg' => array('mailbox_modified', $mailbox)
); );
} }
return true; return true;
break; break;
case 'resource': case 'resource':
@@ -3424,7 +3634,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
} }
break; break;
case 'domain_wide_footer': case 'domain_wide_footer':
if (!is_array($_data['domains'])) { if (!is_array($_data['domains'])) {
$domains = array(); $domains = array();
$domains[] = $_data['domains']; $domains[] = $_data['domains'];
@@ -3677,7 +3887,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
// prepend domain to array // prepend domain to array
$params = array(); $params = array();
foreach ($tags as $key => $val){ foreach ($tags as $key => $val){
array_push($params, '%'.$_data.'%'); array_push($params, '%'.$_data.'%');
array_push($params, '%'.$val.'%'); array_push($params, '%'.$val.'%');
} }
@@ -3686,7 +3896,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) { while($row = array_shift($rows)) {
if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], explode('@', $row['username'])[1])) if (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], explode('@', $row['username'])[1]))
$mailboxes[] = $row['username']; $mailboxes[] = $row['username'];
} }
} }
@@ -4241,7 +4451,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
while($row = array_shift($rows)) { while($row = array_shift($rows)) {
if ($_SESSION['mailcow_cc_role'] == "admin") if ($_SESSION['mailcow_cc_role'] == "admin")
$domains[] = $row['domain']; $domains[] = $row['domain'];
elseif (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['domain'])) elseif (hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['domain']))
$domains[] = $row['domain']; $domains[] = $row['domain'];
} }
} else { } else {
@@ -4401,19 +4611,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
$_data = (isset($_data)) ? intval($_data) : null; $_data = (isset($_data)) ? intval($_data) : null;
if (isset($_data)){ if (isset($_data)){
$stmt = $pdo->prepare("SELECT * FROM `templates` $stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `id` = :id AND type = :type"); WHERE `id` = :id AND type = :type");
$stmt->execute(array( $stmt->execute(array(
":id" => $_data, ":id" => $_data,
":type" => "domain" ":type" => "domain"
)); ));
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){ if (empty($row)){
return false; return false;
} }
$row["attributes"] = json_decode($row["attributes"], true); $row["attributes"] = json_decode($row["attributes"], true);
return $row; return $row;
} }
@@ -4421,11 +4631,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'domain'"); $stmt = $pdo->prepare("SELECT * FROM `templates` WHERE `type` = 'domain'");
$stmt->execute(); $stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($rows)){ if (empty($rows)){
return false; return false;
} }
foreach($rows as $key => $row){ foreach($rows as $key => $row){
$rows[$key]["attributes"] = json_decode($row["attributes"], true); $rows[$key]["attributes"] = json_decode($row["attributes"], true);
} }
@@ -4527,6 +4737,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
else if ($SaslLogs['service'] == 'pop3') { else if ($SaslLogs['service'] == 'pop3') {
$last_pop3_login = strtotime($SaslLogs['datetime']); $last_pop3_login = strtotime($SaslLogs['datetime']);
}
else if ($SaslLogs['service'] == 'SSO') {
$last_sso_login = strtotime($SaslLogs['datetime']);
} }
} }
if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { if (!isset($last_imap_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
@@ -4537,10 +4750,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) { if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_pop3_login = 0; $last_pop3_login = 0;
}
if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_sso_login = 0;
} }
$mailboxdata['last_imap_login'] = $last_imap_login; $mailboxdata['last_imap_login'] = $last_imap_login;
$mailboxdata['last_smtp_login'] = $last_smtp_login; $mailboxdata['last_smtp_login'] = $last_smtp_login;
$mailboxdata['last_pop3_login'] = $last_pop3_login; $mailboxdata['last_pop3_login'] = $last_pop3_login;
$mailboxdata['last_sso_login'] = $last_sso_login;
if (!isset($_extra) || $_extra != 'reduced') { if (!isset($_extra) || $_extra != 'reduced') {
$rl = ratelimit('get', 'mailbox', $_data); $rl = ratelimit('get', 'mailbox', $_data);
@@ -4591,19 +4808,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
$_data = (isset($_data)) ? intval($_data) : null; $_data = (isset($_data)) ? intval($_data) : null;
if (isset($_data)){ if (isset($_data)){
$stmt = $pdo->prepare("SELECT * FROM `templates` $stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `id` = :id AND type = :type"); WHERE `id` = :id AND type = :type");
$stmt->execute(array( $stmt->execute(array(
":id" => $_data, ":id" => $_data,
":type" => "mailbox" ":type" => "mailbox"
)); ));
$row = $stmt->fetch(PDO::FETCH_ASSOC); $row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)){ if (empty($row)){
return false; return false;
} }
$row["attributes"] = json_decode($row["attributes"], true); $row["attributes"] = json_decode($row["attributes"], true);
return $row; return $row;
} }
@@ -5045,7 +5262,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$ids = $_data['ids']; $ids = $_data['ids'];
} }
foreach ($ids as $id) { foreach ($ids as $id) {
// delete template // delete template
$stmt = $pdo->prepare("DELETE FROM `templates` $stmt = $pdo->prepare("DELETE FROM `templates`
@@ -5212,7 +5429,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => 'Could not move maildir to garbage collector: variables local_part and/or domain empty' 'msg' => 'Could not move maildir to garbage collector: variables local_part and/or domain empty'
); );
} }
if (strtolower(getenv('SKIP_SOLR')) == 'n') { if (strtolower(getenv('SKIP_SOLR')) == 'n' && strtolower(getenv('FLATCURVE_EXPERIMENTAL')) != 'y') {
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot-fts/update?commit=true'); curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot-fts/update?commit=true');
curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml')); curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml'));
@@ -5358,7 +5575,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
update_sogo_static_view($username); update_sogo_static_view($username);
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
@@ -5385,7 +5602,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$ids = $_data['ids']; $ids = $_data['ids'];
} }
foreach ($ids as $id) { foreach ($ids as $id) {
// delete template // delete template
$stmt = $pdo->prepare("DELETE FROM `templates` $stmt = $pdo->prepare("DELETE FROM `templates`
@@ -5394,7 +5611,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
":id" => $id, ":id" => $id,
":type" => "mailbox", ":type" => "mailbox",
":template" => "Default" ":template" => "Default"
)); ));
} }
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@@ -5468,7 +5685,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
} }
break; break;
case 'tags_domain': case 'tags_domain':
if (!is_array($_data['domain'])) { if (!is_array($_data['domain'])) {
$domains = array(); $domains = array();
$domains[] = $_data['domain']; $domains[] = $_data['domain'];
@@ -5481,7 +5698,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$wasModified = false; $wasModified = false;
foreach ($domains as $domain) { foreach ($domains as $domain) {
if (!is_valid_domain_name($domain)) { if (!is_valid_domain_name($domain)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@@ -5498,7 +5715,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
return false; return false;
} }
foreach($tags as $tag){ foreach($tags as $tag){
// delete tag // delete tag
$wasModified = true; $wasModified = true;
@@ -5553,7 +5770,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
// delete tags // delete tags
foreach($tags as $tag){ foreach($tags as $tag){
$wasModified = true; $wasModified = true;
$stmt = $pdo->prepare("DELETE FROM `tags_mailbox` WHERE `username` = :username AND `tag_name` = :tag_name"); $stmt = $pdo->prepare("DELETE FROM `tags_mailbox` WHERE `username` = :username AND `tag_name` = :tag_name");
$stmt->execute(array( $stmt->execute(array(
':username' => $username, ':username' => $username,
+15 -1
View File
@@ -3,7 +3,7 @@ function init_db_schema() {
try { try {
global $pdo; global $pdo;
$db_version = "26022024_1433"; $db_version = "29072024_1000";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -483,6 +483,7 @@ function init_db_schema() {
"quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'", "quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'",
"quarantine_category" => "TINYINT(1) NOT NULL DEFAULT '1'", "quarantine_category" => "TINYINT(1) NOT NULL DEFAULT '1'",
"app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'", "app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'",
"pw_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
), ),
"keys" => array( "keys" => array(
"primary" => array( "primary" => array(
@@ -694,6 +695,19 @@ function init_db_schema() {
), ),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
), ),
"reset_password" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",
"token" => "VARCHAR(255) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
),
"keys" => array(
"primary" => array(
"" => array("token", "created")
),
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"imapsync" => array( "imapsync" => array(
"cols" => array( "cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT", "id" => "INT NOT NULL AUTO_INCREMENT",
+162 -16
View File
@@ -1039,6 +1039,73 @@
}, },
"time": "2017-04-19T22:01:50+00:00" "time": "2017-04-19T22:01:50+00:00"
}, },
{
"name": "symfony/deprecation-contracts",
"version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-04-18T09:32:20+00:00"
},
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.24.0", "version": "v1.24.0",
@@ -1287,6 +1354,82 @@
], ],
"time": "2021-09-13T13:58:33+00:00" "time": "2021-09-13T13:58:33+00:00"
}, },
{
"name": "symfony/polyfill-php81",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{ {
"name": "symfony/translation", "name": "symfony/translation",
"version": "v6.0.5", "version": "v6.0.5",
@@ -1604,34 +1747,37 @@
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v3.4.3", "version": "v3.14.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/Twig.git", "url": "https://github.com/twigphp/Twig.git",
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58" "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/c38fd6b0b7f370c198db91ffd02e23b517426b58", "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58", "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.2.5", "php": ">=8.0.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8", "symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3" "symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php81": "^1.29"
}, },
"require-dev": { "require-dev": {
"psr/container": "^1.0", "psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
}, },
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"autoload": { "autoload": {
"files": [
"src/Resources/core.php",
"src/Resources/debug.php",
"src/Resources/escaper.php",
"src/Resources/string_loader.php"
],
"psr-4": { "psr-4": {
"Twig\\": "src/" "Twig\\": "src/"
} }
@@ -1664,7 +1810,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/twigphp/Twig/issues", "issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.4.3" "source": "https://github.com/twigphp/Twig/tree/v3.14.0"
}, },
"funding": [ "funding": [
{ {
@@ -1676,7 +1822,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-09-28T08:42:51+00:00" "time": "2024-09-09T17:55:12+00:00"
}, },
{ {
"name": "yubico/u2flib-server", "name": "yubico/u2flib-server",
@@ -1728,5 +1874,5 @@
"prefer-lowest": false, "prefer-lowest": false,
"platform": [], "platform": [],
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.3.0" "plugin-api-version": "2.6.0"
} }
@@ -0,0 +1,33 @@
<?xml version='1.0' standalone='yes'?>
<extension name="enotify">
<command name="notify">
<parameter type="tag" name="from" occurrence="optional">
<parameter type="string" name="from-address" />
</parameter>
<parameter type="tag" name="importance" regex="(1|2|3)" occurrence="optional" />
<parameter type="tag" name="options" occurrence="optional">
<parameter type="stringlist" name="option-strings" />
</parameter>
<parameter type="tag" name="message" occurrence="optional">
<parameter type="string" name="message-text" />
</parameter>
<parameter type="string" name="method" />
</command>
<test name="valid_notify_method">
<parameter type="stringlist" name="notification-uris" />
</test>
<test name="notify_method_capability">
<parameter type="string" name="notification-uri" />
<parameter type="string" name="notification-capability" />
<parameter type="stringlist" name="key-list" />
</test>
<modifier name="encodeurl" />
</extension>
@@ -0,0 +1,58 @@
<?xml version='1.0' standalone='yes'?>
<extension name="mime">
<command name="foreverypart">
<parameter type="string" name="name" occurrence="optional" />
<block />
</command>
<command name="break">
<parameter type="string" name="name" occurrence="optional" />
</command>
<tagged-argument extends="(header|address|exists)">
<parameter type="tag" name="mime" regex="mime" occurrence="optional" />
</tagged-argument>
<tagged-argument extends="(header|address|exists)">
<parameter type="tag" name="anychild" regex="anychild" occurrence="optional" />
</tagged-argument>
<tagged-argument extends="(header)">
<parameter type="tag" name="type" occurrence="optional" />
</tagged-argument>
<tagged-argument extends="(header)">
<parameter type="tag" name="subtype" occurrence="optional" />
</tagged-argument>
<tagged-argument extends="(header)">
<parameter type="tag" name="contenttype" occurrence="optional" />
</tagged-argument>
<tagged-argument extends="(header)">
<parameter type="tag" name="param" regex="param" occurrence="optional">
<parameter type="stringlist" name="param-list" />
</parameter>
</tagged-argument>
<tagged-argument extends="(header|address|exists)">
<parameter type="stringlist" name="header-names" />
</tagged-argument>
<tagged-argument extends="(header)">
<parameter type="stringlist" name="key-list" />
</tagged-argument>
<action name="replace">
<parameter type="tag" name="mime" regex="mime" occurrence="optional" />
<parameter type="string" name="subject" occurrence="optional" />
<parameter type="string" name="from" occurrence="optional" />
<parameter type="string" name="replacement" />
</action>
<action name="enclose">
<parameter type="string" name="subject" occurrence="optional" />
<parameter type="stringlist" name="headers" occurrence="optional" />
<parameter type="string" name="text" />
</action>
<action name="extracttext">
<parameter type="tag" name="first" regex="first" occurrence="optional" />
<parameter type="number" name="number" occurrence="optional" />
<parameter type="string" name="varname" />
</action>
</extension>
+15 -2
View File
@@ -3,8 +3,21 @@
// autoload.php @generated by Composer // autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) { if (PHP_VERSION_ID < 50600) {
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; if (!headers_sent()) {
exit(1); header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
} }
require_once __DIR__ . '/composer/autoload_real.php'; require_once __DIR__ . '/composer/autoload_real.php';
+72 -65
View File
@@ -42,35 +42,37 @@ namespace Composer\Autoload;
*/ */
class ClassLoader class ClassLoader
{ {
/** @var ?string */ /** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir; private $vendorDir;
// PSR-4 // PSR-4
/** /**
* @var array[] * @var array<string, array<string, int>>
* @psalm-var array<string, array<string, int>>
*/ */
private $prefixLengthsPsr4 = array(); private $prefixLengthsPsr4 = array();
/** /**
* @var array[] * @var array<string, list<string>>
* @psalm-var array<string, array<int, string>>
*/ */
private $prefixDirsPsr4 = array(); private $prefixDirsPsr4 = array();
/** /**
* @var array[] * @var list<string>
* @psalm-var array<string, string>
*/ */
private $fallbackDirsPsr4 = array(); private $fallbackDirsPsr4 = array();
// PSR-0 // PSR-0
/** /**
* @var array[] * List of PSR-0 prefixes
* @psalm-var array<string, array<string, string[]>> *
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/ */
private $prefixesPsr0 = array(); private $prefixesPsr0 = array();
/** /**
* @var array[] * @var list<string>
* @psalm-var array<string, string>
*/ */
private $fallbackDirsPsr0 = array(); private $fallbackDirsPsr0 = array();
@@ -78,8 +80,7 @@ class ClassLoader
private $useIncludePath = false; private $useIncludePath = false;
/** /**
* @var string[] * @var array<string, string>
* @psalm-var array<string, string>
*/ */
private $classMap = array(); private $classMap = array();
@@ -87,29 +88,29 @@ class ClassLoader
private $classMapAuthoritative = false; private $classMapAuthoritative = false;
/** /**
* @var bool[] * @var array<string, bool>
* @psalm-var array<string, bool>
*/ */
private $missingClasses = array(); private $missingClasses = array();
/** @var ?string */ /** @var string|null */
private $apcuPrefix; private $apcuPrefix;
/** /**
* @var self[] * @var array<string, self>
*/ */
private static $registeredLoaders = array(); private static $registeredLoaders = array();
/** /**
* @param ?string $vendorDir * @param string|null $vendorDir
*/ */
public function __construct($vendorDir = null) public function __construct($vendorDir = null)
{ {
$this->vendorDir = $vendorDir; $this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
} }
/** /**
* @return string[] * @return array<string, list<string>>
*/ */
public function getPrefixes() public function getPrefixes()
{ {
@@ -121,8 +122,7 @@ class ClassLoader
} }
/** /**
* @return array[] * @return array<string, list<string>>
* @psalm-return array<string, array<int, string>>
*/ */
public function getPrefixesPsr4() public function getPrefixesPsr4()
{ {
@@ -130,8 +130,7 @@ class ClassLoader
} }
/** /**
* @return array[] * @return list<string>
* @psalm-return array<string, string>
*/ */
public function getFallbackDirs() public function getFallbackDirs()
{ {
@@ -139,8 +138,7 @@ class ClassLoader
} }
/** /**
* @return array[] * @return list<string>
* @psalm-return array<string, string>
*/ */
public function getFallbackDirsPsr4() public function getFallbackDirsPsr4()
{ {
@@ -148,8 +146,7 @@ class ClassLoader
} }
/** /**
* @return string[] Array of classname => path * @return array<string, string> Array of classname => path
* @psalm-return array<string, string>
*/ */
public function getClassMap() public function getClassMap()
{ {
@@ -157,8 +154,7 @@ class ClassLoader
} }
/** /**
* @param string[] $classMap Class to filename map * @param array<string, string> $classMap Class to filename map
* @psalm-param array<string, string> $classMap
* *
* @return void * @return void
*/ */
@@ -175,24 +171,25 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given prefix, either * Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix. * appending or prepending to the ones previously set for this prefix.
* *
* @param string $prefix The prefix * @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories * @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories * @param bool $prepend Whether to prepend the directories
* *
* @return void * @return void
*/ */
public function add($prefix, $paths, $prepend = false) public function add($prefix, $paths, $prepend = false)
{ {
$paths = (array) $paths;
if (!$prefix) { if (!$prefix) {
if ($prepend) { if ($prepend) {
$this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0 = array_merge(
(array) $paths, $paths,
$this->fallbackDirsPsr0 $this->fallbackDirsPsr0
); );
} else { } else {
$this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0, $this->fallbackDirsPsr0,
(array) $paths $paths
); );
} }
@@ -201,19 +198,19 @@ class ClassLoader
$first = $prefix[0]; $first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) { if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths; $this->prefixesPsr0[$first][$prefix] = $paths;
return; return;
} }
if ($prepend) { if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths, $paths,
$this->prefixesPsr0[$first][$prefix] $this->prefixesPsr0[$first][$prefix]
); );
} else { } else {
$this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix], $this->prefixesPsr0[$first][$prefix],
(array) $paths $paths
); );
} }
} }
@@ -222,9 +219,9 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given namespace, either * Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace. * appending or prepending to the ones previously set for this namespace.
* *
* @param string $prefix The prefix/namespace, with trailing '\\' * @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories * @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories * @param bool $prepend Whether to prepend the directories
* *
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* *
@@ -232,17 +229,18 @@ class ClassLoader
*/ */
public function addPsr4($prefix, $paths, $prepend = false) public function addPsr4($prefix, $paths, $prepend = false)
{ {
$paths = (array) $paths;
if (!$prefix) { if (!$prefix) {
// Register directories for the root namespace. // Register directories for the root namespace.
if ($prepend) { if ($prepend) {
$this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4 = array_merge(
(array) $paths, $paths,
$this->fallbackDirsPsr4 $this->fallbackDirsPsr4
); );
} else { } else {
$this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4, $this->fallbackDirsPsr4,
(array) $paths $paths
); );
} }
} elseif (!isset($this->prefixDirsPsr4[$prefix])) { } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
@@ -252,18 +250,18 @@ class ClassLoader
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
} }
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths; $this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) { } elseif ($prepend) {
// Prepend directories for an already registered namespace. // Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths, $paths,
$this->prefixDirsPsr4[$prefix] $this->prefixDirsPsr4[$prefix]
); );
} else { } else {
// Append directories for an already registered namespace. // Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix], $this->prefixDirsPsr4[$prefix],
(array) $paths $paths
); );
} }
} }
@@ -272,8 +270,8 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given prefix, * Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix. * replacing any others previously set for this prefix.
* *
* @param string $prefix The prefix * @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories * @param list<string>|string $paths The PSR-0 base directories
* *
* @return void * @return void
*/ */
@@ -290,8 +288,8 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given namespace, * Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace. * replacing any others previously set for this namespace.
* *
* @param string $prefix The prefix/namespace, with trailing '\\' * @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories * @param list<string>|string $paths The PSR-4 base directories
* *
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* *
@@ -425,7 +423,8 @@ class ClassLoader
public function loadClass($class) public function loadClass($class)
{ {
if ($file = $this->findFile($class)) { if ($file = $this->findFile($class)) {
includeFile($file); $includeFile = self::$includeFile;
$includeFile($file);
return true; return true;
} }
@@ -476,9 +475,9 @@ class ClassLoader
} }
/** /**
* Returns the currently registered loaders indexed by their corresponding vendor directories. * Returns the currently registered loaders keyed by their corresponding vendor directories.
* *
* @return self[] * @return array<string, self>
*/ */
public static function getRegisteredLoaders() public static function getRegisteredLoaders()
{ {
@@ -555,18 +554,26 @@ class ClassLoader
return false; return false;
} }
}
/** /**
* Scope isolated include. * @return void
* */
* Prevents access to $this/self from included files. private static function initializeIncludeClosure()
* {
* @param string $file if (self::$includeFile !== null) {
* @return void return;
* @private }
*/
function includeFile($file) /**
{ * Scope isolated include.
include $file; *
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
} }
+12 -5
View File
@@ -98,7 +98,7 @@ class InstalledVersions
{ {
foreach (self::getInstalled() as $installed) { foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) { if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
} }
} }
@@ -119,7 +119,7 @@ class InstalledVersions
*/ */
public static function satisfies(VersionParser $parser, $packageName, $constraint) public static function satisfies(VersionParser $parser, $packageName, $constraint)
{ {
$constraint = $parser->parseConstraints($constraint); $constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint); return $provided->matches($constraint);
@@ -328,7 +328,9 @@ class InstalledVersions
if (isset(self::$installedByVendor[$vendorDir])) { if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir]; $installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) { } elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1]; self::$installed = $installed[count($installed) - 1];
} }
@@ -340,12 +342,17 @@ class InstalledVersions
// only require the installed.php file if this file is loaded from its dumped location, // only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') { if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php'; /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else { } else {
self::$installed = array(); self::$installed = array();
} }
} }
$installed[] = self::$installed;
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed; return $installed;
} }
@@ -7,7 +7,9 @@ $baseDir = dirname($vendorDir);
return array( return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'CURLStringFile' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+6
View File
@@ -10,8 +10,14 @@ return array(
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php',
'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php', 'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php',
'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php', 'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php',
'04c6c5c2f7095ccf6c481d3e53e1776f' => $vendorDir . '/mustangostang/spyc/Spyc.php', '04c6c5c2f7095ccf6c481d3e53e1776f' => $vendorDir . '/mustangostang/spyc/Spyc.php',
'89efb1254ef2d1c5d80096acd12c4098' => $vendorDir . '/twig/twig/src/Resources/core.php',
'ffecb95d45175fd40f75be8a23b34f90' => $vendorDir . '/twig/twig/src/Resources/debug.php',
'c7baa00073ee9c61edf148c51917cfb4' => $vendorDir . '/twig/twig/src/Resources/escaper.php',
'f844ccf1d25df8663951193c3fc307c8' => $vendorDir . '/twig/twig/src/Resources/string_loader.php',
); );
+1
View File
@@ -8,6 +8,7 @@ $baseDir = dirname($vendorDir);
return array( return array(
'Twig\\' => array($vendorDir . '/twig/twig/src'), 'Twig\\' => array($vendorDir . '/twig/twig/src'),
'Tightenco\\Collect\\' => array($vendorDir . '/tightenco/collect/src/Collect'), 'Tightenco\\Collect\\' => array($vendorDir . '/tightenco/collect/src/Collect'),
'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
+10 -17
View File
@@ -33,25 +33,18 @@ class ComposerAutoloaderInit873464e4bd965a3168f133248b1b218b
$loader->register(true); $loader->register(true);
$includeFiles = \Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::$files; $filesToLoad = \Composer\Autoload\ComposerStaticInit873464e4bd965a3168f133248b1b218b::$files;
foreach ($includeFiles as $fileIdentifier => $file) { $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
composerRequire873464e4bd965a3168f133248b1b218b($fileIdentifier, $file); if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
} }
return $loader; return $loader;
} }
} }
/**
* @param string $fileIdentifier
* @param string $file
* @return void
*/
function composerRequire873464e4bd965a3168f133248b1b218b($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}
+13
View File
@@ -11,10 +11,16 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php', 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php',
'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php', 'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php',
'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php', 'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php',
'04c6c5c2f7095ccf6c481d3e53e1776f' => __DIR__ . '/..' . '/mustangostang/spyc/Spyc.php', '04c6c5c2f7095ccf6c481d3e53e1776f' => __DIR__ . '/..' . '/mustangostang/spyc/Spyc.php',
'89efb1254ef2d1c5d80096acd12c4098' => __DIR__ . '/..' . '/twig/twig/src/Resources/core.php',
'ffecb95d45175fd40f75be8a23b34f90' => __DIR__ . '/..' . '/twig/twig/src/Resources/debug.php',
'c7baa00073ee9c61edf148c51917cfb4' => __DIR__ . '/..' . '/twig/twig/src/Resources/escaper.php',
'f844ccf1d25df8663951193c3fc307c8' => __DIR__ . '/..' . '/twig/twig/src/Resources/string_loader.php',
); );
public static $prefixLengthsPsr4 = array ( public static $prefixLengthsPsr4 = array (
@@ -25,6 +31,7 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
), ),
'S' => 'S' =>
array ( array (
'Symfony\\Polyfill\\Php81\\' => 23,
'Symfony\\Polyfill\\Php80\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Ctype\\' => 23, 'Symfony\\Polyfill\\Ctype\\' => 23,
@@ -80,6 +87,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
array ( array (
0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect', 0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect',
), ),
'Symfony\\Polyfill\\Php81\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php81',
),
'Symfony\\Polyfill\\Php80\\' => 'Symfony\\Polyfill\\Php80\\' =>
array ( array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80', 0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
@@ -170,7 +181,9 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
public static $classMap = array ( public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'CURLStringFile' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/CURLStringFile.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+168 -16
View File
@@ -1068,6 +1068,76 @@
], ],
"install-path": "../soundasleep/html2text" "install-path": "../soundasleep/html2text"
}, },
{
"name": "symfony/deprecation-contracts",
"version": "v3.5.0",
"version_normalized": "3.5.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"time": "2024-04-18T09:32:20+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/deprecation-contracts"
},
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.24.0", "version": "v1.24.0",
@@ -1325,6 +1395,85 @@
], ],
"install-path": "../symfony/polyfill-php80" "install-path": "../symfony/polyfill-php80"
}, },
{
"name": "symfony/polyfill-php81",
"version": "v1.31.0",
"version_normalized": "1.31.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"time": "2024-09-09T11:45:10+00:00",
"type": "library",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-php81"
},
{ {
"name": "symfony/translation", "name": "symfony/translation",
"version": "v6.0.5", "version": "v6.0.5",
@@ -1654,37 +1803,40 @@
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v3.4.3", "version": "v3.14.0",
"version_normalized": "3.4.3.0", "version_normalized": "3.14.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/Twig.git", "url": "https://github.com/twigphp/Twig.git",
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58" "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/c38fd6b0b7f370c198db91ffd02e23b517426b58", "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58", "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.2.5", "php": ">=8.0.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8", "symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3" "symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php81": "^1.29"
}, },
"require-dev": { "require-dev": {
"psr/container": "^1.0", "psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
}, },
"time": "2022-09-28T08:42:51+00:00", "time": "2024-09-09T17:55:12+00:00",
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
},
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {
"files": [
"src/Resources/core.php",
"src/Resources/debug.php",
"src/Resources/escaper.php",
"src/Resources/string_loader.php"
],
"psr-4": { "psr-4": {
"Twig\\": "src/" "Twig\\": "src/"
} }
@@ -1717,7 +1869,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/twigphp/Twig/issues", "issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.4.3" "source": "https://github.com/twigphp/Twig/tree/v3.14.0"
}, },
"funding": [ "funding": [
{ {
+23 -5
View File
@@ -3,7 +3,7 @@
'name' => '__root__', 'name' => '__root__',
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => '8e0b1d8aee4af02311692cb031695cc2ac3850fd', 'reference' => '220fdbb168792c07493db330d898b345cc902055',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@@ -13,7 +13,7 @@
'__root__' => array( '__root__' => array(
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => '8e0b1d8aee4af02311692cb031695cc2ac3850fd', 'reference' => '220fdbb168792c07493db330d898b345cc902055',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@@ -175,6 +175,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.5.0',
'version' => '3.5.0.0',
'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array( 'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.24.0', 'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0', 'version' => '1.24.0.0',
@@ -202,6 +211,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'symfony/polyfill-php81' => array(
'pretty_version' => 'v1.31.0',
'version' => '1.31.0.0',
'reference' => '4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php81',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/translation' => array( 'symfony/translation' => array(
'pretty_version' => 'v6.0.5', 'pretty_version' => 'v6.0.5',
'version' => '6.0.5.0', 'version' => '6.0.5.0',
@@ -245,9 +263,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'twig/twig' => array( 'twig/twig' => array(
'pretty_version' => 'v3.4.3', 'pretty_version' => 'v3.14.0',
'version' => '3.4.3.0', 'version' => '3.14.0.0',
'reference' => 'c38fd6b0b7f370c198db91ffd02e23b517426b58', 'reference' => '126b2c97818dbff0cdf3fbfc881aedb3d40aae72',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../twig/twig', 'install_path' => __DIR__ . '/../twig/twig',
'aliases' => array(), 'aliases' => array(),
+2 -2
View File
@@ -4,8 +4,8 @@
$issues = array(); $issues = array();
if (!(PHP_VERSION_ID >= 80002)) { if (!(PHP_VERSION_ID >= 80100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.2". You are running ' . PHP_VERSION . '.'; $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
} }
if ($issues) { if ($issues) {
@@ -0,0 +1,5 @@
CHANGELOG
=========
The changelog is maintained for all Symfony contracts at the following URL:
https://github.com/symfony/contracts/blob/main/CHANGELOG.md
@@ -0,0 +1,19 @@
Copyright (c) 2020-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,26 @@
Symfony Deprecation Contracts
=============================
A generic function and convention to trigger deprecation notices.
This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices.
By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component,
the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments.
The function requires at least 3 arguments:
- the name of the Composer package that is triggering the deprecation
- the version of the package that introduced the deprecation
- the message of the deprecation
- more arguments can be provided: they will be inserted in the message using `printf()` formatting
Example:
```php
trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin');
```
This will generate the following message:
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
While not recommended, the deprecation notices can be completely ignored by declaring an empty
`function trigger_deprecation() {}` in your application.
@@ -0,0 +1,35 @@
{
"name": "symfony/deprecation-contracts",
"type": "library",
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=8.1"
},
"autoload": {
"files": [
"function.php"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
}
}
@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!function_exists('trigger_deprecation')) {
/**
* Triggers a silenced deprecation notice.
*
* @param string $package The name of the Composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The message of the deprecation
* @param mixed ...$args Values to insert in the message using printf() formatting
*
* @author Nicolas Grekas <p@tchwork.com>
*/
function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void
{
@trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
}
}
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2021-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Php81;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Php81
{
public static function array_is_list(array $array): bool
{
if ([] === $array || $array === array_values($array)) {
return true;
}
$nextKey = -1;
foreach ($array as $k => $v) {
if ($k !== ++$nextKey) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,18 @@
Symfony Polyfill / Php81
========================
This component provides features added to PHP 8.1 core:
- [`array_is_list`](https://php.net/array_is_list)
- [`enum_exists`](https://php.net/enum-exists)
- [`MYSQLI_REFRESH_REPLICA`](https://php.net/mysqli.constants#constantmysqli-refresh-replica) constant
- [`ReturnTypeWillChange`](https://wiki.php.net/rfc/internal_method_return_types)
- [`CURLStringFile`](https://php.net/CURLStringFile) (but only if PHP >= 7.4 is used)
More information can be found in the
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
License
=======
This library is released under the [MIT license](LICENSE).
@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (\PHP_VERSION_ID >= 70400 && extension_loaded('curl')) {
/**
* @property string $data
*/
class CURLStringFile extends CURLFile
{
private $data;
public function __construct(string $data, string $postname, string $mime = 'application/octet-stream')
{
$this->data = $data;
parent::__construct('data://application/octet-stream;base64,'.base64_encode($data), $mime, $postname);
}
public function __set(string $name, $value): void
{
if ('data' !== $name) {
$this->$name = $value;
return;
}
if (is_object($value) ? !method_exists($value, '__toString') : !is_scalar($value)) {
throw new TypeError('Cannot assign '.gettype($value).' to property CURLStringFile::$data of type string');
}
$this->name = 'data://application/octet-stream;base64,'.base64_encode($value);
}
public function __isset(string $name): bool
{
return isset($this->$name);
}
public function &__get(string $name)
{
return $this->$name;
}
}
}
@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (\PHP_VERSION_ID < 80100) {
#[Attribute(Attribute::TARGET_METHOD)]
final class ReturnTypeWillChange
{
public function __construct()
{
}
}
}
@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Php81 as p;
if (\PHP_VERSION_ID >= 80100) {
return;
}
if (defined('MYSQLI_REFRESH_SLAVE') && !defined('MYSQLI_REFRESH_REPLICA')) {
define('MYSQLI_REFRESH_REPLICA', 64);
}
if (!function_exists('array_is_list')) {
function array_is_list(array $array): bool { return p\Php81::array_is_list($array); }
}
if (!function_exists('enum_exists')) {
function enum_exists(string $enum, bool $autoload = true): bool { return $autoload && class_exists($enum) && false; }
}
@@ -0,0 +1,33 @@
{
"name": "symfony/polyfill-php81",
"type": "library",
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"keywords": ["polyfill", "shim", "compatibility", "portable"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2"
},
"autoload": {
"psr-4": { "Symfony\\Polyfill\\Php81\\": "" },
"files": [ "bootstrap.php" ],
"classmap": [ "Resources/stubs" ]
},
"minimum-stability": "dev",
"extra": {
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
}
}
-18
View File
@@ -1,18 +0,0 @@
; top-most EditorConfig file
root = true
; Unix-style newlines
[*]
end_of_line = LF
[*.php]
indent_style = space
indent_size = 4
[*.test]
indent_style = space
indent_size = 4
[*.rst]
indent_style = space
indent_size = 4
@@ -1,4 +0,0 @@
/doc/ export-ignore
/extra/ export-ignore
/tests/ export-ignore
/phpunit.xml.dist export-ignore
@@ -1,149 +0,0 @@
name: "CI"
on:
pull_request:
push:
branches:
- '3.x'
env:
SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE: 1
permissions:
contents: read
jobs:
tests:
name: "PHP ${{ matrix.php-version }}"
runs-on: 'ubuntu-latest'
continue-on-error: ${{ matrix.experimental }}
strategy:
matrix:
php-version:
- '7.2.5'
- '7.3'
- '7.4'
- '8.0'
- '8.1'
experimental: [false]
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
with:
coverage: "none"
php-version: ${{ matrix.php-version }}
ini-values: memory_limit=-1
- name: "Add PHPUnit matcher"
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- run: composer install
- name: "Install PHPUnit"
run: vendor/bin/simple-phpunit install
- name: "PHPUnit version"
run: vendor/bin/simple-phpunit --version
- name: "Run tests"
run: vendor/bin/simple-phpunit
extension-tests:
needs:
- 'tests'
name: "${{ matrix.extension }} with PHP ${{ matrix.php-version }}"
runs-on: 'ubuntu-latest'
continue-on-error: true
strategy:
matrix:
php-version:
- '7.2.5'
- '7.3'
- '7.4'
- '8.0'
- '8.1'
extension:
- 'extra/cache-extra'
- 'extra/cssinliner-extra'
- 'extra/html-extra'
- 'extra/inky-extra'
- 'extra/intl-extra'
- 'extra/markdown-extra'
- 'extra/string-extra'
- 'extra/twig-extra-bundle'
experimental: [false]
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
with:
coverage: "none"
php-version: ${{ matrix.php-version }}
ini-values: memory_limit=-1
- name: "Add PHPUnit matcher"
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- run: composer install
- name: "Install PHPUnit"
run: vendor/bin/simple-phpunit install
- name: "PHPUnit version"
run: vendor/bin/simple-phpunit --version
- name: "Composer install"
working-directory: ${{ matrix.extension}}
run: composer install
- name: "Run tests"
working-directory: ${{ matrix.extension}}
run: ../../vendor/bin/simple-phpunit
#
# Drupal does not support Twig 3 now!
#
# integration-tests:
# needs:
# - 'tests'
#
# name: "Integration tests with PHP ${{ matrix.php-version }}"
#
# runs-on: 'ubuntu-20.04'
#
# continue-on-error: true
#
# strategy:
# matrix:
# php-version:
# - '7.3'
#
# steps:
# - name: "Checkout code"
# uses: actions/checkout@v2
#
# - name: "Install PHP with extensions"
# uses: shivammathur/setup-php@2
# with:
# coverage: "none"
# extensions: "gd, pdo_sqlite"
# php-version: ${{ matrix.php-version }}
# ini-values: memory_limit=-1
# tools: composer:v2
#
# - run: bash ./tests/drupal_test.sh
# shell: "bash"
@@ -1,64 +0,0 @@
name: "Documentation"
on:
pull_request:
push:
branches:
- '2.x'
- '3.x'
permissions:
contents: read
jobs:
build:
name: "Build"
runs-on: ubuntu-latest
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- name: "Set-up PHP"
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
coverage: none
tools: "composer:v2"
- name: Get composer cache directory
id: composercache
working-directory: doc/_build
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: "Install dependencies"
working-directory: doc/_build
run: composer install --prefer-dist --no-progress
- name: "Build the docs"
working-directory: doc/_build
run: php build.php --disable-cache
doctor-rst:
name: "DOCtor-RST"
runs-on: ubuntu-latest
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- name: "Run DOCtor-RST"
uses: docker://oskarstark/doctor-rst
with:
args: --short
env:
DOCS_DIR: 'doc/'
@@ -1,6 +0,0 @@
/doc/_build/vendor
/doc/_build/output
/composer.lock
/phpunit.xml
/vendor
.phpunit.result.cache
@@ -1,20 +0,0 @@
<?php
return (new PhpCsFixer\Config())
->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'@PHPUnit75Migration:risky' => true,
'php_unit_dedicate_assert' => ['target' => '5.6'],
'array_syntax' => ['syntax' => 'short'],
'php_unit_fqcn_annotation' => true,
'no_unreachable_default_argument_value' => false,
'braces' => ['allow_single_line_closure' => true],
'heredoc_to_nowdoc' => false,
'ordered_imports' => true,
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'],
])
->setRiskyAllowed(true)
->setFinder((new PhpCsFixer\Finder())->in(__DIR__))
;
+176 -1
View File
@@ -1,3 +1,178 @@
# 3.14.0 (2024-09-09)
* Fix a security issue when an included sandboxed template has been loaded before without the sandbox context
* Add the possibility to reset globals via `Environment::resetGlobals()`
* Deprecate `Environment::mergeGlobals()`
# 3.13.0 (2024-09-07)
* Add the `types` tag (experimental)
* Deprecate the `Twig\Test\NodeTestCase::getTests()` data provider, override `provideTests()` instead.
* Mark `Twig\Test\NodeTestCase::getEnvironment()` as final, override `createEnvironment()` instead.
* Deprecate `Twig\Test\NodeTestCase::getVariableGetter()`, call `createVariableGetter()` instead.
* Deprecate `Twig\Test\NodeTestCase::getAttributeGetter()`, call `createAttributeGetter()` instead.
* Deprecate not overriding `Twig\Test\IntegrationTestCase::getFixturesDirectory()`, this method will be abstract in 4.0
* Marked `Twig\Test\IntegrationTestCase::getTests()` and `getLegacyTests()` as final
# 3.12.0 (2024-08-29)
* Deprecate the fact that the `extends` and `use` tags are always allowed in a sandboxed template.
This behavior will change in 4.0 where these tags will need to be explicitly allowed like any other tag.
* Deprecate the "tag" constructor argument of the "Twig\Node\Node" class as the tag is now automatically set by the Parser when needed
* Fix precedence of two-word tests when the first word is a valid test
* Deprecate the `spaceless` filter
* Deprecate some internal methods from `Parser`: `getBlockStack()`, `hasBlock()`, `getBlock()`, `hasMacro()`, `hasTraits()`, `getParent()`
* Deprecate passing `null` to `Twig\Parser::setParent()`
* Update `Node::__toString()` to include the node tag if set
* Add support for integers in methods of `Twig\Node\Node` that take a Node name
* Deprecate not passing a `BodyNode` instance as the body of a `ModuleNode` or `MacroNode` constructor
* Deprecate returning "null" from "TokenParserInterface::parse()".
* Deprecate `OptimizerNodeVisitor::OPTIMIZE_TEXT_NODES`
* Fix performance regression when `use_yield` is `false` (which is the default)
* Improve compatibility when `use_yield` is `false` (as extensions still using `echo` will work as is)
* Accept colons (`:`) in addition to equals (`=`) to separate argument names and values in named arguments
* Add the `html_cva` function (in the HTML extra package)
* Add support for named arguments to the `block` and `attribute` functions
* Throw a SyntaxError exception at compile time when a Twig callable has not the minimum number of required arguments
* Add a `CallableArgumentsExtractor` class
* Deprecate passing a name to `FunctionExpression`, `FilterExpression`, and `TestExpression`;
pass a `TwigFunction`, `TwigFilter`, or `TestFilter` instead
* Deprecate all Twig callable attributes on `FunctionExpression`, `FilterExpression`, and `TestExpression`
* Deprecate the `filter` node of `FilterExpression`
* Add the notion of Twig callables (functions, filters, and tests)
* Bump minimum PHP version to 8.0
* Fix integration tests when a test has more than one data/expect section and deprecations
* Add the `enum_cases` function
# 3.11.0 (2024-08-08)
* Deprecate `OptimizerNodeVisitor::OPTIMIZE_RAW_FILTER`
* Add `Twig\Cache\ChainCache` and `Twig\Cache\ReadOnlyFilesystemCache`
* Add the possibility to deprecate attributes and nodes on `Node`
* Add the possibility to add a package and a version to the `deprecated` tag
* Add the possibility to add a package for filter/function/test deprecations
* Mark `ConstantExpression` as being `@final`
* Add the `find` filter
* Fix optimizer mode validation in `OptimizerNodeVisitor`
* Add the possibility to yield from a generator in `PrintNode`
* Add the `shuffle` filter
* Add the `singular` and `plural` filters in `StringExtension`
* Deprecate the second argument of `Twig\Node\Expression\CallExpression::compileArguments()`
* Deprecate `Twig\ExpressionParser\parseHashExpression()` in favor of
`Twig\ExpressionParser::parseMappingExpression()`
* Deprecate `Twig\ExpressionParser\parseArrayExpression()` in favor of
`Twig\ExpressionParser::parseSequenceExpression()`
* Add `sequence` and `mapping` tests
* Deprecate `Twig\Node\Expression\NameExpression::isSimple()` and
`Twig\Node\Expression\NameExpression::isSpecial()`
# 3.10.3 (2024-05-16)
* Fix missing ; in generated code
# 3.10.2 (2024-05-14)
* Fix support for the deprecated escaper signature
# 3.10.1 (2024-05-12)
* Fix BC break on escaper extension
* Fix constant return type
# 3.10.0 (2024-05-11)
* Make `CoreExtension::formatDate`, `CoreExtension::convertDate`, and
`CoreExtension::formatNumber` part of the public API
* Add `needs_charset` option for filters and functions
* Extract the escaping logic from the `EscaperExtension` class to a new
`EscaperRuntime` class.
The following methods from ``Twig\\Extension\\EscaperExtension`` are
deprecated: ``setEscaper()``, ``getEscapers()``, ``setSafeClasses``,
``addSafeClasses()``. Use the same methods on the
``Twig\\Runtime\\EscaperRuntime`` class instead.
* Fix capturing output from extensions that still use echo
* Fix a PHP warning in the Lexer on malformed templates
* Fix blocks not available under some circumstances
* Synchronize source context in templates when setting a Node on a Node
# 3.9.3 (2024-04-18)
* Add missing `twig_escape_filter_is_safe` deprecated function
* Fix yield usage with CaptureNode
* Add missing unwrap call when using a TemplateWrapper instance internally
* Ensure Lexer is initialized early on
# 3.9.2 (2024-04-17)
* Fix usage of display_end hook
# 3.9.1 (2024-04-17)
* Fix missing `$blocks` variable in `CaptureNode`
# 3.9.0 (2024-04-16)
* Add support for PHP 8.4
* Deprecate AbstractNodeVisitor
* Deprecate passing Template to Environment::resolveTemplate(), Environment::load(), and Template::loadTemplate()
* Add a new "yield" mode for output generation;
Node implementations that use "echo" or "print" should use "yield" instead;
all Node implementations should be flagged with `#[YieldReady]` once they've been made ready for "yield";
the "use_yield" Environment option can be turned on when all nodes have been made `#[YieldReady]`;
"yield" will be the only strategy supported in the next major version
* Add return type for Symfony 7 compatibility
* Fix premature loop exit in Security Policy lookup of allowed methods/properties
* Deprecate all internal extension functions in favor of methods on the extension classes
* Mark all extension functions as @internal
* Add SourcePolicyInterface to selectively enable the Sandbox based on a template's Source
* Throw a proper Twig exception when using cycle on an empty array
# 3.8.0 (2023-11-21)
* Catch errors thrown during template rendering
* Fix IntlExtension::formatDateTime use of date formatter prototype
* Fix premature loop exit in Security Policy lookup of allowed methods/properties
* Remove NumberFormatter::TYPE_CURRENCY (deprecated in PHP 8.3)
* Restore return type annotations
* Allow Symfony 7 packages to be installed
* Deprecate `twig_test_iterable` function. Use the native `is_iterable` instead.
# 3.7.1 (2023-08-28)
* Fix some phpdocs
# 3.7.0 (2023-07-26)
* Add support for the ...spread operator on arrays and hashes
# 3.6.1 (2023-06-08)
* Suppress some native return type deprecation messages
# 3.6.0 (2023-05-03)
* Allow psr/container 2.0
* Add the new PHP 8.0 IntlDateFormatter::RELATIVE_* constants for date formatting
* Make the Lexer initialize itself lazily
# 3.5.1 (2023-02-08)
* Arrow functions passed to the "reduce" filter now accept the current key as a third argument
* Restores the leniency of the matches twig comparison
* Fix error messages in sandboxed mode for "has some" and "has every"
# 3.5.0 (2022-12-27)
* Make Twig\ExpressionParser non-internal
* Add "has some" and "has every" operators
* Add Compile::reset()
* Throw a better runtime error when the "matches" regexp is not valid
* Add "twig *_names" intl functions
* Fix optimizing closures callbacks
* Add a better exception when getting an undefined constant via `constant`
* Fix `if` nodes when outside of a block and with an empty body
# 3.4.3 (2022-09-28) # 3.4.3 (2022-09-28)
* Fix a security issue on filesystem loader (possibility to load a template outside a configured directory) * Fix a security issue on filesystem loader (possibility to load a template outside a configured directory)
@@ -141,7 +316,7 @@
* removed Parser::isReservedMacroName() * removed Parser::isReservedMacroName()
* removed SanboxedPrintNode * removed SanboxedPrintNode
* removed Node::setTemplateName() * removed Node::setTemplateName()
* made classes maked as "@final" final * made classes marked as "@final" final
* removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface * removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface
* removed the "spaceless" tag * removed the "spaceless" tag
* removed Twig\Environment::getBaseTemplateClass() and Twig\Environment::setBaseTemplateClass() * removed Twig\Environment::getBaseTemplateClass() and Twig\Environment::setBaseTemplateClass()
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009-2022 by the Twig Team. Copyright (c) 2009-present by the Twig Team.
All rights reserved. All rights reserved.
+1 -1
View File
@@ -11,7 +11,7 @@ Sponsors
.. raw:: html .. raw:: html
<a href="https://blackfire.io/docs/introduction?utm_source=twig&utm_medium=github_readme&utm_campaign=logo"> <a href="https://docs.blackfire.io/introduction?utm_source=twig&utm_medium=github_readme&utm_campaign=logo">
<img src="https://static.blackfire.io/assets/intemporals/logo/png/blackfire-io_secondary_horizontal_transparent.png?1" width="255px" alt="Blackfire.io"> <img src="https://static.blackfire.io/assets/intemporals/logo/png/blackfire-io_secondary_horizontal_transparent.png?1" width="255px" alt="Blackfire.io">
</a> </a>
+12 -9
View File
@@ -24,15 +24,23 @@
} }
], ],
"require": { "require": {
"php": ">=7.2.5", "php": ">=8.0.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "^1.3", "symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-ctype": "^1.8" "symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-php81": "^1.29"
}, },
"require-dev": { "require-dev": {
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0",
"psr/container": "^1.0" "psr/container": "^1.0|^2.0"
}, },
"autoload": { "autoload": {
"files": [
"src/Resources/core.php",
"src/Resources/debug.php",
"src/Resources/escaper.php",
"src/Resources/string_loader.php"
],
"psr-4" : { "psr-4" : {
"Twig\\" : "src/" "Twig\\" : "src/"
} }
@@ -41,10 +49,5 @@
"psr-4" : { "psr-4" : {
"Twig\\Tests\\" : "tests/" "Twig\\Tests\\" : "tests/"
} }
},
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
}
} }
} }
@@ -0,0 +1,136 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class AbstractTwigCallable implements TwigCallableInterface
{
protected $options;
private $name;
private $dynamicName;
private $callable;
private $arguments;
public function __construct(string $name, $callable = null, array $options = [])
{
$this->name = $this->dynamicName = $name;
$this->callable = $callable;
$this->arguments = [];
$this->options = array_merge([
'needs_environment' => false,
'needs_context' => false,
'needs_charset' => false,
'is_variadic' => false,
'deprecated' => false,
'deprecating_package' => '',
'alternative' => null,
], $options);
}
public function __toString(): string
{
return \sprintf('%s(%s)', static::class, $this->name);
}
public function getName(): string
{
return $this->name;
}
public function getDynamicName(): string
{
return $this->dynamicName;
}
public function getCallable()
{
return $this->callable;
}
public function getNodeClass(): string
{
return $this->options['node_class'];
}
public function needsCharset(): bool
{
return $this->options['needs_charset'];
}
public function needsEnvironment(): bool
{
return $this->options['needs_environment'];
}
public function needsContext(): bool
{
return $this->options['needs_context'];
}
public function withDynamicArguments(string $name, string $dynamicName, array $arguments): self
{
$new = clone $this;
$new->name = $name;
$new->dynamicName = $dynamicName;
$new->arguments = $arguments;
return $new;
}
/**
* @deprecated since Twig 3.12, use withDynamicArguments() instead
*/
public function setArguments(array $arguments): void
{
trigger_deprecation('twig/twig', '3.12', 'The "%s::setArguments()" method is deprecated, use "%s::withDynamicArguments()" instead.', static::class, static::class);
$this->arguments = $arguments;
}
public function getArguments(): array
{
return $this->arguments;
}
public function isVariadic(): bool
{
return $this->options['is_variadic'];
}
public function isDeprecated(): bool
{
return (bool) $this->options['deprecated'];
}
public function getDeprecatingPackage(): string
{
return $this->options['deprecating_package'];
}
public function getDeprecatedVersion(): string
{
return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];
}
public function getAlternative(): ?string
{
return $this->options['alternative'];
}
public function getMinimalNumberOfRequiredArguments(): int
{
return ($this->options['needs_charset'] ? 1 : 0) + ($this->options['needs_environment'] ? 1 : 0) + ($this->options['needs_context'] ? 1 : 0) + \count($this->arguments);
}
}
@@ -0,0 +1,20 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Attribute;
/**
* Marks nodes that are ready to accept a TwigCallable instead of its name.
*/
#[\Attribute(\Attribute::TARGET_METHOD)]
final class FirstClassTwigCallableReady
{
}
@@ -0,0 +1,20 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Attribute;
/**
* Marks nodes that are ready for using "yield" instead of "echo" or "print()" for rendering.
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
final class YieldReady
{
}
@@ -0,0 +1,79 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Cache;
/**
* Chains several caches together.
*
* Cached items are fetched from the first cache having them in its data store.
* They are saved and deleted in all adapters at once.
*
* @author Quentin Devos <quentin@devos.pm>
*/
final class ChainCache implements CacheInterface
{
/**
* @param iterable<CacheInterface> $caches The ordered list of caches used to store and fetch cached items
*/
public function __construct(
private iterable $caches,
) {
}
public function generateKey(string $name, string $className): string
{
return $className.'#'.$name;
}
public function write(string $key, string $content): void
{
$splitKey = $this->splitKey($key);
foreach ($this->caches as $cache) {
$cache->write($cache->generateKey(...$splitKey), $content);
}
}
public function load(string $key): void
{
[$name, $className] = $this->splitKey($key);
foreach ($this->caches as $cache) {
$cache->load($cache->generateKey($name, $className));
if (class_exists($className, false)) {
break;
}
}
}
public function getTimestamp(string $key): int
{
$splitKey = $this->splitKey($key);
foreach ($this->caches as $cache) {
if (0 < $timestamp = $cache->getTimestamp($cache->generateKey(...$splitKey))) {
return $timestamp;
}
}
return 0;
}
/**
* @return string[]
*/
private function splitKey(string $key): array
{
return array_reverse(explode('#', $key, 2));
}
}
@@ -50,11 +50,11 @@ class FilesystemCache implements CacheInterface
if (false === @mkdir($dir, 0777, true)) { if (false === @mkdir($dir, 0777, true)) {
clearstatcache(true, $dir); clearstatcache(true, $dir);
if (!is_dir($dir)) { if (!is_dir($dir)) {
throw new \RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir)); throw new \RuntimeException(\sprintf('Unable to create the cache directory (%s).', $dir));
} }
} }
} elseif (!is_writable($dir)) { } elseif (!is_writable($dir)) {
throw new \RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir)); throw new \RuntimeException(\sprintf('Unable to write in the cache directory (%s).', $dir));
} }
$tmpFile = tempnam($dir, basename($key)); $tmpFile = tempnam($dir, basename($key));
@@ -63,7 +63,7 @@ class FilesystemCache implements CacheInterface
if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) {
// Compile cached file into bytecode cache // Compile cached file into bytecode cache
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { if (\function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
@opcache_invalidate($key, true); @opcache_invalidate($key, true);
} elseif (\function_exists('apc_compile_file')) { } elseif (\function_exists('apc_compile_file')) {
apc_compile_file($key); apc_compile_file($key);
@@ -73,7 +73,7 @@ class FilesystemCache implements CacheInterface
return; return;
} }
throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $key)); throw new \RuntimeException(\sprintf('Failed to write cache file "%s".', $key));
} }
public function getTimestamp(string $key): int public function getTimestamp(string $key): int
@@ -0,0 +1,25 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Cache;
/**
* Implements a cache on the filesystem that can only be read, not written to.
*
* @author Quentin Devos <quentin@devos.pm>
*/
class ReadOnlyFilesystemCache extends FilesystemCache
{
public function write(string $key, string $content): void
{
// Do nothing with the content, it's a read-only filesystem.
}
}

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