From 95e06087494cb28e4a40c68147bcf148b825d231 Mon Sep 17 00:00:00 2001 From: Markku Post Date: Thu, 23 Oct 2025 00:27:13 +0300 Subject: [PATCH 01/13] [Web] Disable login on autodiscover/autoconfig domains Autodiscover and autoconfig domains (autodiscover.*, autoconfig.*) are intended solely for client autoconfiguration endpoints and should not display the mailcow login page. This change check the hostname and disables unauthenticated users from seeing the login page on those domains; HTTP 404 response is returned when necessary. --- data/web/index.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/web/index.php b/data/web/index.php index d4fa46e74..a1ff9310f 100644 --- a/data/web/index.php +++ b/data/web/index.php @@ -27,6 +27,12 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == ' exit(); } +$host = strtolower($_SERVER['HTTP_HOST'] ?? ''); +if (str_starts_with($host, 'autodiscover.') || str_starts_with($host, 'autoconfig.')) { + http_response_code(404); + exit(); +} + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING']; From 39f29e6c30c55e306ca1ced585835dcdb783f834 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:41:38 +0000 Subject: [PATCH 02/13] chore(deps): update dependency imagick/imagick to v3.8.1 Signed-off-by: milkmaker --- data/Dockerfiles/phpfpm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index b8a7a432a..16c4b6021 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -5,7 +5,7 @@ LABEL maintainer = "The Infrastructure Company GmbH " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?.*)$ ARG APCU_PECL_VERSION=5.1.27 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?.*)$ -ARG IMAGICK_PECL_VERSION=3.8.0 +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 # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?.*)$ From 1bd795a9c6f907a53338d7de969882b18a4dfc0d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:41:42 +0000 Subject: [PATCH 03/13] chore(deps): update dependency krakjoe/apcu to v5.1.28 Signed-off-by: milkmaker --- data/Dockerfiles/phpfpm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index b8a7a432a..05bdf129b 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -3,7 +3,7 @@ FROM php:8.2-fpm-alpine3.21 LABEL maintainer = "The Infrastructure Company GmbH " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?.*)$ -ARG APCU_PECL_VERSION=5.1.27 +ARG APCU_PECL_VERSION=5.1.28 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?.*)$ ARG IMAGICK_PECL_VERSION=3.8.0 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?.*)$ From 4cdb97c6998226a9e6daad01f151fe8c757bf028 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:41:50 +0000 Subject: [PATCH 04/13] chore(deps): update dependency php-memcached-dev/php-memcached to v3.4.0 Signed-off-by: milkmaker --- data/Dockerfiles/phpfpm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index b8a7a432a..7f1032f7f 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -9,7 +9,7 @@ ARG IMAGICK_PECL_VERSION=3.8.0 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?.*)$ ARG MAILPARSE_PECL_VERSION=3.1.9 # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?.*)$ -ARG MEMCACHED_PECL_VERSION=3.3.0 +ARG MEMCACHED_PECL_VERSION=3.4.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?.*)$ ARG REDIS_PECL_VERSION=6.2.0 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?.*)$ From 01cf72cdefdd6ab8eba462875f87ce26c2189ebd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:41:54 +0000 Subject: [PATCH 05/13] chore(deps): update dependency phpredis/phpredis to v6.3.0 Signed-off-by: milkmaker --- data/Dockerfiles/phpfpm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index b8a7a432a..7ff78c916 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -11,7 +11,7 @@ ARG MAILPARSE_PECL_VERSION=3.1.9 # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?.*)$ ARG MEMCACHED_PECL_VERSION=3.3.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?.*)$ -ARG REDIS_PECL_VERSION=6.2.0 +ARG REDIS_PECL_VERSION=6.3.0 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?.*)$ ARG COMPOSER_VERSION=2.8.6 From 689336b3e178c035776991eafdc9496bd1a5cf63 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:41:59 +0000 Subject: [PATCH 06/13] chore(deps): update dependency tianon/gosu to v1.19 Signed-off-by: milkmaker --- data/Dockerfiles/dovecot/Dockerfile | 2 +- data/Dockerfiles/sogo/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 10e141ab8..f1152c8a1 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -3,7 +3,7 @@ FROM alpine:3.21 LABEL maintainer="The Infrastructure Company GmbH " # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ -ARG GOSU_VERSION=1.17 +ARG GOSU_VERSION=1.19 ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index f2981ad04..ed7e07ed6 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -6,7 +6,7 @@ ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_VERSION=bookworm ARG SOGO_DEBIAN_REPOSITORY=https://packagingv2.sogo.nu/sogo-nightly-debian/ # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ -ARG GOSU_VERSION=1.17 +ARG GOSU_VERSION=1.19 ENV LC_ALL=C # Prerequisites From c060c205d3ab8c6a16504b688ec2fd8806b1f1a8 Mon Sep 17 00:00:00 2001 From: bluewalk Date: Sun, 21 Dec 2025 16:56:16 +0100 Subject: [PATCH 07/13] Fixes issue #6489 --- data/web/autoconfig.php | 4 ++-- data/web/inc/vars.inc.php | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/web/autoconfig.php b/data/web/autoconfig.php index 6a528d4a2..bb4a55cb6 100644 --- a/data/web/autoconfig.php +++ b/data/web/autoconfig.php @@ -29,8 +29,8 @@ header('Content-Type: application/xml'); %EMAILDOMAIN% - A mailcow mail server - mail server + + diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 9f3208e3d..f206ad633 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -33,6 +33,8 @@ if ($https_port === FALSE) { //$https_port = 1234; // Other settings => $autodiscover_config = array( + 'displayName' => 'A mailcow mail server', + 'displayShortName' => 'mail server', // General autodiscover service type: "activesync" or "imap" // emClient uses autodiscover, but does not support ActiveSync. mailcow excludes emClient from ActiveSync. // With SOGo disabled, the type will always fallback to imap. CalDAV and CardDAV will be excluded, too. From 70101d1187a99ade3133e740c93af198ac7f1bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20S=C3=BCtterlin?= Date: Thu, 1 Jan 2026 16:48:33 +0100 Subject: [PATCH 08/13] fix: Password for mobileconfig that conforms to password-complexity policy --- data/web/inc/functions.inc.php | 36 ++++++++++++++++++++++++++++++++++ data/web/mobileconfig.php | 11 ++++++----- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 1947ec465..23b8d701d 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -205,6 +205,42 @@ function password_complexity($_action, $_data = null) { break; } } + +function password_generate(){ + $password_complexity = password_complexity('get'); + $min_length = max(16, intval($password_complexity['length'])); + + $lowercase = range('a', 'z'); + $uppercase = range('A', 'Z'); + $digits = range(0, 9); + $special_chars = str_split('!@#$%^&*()?='); + + $password = [ + $lowercase[random_int(0, count($lowercase) - 1)], + $uppercase[random_int(0, count($uppercase) - 1)], + $digits[random_int(0, count($digits) - 1)], + $special_chars[random_int(0, count($special_chars) - 1)], + ]; + + $all = array_merge($lowercase, $uppercase, $digits, $special_chars); + + while (count($password) < $min_length) { + $password[] = $all[random_int(0, count($all) - 1)]; + } + + // Cryptographically secure shuffle using Fisher-Yates algorithm + $count = count($password); + for ($i = $count - 1; $i > 0; $i--) { + $j = random_int(0, $i); + $temp = $password[$i]; + $password[$i] = $password[$j]; + $password[$j] = $temp; + } + + return implode('', $password); + +} + function password_check($password1, $password2) { $password_complexity = password_complexity('get'); diff --git a/data/web/mobileconfig.php b/data/web/mobileconfig.php index 44aaa30ae..7c0ead7f5 100644 --- a/data/web/mobileconfig.php +++ b/data/web/mobileconfig.php @@ -34,15 +34,15 @@ catch(PDOException $e) { if (isset($_GET['only_email'])) { $onlyEmailAccount = true; - $description = 'IMAP'; + $description = 'IMAP'; } else { $onlyEmailAccount = false; - $description = 'IMAP, CalDAV, CardDAV'; + $description = 'IMAP, CalDAV, CardDAV'; } if (isset($_GET['app_password'])) { $app_password = true; $description .= ' with application password'; - + if (strpos($_SERVER['HTTP_USER_AGENT'], 'iPad') !== FALSE) $platform = 'iPad'; elseif (strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== FALSE) @@ -51,8 +51,9 @@ if (isset($_GET['app_password'])) { $platform = 'Mac'; else $platform = $_SERVER['HTTP_USER_AGENT']; - - $password = bin2hex(openssl_random_pseudo_bytes(16)); + + $password = password_generate(); + $attr = array( 'app_name' => $platform, 'app_passwd' => $password, From 71fa3ecebc9e11ee9936ed5bdf69bfcb90d9c1dc Mon Sep 17 00:00:00 2001 From: milkmaker Date: Wed, 7 Jan 2026 17:22:01 +0100 Subject: [PATCH 09/13] update postscreen_access.cidr (#6987) --- data/conf/postfix/postscreen_access.cidr | 101 ++++++++--------------- 1 file changed, 34 insertions(+), 67 deletions(-) diff --git a/data/conf/postfix/postscreen_access.cidr b/data/conf/postfix/postscreen_access.cidr index 694b98636..9ff9f6265 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 Mon Dec 1 00:24:43 UTC 2025 +# Whitelist generated by Postwhite v3.4 on Thu Jan 1 00:24:01 UTC 2026 # https://github.com/stevejenkins/postwhite/ -# 2186 total rules +# 2105 total rules 2a00:1450:4000::/36 permit 2a01:111:f400::/48 permit 2a01:111:f403:2800::/53 permit @@ -54,8 +54,8 @@ 8.36.116.0/24 permit 8.39.144.0/24 permit 12.130.86.238 permit -13.107.213.69 permit -13.107.246.69 permit +13.107.213.38 permit +13.107.246.38 permit 13.108.16.0/20 permit 13.110.208.0/21 permit 13.110.209.0/24 permit @@ -65,7 +65,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 @@ -296,14 +295,6 @@ 52.94.124.0/28 permit 52.95.48.152/29 permit 52.95.49.88/29 permit -52.96.91.34 permit -52.96.111.82 permit -52.96.172.98 permit -52.96.222.194 permit -52.96.222.226 permit -52.96.223.2 permit -52.96.228.130 permit -52.96.229.242 permit 52.100.0.0/15 permit 52.102.0.0/16 permit 52.103.0.0/17 permit @@ -397,19 +388,8 @@ 64.207.219.143 permit 64.233.160.0/19 permit 65.52.80.137 permit -65.54.121.120/29 permit 65.55.29.77 permit -65.55.33.64/28 permit 65.55.42.224/28 permit -65.55.52.224/27 permit -65.55.78.128/25 permit -65.55.81.48/28 permit -65.55.94.0/25 permit -65.55.113.64/26 permit -65.55.126.0/25 permit -65.55.174.0/25 permit -65.55.178.128/27 permit -65.55.234.192/26 permit 65.110.161.77 permit 65.123.29.213 permit 65.123.29.220 permit @@ -529,7 +509,6 @@ 69.169.224.0/20 permit 69.171.232.0/24 permit 69.171.244.0/23 permit -70.37.151.128/25 permit 70.42.149.35 permit 72.3.185.0/24 permit 72.14.192.0/18 permit @@ -654,12 +633,18 @@ 81.169.146.245 permit 81.169.146.246 permit 81.223.46.0/27 permit +82.165.159.2 permit +82.165.159.3 permit +82.165.159.4 permit 82.165.159.12 permit 82.165.159.13 permit 82.165.159.14 permit +82.165.159.34 permit +82.165.159.35 permit 82.165.159.40 permit 82.165.159.41 permit 82.165.159.42 permit +82.165.159.45 permit 82.165.159.130 permit 82.165.159.131 permit 85.9.206.169 permit @@ -715,8 +700,6 @@ 91.198.2.0/24 permit 91.211.240.0/22 permit 94.236.119.0/26 permit -94.245.112.0/27 permit -94.245.112.10/31 permit 95.131.104.0/21 permit 95.217.114.154 permit 96.43.144.0/20 permit @@ -1354,11 +1337,6 @@ 108.179.144.0/20 permit 109.224.244.0/24 permit 109.237.142.0/24 permit -111.221.23.128/25 permit -111.221.26.0/27 permit -111.221.66.0/25 permit -111.221.69.128/25 permit -111.221.112.0/21 permit 112.19.199.64/29 permit 112.19.242.64/29 permit 116.214.12.47 permit @@ -1420,6 +1398,7 @@ 129.153.194.228 permit 129.154.255.129 permit 129.158.56.255 permit +129.158.62.153 permit 129.159.22.159 permit 129.159.87.137 permit 129.213.195.191 permit @@ -1441,16 +1420,6 @@ 134.170.143.0/24 permit 134.170.174.0/24 permit 135.84.216.0/22 permit -136.143.160.0/24 permit -136.143.161.0/24 permit -136.143.162.0/24 permit -136.143.176.0/24 permit -136.143.177.0/24 permit -136.143.178.49 permit -136.143.182.0/23 permit -136.143.184.0/24 permit -136.143.188.0/24 permit -136.143.190.0/23 permit 136.146.128.0/20 permit 136.147.128.0/20 permit 136.147.135.0/24 permit @@ -1468,6 +1437,8 @@ 139.138.58.119 permit 139.180.17.0/24 permit 140.238.148.191 permit +141.148.55.217 permit +141.148.91.244 permit 141.148.159.229 permit 141.193.32.0/23 permit 141.193.184.32/27 permit @@ -1513,6 +1484,7 @@ 149.72.234.184 permit 149.72.248.236 permit 149.97.173.180 permit +150.136.21.199 permit 150.230.98.160 permit 151.145.38.14 permit 152.67.105.195 permit @@ -1522,17 +1494,7 @@ 155.248.220.138 permit 155.248.234.149 permit 155.248.237.141 permit -157.55.9.128/25 permit -157.55.11.0/25 permit -157.55.49.0/25 permit -157.55.61.0/24 permit -157.55.157.128/25 permit -157.55.225.0/25 permit -157.56.24.0/25 permit 157.56.120.128/26 permit -157.56.232.0/21 permit -157.56.240.0/20 permit -157.56.248.0/21 permit 157.58.30.128/25 permit 157.58.196.96/29 permit 157.58.249.3 permit @@ -1582,6 +1544,9 @@ 163.114.135.16 permit 163.116.128.0/17 permit 163.192.116.87 permit +163.192.125.176 permit +163.192.196.146 permit +163.192.204.161 permit 164.152.23.32 permit 164.152.25.241 permit 164.177.132.168/30 permit @@ -1614,6 +1579,7 @@ 168.245.12.252 permit 168.245.46.9 permit 168.245.127.231 permit +170.9.232.254 permit 170.10.128.0/24 permit 170.10.129.0/24 permit 170.10.132.56/29 permit @@ -1623,7 +1589,6 @@ 173.0.84.224/27 permit 173.0.94.244/30 permit 173.194.0.0/16 permit -173.194.0.0/17 permit 173.203.79.182 permit 173.203.81.39 permit 173.224.161.128/25 permit @@ -1852,7 +1817,6 @@ 204.14.232.64/28 permit 204.14.234.64/28 permit 204.75.142.0/24 permit -204.79.197.212 permit 204.92.114.187 permit 204.92.114.203 permit 204.92.114.204/31 permit @@ -1878,23 +1842,13 @@ 206.165.246.80/29 permit 206.191.224.0/19 permit 206.246.157.1 permit -207.46.4.128/25 permit 207.46.22.35 permit 207.46.50.72 permit 207.46.50.82 permit -207.46.50.192/26 permit -207.46.50.224 permit 207.46.52.71 permit 207.46.52.79 permit -207.46.58.128/25 permit -207.46.116.128/29 permit -207.46.132.128/27 permit -207.46.198.0/25 permit -207.46.200.0/27 permit 207.67.38.0/24 permit 207.67.98.192/27 permit -207.68.176.0/26 permit -207.68.176.96/27 permit 207.97.204.96/29 permit 207.126.144.0/20 permit 207.171.160.0/19 permit @@ -1993,11 +1947,19 @@ 212.82.111.228/31 permit 212.82.111.230 permit 212.123.28.40 permit +212.227.15.3 permit +212.227.15.4 permit +212.227.15.5 permit +212.227.15.6 permit 212.227.15.7 permit 212.227.15.8 permit +212.227.15.14 permit 212.227.15.15 permit 212.227.15.18 permit 212.227.15.19 permit +212.227.15.25 permit +212.227.15.26 permit +212.227.15.29 permit 212.227.15.44 permit 212.227.15.45 permit 212.227.15.46 permit @@ -2005,11 +1967,17 @@ 212.227.15.50 permit 212.227.15.52 permit 212.227.15.53 permit +212.227.15.54 permit +212.227.15.55 permit 212.227.17.1 permit 212.227.17.2 permit 212.227.17.7 permit +212.227.17.11 permit +212.227.17.12 permit 212.227.17.16 permit 212.227.17.17 permit +212.227.17.18 permit +212.227.17.19 permit 212.227.17.20 permit 212.227.17.21 permit 212.227.17.22 permit @@ -2035,8 +2003,6 @@ 213.199.128.145 permit 213.199.138.181 permit 213.199.138.191 permit -213.199.161.128/27 permit -213.199.177.0/26 permit 216.17.150.242 permit 216.17.150.251 permit 216.24.224.0/20 permit @@ -2064,7 +2030,6 @@ 216.39.62.60/31 permit 216.39.62.136/29 permit 216.39.62.144/31 permit -216.58.192.0/19 permit 216.66.217.240/29 permit 216.71.138.33 permit 216.71.152.207 permit @@ -2094,6 +2059,8 @@ 216.205.24.0/24 permit 216.221.160.0/19 permit 216.239.32.0/19 permit +217.72.192.77 permit +217.72.192.78 permit 217.77.141.52 permit 217.77.141.59 permit 217.175.194.0/24 permit From e727620bd39a17284d3eabc05a74cabfbcd3709e Mon Sep 17 00:00:00 2001 From: milkmaker Date: Wed, 7 Jan 2026 17:23:31 +0100 Subject: [PATCH 10/13] Translations update from Weblate (#7002) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Web] Updated lang.zh-cn.json Co-authored-by: ガラスのような夢 * [Web] Updated lang.pl-pl.json Co-authored-by: Monika Bark --------- Co-authored-by: ガラスのような夢 Co-authored-by: Monika Bark --- data/web/lang/lang.pl-pl.json | 17 ++++++++++++++--- data/web/lang/lang.zh-cn.json | 7 +++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/data/web/lang/lang.pl-pl.json b/data/web/lang/lang.pl-pl.json index d78d46f7a..9ca696adb 100644 --- a/data/web/lang/lang.pl-pl.json +++ b/data/web/lang/lang.pl-pl.json @@ -675,7 +675,7 @@ "timeout1": "Limit czasu połączenia z serwerem zdalnym", "timeout2": "Limit czasu połączenia z serwerem lokalnym", "validate_save": "Zatwierdź i zapisz", - "pushover_info": "Ustawienia powiadomień push będą miały zastosowanie do wszystkich czystych (niespamowych) wiadomości dostarczanych do %s, w tym aliasów (współdzielonych, niewspółdzielonych, oznaczonych)", + "pushover_info": "Ustawienia powiadomień push będą miały zastosowanie do wszystkich czystych (niespamowych) wiadomości dostarczanych do %s w tym aliasów (współdzielonych, niewspółdzielonych, oznaczonych).", "mailbox_quota_def": "Domyślny limit skrzynki pocztowej", "mailbox_relayhost_info": "Dotyczy wyłącznie skrzynki pocztowej i bezpośrednich aliasów, nadpisuje ustawienie serwera pośredniczącego (relayhost) dla domeny.", "maxbytespersecond": "Max. Ilość bajtów na sekundę
(0 = unlimited)", @@ -687,7 +687,17 @@ "mbox_rl_info": "Ten limit szybkości dotyczy nazwy logowania SASL i odpowiada dowolnemu adresowi „from” używanemu przez zalogowanego użytkownika. Limit szybkości dla skrzynki pocztowej nadpisuje limit szybkości dla całej domeny.", "nexthop": "Następny hop", "private_comment": "Prywatny komentarz", - "public_comment": "Komentarz publiczny" + "public_comment": "Komentarz publiczny", + "mta_sts": "Konfiguruj MTA-STS", + "mta_sts_info": "MTA-STS to standard wymuszający dostarczanie poczty elektronicznej pomiędzy serwerami pocztowymi z użyciem TLS oraz ważnych certyfikatów.\n\nJest stosowany wtedy, gdy użycie DANE nie jest możliwe z powodu braku lub nieobsługiwanego DNSSEC.\n\n
Uwaga: Jeżeli domena odbiorcza obsługuje DANE z DNSSEC, DANE jest zawsze preferowane — MTA-STS działa wyłącznie jako mechanizm zapasowy.", + "mta_sts_version": "Wersja.", + "mta_sts_version_info": "Określa wersję standardu MTA-STS — obecnie jedyną prawidłową wartością jest STSv1..", + "mta_sts_mode": "Tryb.", + "mta_sts_mode_info": "Dostępne są trzy tryby do wyboru:\n
  • testing – polityka jest wyłącznie monitorowana, a naruszenia nie mają wpływu na dostarczanie poczty.
  • enforce – polityka jest ściśle egzekwowana; połączenia bez ważnego TLS są odrzucane.
  • none – polityka jest publikowana, lecz nie jest stosowana.
