diff --git a/.github/workflows/close_old_issues_and_prs.yml b/.github/workflows/close_old_issues_and_prs.yml index 855684df9..bba04b0d1 100644 --- a/.github/workflows/close_old_issues_and_prs.yml +++ b/.github/workflows/close_old_issues_and_prs.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write steps: - name: Mark/Close Stale Issues and Pull Requests 🗑️ - uses: actions/stale@v10.3.0 + uses: actions/stale@v10.4.0 with: repo-token: ${{ secrets.STALE_ACTION_PAT }} days-before-stale: 60 diff --git a/.github/workflows/image_builds.yml b/.github/workflows/image_builds.yml index c8ec7ccae..aa56b0381 100644 --- a/.github/workflows/image_builds.yml +++ b/.github/workflows/image_builds.yml @@ -27,7 +27,7 @@ jobs: - "watchdog-mailcow" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Setup Docker run: | curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh diff --git a/.github/workflows/pr_to_nightly.yml b/.github/workflows/pr_to_nightly.yml index a3ea5d80b..8d6b2524d 100644 --- a/.github/workflows/pr_to_nightly.yml +++ b/.github/workflows/pr_to_nightly.yml @@ -8,11 +8,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Run the Action - uses: devops-infra/action-pull-request@v1.2.0 + uses: devops-infra/action-pull-request@v1.4.0 with: github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }} title: Automatic PR to nightly from ${{ github.event.repository.updated_at}} diff --git a/.github/workflows/rebuild_backup_image.yml b/.github/workflows/rebuild_backup_image.yml index b18d339ba..4490afee1 100644 --- a/.github/workflows/rebuild_backup_image.yml +++ b/.github/workflows/rebuild_backup_image.yml @@ -13,7 +13,7 @@ jobs: packages: write steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set up QEMU uses: docker/setup-qemu-action@v4 diff --git a/.github/workflows/update_postscreen_access_list.yml b/.github/workflows/update_postscreen_access_list.yml index 066616aee..ed63281c7 100644 --- a/.github/workflows/update_postscreen_access_list.yml +++ b/.github/workflows/update_postscreen_access_list.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Generate postscreen_access.cidr run: | diff --git a/data/Dockerfiles/nginx/Dockerfile b/data/Dockerfiles/nginx/Dockerfile index 30297362f..65b34838e 100644 --- a/data/Dockerfiles/nginx/Dockerfile +++ b/data/Dockerfiles/nginx/Dockerfile @@ -1,4 +1,4 @@ -FROM nginx:1.30.2-alpine +FROM nginx:1.30.3-alpine LABEL maintainer "The Infrastructure Company GmbH " ENV PIP_BREAK_SYSTEM_PACKAGES=1 diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 7917d7f7d..ca252163a 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -7,13 +7,13 @@ ARG APCU_PECL_VERSION=5.1.28 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?.*)$ ARG IMAGICK_PECL_VERSION=3.8.1 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?.*)$ -ARG MAILPARSE_PECL_VERSION=3.1.9 +ARG MAILPARSE_PECL_VERSION=3.2.0 # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?.*)$ ARG MEMCACHED_PECL_VERSION=3.4.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?.*)$ ARG REDIS_PECL_VERSION=6.3.0 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?.*)$ -ARG COMPOSER_VERSION=2.9.5 +ARG COMPOSER_VERSION=2.10.2 RUN apk add -U --no-cache autoconf \ aspell-dev \ diff --git a/data/Dockerfiles/postfix/Dockerfile b/data/Dockerfiles/postfix/Dockerfile index 994612ec4..cf0647692 100644 --- a/data/Dockerfiles/postfix/Dockerfile +++ b/data/Dockerfiles/postfix/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim +FROM debian:trixie-slim LABEL maintainer="The Infrastructure Company GmbH " diff --git a/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf b/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf index 8e15932a2..bbc9420fe 100644 --- a/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf +++ b/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf @@ -1,4 +1,4 @@ -@version: 3.38 +@version: 4.8 @include "scl.conf" options { chain_hostnames(off); @@ -7,7 +7,7 @@ options { dns_cache(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); - stats_freq(0); + stats(freq(0)); bad_hostname("^gconfd$"); }; source s_src { diff --git a/data/Dockerfiles/postfix/syslog-ng.conf b/data/Dockerfiles/postfix/syslog-ng.conf index fc7d1aa0f..8f09d04a4 100644 --- a/data/Dockerfiles/postfix/syslog-ng.conf +++ b/data/Dockerfiles/postfix/syslog-ng.conf @@ -1,4 +1,4 @@ -@version: 3.38 +@version: 4.8 @include "scl.conf" options { chain_hostnames(off); @@ -7,7 +7,7 @@ options { dns_cache(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); - stats_freq(0); + stats(freq(0)); bad_hostname("^gconfd$"); }; source s_src { diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index d0c710428..d37d1226a 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -2,7 +2,7 @@ FROM debian:trixie-slim LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive -ARG RSPAMD_VER=rspamd_3.14.3-1~236eb65 +ARG RSPAMD_VER=rspamd_4.1.0-1~e2b0b18 ARG CODENAME=trixie ENV LC_ALL=C diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index 1c7287200..b4b401e8c 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -1,6 +1,6 @@ # SOGo built from source to enable security patch application # Repository: https://github.com/Alinto/sogo -# Version: SOGo-5.12.8 +# Version: SOGo-5.12.9 # # Applied security patches: # - @@ -12,8 +12,8 @@ FROM debian:bookworm LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive -ARG SOGO_VERSION=SOGo-5.12.8 -ARG SOPE_VERSION=SOPE-5.12.8 +ARG SOGO_VERSION=SOGo-5.12.9 +ARG SOPE_VERSION=SOPE-5.12.9 # Security patches to apply (space-separated commit hashes) ARG SOGO_SECURITY_PATCHES="" # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index f091cb3f9..080be499a 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -37,7 +37,7 @@ postscreen_access_list = permit_mynetworks, cidr:/opt/postfix/conf/postscreen_access.cidr, tcp:127.0.0.1:10027 postscreen_bare_newline_enable = no -postscreen_blacklist_action = drop +postscreen_denylist_action = drop postscreen_cache_cleanup_interval = 24h postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache postscreen_dnsbl_action = enforce @@ -107,8 +107,6 @@ smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch, reject_unknown_sender_domain smtpd_soft_error_limit = 3 smtpd_tls_auth_only = yes -smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem -smtpd_tls_eecdh_grade = auto smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA smtpd_tls_loglevel = 1 diff --git a/data/conf/postfix/master.cf b/data/conf/postfix/master.cf index d5114df28..fb49f2df2 100644 --- a/data/conf/postfix/master.cf +++ b/data/conf/postfix/master.cf @@ -29,7 +29,6 @@ smtps inet n - n - - smtpd # TLS protocol can be modified by setting submission_smtpd_tls_mandatory_protocols in extra.cf submission inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject - -o smtpd_enforce_tls=yes -o smtpd_tls_security_level=encrypt -o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols -o tls_preempt_cipherlist=yes @@ -38,7 +37,6 @@ submission inet n - n - - smtpd 10587 inet n - n - - smtpd -o smtpd_upstream_proxy_protocol=haproxy -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject - -o smtpd_enforce_tls=yes -o smtpd_tls_security_level=encrypt -o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols -o tls_preempt_cipherlist=yes diff --git a/data/conf/postfix/postscreen_access.cidr b/data/conf/postfix/postscreen_access.cidr index 285ea7a81..6fcedff95 100644 --- a/data/conf/postfix/postscreen_access.cidr +++ b/data/conf/postfix/postscreen_access.cidr @@ -1,6 +1,6 @@ -# Whitelist generated by Postwhite v3.4 on Fri May 1 00:43:37 UTC 2026 +# Whitelist generated by Postwhite v3.4 on Wed Jul 1 00:51:20 UTC 2026 # https://github.com/stevejenkins/postwhite/ -# 2249 total rules +# 2251 total rules 2a00:1450:4000::/36 permit 2a00:1450:4864::/56 permit 2a01:111:f400::/48 permit @@ -56,14 +56,8 @@ 8.25.194.0/23 permit 8.25.196.0/23 permit 8.36.116.0/24 permit -8.39.54.0/23 permit -8.39.54.250/31 permit 8.39.144.0/24 permit -8.40.222.0/23 permit -8.40.222.250/31 permit 12.130.86.238 permit -13.107.213.40 permit -13.107.246.40 permit 13.108.16.0/20 permit 13.110.208.0/21 permit 13.110.209.0/24 permit @@ -73,7 +67,6 @@ 13.111.191.0/24 permit 13.216.7.111 permit 13.216.54.180 permit -13.247.164.219 permit 15.200.21.50 permit 15.200.44.248 permit 15.200.201.185 permit @@ -177,7 +170,6 @@ 34.215.104.144 permit 34.218.115.239 permit 34.225.212.172 permit -34.241.242.183 permit 35.83.148.184 permit 35.155.198.111 permit 35.158.23.94 permit @@ -201,7 +193,6 @@ 40.233.64.216 permit 40.233.83.78 permit 40.233.88.28 permit -43.239.212.33 permit 44.206.138.57 permit 44.210.169.44 permit 44.217.45.156 permit @@ -284,7 +275,6 @@ 51.83.17.38 permit 52.1.14.157 permit 52.5.230.59 permit -52.6.74.205 permit 52.12.53.23 permit 52.13.214.179 permit 52.26.1.71 permit @@ -341,7 +331,6 @@ 54.244.54.130 permit 54.244.242.0/24 permit 54.255.61.23 permit -56.124.6.228 permit 57.103.64.0/18 permit 57.129.93.249 permit 62.13.128.0/24 permit @@ -408,7 +397,6 @@ 65.110.161.77 permit 65.123.29.213 permit 65.123.29.220 permit -65.154.166.0/24 permit 65.212.180.36 permit 66.102.0.0/20 permit 66.119.150.192/26 permit @@ -1222,9 +1210,6 @@ 99.78.197.208/28 permit 103.9.96.0/22 permit 103.28.42.0/24 permit -103.84.217.15 permit -103.84.217.238 permit -103.89.75.238 permit 103.151.192.0/23 permit 103.168.172.128/27 permit 103.237.104.0/22 permit @@ -1385,9 +1370,6 @@ 117.120.16.0/21 permit 119.42.242.52/31 permit 119.42.242.156 permit -121.244.91.48 permit -121.244.91.52 permit -122.15.156.182 permit 123.126.78.64/29 permit 124.108.96.24/31 permit 124.108.96.28/31 permit @@ -1470,21 +1452,7 @@ 134.170.141.64/26 permit 134.170.143.0/24 permit 134.170.174.0/24 permit -135.84.80.0/24 permit -135.84.81.0/24 permit -135.84.82.0/24 permit -135.84.83.0/24 permit 135.84.216.0/22 permit -136.143.160.0/24 permit -136.143.161.0/24 permit -136.143.162.0/24 permit -136.143.176.0/24 permit -136.143.177.0/24 permit -136.143.178.49 permit -136.143.182.0/23 permit -136.143.184.0/24 permit -136.143.188.0/24 permit -136.143.190.0/23 permit 136.146.128.0/20 permit 136.147.128.0/20 permit 136.147.135.0/24 permit @@ -1504,7 +1472,6 @@ 139.138.46.219 permit 139.138.57.55 permit 139.138.58.119 permit -139.167.79.86 permit 139.177.108.0/25 permit 139.180.17.0/24 permit 140.238.148.191 permit @@ -1554,12 +1521,14 @@ 147.243.128.26 permit 148.105.0.0/16 permit 148.105.8.0/21 permit +148.116.32.128/25 permit 149.72.0.0/16 permit 149.72.234.184 permit 149.72.248.236 permit 149.97.173.180 permit 149.118.160.128/25 permit 150.136.21.199 permit +150.171.109.183 permit 150.230.98.160 permit 151.145.38.14 permit 152.67.105.195 permit @@ -1637,8 +1606,9 @@ 164.177.132.168/30 permit 165.1.100.0/25 permit 165.173.128.0/24 permit -165.173.180.1 permit +165.173.180.0/24 permit 165.173.180.250/31 permit +165.173.182.0/24 permit 165.173.182.250/31 permit 165.173.189.205 permit 166.78.68.0/22 permit @@ -1673,19 +1643,6 @@ 168.245.12.252 permit 168.245.46.9 permit 168.245.127.231 permit -169.148.129.0/24 permit -169.148.131.0/24 permit -169.148.138.0/24 permit -169.148.142.10 permit -169.148.142.33 permit -169.148.144.0/25 permit -169.148.144.10 permit -169.148.146.0/23 permit -169.148.174.10 permit -169.148.175.3 permit -169.148.179.3 permit -169.148.188.0/24 permit -169.148.188.182 permit 170.9.232.254 permit 170.10.128.0/24 permit 170.10.129.0/24 permit @@ -1700,6 +1657,7 @@ 173.203.81.39 permit 173.224.161.128/25 permit 173.224.165.0/26 permit +173.224.166.48/28 permit 174.36.84.8/29 permit 174.36.84.16/29 permit 174.36.84.32/29 permit @@ -1891,16 +1849,7 @@ 199.16.156.0/22 permit 199.33.145.1 permit 199.33.145.32 permit -199.34.22.36 permit 199.59.148.0/22 permit -199.67.80.2 permit -199.67.80.20 permit -199.67.82.2 permit -199.67.82.20 permit -199.67.84.0/24 permit -199.67.86.0/24 permit -199.67.88.0/24 permit -199.67.90.0/24 permit 199.101.161.130 permit 199.101.162.0/25 permit 199.122.120.0/21 permit @@ -1956,8 +1905,6 @@ 204.92.114.187 permit 204.92.114.203 permit 204.92.114.204/31 permit -204.141.32.0/23 permit -204.141.42.0/23 permit 204.216.164.202 permit 204.220.160.0/21 permit 204.220.168.0/21 permit @@ -2231,7 +2178,7 @@ 2001:748:400:3301::4 permit 2404:6800:4000::/36 permit 2404:6800:4864::/56 permit -2603:1061:14:72::1 permit +2603:1061:14:1c2::1 permit 2607:13c0:0001:0000:0000:0000:0000:7000/116 permit 2607:13c0:0002:0000:0000:0000:0000:1000/116 permit 2607:13c0:0004:0000:0000:0000:0000:0000/116 permit diff --git a/data/conf/rspamd/local.d/metadata_exporter.conf b/data/conf/rspamd/local.d/metadata_exporter.conf index daaa79b4e..cc85faacd 100644 --- a/data/conf/rspamd/local.d/metadata_exporter.conf +++ b/data/conf/rspamd/local.d/metadata_exporter.conf @@ -3,8 +3,7 @@ rules { backend = "http"; url = "http://nginx:9081/pipe.php"; selector = "reject_no_global_bl"; - formatter = "default"; - meta_headers = true; + formatter = "multipart"; } RLINFO { backend = "http"; @@ -16,8 +15,7 @@ rules { backend = "http"; url = "http://nginx:9081/pushover.php"; selector = "mailcow_rcpt"; - formatter = "json"; - meta_headers = true; + formatter = "multipart"; } } diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php index 003639b08..0a9ed59e2 100644 --- a/data/conf/rspamd/meta_exporter/pipe.php +++ b/data/conf/rspamd/meta_exporter/pipe.php @@ -32,47 +32,42 @@ function parse_email($email) { $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); } -if (!function_exists('getallheaders')) { - function getallheaders() { - if (!is_array($_SERVER)) { - return array(); - } - $headers = array(); - foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) == 'HTTP_') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; - } - } - return $headers; - } +// rspamd metadata_exporter (multipart formatter): +// - $_POST['metadata'] JSON with the rspamd metadata +// - $_FILES['message'] raw RFC822 message +if (empty($_POST['metadata']) || !isset($_FILES['message']) || $_FILES['message']['error'] !== UPLOAD_ERR_OK) { + error_log("QUARANTINE: missing multipart parts from rspamd" . PHP_EOL); + http_response_code(400); + exit; } -$raw_data_content = file_get_contents('php://input'); +$meta = json_decode($_POST['metadata'], true); +if (!is_array($meta)) { + error_log("QUARANTINE: cannot decode metadata JSON" . PHP_EOL); + http_response_code(400); + exit; +} + +$raw_data_content = file_get_contents($_FILES['message']['tmp_name']); $raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8"); -$headers = getallheaders(); +$raw_size = (int)$_FILES['message']['size']; -$qid = $headers['X-Rspamd-Qid']; -$fuzzy = $headers['X-Rspamd-Fuzzy']; -$subject = iconv_mime_decode($headers['X-Rspamd-Subject']); -$score = $headers['X-Rspamd-Score']; -$rcpts = $headers['X-Rspamd-Rcpt']; -$user = $headers['X-Rspamd-User']; -$ip = $headers['X-Rspamd-Ip']; -$action = $headers['X-Rspamd-Action']; -$sender = $headers['X-Rspamd-From']; -$symbols = $headers['X-Rspamd-Symbols']; - -$raw_size = (int)$_SERVER['CONTENT_LENGTH']; +$qid = $meta['qid'] ?? 'unknown'; +$subject = iconv_mime_decode($meta['subject'] ?? ''); +$score = $meta['score'] ?? 0; +$rcpts = $meta['rcpt'] ?? array(); +$user = $meta['user'] ?? 'unknown'; +$ip = $meta['ip'] ?? 'unknown'; +$action = $meta['action'] ?? 'no action'; +$sender = $meta['from'] ?? ''; +$symbols = json_encode($meta['symbols'] ?? array()); +$fuzzy = json_encode(is_array($meta['fuzzy'] ?? null) ? $meta['fuzzy'] : array()); if (empty($sender)) { error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL); $sender = 'empty-env-from@localhost'; } -if ($fuzzy == 'unknown') { - $fuzzy = '[]'; -} - try { $max_size = (int)$redis->Get('Q_MAX_SIZE'); if (($max_size * 1048576) < $raw_size) { @@ -94,7 +89,7 @@ catch (RedisException $e) { $rcpt_final_mailboxes = array(); // Loop through all rcpts -foreach (json_decode($rcpts, true) as $rcpt) { +foreach ($rcpts as $rcpt) { // Remove tag $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); diff --git a/data/conf/rspamd/meta_exporter/pushover.php b/data/conf/rspamd/meta_exporter/pushover.php index efe5fdd52..a2cd0995b 100644 --- a/data/conf/rspamd/meta_exporter/pushover.php +++ b/data/conf/rspamd/meta_exporter/pushover.php @@ -32,50 +32,46 @@ function parse_email($email) { $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); } -if (!function_exists('getallheaders')) { - function getallheaders() { - if (!is_array($_SERVER)) { - return array(); - } - $headers = array(); - foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) == 'HTTP_') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; - } - } - return $headers; - } +// rspamd metadata_exporter (multipart formatter): metadata JSON arrives as $_POST['metadata']. +if (empty($_POST['metadata'])) { + error_log("NOTIFY: missing metadata part from rspamd" . PHP_EOL); + http_response_code(400); + exit; } -$headers = getallheaders(); -$json_body = json_decode(file_get_contents('php://input')); +$meta = json_decode($_POST['metadata'], true); +if (!is_array($meta)) { + error_log("NOTIFY: cannot decode metadata JSON" . PHP_EOL); + http_response_code(400); + exit; +} -$qid = $headers['X-Rspamd-Qid']; -$rcpts = $headers['X-Rspamd-Rcpt']; -$sender = $headers['X-Rspamd-From']; -$ip = $headers['X-Rspamd-Ip']; -$subject = iconv_mime_decode($headers['X-Rspamd-Subject']); -$messageid= $json_body->message_id; +$qid = $meta['qid'] ?? 'unknown'; +$rcpts = $meta['rcpt'] ?? array(); +$sender = $meta['from'] ?? ''; +$ip = $meta['ip'] ?? 'unknown'; +$subject = iconv_mime_decode($meta['subject'] ?? ''); +$messageid= $meta['message_id'] ?? ''; $priority = 0; -$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true); +$symbols_array = $meta['symbols'] ?? array(); if (is_array($symbols_array)) { foreach ($symbols_array as $symbol) { - if ($symbol['name'] == 'HAS_X_PRIO_ONE') { + if (($symbol['name'] ?? null) == 'HAS_X_PRIO_ONE') { $priority = 1; break; } } } -$sender_address = $json_body->header_from[0]; +$sender_address = $meta['header_from'][0] ?? ''; $sender_name = '-'; if (preg_match('/(?.*?)<(?
.*?)>/i', $sender_address, $matches)) { $sender_address = $matches['address']; $sender_name = trim($matches['name'], '"\' '); } -$to_address = $json_body->header_to[0]; +$to_address = $meta['header_to'][0] ?? ''; $to_name = '-'; if (preg_match('/(?.*?)<(?
.*?)>/i', $to_address, $matches)) { $to_address = $matches['address']; @@ -85,7 +81,7 @@ if (preg_match('/(?.*?)<(?
.*?)>/i', $to_address, $matches)) { $rcpt_final_mailboxes = array(); // Loop through all rcpts -foreach (json_decode($rcpts, true) as $rcpt) { +foreach ($rcpts as $rcpt) { // Remove tag $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml index cf54f6add..77c58b609 100644 --- a/data/web/api/openapi.yaml +++ b/data/web/api/openapi.yaml @@ -1072,6 +1072,7 @@ paths: password2: "*" quota: "3072" force_pw_update: "1" + force_tfa: "1" tls_enforce_in: "1" tls_enforce_out: "1" tags: ["tag1", "tag2"] @@ -1118,6 +1119,7 @@ paths: password2: atedismonsin quota: "3072" force_pw_update: "1" + force_tfa: "1" tls_enforce_in: "1" tls_enforce_out: "1" tags: ["tag1", "tag2"] @@ -1151,6 +1153,9 @@ paths: force_pw_update: description: forces the user to update its password on first login type: boolean + force_tfa: + description: force 2FA enrollment at login + type: boolean tls_enforce_in: description: force inbound email tls encryption type: boolean @@ -2510,7 +2515,7 @@ paths: description: >- Using this endpoint you can perform actions on quarantine items. It is possible to release emails from quarantine into to the inbox, or learn them as ham to improve Rspamd filtering. - You must provide the quarantine item IDs. You can get the IDs using the GET method. + You must provide the quarantine item IDs. You can get the IDs using the GET method. operationId: Edit mails in Quarantine requestBody: content: @@ -3414,6 +3419,7 @@ paths: - mailbox - active: "1" force_pw_update: "0" + force_tfa: "0" name: Full name password: "*" password2: "*" @@ -3464,6 +3470,7 @@ paths: attr: active: "1" force_pw_update: "0" + force_tfa: "0" name: Full name authsource: mailcow password: "" @@ -3487,6 +3494,9 @@ paths: force_pw_update: description: force user to change password on next login type: boolean + force_tfa: + description: force 2FA enrollment at login + type: boolean name: description: Full name of the mailbox user type: string @@ -4881,6 +4891,7 @@ paths: - active: "1" attributes: force_pw_update: "0" + force_tfa: "0" mailbox_format: "maildir:" quarantine_notification: never sogo_access: "1" @@ -5805,6 +5816,7 @@ paths: - active: "1" attributes: force_pw_update: "0" + force_tfa: "0" mailbox_format: "maildir:" quarantine_notification: never sogo_access: "1" diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index adb330ea8..a9f17705d 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -3505,7 +3505,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // Track affected mailboxes for SOGo update $update_sogo_mailboxes[] = $username; } - return true; break; case 'mailbox_rename': $domain = $_data['domain']; @@ -3828,6 +3827,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame']; $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value']; $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update']; + $attr["force_tfa"] = isset($_data['force_tfa']) ? intval($_data['force_tfa']) : $is_now['force_tfa']; $attr["sogo_access"] = isset($_data['sogo_access']) ? intval($_data['sogo_access']) : $is_now['sogo_access']; $attr["active"] = isset($_data['active']) ? intval($_data['active']) : $is_now['active']; $attr["tls_enforce_in"] = isset($_data['tls_enforce_in']) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in']; @@ -6127,7 +6127,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { // Track affected mailboxes for SOGo update $update_sogo_mailboxes[] = $username; } - return true; break; case 'mailbox_templates': if ($_SESSION['mailcow_cc_role'] != "admin") { diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index e8edb9940..0a0192798 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -424,6 +424,11 @@ $(document).ready(function() { } else { $('#force_pw_update').prop('checked', false); } + if (template.force_tfa == 1){ + $('#force_tfa').prop('checked', true); + } else { + $('#force_tfa').prop('checked', false); + } if (template.sogo_access == 1){ $('#sogo_access').prop('checked', true); } else { @@ -1242,6 +1247,7 @@ jQuery(function($){ item.attributes.eas_access = '' + (item.attributes.eas_access == 1 ? '1' : '0') + ''; item.attributes.dav_access = '' + (item.attributes.dav_access == 1 ? '1' : '0') + ''; item.attributes.sogo_access = '' + (item.attributes.sogo_access == 1 ? '1' : '0') + ''; + item.attributes.force_tfa = '' + (item.attributes.force_tfa == 1 ? '1' : '0') + ''; if (item.attributes.quarantine_notification === 'never') { item.attributes.quarantine_notification = lang.never; } else if (item.attributes.quarantine_notification === 'hourly') { @@ -1385,6 +1391,11 @@ jQuery(function($){ return 1==data?'':''; } }, + { + title: lang.force_tfa, + data: 'attributes.force_tfa', + defaultContent: '' + }, { title: lang_edit.ratelimit, data: 'attributes.ratelimit', diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 3672762df..c19eb209b 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -150,7 +150,7 @@ "arrival_time": "Ankunftszeit (Serverzeit)", "authed_user": "Auth. Benutzer", "ays": "Soll der Vorgang wirklich ausgeführt werden?", - "ban_list_info": "Übersicht ausgesperrter Netzwerke: Netzwerk (verbleibende Bannzeit) - [Aktionen].
IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.
Rote Labels sind Indikatoren für aktive Allowlist-Einträge.", + "ban_list_info": "Übersicht ausgesperrter Netzwerke: Netzwerk (verbleibende Bannzeit) - [Aktionen].
IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Banns nach wenigen Sekunden.
Rote Labels kennzeichnen aktive permanente Sperren durch Denylisting.", "change_logo": "Logo ändern", "configuration": "Konfiguration", "convert_html_to_text": "Konvertiere HTML zu reinem Text", @@ -1086,7 +1086,7 @@ "rspamd_result": "Rspamd-Ergebnis", "sender": "Sender (SMTP)", "sender_header": "Sender (\"From\"-Header)", - "settings_info": "Maximale Anzahl der zurückgehaltenen E-Mails: %s
Maximale Größe einer zu speichernden E-Mail: %s MiB", + "settings_info": "Maximale Anzahl der zurückgehaltenen E-Mails (pro Mailbox): %s
Maximale Größe einer zu speichernden E-Mail: %s MiB", "show_item": "Details", "spam": "Spam", "spam_score": "Bewertung", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index e786bcbe5..873d7b98d 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -929,6 +929,7 @@ "filters": "Filters", "fname": "Full name", "force_pw_update": "Force password update at next login", + "force_tfa": "TFA", "gal": "Global Address List", "goto_ham": "Learn as ham", "goto_spam": "Learn as spam", @@ -1086,7 +1087,7 @@ "rspamd_result": "Rspamd result", "sender": "Sender (SMTP)", "sender_header": "Sender (\"From\" header)", - "settings_info": "Maximum amount of elements to be quarantined: %s
Maximum email size: %s MiB", + "settings_info": "Maximum amount of elements to be quarantined (per mailbox): %s
Maximum email size: %s MiB", "show_item": "Show item", "spam": "Spam", "spam_score": "Score", diff --git a/data/web/lang/lang.fr-fr.json b/data/web/lang/lang.fr-fr.json index 43c9c065a..9ee916713 100644 --- a/data/web/lang/lang.fr-fr.json +++ b/data/web/lang/lang.fr-fr.json @@ -111,7 +111,8 @@ "app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application", "dry": "Simuler la synchronisation", "internal": "Interne", - "internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine." + "internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine.", + "sender_allowed": "Autoriser l'envoi sous cet alias" }, "admin": { "access": "Accès", @@ -556,7 +557,9 @@ "max_age_invalid": "L'âge maximum %s est invalide", "mode_invalid": "Le mode %s est invalide", "mx_invalid": "L'enregistrement MX %s est invalide", - "version_invalid": "La version %s est invalide" + "version_invalid": "La version %s est invalide", + "tfa_removal_blocked": "L’authentification à deux facteurs ne peut pas être supprimée, car elle est requise pour votre compte.", + "quarantine_category_invalid": "La catégorie de quarantaine doit être l’une des suivantes : add_header, reject, all\"" }, "debug": { "chart_this_server": "Graphique (ce serveur)", @@ -755,7 +758,9 @@ "mta_sts_info": "MTA-STS est un standard qui oblige la délivrance des courriels entre les serveurs de courriels à utiliser TLS avec des certificats valides.
Il est utilisé quand DANE n'est pas possible à cause d'un manque ou d'un non support de DNSSEC.
Note : Si le domaine du destinataire supporte DANE avec DNSSEC, DANE est toujours préféré – MTA-STS sert seulement en secours.", "mta_sts_mode_info": "Il y a trois modes parmi lesquels choisir :
  • testing – la politique est seulement surveillée, les violations n'ont pas d'impact.
  • enforce – la politique est appliquée strictement, les connexions sans TLS valide sont rejetées.
  • none – la politique est publiée mais non appliquée.
", "mta_sts_max_age_info": "Durée en secondes pendant laquelle les serveurs de courriel peuvent mettre en cache cette politique avant de revérifier.", - "mta_sts_mx_info": "Autoriser l'envoi uniquement aux noms d'hôtes des serveurs de courriels indiqués explicitement ; le MTA émetteur vérifie si le nom d'hôte DNS du MX correspond à la liste de la politique, et autorise la délivrance seulement avec un certificat TLS valide (protège contre le MITM)." + "mta_sts_mx_info": "Autoriser l'envoi uniquement aux noms d'hôtes des serveurs de courriels indiqués explicitement ; le MTA émetteur vérifie si le nom d'hôte DNS du MX correspond à la liste de la politique, et autorise la délivrance seulement avec un certificat TLS valide (protège contre le MITM).", + "sender_allowed": "Autoriser l'envoi sous cet alias", + "sender_allowed_info": "Si cette option est désactivée, cet alias peut uniquement recevoir des courriels. Utilisez les ACL d’expéditeur pour autoriser certaines boîtes aux lettres à envoyer des messages via cet alias." }, "footer": { "cancel": "Annuler", @@ -988,7 +993,8 @@ "recipient": "Destinataire", "open_logs": "Afficher les journaux", "iam": "Fournisseur d'identité", - "internal": "Interne" + "internal": "Interne", + "force_tfa": "TFA" }, "oauth2": { "access_denied": "Veuillez vous connecter en tant que propriétaire de la boîte de réception pour accorder l’accès via Oauth2.", @@ -1189,7 +1195,11 @@ "yubi_otp": "Authentification OTP Yubico", "authenticators": "Authentificateurs", "u2f_deprecated_important": "Veuillez enregistrer votre clé dans le panneau d'administration avec la nouvelle méthode WebAuthn.", - "u2f_deprecated": "Il semble que votre clé ait été enregistrée à l'aide de la méthode U2F obsolète. Nous allons désactiver l'authentification à deux facteurs pour vous et supprimer votre clé." + "u2f_deprecated": "Il semble que votre clé ait été enregistrée à l'aide de la méthode U2F obsolète. Nous allons désactiver l'authentification à deux facteurs pour vous et supprimer votre clé.", + "force_tfa": "Imposer l'authentification à deux facteurs lors de la connexion", + "force_tfa_info": "L’utilisateur doit configurer l’authentification à deux facteurs avant de pouvoir accéder à l’interface.", + "setup_title": "Authentification à deux facteurs requise", + "setup_required": "Votre compte nécessite l’authentification à deux facteurs. Veuillez configurer une méthode 2FA pour continuer." }, "fido2": { "set_fn": "Définir un nom", @@ -1378,7 +1388,8 @@ "forever": "Pour toujours", "spam_aliases_info": "Un alias de spam est une adresse de courriel temporaire qui peut être utilisée pour protéger les véritables adresses de courriel.
De manière optionnelle, une durée d'expiration peut être définie afin que l'alias soit automatiquement désactivé après la période définie, éliminant ainsi les adresses étant abusées ou ayant fuité.", "authentication": "Authentification", - "protocols": "Protocoles" + "protocols": "Protocoles", + "pw_update_required": "Votre compte nécessite un changement de mot de passe. Veuillez définir un nouveau mot de passe pour continuer." }, "warning": { "cannot_delete_self": "Impossible de supprimer l’utilisateur connecté", diff --git a/data/web/lang/lang.lv-lv.json b/data/web/lang/lang.lv-lv.json index fd29e594a..df1697282 100644 --- a/data/web/lang/lang.lv-lv.json +++ b/data/web/lang/lang.lv-lv.json @@ -13,7 +13,7 @@ "domain_relayhost": "Mainīt domēna relayhost", "eas_reset": "EAS ierīču atiestatīšana", "extend_sender_acl": "Ļauj paplašināt sūtītāja ACL ar ārējām adresēm", - "login_as": "Pieteikšanās kā pastkastes lietotājam", + "login_as": "Pieteikties kā pastkastes lietotājam", "mailbox_relayhost": "Pasta kastītes relayhost maiņa", "prohibited": "Aizliegts ar ACL", "protocol_access": "Protokola piekļuves maiņa", @@ -91,12 +91,12 @@ "activate_api": "Aktivizēt API", "active": "Aktīvs", "add": "Pievienot", - "add_domain_admin": "Pievienot domēna administratoru", + "add_domain_admin": "Pievienot domēna pārvaldītāju", "add_forwarding_host": "Pievienot pāradresācijas hostu", "add_relayhost": "Pievienot Relayhost", "add_row": "Pievienot rindu", - "admin": "Administrators", - "admin_details": "Labot administratora detaļas", + "admin": "Pārvaldītājs", + "admin_details": "Labot informāciju par pārvaldītāju", "admin_domains": "Domēna uzdevumi", "api_allow_from": "Atļaut API piekļuvi no šīm IP", "api_key": "API atslēga", @@ -114,7 +114,7 @@ "dkim_keys": "ARC/DKIM atslēgas", "dkim_private_key": "Privāta atslēga", "domain": "Domēns", - "domain_admins": "Domēna administratori", + "domain_admins": "Domēna pārvaldītāji", "edit": "Labot", "empty": "Nav iznākuma", "f2b_ban_time": "Aizlieguma laiks (s)", @@ -188,7 +188,14 @@ "quarantine_max_score": "Atmest paziņojumu, ja e-pasta ziņojuma mēstuļu novērtējums ir augstāks par šo vērtību:
Noklusējums ir 9999.0", "options": "Iespējas", "password_reset_settings": "Paroļu atkopes iestatījumi", - "password_settings": "Paroļu iestatījumi" + "password_settings": "Paroļu iestatījumi", + "add_admin": "Pievienot pārvaldītāju", + "admins": "Pārvaldītāji", + "admins_ldap": "LDAP pārvaldītāji", + "admin_quicklink": "Paslēpt ātro saiti uz pārvaldītāju pieteikšanās lapu", + "domain_admin": "Domēna pārvaldītājs", + "domainadmin_quicklink": "Paslēpt ātro saiti uz domēna pārvaldītāju pieteikšanās lapu", + "user_quicklink": "Paslēpt ātro saiti uz lietotāju pieteikšanās lapu" }, "danger": { "access_denied": "Piekļuve liegta, vai nepareizi dati", @@ -244,7 +251,10 @@ "app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs", "img_dimensions_exceeded": "Attēls pārsniedz lielāko pieļaujamo attēla lielumu", "img_size_exceeded": "Attēls pārsniedz lielāko pieļaujamo datnes lielumu", - "version_invalid": "Versija %s ir nederīga" + "version_invalid": "Versija %s ir nederīga", + "generic_server_error": "Atgadījās neparedzēta servera kļūda. Lūgums sazināties ar pārvaldītāju.", + "password_reset_na": "Paroļu atkope šobrīd nav pieejama. Lūgums sazināties ar pārvaldītāju.", + "recovery_email_failed": "Nevarēja nosūtīt atkopes e-pasta ziņojumu. Lūgums sazināties ar pārvaldītāju." }, "diagnostics": { "cname_from_a": "Vērtība, kas iegūta no A/AAAA ieraksta. Tas tiek atbalstīts tik ilgi, kamēr ieraksts norāda uz pareizo resursu.", @@ -266,7 +276,7 @@ "delete2duplicates": "Izdzēst atkārtojošos vienumus galamērķī", "description": "Apraksts", "domain": "Labot domēnu", - "domain_admin": "Labot domēna administratoru", + "domain_admin": "Labot domēna pārvaldītāju", "domain_quota": "Domēna kvota", "domains": "Domēni", "dont_check_sender_acl": "Atspējot sūtītāju pārbaudi domēnam %s (+ aizstājdomēni)", @@ -329,7 +339,8 @@ "app_passwd": "Lietotnes parole", "mta_sts_version": "Versija", "mta_sts_version_info": "Norāda MTA-STS standarta versiju – pašreiz ir derīga tikai STSv1.", - "sender_acl_disabled": "Sūtītāja pārbaude ir atspējota" + "sender_acl_disabled": "Sūtītāja pārbaude ir atspējota", + "admin": "Labot pārvaldītāju" }, "footer": { "cancel": "Atcelt", @@ -362,7 +373,14 @@ "username": "Lietotājvārds", "fido2_webauthn": "FIDO/WebAuthn pieteikšanās", "mobileconfig_info": "Lūgums pieteikties kā pastkastes lietotājam, lai lejupielādētu pieprasīto Apple savienojuma profilu.", - "other_logins": "vai pieslēgties ar" + "other_logins": "vai pieteikties ar", + "forgot_password": "> Aizmirsta parole?", + "login_linkstext": "Nepareiza pieteikšanās?", + "login_domainadmintext": "Pieteikties kā domēna pārvaldītājam", + "login_admintext": "Pieteikties kā pārvaldītājam", + "login_user": "Lietotu pieteikšanās", + "login_dadmin": "Domēna pārvaldītāju pieteikšanās", + "login_admin": "Pārvaldītāju pieteikšanās" }, "mailbox": { "action": "Rīcība", @@ -397,7 +415,7 @@ "description": "Apraksts", "dkim_key_length": "DKIM atslēgas garums (bits)", "domain": "Domēns", - "domain_admins": "Domēna administratori", + "domain_admins": "Domēna pārvaldītāji", "domain_aliases": "Domēna aizstājvārdi", "domain_quota": "Kvota", "domain_quota_total": "Kopējais domēna ierobežojums", @@ -507,7 +525,7 @@ "imap_smtp_server_auth_info": "Lūgums izmantot pilnu e-pasta adresi un PLAIN autentificēšanās mehānismu.
\nPieteikšanās dati tiks šifrēti ar servera puses obligātu šifrēšanu." }, "success": { - "admin_modified": "Izmaiņas administrātoram ir saglabātas", + "admin_modified": "Pārvaldītāja izmaiņas tika saglabātas", "alias_added": "Aizstājadrese %s (%d) tika pievienota", "alias_domain_removed": "Aizstājdomēns %s tika noņemts", "alias_modified": "Aizstājadreses izmaiņas %s tika saglabātas", @@ -518,9 +536,9 @@ "dkim_added": "DKIM atslēga saglabāta", "dkim_removed": "DKIM atslēga %s ir noņemta", "domain_added": "Pievienots domēns %s", - "domain_admin_added": "Domēna administrātors %s pievienots", - "domain_admin_modified": "Izmaiņas domēna administrātoram %s ir saglabātas", - "domain_admin_removed": "Domēna administrators %s tika noņemts", + "domain_admin_added": "Tika pievienots domēna pārvaldītājs %s", + "domain_admin_modified": "Domēna pārvaldītāja %s izmaiņas tika saglabātas", + "domain_admin_removed": "Tika noņemts domēna pārvaldītājs %s", "domain_modified": "Izmaiņas domēnam %s ir saglabātas", "domain_removed": "Domēns %s ir noņemts", "eas_reset": "ActiveSync ierīces priekš lietotāja %s tika atiestatītas", @@ -548,7 +566,9 @@ "verified_yotp_login": "Apliecināta Yubico OTP pieteikšanās", "app_passwd_removed": "Noņemta lietotnes parole ar Id %s", "app_passwd_added": "Pievienota jauna lietotnes parole", - "f2b_banlist_refreshed": "Liegumu saraksta Id tika sekmīgi atsvaidzināts." + "f2b_banlist_refreshed": "Liegumu saraksta Id tika sekmīgi atsvaidzināts.", + "admin_added": "Tika pievienots pārvaldītājs %s", + "admin_removed": "Tika noņemts pārvaldītājs %s" }, "tfa": { "api_register": "%s izmanto Yubico Cloud API. Lūdzu iegūstiet API atslēgu priekš Jūsu atslēgashere", @@ -569,7 +589,8 @@ "waiting_usb_auth": "Gaida USB ierīci...

Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.", "waiting_usb_register": "Gaida USB ierīci...

Lūgums augstāk ievadīt savu paroli un apstiprināt reģistrēšanos ar USB ierīces pogas nospiešanu.", "yubi_otp": "Yubico OTP autentifikators", - "authenticators": "Autentificētāji" + "authenticators": "Autentificētāji", + "u2f_deprecated_important": "Lūgums reģistrēt savu atslēgu pārvaldības lapā ar jauno WebAuthn veidu." }, "user": { "action": "Rīcība", @@ -701,7 +722,8 @@ "warning": { "domain_added_sogo_failed": "Domēns pievienots, bet neizdevās pārsāknēt SOGO. Lūgums pārbaudīt servera žurnālus.", "dovecot_restart_failed": "Dovecot neizdevās pārsāknēties. Lūgums pārbaudīt žurnālus", - "is_not_primary_alias": "Izlaists aizstājvārds %s, kas nav galvenais" + "is_not_primary_alias": "Izlaists aizstājvārds %s, kas nav galvenais", + "no_active_admin": "Nevar deaktivēt pēdējo aktīvo pārvaldītāju" }, "oauth2": { "access_denied": "Lūgums pieteikties kā pastkastes īpašniekam, lai nodrošinātu piekļuvi ar OAuth2." diff --git a/data/web/lang/lang.si-si.json b/data/web/lang/lang.si-si.json index f5ba53f98..39d57eb31 100644 --- a/data/web/lang/lang.si-si.json +++ b/data/web/lang/lang.si-si.json @@ -1001,7 +1001,8 @@ "weekly": "Tedensko", "sieve_info": "Na uporabnika lahko shranite več filtrov, vendar je lahko hkrati aktiven le en predfilter in en postfilter.
\nVsak filter bo obdelan v opisanem vrstnem redu. Niti neuspešen skript niti izdan ukaz »keep;« ne bosta ustavila obdelave nadaljnjih skript. Spremembe globalnih skriptov sita bodo sprožile ponovni zagon Dovecota.

Globalni predfilter sita • Predfilter • Uporabniški skripti • Postfilter • Globalni postfilter sita", "tls_policy_maps_info": "Ta preslikava pravilnikov preglasi pravila odhodnega prenosa TLS neodvisno od uporabnikovih nastavitev pravilnikov TLS.
\n Za več informacij preverite dokumentacijo »smtp_tls_policy_maps«.", - "internal": "Notranje" + "internal": "Notranje", + "force_tfa": "TFA" }, "fido2": { "known_ids": "Znani ID-ji", diff --git a/data/web/lang/lang.vi-vn.json b/data/web/lang/lang.vi-vn.json index 523b5dca9..42813fd25 100644 --- a/data/web/lang/lang.vi-vn.json +++ b/data/web/lang/lang.vi-vn.json @@ -268,10 +268,10 @@ "includes": "Bao gồm những người nhận này", "ip_check": "Kiểm tra IP", "ip_check_disabled": "Kiểm tra IP đã bị vô hiệu hóa. Bạn có thể bật nó trong
Hệ thống > Cấu hình > Tùy chọn > Tùy chỉnh", - "ip_check_opt_in": "Chọn tham gia sử dụng dịch vụ bên thứ ba ipv4.mailcow.emailipv6.mailcow.email để phân giải địa chỉ IP bên ngoài.", + "ip_check_opt_in": "Đăng ký tham gia sử dụng dịch vụ bên thứ ba dùng ipv4.mailcow.emailipv6.mailcow.email để phân giải địa chỉ IP bên ngoài.", "is_mx_based": "Dựa trên MX", "last_applied": "Áp dụng lần cuối", - "license_info": "Giấy phép không bắt buộc nhưng giúp phát triển thêm.
Đăng ký GUID của bạn tại đây hoặc mua hỗ trợ cho cài đặt mailcow của bạn.", + "license_info": "Giấy phép tuy không bắt buộc nhưng giúp phát triển thêm.
Đăng ký GUID của bạn tại đây hoặc mua hỗ trợ cho cài đặt mailcow của bạn.", "link": "Liên kết", "loading": "Vui lòng đợi...", "login_time": "Thời gian đăng nhập", @@ -556,7 +556,9 @@ "validity_missing": "Vui lòng gán thời hạn hiệu lực", "value_missing": "Vui lòng cung cấp tất cả các giá trị", "version_invalid": "Phiên bản %s không hợp lệ", - "yotp_verification_failed": "Xác thực Yubico OTP thất bại: %s" + "yotp_verification_failed": "Xác thực Yubico OTP thất bại: %s", + "tfa_removal_blocked": "Xác thực hai yếu tố không thể bị xóa vì đây là yêu cầu bắt buộc đối với tài khoản của bạn.", + "quarantine_category_invalid": "Danh mục cách ly phải là một trong các loại sau : add_header, reject, all." }, "datatables": { "collapse_all": "Thu gọn tất cả", @@ -579,7 +581,9 @@ "aria": { "sortAscending": ": kích hoạt để sắp xếp cột tăng dần", "sortDescending": ": kích hoạt để sắp xếp cột giảm dần" - } + }, + "decimal": ".", + "thousands": "," }, "debug": { "architecture": "Kiến trúc", @@ -693,6 +697,19 @@ "internal_info": "Bí danh nội bộ chỉ có thể truy cập từ tên miền sở hữu hoặc tên miền bí danh.", "kind": "Loại", "last_modified": "Sửa đổi lần cuối", - "lookup_mx": "Đích là một biểu thức chính quy để khớp với tên MX (.*.google.com để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)" + "lookup_mx": "Đích là một biểu thức chính quy để khớp với tên MX (.*.google.com để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)", + "sender_allowed": "Cho phép gửi đi bằng bí danh", + "sender_allowed_info": "Nếu bị vô hiệu hóa, bí danh này chỉ có thể nhận thư. Sử dụng ACL của người gửi để ghi đè và cấp quyền gửi cho các hộp thư cụ thể.", + "mailbox": "Chỉnh sửa hộp thư", + "mailbox_quota_def": "Hạn mức hộp thư mặc định", + "mailbox_relayhost_info": "Chỉ áp dụng cho hộp thư và các bí danh trực tiếp, thao tác này sẽ ghi đè lên máy chủ chuyển tiếp tên miền.", + "mailbox_rename": "Đổi tên hộp thư", + "mailbox_rename_agree": "Tôi đã tạo bản sao lưu.", + "mailbox_rename_warning": "QUAN TRỌNG! Hãy tạo bản sao lưu trước khi đổi tên hộp thư.", + "mailbox_rename_alias": "Tạo tự động bí danh", + "mailbox_rename_title": "Tên mới của hộp thư cục bộ", + "max_aliases": "Số lượng Bí danh tối đa", + "max_mailboxes": "Số lượng hộp thư tối đa có thể có", + "max_quota": "Dung lượng tối đa cho mỗi hộp thư (MiB)" } } diff --git a/data/web/mobileconfig.php b/data/web/mobileconfig.php index 7c0ead7f5..c57c48857 100644 --- a/data/web/mobileconfig.php +++ b/data/web/mobileconfig.php @@ -65,6 +65,7 @@ if (isset($_GET['app_password'])) { $attr['protocols'][] = 'dav_access'; } app_passwd("add", $attr); + $password = htmlspecialchars($password, ENT_NOQUOTES); } else { $app_password = false; } diff --git a/data/web/templates/edit/mailbox-templates.twig b/data/web/templates/edit/mailbox-templates.twig index ddc139586..8384fa053 100644 --- a/data/web/templates/edit/mailbox-templates.twig +++ b/data/web/templates/edit/mailbox-templates.twig @@ -8,6 +8,7 @@ + @@ -165,6 +166,14 @@ +
+
+
+ + {{ lang.tfa.force_tfa_info }} +
+
+
{% if not skip_sogo %}
diff --git a/docker-compose.yml b/docker-compose.yml index 615e19f57..30a7ba96b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,7 +84,7 @@ services: - clamd rspamd-mailcow: - image: ghcr.io/mailcow/rspamd:3.14.3-1 + image: ghcr.io/mailcow/rspamd:4.1.0-1 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -117,7 +117,7 @@ services: - rspamd php-fpm-mailcow: - image: ghcr.io/mailcow/phpfpm:8.2.29-2 + image: ghcr.io/mailcow/phpfpm:8.2.29-3 command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: - redis-mailcow @@ -200,7 +200,7 @@ services: - phpfpm sogo-mailcow: - image: ghcr.io/mailcow/sogo:5.12.8-1 + image: ghcr.io/mailcow/sogo:5.12.9-1 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} @@ -339,7 +339,7 @@ services: - dovecot postfix-mailcow: - image: ghcr.io/mailcow/postfix:3.7.11-2 + image: ghcr.io/mailcow/postfix:3.10.12-1 depends_on: mysql-mailcow: condition: service_started @@ -419,7 +419,7 @@ services: - php-fpm-mailcow - sogo-mailcow - rspamd-mailcow - image: ghcr.io/mailcow/nginx:1.30.2-1 + image: ghcr.io/mailcow/nginx:1.30.3-1 dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: diff --git a/helper-scripts/docker-compose.override.yml.d/EXTERNAL_MYSQL_SOCKET/docker-compose.override.yml b/helper-scripts/docker-compose.override.yml.d/EXTERNAL_MYSQL_SOCKET/docker-compose.override.yml index 5ef729a48..fd47e15fe 100644 --- a/helper-scripts/docker-compose.override.yml.d/EXTERNAL_MYSQL_SOCKET/docker-compose.override.yml +++ b/helper-scripts/docker-compose.override.yml.d/EXTERNAL_MYSQL_SOCKET/docker-compose.override.yml @@ -25,6 +25,6 @@ services: - /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock mysql-mailcow: - image: alpine:3.23 + image: alpine:3.24 command: /bin/true restart: "no"