.", + "mta_sts_max_age": "Maksymalny czas obowiązywania.", + "mta_sts_max_age_info": "Czas (w sekundach) przechowywania polityki w cache przez serwery odbierające..", + "mta_sts_mx": "serwer MX.", + "mta_sts_mx_info": "Umożliwia wysyłanie poczty wyłącznie do jawnie wymienionych nazw hostów serwerów pocztowych; wysyłający MTA sprawdza, czy nazwa hosta MX w DNS odpowiada liście z polityki, i zezwala na dostarczenie tylko przy użyciu ważnego certyfikatu TLS (ochrona przed atakami MITM).." }, "footer": { "cancel": "Anuluj", @@ -1179,7 +1189,8 @@ "waiting": "Oczekuje", "with_app_password": "z hasłem aplikacji", "year": "rok", - "years": "lata" + "years": "lata", + "spam_aliases_info": "Alias antyspamowy to tymczasowy adres e-mail, który może być używany do ochrony właściwych adresów pocztowych.
Opcjonalnie można ustawić czas wygaśnięcia, po którym alias zostanie automatycznie dezaktywowany, co pozwala skutecznie pozbyć się nadużywanych lub ujawnionych adresów." }, "warning": { "session_ua": "Nieprawidłowy token formularza: Błąd walidacji User-Agent", diff --git a/data/web/lang/lang.zh-cn.json b/data/web/lang/lang.zh-cn.json index 94473a405..f31d865cb 100644 --- a/data/web/lang/lang.zh-cn.json +++ b/data/web/lang/lang.zh-cn.json @@ -1321,7 +1321,7 @@ "sogo_profile_reset": "重置 SOGo 个人资料", "sogo_profile_reset_help": "此操作会不可恢复地删除用户的 SOGo 个人资料并删除所有联系人和日历数据。", "sogo_profile_reset_now": "立即重置个人资料", - "spam_aliases": "临时邮箱别名", + "spam_aliases": "垃圾邮件别名", "spam_score_reset": "重置为服务器默认值", "spamfilter": "垃圾邮件过滤器", "spamfilter_behavior": "分数", @@ -1381,7 +1381,10 @@ "protocols": "协议", "authentication": "认证", "tfa_info": "两步验证有助于保护您的账户安全。启用后,对于不支持两步验证的应用程序或服务(例如邮件客户端),需要使用应用专用密码进行登录。", - "overview": "概览" + "overview": "概览", + "expire_never": "永不过期", + "forever": "永久", + "spam_aliases_info": "垃圾邮件别名是一种临时电子邮件地址,可用于保护真实电子邮件地址。
还可以选择设置过期时间,以便在设定的时间后自动停用别名,从而有效地销毁被滥用或泄露的地址。" }, "warning": { "cannot_delete_self": "不能删除已登录的用户", From 0999c9e9ab7d817dd8da17964e58502089757afe Mon Sep 17 00:00:00 2001 From: milkmaker Date: Fri, 23 Jan 2026 22:02:55 +0100 Subject: [PATCH 11/13] Translations update from Weblate (#7014) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Web] Updated lang.zh-cn.json Co-authored-by: 雨 * [Web] Updated lang.pl-pl.json Co-authored-by: Monika Bark Co-authored-by: milkmaker --------- Co-authored-by: 雨 Co-authored-by: Monika Bark --- data/web/lang/lang.pl-pl.json | 20 ++++++++++++++++++-- data/web/lang/lang.zh-cn.json | 4 ++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/data/web/lang/lang.pl-pl.json b/data/web/lang/lang.pl-pl.json index 9ca696adb..758d0cb40 100644 --- a/data/web/lang/lang.pl-pl.json +++ b/data/web/lang/lang.pl-pl.json @@ -697,7 +697,16 @@ "mta_sts_max_age": "Maksymalny czas obowiązywania.", "mta_sts_max_age_info": "Czas (w sekundach) przechowywania polityki w cache przez serwery odbierające..", "mta_sts_mx": "serwer MX.", - "mta_sts_mx_info": "Umożliwia wysyłanie poczty wyłącznie do jawnie wymienionych nazw hostów serwerów pocztowych; wysyłający MTA sprawdza, czy nazwa hosta MX w DNS odpowiada liście z polityki, i zezwala na dostarczenie tylko przy użyciu ważnego certyfikatu TLS (ochrona przed atakami MITM).." + "mta_sts_mx_info": "Umożliwia wysyłanie poczty wyłącznie do jawnie wymienionych nazw hostów serwerów pocztowych; wysyłający MTA sprawdza, czy nazwa hosta MX w DNS odpowiada liście z polityki, i zezwala na dostarczenie tylko przy użyciu ważnego certyfikatu TLS (ochrona przed atakami MITM)..", + "mta_sts_mx_notice": "Dopuszcza się podanie wielu serwerów MX, rozdzielonych przecinkami..", + "none_inherit": "Brak /Dziedzicz", + "password_recovery_email": "Email do odzyskiwania hasła", + "pushover": "Pushover", + "pushover_evaluate_x_prio": "Eskaluj wiadomości o wysokim priorytecie [X-Priority: 1]", + "pushover_only_x_prio": "Uwzględniaj wyłącznie wiadomości o wysokim priorytecie [X-Priority: 1]", + "pushover_sender_array": "Uwzględniaj wyłącznie następujące adresy e-mail nadawców (oddzielone przecinkami)", + "pushover_sender_regex": "Bierz pod uwagę następujący regex nadawcy", + "pushover_text": "Tekst powiadomienia" }, "footer": { "cancel": "Anuluj", @@ -854,7 +863,14 @@ "template": "Szablon", "tls_map_dest": "Miejsce docelowe", "tls_map_dest_info": "Przykłady: example.org, .example.org, [mail.example.org]:25", - "tls_map_parameters": "Parametry" + "tls_map_parameters": "Parametry", + "add_recipient_map_entry": "Dodaj mapę odbiorców", + "add_template": "Dodaj szablon", + "add_tls_policy_map": "Dodaj mapę polityk TLS", + "address_rewriting": "Przepisywanie adresów", + "alias_domain_alias_hint": "Aliasy nie są automatycznie stosowane do aliasów domen. Adres aliasu my-alias@domain nie obejmuje adresu my-alias@alias-domain (gdzie „alias-domain” jest przykładową domeną aliasową dla „domain”).\n
Aby przekierować pocztę do zewnętrznej skrzynki, użyj filtra Sieve (zob. kartę „Filtry” lub SOGo → Przekazywanie). Skorzystaj z opcji „Rozszerz alias na domeny aliasowe”, aby automatycznie dodać brakujące aliasy.", + "alias_domain_backupmx": "Domena aliasowa nieaktywna dla domeny przekaźnikowej", + "all_domains": "Wszystkie domeny" }, "quarantine": { "action": "Działanie", diff --git a/data/web/lang/lang.zh-cn.json b/data/web/lang/lang.zh-cn.json index f31d865cb..fe225a5df 100644 --- a/data/web/lang/lang.zh-cn.json +++ b/data/web/lang/lang.zh-cn.json @@ -582,13 +582,13 @@ "username": "用户名", "container_disabled": "容器已被停止或禁用", "container_running": "运行中", - "cores": "核心数", + "cores": "核", "memory": "内存", "error_show_ip": "无法解析公网IP地址", "show_ip": "显示公网IP", "update_available": "有可用更新", "update_failed": "无法检查更新", - "architecture": "结构", + "architecture": "架构", "container_stopped": "已停止", "current_time": "系统时间", "timezone": "时区", From 382ee34d0e9abbe009254f330b4465cdec4e39df Mon Sep 17 00:00:00 2001 From: milkmaker Date: Mon, 26 Jan 2026 20:15:47 +0100 Subject: [PATCH 12/13] [Web] Updated lang.hu-hu.json (#7020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sándor --- data/web/lang/lang.hu-hu.json | 117 ++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 6 deletions(-) diff --git a/data/web/lang/lang.hu-hu.json b/data/web/lang/lang.hu-hu.json index 2f78d5b21..e51748c48 100644 --- a/data/web/lang/lang.hu-hu.json +++ b/data/web/lang/lang.hu-hu.json @@ -295,7 +295,9 @@ "user_quicklink": "Gyorshivatkozás elrejtése a Felhasználói bejelentkezési oldalra", "validate_license_now": "GUID érvényesítése a licenszszerverrel szemben", "yes": "✓", - "success": "Siker" + "success": "Siker", + "login_page": "Belépő oldal", + "needs_restart": "újraindítást igényel" }, "edit": { "active": "Aktív", @@ -1070,7 +1072,7 @@ "post_domain_add": "A \"sogo-mailcow\" SOGo konténert újra kell indítani egy új tartomány hozzáadása után!

Kiegészítésképpen a tartományok DNS-konfigurációját is felül kell vizsgálni. A DNS-konfiguráció jóváhagyása után indítsa újra az \"acme-mailcow\"-t, hogy automatikusan generáljon tanúsítványokat az új tartományhoz (autoconfig.<domain>, autodiscover.<domain>).
Ez a lépés opcionális, és 24 óránként megismétlődik.", "dry": "Szinkronizálás szimulálása", "inactive": "Inaktív", - "kind": "Kedves", + "kind": "Típus", "mailbox_quota_m": "Maximális kvóta postafiókonként (MiB)", "mailbox_username": "Felhasználónév (az e-mail cím bal oldali része)", "max_aliases": "Max. lehetséges álnevek", @@ -1092,9 +1094,9 @@ "exclude": "Objektumok kizárása (regex)", "full_name": "Teljes név", "gal": "Globális címlista", - "goto_ham": "Tanulj sonkaként", + "goto_ham": "Tanítás valódi levélként", "goto_null": "Leveleket csendben eldobni", - "goto_spam": "Tanuld spamként", + "goto_spam": "Tanítás spamként", "syncjob_hint": "Ne feledje, hogy a jelszavakat egyszerű szöveges formában kell elmenteni!", "target_address": "Továbbítási címek", "target_address_info": "Teljes e-mail cím(ek) (vesszővel elválasztva).", @@ -1102,7 +1104,7 @@ "comment_info": "A privát megjegyzés nem látható a felhasználó számára, míg a nyilvános megjegyzés tooltip-ként jelenik meg, amikor a felhasználó áttekintésében a megjegyzésre mutat.", "custom_params": "Egyéni paraméterek", "gal_info": "A GAL tartalmazza a tartomány összes objektumát, és egyetlen felhasználó sem szerkesztheti. A SOGo-ban a Szabad/Elfoglalt információ hiányzik, ha ki van kapcsolva! Indítsa újra a SOGo-t a változások alkalmazásához.", - "hostname": "Házigazda", + "hostname": "Hoszt", "backup_mx_options": "Továbbítási opciók", "custom_params_hint": "Megfelelő: --param=xy, Rossz: --param xy", "delete1": "Törlés a forrásból, ha befejeződött", @@ -1140,6 +1142,109 @@ "sieve_type": "Szűrő típusa", "skipcrossduplicates": "Duplikált üzenetek átugrása mappák között (érkezési sorrendben)", "subscribeall": "Feliratkozás minden mappára", - "syncjob": "Szinkronizálási feladat hozzáadása" + "syncjob": "Szinkronizálási feladat hozzáadása", + "internal": "Belső", + "internal_info": "Belső álnevek csak a saját domain vagy domain álnév számára elérhető." + }, + "danger": { + "access_denied": "Hozzáférés megtagatva vagy nem megfelelő űrlap adat", + "alias_domain_invalid": "Az alias domain %s érvénytelen", + "alias_empty": "Az alias cím nem lehet üres", + "alias_goto_identical": "Az alias és a goto cím nem lehetnek azonosak", + "alias_invalid": "Az alias cím %s érvénytelen", + "aliasd_targetd_identical": "Az alias tartomány nem lehet azonos a céltartománnyal: %s", + "aliases_in_use": "A maximális aliasoknak nagyobbnak vagy egyenlőnek kell lenniük mint %d", + "app_name_empty": "Az alkalmazás neve nem lehet üres", + "app_passwd_id_invalid": "Alkalmazás jelszó ID %s érvénytelen", + "authsource_in_use": "A személyazonosság szolgáltatót nem lehet megváltoztatni vagy törölni, mivel ez jelenleg használatban van legalább 1 felhasználónál.", + "bcc_empty": "BCC cél nem lehet üres", + "bcc_exists": "A %s típushoz létezik egy %s típusú BCC térkép.", + "bcc_must_be_email": "A BCC cél %s nem érvényes e-mail cím", + "comment_too_long": "Túl hosszú megjegyzés, max 160 karakter megengedett", + "cors_invalid_method": "Érvénytelen Allow-Method lett megadva", + "cors_invalid_origin": "Érvénytelen Allow-Origin lett megadva", + "defquota_empty": "A postafiókonkénti alapértelmezett kvóta nem lehet 0.", + "demo_mode_enabled": "Demo mód engedélyezve", + "description_invalid": "A %s erőforrás leírása érvénytelen", + "dkim_domain_or_sel_exists": "A \"%s\" DKIM-kulcs létezik, és nem lesz felülírva", + "dkim_domain_or_sel_invalid": "DKIM tartomány vagy szelektor érvénytelen: %s", + "domain_cannot_match_hostname": "A tartomány nem egyezik a hostnévvel", + "domain_exists": "A %s domain már létezik", + "domain_invalid": "A domain név üres vagy érvénytelen", + "domain_not_empty": "Nem lehet eltávolítani a nem üres domaint %s", + "domain_not_found": "Nem található domain %s", + "domain_quota_m_in_use": "A domain kvótának nagyobbnak vagy egyenlőnek kell lennie %s MiB-nál", + "extended_sender_acl_denied": "hiányzó ACL külső küldő cím beállításához", + "extra_acl_invalid": "A \"%s\" külső feladó címe érvénytelen", + "extra_acl_invalid_domain": "Külső feladó \"%s\" érvénytelen tartományt használ", + "fido2_verification_failed": "FIDO2 ellenőrzés sikertelen: %s", + "file_open_error": "A fájl nem nyitható meg írásra", + "filter_type": "Rossz szűrőtípus", + "from_invalid": "A feladó nem lehet üres", + "generic_server_error": "Váratlan szerver hiba keletkezett. Vedd fel a kapcsolatot az adminisztrátorral.", + "global_filter_write_error": "Nem tudott szűrőfájlt írni: %s", + "global_map_invalid": "Globális térkép azonosítója %s érvénytelen", + "global_map_write_error": "Nem tudott globális térképet írni ID %s: %s", + "goto_empty": "Egy alias címnek legalább egy érvényes goto címet kell tartalmaznia.", + "goto_invalid": "Goto cím %s érvénytelen", + "ham_learn_error": "Ham tanulási hiba: %s", + "iam_test_connection": "Kapcsolódás sikertelen", + "imagick_exception": "Hiba: Kép olvasása közben Imagick hiba keletkezett", + "img_dimensions_exceeded": "A kép meghaladja a maximális méretet", + "img_invalid": "A képfájlt nem lehet érvényesíteni", + "img_size_exceeded": "A kép meghaladja a maximális fájl méretet", + "img_tmp_missing": "A képfájlt nem lehet érvényesíteni: Ideiglenes fájl nem található", + "invalid_bcc_map_type": "Érvénytelen a BCC térkép típusa", + "invalid_destination": "A \"%s\" célállomás formátum érvénytelen", + "invalid_filter_type": "Érvénytelen szűrőtípus", + "invalid_host": "Érvénytelen host megadva: %s", + "invalid_mime_type": "Érvénytelen mime típus", + "invalid_nexthop": "A következő ugrás formátuma érvénytelen", + "invalid_nexthop_authenticated": "A következő ugrás más hitelesítő adatokkal létezik, kérjük, először frissítse a meglévő hitelesítő adatokat ehhez a következő ugráshoz.", + "invalid_recipient_map_new": "Érvénytelen új címzett megadása: %s", + "invalid_recipient_map_old": "Érvénytelen eredeti címzett van megadva: %s", + "invalid_reset_token": "Érvénytelen visszaállító kulcs", + "ip_list_empty": "Az engedélyezett IP-k listája nem lehet üres", + "is_alias": "%s már ismert álnév címként", + "is_alias_or_mailbox": "%s már ismert alias, egy postafiók vagy egy alias tartományból kiterjesztett alias cím.", + "is_spam_alias": "%s már ismert ideiglenes alias cím (spam alias cím)", + "last_key": "Az utolsó kulcs nem törölhető, kérjük, helyette deaktiválja a TFA-t.", + "login_failed": "A bejelentkezés sikertelen", + "mailbox_defquota_exceeds_mailbox_maxquota": "Az alapértelmezett kvóta meghaladja a maximális kvótakorlátot", + "mailbox_invalid": "A postafiók neve érvénytelen", + "mailbox_quota_exceeded": "A kvóta meghaladja a tartományi korlátot (max. %d MiB)", + "mailbox_quota_exceeds_domain_quota": "A maximális kvóta meghaladja a tartományi kvótakorlátot", + "mailbox_quota_left_exceeded": "Nincs elég hely (maradék hely: %d MiB)", + "mailboxes_in_use": "A maximális postafiókoknak nagyobbnak vagy egyenlőnek kell lenniük %d-vel.", + "malformed_username": "Hibás felhasználónév", + "map_content_empty": "A térkép tartalma nem lehet üres", + "max_age_invalid": "Maximális kor %s érvénytelen", + "max_alias_exceeded": "Max. aliasok túllépése", + "max_mailbox_exceeded": "Max. postafiókok túllépése (%d %d-ből %d)", + "max_quota_in_use": "A postafiók kvótának nagyobbnak vagy egyenlőnek kell lennie %d MiB-nél", + "maxquota_empty": "A postafiókonkénti maximális kvóta nem lehet 0.", + "mode_invalid": "%s mód érvénytelen", + "mx_invalid": "%s MX rekord érvénytelen", + "mysql_error": "MySQL hiba: %s", + "network_host_invalid": "Érvénytelen hálózat vagy állomás: %s", + "next_hop_interferes": "%s zavarja a nexthop %s-t", + "next_hop_interferes_any": "Egy meglévő következő ugrás zavarja a %s-t.", + "nginx_reload_failed": "Az Nginx újratöltése sikertelen: %s", + "no_user_defined": "Nincs felhasználó által meghatározott", + "object_exists": "Az objektum %s már létezik", + "object_is_not_numeric": "Az érték %s nem numerikus", + "password_complexity": "A jelszó nem felel meg a szabályzatnak", + "password_empty": "A jelszó nem lehet üres", + "password_mismatch": "A megerősítő jelszó nem egyezik", + "password_reset_invalid_user": "A fiók nem található vagy nem lett megadva visszaállításhoz email cím", + "password_reset_na": "A jelszó visszaállítás jelenleg nem elérhető. Vedd fel a kapcsolatot az adminisztrátorral.", + "policy_list_from_exists": "A megadott nevű rekord létezik", + "policy_list_from_invalid": "A rekord érvénytelen formátumú", + "private_key_error": "Privát kulcs hiba: %s", + "pushover_credentials_missing": "Pushover token és/vagy kulcs hiányzik", + "pushover_key": "A pushover kulcs rossz formátumú", + "pushover_token": "A Pushover token rossz formátumú", + "quota_not_0_not_numeric": "A kvótának numerikusnak és >= 0-nak kell lennie.", + "recipient_map_entry_exists": "Létezik egy \"%s\" címzett-térkép bejegyzés" } } From 56ea4302ed13ac9aa344dc840689c4dd6ac76b68 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it <75116288+FreddleSpl0it@users.noreply.github.com> Date: Wed, 28 Jan 2026 09:49:33 +0100 Subject: [PATCH 13/13] [Web] Allow admins to limit EAS and DAV access for mailbox users --- data/conf/dovecot/auth/mailcowauth.php | 15 +++-- data/web/autodiscover.php | 2 +- data/web/inc/functions.auth.inc.php | 57 ++++++------------- data/web/inc/functions.mailbox.inc.php | 20 +++++++ data/web/inc/init_db.inc.php | 4 +- data/web/inc/prerequisites.inc.php | 2 +- data/web/inc/triggers.admin.inc.php | 2 +- data/web/inc/triggers.domainadmin.inc.php | 2 +- data/web/inc/triggers.user.inc.php | 2 +- data/web/inc/vars.inc.php | 6 ++ data/web/js/site/mailbox.js | 32 +++++++++++ data/web/sogo-auth.php | 14 +++-- .../web/templates/edit/mailbox-templates.twig | 2 + data/web/templates/edit/mailbox.twig | 2 + data/web/templates/modals/mailbox.twig | 4 ++ data/web/templates/user/tab-user-auth.twig | 2 + 16 files changed, 111 insertions(+), 57 deletions(-) diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php index 06c0bd995..d1c7381f6 100644 --- a/data/conf/dovecot/auth/mailcowauth.php +++ b/data/conf/dovecot/auth/mailcowauth.php @@ -80,14 +80,21 @@ if ($isSOGoRequest) { } if ($result === false){ // If it's a SOGo Request, don't check for protocol access - $service = ($isSOGoRequest) ? false : array($post['service'] => true); - $result = apppass_login($post['username'], $post['password'], $service, array( + if ($isSOGoRequest) { + $service = 'SOGO'; + $post['service'] = 'NONE'; + } else { + $service = $post['service']; + } + + $result = apppass_login($post['username'], $post['password'], array( + 'service' => $post['service'], 'is_internal' => true, 'remote_addr' => $post['real_rip'] )); if ($result) { - error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']); - set_sasl_log($post['username'], $post['real_rip'], $post['service']); + error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $service . " from IP " . $post['real_rip']); + set_sasl_log($post['username'], $post['real_rip'], $service); } } if ($result === false){ diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index 224f94f71..d3cda4004 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -79,7 +79,7 @@ if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])) { exit(0); } -$login_role = check_login($login_user, $login_pass, array('eas' => TRUE)); +$login_role = check_login($login_user, $login_pass, array('service' => 'EAS')); if ($login_role === "user") { header("Content-Type: application/xml"); diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 059dd4cd9..3903ba642 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -1,10 +1,11 @@ fetch(PDO::FETCH_ASSOC); if (!empty($row)) { - // check if user has access to service (imap, smtp, pop3, sieve) if service is set + // check if user has access to service (imap, smtp, pop3, sieve, dav, eas) if service is set $row['attributes'] = json_decode($row['attributes'], true); - if (isset($service)) { - $key = strtolower($service) . "_access"; + if ($extra['service'] != 'NONE') { + $key = strtolower($extra['service']) . "_access"; if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') { return false; } @@ -253,8 +240,8 @@ function user_login($user, $pass, $extra = null){ // check if user has access to service (imap, smtp, pop3, sieve) if service is set $row['attributes'] = json_decode($row['attributes'], true); - if (isset($service)) { - $key = strtolower($service) . "_access"; + if ($extra['service'] != 'NONE') { + $key = strtolower($extra['service']) . "_access"; if (isset($row['attributes'][$key]) && $row['attributes'][$key] != '1') { return false; } @@ -408,7 +395,7 @@ function user_login($user, $pass, $extra = null){ return false; } -function apppass_login($user, $pass, $app_passwd_data, $extra = null){ +function apppass_login($user, $pass, $extra = null){ global $pdo; $is_internal = $extra['is_internal']; @@ -424,20 +411,8 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){ return false; } - $protocol = false; - if ($app_passwd_data['eas']){ - $protocol = 'eas'; - } else if ($app_passwd_data['dav']){ - $protocol = 'dav'; - } else if ($app_passwd_data['smtp']){ - $protocol = 'smtp'; - } else if ($app_passwd_data['imap']){ - $protocol = 'imap'; - } else if ($app_passwd_data['sieve']){ - $protocol = 'sieve'; - } else if ($app_passwd_data['pop3']){ - $protocol = 'pop3'; - } else if (!$is_internal) { + $extra['service'] = !isset($extra['service']) ? 'NONE' : $extra['service']; + if (!$is_internal && $extra['service'] == 'NONE') { return false; } @@ -458,7 +433,7 @@ function apppass_login($user, $pass, $app_passwd_data, $extra = null){ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { - if ($protocol && $row[$protocol . '_access'] != '1'){ + if ($extra['service'] != 'NONE' && $row[strtolower($extra['service']) . '_access'] != '1'){ continue; } diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index d8e4e178a..eb7d82dff 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1075,6 +1075,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + $_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0; + $_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0; } $active = (isset($_data['active'])) ? intval($_data['active']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['active']); $force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); @@ -1085,6 +1087,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $pop3_access = (isset($_data['pop3_access'])) ? intval($_data['pop3_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); $smtp_access = (isset($_data['smtp_access'])) ? intval($_data['smtp_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); $sieve_access = (isset($_data['sieve_access'])) ? intval($_data['sieve_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); + $eas_access = (isset($_data['eas_access'])) ? intval($_data['eas_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']); + $dav_access = (isset($_data['dav_access'])) ? intval($_data['dav_access']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']); $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0; $quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); @@ -1103,6 +1107,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'pop3_access' => strval($pop3_access), 'smtp_access' => strval($smtp_access), 'sieve_access' => strval($sieve_access), + 'eas_access' => strval($eas_access), + 'dav_access' => strval($dav_access), 'relayhost' => strval($relayhost), 'passwd_update' => time(), 'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']), @@ -1721,12 +1727,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + $attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0; + $attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0; } else { $attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); $attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); $attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); $attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); + $attr['eas_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['eas_access']); + $attr['dav_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['dav_access']); } if (isset($_data['acl'])) { $_data['acl'] = (array)$_data['acl']; @@ -3043,6 +3053,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_data['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + $_data['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0; + $_data['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0; } if (!empty($is_now)) { $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active']; @@ -3052,6 +3064,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { (int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']); (int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']); (int)$sieve_access = (isset($_data['sieve_access']) && hasACLAccess("protocol_access")) ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']); + (int)$eas_access = (isset($_data['eas_access']) && hasACLAccess("protocol_access")) ? intval($_data['eas_access']) : intval($is_now['attributes']['eas_access']); + (int)$dav_access = (isset($_data['dav_access']) && hasACLAccess("protocol_access")) ? intval($_data['dav_access']) : intval($is_now['attributes']['dav_access']); (int)$relayhost = (isset($_data['relayhost']) && hasACLAccess("mailbox_relayhost")) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']); (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576); $name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name']; @@ -3335,6 +3349,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access), `attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost), `attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access), + `attributes` = JSON_SET(`attributes`, '$.eas_access', :eas_access), + `attributes` = JSON_SET(`attributes`, '$.dav_access', :dav_access), `attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email), `attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash) WHERE `username` = :username"); @@ -3349,6 +3365,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':pop3_access' => $pop3_access, ':sieve_access' => $sieve_access, ':smtp_access' => $smtp_access, + ':eas_access' => $eas_access, + ':dav_access' => $dav_access, ':recovery_email' => $pw_recovery_email, ':relayhost' => $relayhost, ':username' => $username, @@ -3731,6 +3749,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; + $attr['eas_access'] = (in_array('eas', $_data['protocol_access'])) ? 1 : 0; + $attr['dav_access'] = (in_array('dav', $_data['protocol_access'])) ? 1 : 0; } else { foreach ($is_now as $key => $value){ diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index ffaf12093..67e98a5a8 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -4,7 +4,7 @@ function init_db_schema() try { global $pdo; - $db_version = "10312025_0525"; + $db_version = "28012026_1000"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -1394,6 +1394,8 @@ function init_db_schema() $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.imap_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.imap_access') IS NULL;"); $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.pop3_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.pop3_access') IS NULL;"); $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.smtp_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.smtp_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.eas_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.eas_access') IS NULL;"); + $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.dav_access', \"1\") WHERE JSON_VALUE(`attributes`, '$.dav_access') IS NULL;"); $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_VALUE(`attributes`, '$.mailbox_format') IS NULL;"); $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_VALUE(`attributes`, '$.quarantine_notification') IS NULL;"); $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_category', \"reject\") WHERE JSON_VALUE(`attributes`, '$.quarantine_category') IS NULL;"); diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index deb5da8fa..198e675aa 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -121,7 +121,7 @@ class mailcowPdo extends OAuth2\Storage\Pdo { $this->config['user_table'] = 'mailbox'; } public function checkUserCredentials($username, $password) { - if (check_login($username, $password) == 'user') { + if (check_login($username, $password, array("role" => "user", "service" => "NONE")) == 'user') { return true; } return false; diff --git a/data/web/inc/triggers.admin.inc.php b/data/web/inc/triggers.admin.inc.php index df46a459c..2a02ba511 100644 --- a/data/web/inc/triggers.admin.inc.php +++ b/data/web/inc/triggers.admin.inc.php @@ -44,7 +44,7 @@ if (isset($_GET["cancel_tfa_login"])) { if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { $login_user = strtolower(trim($_POST["login_user"])); - $as = check_login($login_user, $_POST["pass_user"], false, array("role" => "admin")); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "admin", "service" => "MAILCOWUI")); if ($as == "admin") { session_regenerate_id(true); diff --git a/data/web/inc/triggers.domainadmin.inc.php b/data/web/inc/triggers.domainadmin.inc.php index a9f913688..764d9009b 100644 --- a/data/web/inc/triggers.domainadmin.inc.php +++ b/data/web/inc/triggers.domainadmin.inc.php @@ -55,7 +55,7 @@ if (isset($_GET["cancel_tfa_login"])) { if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { $login_user = strtolower(trim($_POST["login_user"])); - $as = check_login($login_user, $_POST["pass_user"], false, array("role" => "domain_admin")); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "domain_admin", "service" => "MAILCOWUI")); if ($as == "domainadmin") { session_regenerate_id(true); diff --git a/data/web/inc/triggers.user.inc.php b/data/web/inc/triggers.user.inc.php index 36176c694..cc33596f9 100644 --- a/data/web/inc/triggers.user.inc.php +++ b/data/web/inc/triggers.user.inc.php @@ -119,7 +119,7 @@ if (isset($_GET["cancel_tfa_login"])) { if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { $login_user = strtolower(trim($_POST["login_user"])); - $as = check_login($login_user, $_POST["pass_user"], false, array("role" => "user")); + $as = check_login($login_user, $_POST["pass_user"], array("role" => "user", "service" => "MAILCOWUI")); if ($as == "user") { set_user_loggedin_session($login_user); diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 9f3208e3d..dc163d629 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -215,6 +215,12 @@ $MAILBOX_DEFAULT_ATTRIBUTES['smtp_access'] = true; // Mailbox has sieve access by default $MAILBOX_DEFAULT_ATTRIBUTES['sieve_access'] = true; +// Mailbox has ActiveSync/EAS access by default +$MAILBOX_DEFAULT_ATTRIBUTES['eas_access'] = true; + +// Mailbox has CalDAV/CardDAV (DAV) access by default +$MAILBOX_DEFAULT_ATTRIBUTES['dav_access'] = true; + // Mailbox receives notifications about... // "add_header" - mail that was put into the Junk folder // "reject" - mail that was rejected diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index df61f8720..7010077db 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -352,6 +352,12 @@ $(document).ready(function() { if (template.sieve_access == 1){ protocol_access.push("sieve"); } + if (template.eas_access == 1){ + protocol_access.push("eas"); + } + if (template.dav_access == 1){ + protocol_access.push("dav"); + } $('#protocol_access').selectpicker('val', protocol_access); var acl = []; @@ -933,6 +939,8 @@ jQuery(function($){ item.imap_access = ''; item.smtp_access = ''; item.sieve_access = ''; + item.eas_access = ''; + item.dav_access = ''; if (item.attributes.quarantine_notification === 'never') { item.quarantine_notification = lang.never; } else if (item.attributes.quarantine_notification === 'hourly') { @@ -1096,6 +1104,18 @@ jQuery(function($){ defaultContent: '', className: 'none' }, + { + title: 'EAS', + data: 'eas_access', + defaultContent: '', + className: 'none' + }, + { + title: 'DAV', + data: 'dav_access', + defaultContent: '', + className: 'none' + }, { title: lang.quarantine_notification, data: 'quarantine_notification', @@ -1209,6 +1229,8 @@ jQuery(function($){ item.attributes.imap_access = '' + (item.attributes.imap_access == 1 ? '1' : '0') + ''; item.attributes.smtp_access = '' + (item.attributes.smtp_access == 1 ? '1' : '0') + ''; item.attributes.sieve_access = '' + (item.attributes.sieve_access == 1 ? '1' : '0') + ''; + 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') + ''; if (item.attributes.quarantine_notification === 'never') { item.attributes.quarantine_notification = lang.never; @@ -1317,6 +1339,16 @@ jQuery(function($){ data: 'attributes.sieve_access', defaultContent: '', }, + { + title: 'EAS', + data: 'attributes.eas_access', + defaultContent: '', + }, + { + title: 'DAV', + data: 'attributes.dav_access', + defaultContent: '', + }, { title: 'SOGO', data: 'attributes.sogo_access', diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 962627baf..2da28d4d4 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -12,18 +12,21 @@ $session_var_pass = 'sogo-sso-pass'; if (isset($_SERVER['PHP_AUTH_USER'])) { // load prerequisites only when required require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php'; + $username = $_SERVER['PHP_AUTH_USER']; $password = $_SERVER['PHP_AUTH_PW']; - $is_eas = false; - $is_dav = false; + + // Determine service type for protocol access check + $service = 'NONE'; $original_uri = isset($_SERVER['HTTP_X_ORIGINAL_URI']) ? $_SERVER['HTTP_X_ORIGINAL_URI'] : ''; if (preg_match('/^(\/SOGo|)\/dav.*/', $original_uri) === 1) { - $is_dav = true; + $service = 'DAV'; } elseif (preg_match('/^(\/SOGo|)\/Microsoft-Server-ActiveSync.*/', $original_uri) === 1) { - $is_eas = true; + $service = 'EAS'; } - $login_check = check_login($username, $password, array('dav' => $is_dav, 'eas' => $is_eas)); + + $login_check = check_login($username, $password, array('service' => $service)); if ($login_check === 'user') { header("X-User: $username"); header("X-Auth: Basic ".base64_encode("$username:$password")); @@ -57,7 +60,6 @@ elseif (isset($_GET['login'])) { $_SESSION['mailcow_cc_role'] = "user"; } // update sasl logs - $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV'; $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES ('SSO', 0, :username, :remote_addr)"); $stmt->execute(array( ':username' => $login, diff --git a/data/web/templates/edit/mailbox-templates.twig b/data/web/templates/edit/mailbox-templates.twig index 65a83cd2a..ddc139586 100644 --- a/data/web/templates/edit/mailbox-templates.twig +++ b/data/web/templates/edit/mailbox-templates.twig @@ -108,6 +108,8 @@ + + diff --git a/data/web/templates/edit/mailbox.twig b/data/web/templates/edit/mailbox.twig index a223a7500..294957ee7 100644 --- a/data/web/templates/edit/mailbox.twig +++ b/data/web/templates/edit/mailbox.twig @@ -281,6 +281,8 @@ + + diff --git a/data/web/templates/modals/mailbox.twig b/data/web/templates/modals/mailbox.twig index 1e8ee53fa..b35c0caa8 100644 --- a/data/web/templates/modals/mailbox.twig +++ b/data/web/templates/modals/mailbox.twig @@ -148,6 +148,8 @@ + + @@ -335,6 +337,8 @@ + + diff --git a/data/web/templates/user/tab-user-auth.twig b/data/web/templates/user/tab-user-auth.twig index bace33489..171545d6c 100644 --- a/data/web/templates/user/tab-user-auth.twig +++ b/data/web/templates/user/tab-user-auth.twig @@ -55,6 +55,8 @@ {% if mailboxdata.attributes.smtp_access == 1 %}
SMTP
{% else %}
SMTP
{% endif %} {% if mailboxdata.attributes.sieve_access == 1 %}
Sieve
{% else %}
Sieve
{% endif %} {% if mailboxdata.attributes.pop3_access == 1 %}
POP3
{% else %}
POP3
{% endif %} + {% if mailboxdata.attributes.eas_access == 1 %}
ActiveSync
{% else %}
ActiveSync
{% endif %} + {% if mailboxdata.attributes.dav_access == 1 %}
CalDAV/CardDAV
{% else %}
CalDAV/CardDAV
{% endif %}