From 70190e52301197eb05b4a58f220db66457d27710 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Fri, 28 Feb 2025 15:38:05 +0100 Subject: [PATCH 01/24] rspamd: remove .info from fishy tlds (default) --- data/conf/rspamd/custom/fishy_tlds.map | 1 - 1 file changed, 1 deletion(-) diff --git a/data/conf/rspamd/custom/fishy_tlds.map b/data/conf/rspamd/custom/fishy_tlds.map index 1b8b2b0d7..f19f70da5 100644 --- a/data/conf/rspamd/custom/fishy_tlds.map +++ b/data/conf/rspamd/custom/fishy_tlds.map @@ -24,7 +24,6 @@ /.+\.guru$/i /.+\.icu$/i /.+\.id$/i -/.+\.info$/i /.+\.in.net$/i /.+\.ir$/i /.+\.jetzt$/i From ceebc56e62b8845241325e6d29b585cdbd0c1bbd Mon Sep 17 00:00:00 2001 From: Nick Bouwhuis <2922075+NickBouwhuis@users.noreply.github.com> Date: Tue, 18 Mar 2025 10:08:08 +0000 Subject: [PATCH 02/24] feat/replace bgp.he.net with bgp.tools --- data/web/js/site/user.js | 2 +- data/web/templates/admin/tab-config-f2b.twig | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/web/js/site/user.js b/data/web/js/site/user.js index 2a5eccdf8..497bdc7de 100644 --- a/data/web/js/site/user.js +++ b/data/web/js/site/user.js @@ -103,7 +103,7 @@ jQuery(function($){ var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); var service = '
' + item.service.toUpperCase() + '
'; var app_password = item.app_password ? ' ' + escapeHtml(item.app_password_name || "App") + '' : ''; - var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '' + item.real_rip + ""; + var real_rip = item.real_rip.startsWith("Web") ? item.real_rip : '' + item.real_rip + ""; var ip_location = item.location ? ' ' : ''; var ip_data = real_rip + ip_location + app_password; $(".last-login").append('
  • ' + local_datetime + " " + service + " " + lang.from + " " + ip_data + "
  • "); diff --git a/data/web/templates/admin/tab-config-f2b.twig b/data/web/templates/admin/tab-config-f2b.twig index 75c626641..d33c242a8 100644 --- a/data/web/templates/admin/tab-config-f2b.twig +++ b/data/web/templates/admin/tab-config-f2b.twig @@ -110,7 +110,7 @@

    - + {{ active_ban.network }} ({{ active_ban.banned_until }}) @@ -130,7 +130,7 @@

    - + {{ perm_ban.network }} From cd3b1ab828bdaacacbffebfd755c0fa919610f1c Mon Sep 17 00:00:00 2001 From: "Marvin A. Ruder" Date: Tue, 25 Mar 2025 20:24:33 +0100 Subject: [PATCH 03/24] Allow disabling Olefy * Fixes #6389 Signed-off-by: Marvin A. Ruder --- data/Dockerfiles/olefy/olefy.py | 9 ++++++++- data/Dockerfiles/watchdog/watchdog.sh | 2 ++ data/web/admin/dashboard.php | 3 +++ docker-compose.yml | 3 +++ generate_config.sh | 4 ++++ update.sh | 2 ++ 6 files changed, 22 insertions(+), 1 deletion(-) diff --git a/data/Dockerfiles/olefy/olefy.py b/data/Dockerfiles/olefy/olefy.py index 776e78648..7c6880929 100644 --- a/data/Dockerfiles/olefy/olefy.py +++ b/data/Dockerfiles/olefy/olefy.py @@ -32,6 +32,13 @@ import time import magic import re +skip_olefy = os.getenv('SKIP_OLEFY', '') + +if skip_olefy.lower() in ['yes', 'y']: + print("SKIP_OLEFY=y, skipping Olefy...") + time.sleep(365 * 24 * 60 * 60) + sys.exit(0) + # merge variables from /etc/olefy.conf and the defaults olefy_listen_addr_string = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1,::1') olefy_listen_port = int(os.getenv('OLEFY_BINDPORT', '10050')) @@ -113,7 +120,7 @@ def oletools( stream, tmp_file_name, lid ): out = bytes(out.decode('utf-8', 'ignore').replace(' ', ' ').replace('\t', '').replace('\n', '').replace('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)', ''), encoding="utf-8") failed = False if out.__len__() < 30: - logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode, + logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode, out.decode('utf-8', 'ignore'), err.decode('utf-8', 'ignore'))) out = b'[ { "error": "Unhandled error - too short olevba response" } ]' failed = True diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index 9d6f6609f..d1c659ce8 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -994,6 +994,7 @@ PID=$! echo "Spawned cert_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) +if [[ "${SKIP_OLEFY}" =~ ^([nN][oO]|[nN])+$ ]]; then ( while true; do if ! olefy_checks; then @@ -1005,6 +1006,7 @@ done PID=$! echo "Spawned olefy_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) +fi ( while true; do diff --git a/data/web/admin/dashboard.php b/data/web/admin/dashboard.php index 473f28de2..443c2ea2a 100644 --- a/data/web/admin/dashboard.php +++ b/data/web/admin/dashboard.php @@ -18,6 +18,7 @@ elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php'; $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $clamd_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_CLAMD"])) ? false : true; +$olefy_status = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["SKIP_OLEFY"])) ? false : true; if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) { @@ -33,6 +34,7 @@ $vmail_df = explode(',', (string)json_decode(docker('post', 'dovecot-mailcow', ' // containers $containers_info = (array) docker('info'); if ($clamd_status === false) unset($containers_info['clamd-mailcow']); +if ($olefy_status === false) unset($containers_info['olefy-mailcow']); ksort($containers_info); $containers = array(); foreach ($containers_info as $container => $container_info) { @@ -77,6 +79,7 @@ $template_data = [ 'gal' => @$_SESSION['gal'], 'license_guid' => license('guid'), 'clamd_status' => $clamd_status, + 'olefy_status' => $olefy_status, 'containers' => $containers, 'ip_check' => customize('get', 'ip_check'), 'lang_admin' => json_encode($lang['admin']), diff --git a/docker-compose.yml b/docker-compose.yml index 2d68b80ce..76739a09c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -176,6 +176,7 @@ services: - COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-mailcow-dockerized} - SKIP_FTS=${SKIP_FTS:-y} - SKIP_CLAMD=${SKIP_CLAMD:-n} + - SKIP_OLEFY=${SKIP_OLEFY:-n} - SKIP_SOGO=${SKIP_SOGO:-n} - ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n} - MASTER=${MASTER:-y} @@ -538,6 +539,7 @@ services: - IP_BY_DOCKER_API=${IP_BY_DOCKER_API:-0} - CHECK_UNBOUND=${CHECK_UNBOUND:-1} - SKIP_CLAMD=${SKIP_CLAMD:-n} + - SKIP_OLEFY=${SKIP_OLEFY:-n} - SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n} - SKIP_SOGO=${SKIP_SOGO:-n} - HTTPS_PORT=${HTTPS_PORT:-443} @@ -601,6 +603,7 @@ services: - OLEFY_LOGLVL=20 - OLEFY_MINLENGTH=500 - OLEFY_DEL_TMP=1 + - SKIP_OLEFY=${SKIP_OLEFY:-n} networks: mailcow-network: aliases: diff --git a/generate_config.sh b/generate_config.sh index c7a7cb64f..c4396a9ce 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -382,6 +382,10 @@ SKIP_UNBOUND_HEALTHCHECK=n SKIP_CLAMD=${SKIP_CLAMD} +# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n + +SKIP_OLEFY=n + # Skip SOGo: Will disable SOGo integration and therefore webmail, DAV protocols and ActiveSync support (experimental, unsupported, not fully implemented) - y/n SKIP_SOGO=n diff --git a/update.sh b/update.sh index 5a258b529..bf5229653 100755 --- a/update.sh +++ b/update.sh @@ -323,6 +323,7 @@ adapt_new_options() { "WATCHDOG_EXTERNAL_CHECKS" "WATCHDOG_SUBJECT" "SKIP_CLAMD" + "SKIP_OLEFY" "SKIP_IP_CHECK" "ADDITIONAL_SAN" "DOVEADM_PORT" @@ -967,6 +968,7 @@ CONFIG_ARRAY=( "WATCHDOG_EXTERNAL_CHECKS" "WATCHDOG_SUBJECT" "SKIP_CLAMD" + "SKIP_OLEFY" "SKIP_IP_CHECK" "ADDITIONAL_SAN" "AUTODISCOVER_SAN" From 8408b82e9c92d34f65f5aae31d6840704764e231 Mon Sep 17 00:00:00 2001 From: "Marvin A. Ruder" Date: Wed, 26 Mar 2025 17:17:13 +0100 Subject: [PATCH 04/24] Add new option with description to existing configuration files during next update * Remove Olefy settings file from rspamd configuration * Have rspamd container generate Olefy settings file at startup if not disabled Signed-off-by: Marvin A. Ruder --- data/Dockerfiles/rspamd/docker-entrypoint.sh | 21 +++++++++++++++++++ .../rspamd/local.d/external_services.conf | 12 ----------- update.sh | 12 +++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) delete mode 100644 data/conf/rspamd/local.d/external_services.conf diff --git a/data/Dockerfiles/rspamd/docker-entrypoint.sh b/data/Dockerfiles/rspamd/docker-entrypoint.sh index cf44c3063..7385488b0 100755 --- a/data/Dockerfiles/rspamd/docker-entrypoint.sh +++ b/data/Dockerfiles/rspamd/docker-entrypoint.sh @@ -81,6 +81,27 @@ EOF redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF NO ONE fi +if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + if [[ -f /etc/rspamd/local.d/external_services.conf ]]; then + rm /etc/rspamd/local.d/external_services.conf + fi +else + cat < /etc/rspamd/local.d/external_services.conf +oletools { + # default olefy settings + servers = "olefy:10055"; + # needs to be set explicitly for Rspamd < 1.9.5 + scan_mime_parts = true; + # mime-part regex matching in content-type or filename + # block all macros + extended = true; + max_size = 3145728; + timeout = 20.0; + retransmits = 1; +} +EOF +fi + # Provide additional lua modules ln -s /usr/lib/$(uname -m)-linux-gnu/liblua5.1-cjson.so.0.0.0 /usr/lib/rspamd/cjson.so diff --git a/data/conf/rspamd/local.d/external_services.conf b/data/conf/rspamd/local.d/external_services.conf deleted file mode 100644 index 2b091ff23..000000000 --- a/data/conf/rspamd/local.d/external_services.conf +++ /dev/null @@ -1,12 +0,0 @@ -oletools { - # default olefy settings - servers = "olefy:10055"; - # needs to be set explicitly for Rspamd < 1.9.5 - scan_mime_parts = true; - # mime-part regex matching in content-type or filename - # block all macros - extended = true; - max_size = 3145728; - timeout = 20.0; - retransmits = 1; -} diff --git a/update.sh b/update.sh index bf5229653..c39d37834 100755 --- a/update.sh +++ b/update.sh @@ -1280,6 +1280,18 @@ for option in "${CONFIG_ARRAY[@]}"; do echo '# CAUTION: Disabling this may expose container ports to other neighbors on the same subnet, even if the ports are bound to localhost' >> mailcow.conf echo 'DISABLE_NETFILTER_ISOLATION_RULE=n' >> mailcow.conf fi + elif [[ "${option}" == "SKIP_CLAMD" ]]; then + if ! grep -q "${option}" mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n' >> mailcow.conf + echo 'SKIP_CLAMD=n' >> mailcow.conf + fi + elif [[ "${option}" == "SKIP_OLEFY" ]]; then + if ! grep -q "${option}" mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n' >> mailcow.conf + echo 'SKIP_OLEFY=n' >> mailcow.conf + fi elif [[ "${option}" == "REDISPASS" ]]; then if ! grep -q "${option}" mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf" From 766c5e85801b9719e3447b935f03bb5dd84fda9e Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 9 Apr 2025 08:02:30 +0200 Subject: [PATCH 05/24] [Dovecot] Ignore app passwords protocol access on SOGo request --- data/conf/dovecot/auth/mailcowauth.php | 4 +++- data/conf/dovecot/auth/passwd-verify.lua | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php index 667419c57..4eda382b7 100644 --- a/data/conf/dovecot/auth/mailcowauth.php +++ b/data/conf/dovecot/auth/mailcowauth.php @@ -79,7 +79,9 @@ if ($isSOGoRequest) { } } if ($result === false){ - $result = apppass_login($post['username'], $post['password'], array($post['service'] => true), array( + // If it's a SOGo Request, don't check for protocol access + $service = (isSOGoRequest) ? false : array($post['service'] => true); + $result = apppass_login($post['username'], $post['password'], $service, array( 'is_internal' => true, 'remote_addr' => $post['real_rip'] )); diff --git a/data/conf/dovecot/auth/passwd-verify.lua b/data/conf/dovecot/auth/passwd-verify.lua index 18c18dbe3..19dcc4bd6 100644 --- a/data/conf/dovecot/auth/passwd-verify.lua +++ b/data/conf/dovecot/auth/passwd-verify.lua @@ -29,7 +29,7 @@ function auth_password_verify(request, password) insecure = true } - if c ~= 200 then + if c ~= 200 and c ~= 401 then dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user) return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, "Upstream error" end From b96a5b1efd50f95711c630e7f26e5636a6fd8850 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 9 Apr 2025 08:07:46 +0200 Subject: [PATCH 06/24] [Web] Fix AJAX urls to absolute path --- data/web/js/site/admin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js index 847c25aa8..b6edf6f80 100644 --- a/data/web/js/site/admin.js +++ b/data/web/js/site/admin.js @@ -51,7 +51,7 @@ jQuery(function($){ $('.submit_rspamd_regex').attr({"disabled": true}); }); $("#show_rspamd_global_filters").click(function() { - $.get("inc/ajax/show_rspamd_global_filters.php"); + $.get("/inc/ajax/show_rspamd_global_filters.php"); $("#confirm_show_rspamd_global_filters").hide(); $("#rspamd_global_filters").removeClass("d-none"); }); @@ -655,7 +655,7 @@ jQuery(function($){ $(this).html(' '); $.ajax({ type: 'GET', - url: 'inc/ajax/relay_check.php', + url: '/inc/ajax/relay_check.php', dataType: 'text', data: $('#test_relayhost_form').serialize(), complete: function (data) { From 4ac839cf492a6428a30d0bd17e30184fbd7aa80f Mon Sep 17 00:00:00 2001 From: milkmaker Date: Thu, 10 Apr 2025 17:11:30 +0200 Subject: [PATCH 07/24] Translations update from Weblate (#6463) * [Web] Updated lang.hu-hu.json Co-authored-by: Anonymous Co-authored-by: milkmaker * [Web] Updated lang.lv-lv.json [Web] Updated lang.lv-lv.json Co-authored-by: Anonymous Co-authored-by: Edgars Andersons Co-authored-by: milkmaker * [Web] Added lang.bg-bg.json Co-authored-by: Peter Co-authored-by: milkmaker * [Web] Updated lang.tr-tr.json Co-authored-by: Anonymous Co-authored-by: milkmaker * [Web] Updated lang.uk-ua.json Co-authored-by: Anonymous Co-authored-by: milkmaker * [Web] Updated lang.zh-cn.json Co-authored-by: Easton Man Co-authored-by: milkmaker --------- Co-authored-by: Anonymous Co-authored-by: Edgars Andersons Co-authored-by: Peter Co-authored-by: Easton Man --- data/web/lang/lang.bg-bg.json | 1 + data/web/lang/lang.hu-hu.json | 2 +- data/web/lang/lang.lv-lv.json | 63 ++++++++++++++++++++++++----------- data/web/lang/lang.tr-tr.json | 2 +- data/web/lang/lang.uk-ua.json | 2 +- data/web/lang/lang.zh-cn.json | 63 +++++++++++++++++++++++++++++++---- 6 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 data/web/lang/lang.bg-bg.json diff --git a/data/web/lang/lang.bg-bg.json b/data/web/lang/lang.bg-bg.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/data/web/lang/lang.bg-bg.json @@ -0,0 +1 @@ +{} diff --git a/data/web/lang/lang.hu-hu.json b/data/web/lang/lang.hu-hu.json index 97f575a69..3b9eb5fbc 100644 --- a/data/web/lang/lang.hu-hu.json +++ b/data/web/lang/lang.hu-hu.json @@ -590,4 +590,4 @@ "app_name": "Alkalmazás neve", "app_passwd_protocols": "Engedélyezett protokollok az alkalmazás jelszavához" } -} \ No newline at end of file +} diff --git a/data/web/lang/lang.lv-lv.json b/data/web/lang/lang.lv-lv.json index 545bbe475..2b433b8e8 100644 --- a/data/web/lang/lang.lv-lv.json +++ b/data/web/lang/lang.lv-lv.json @@ -16,7 +16,16 @@ "login_as": "Pieteikšanās kā pastkastes lietotājam", "mailbox_relayhost": "Pasta kastītes relayhost maiņa", "prohibited": "Aizliegts ar ACL", - "protocol_access": "Protokola piekļuves maiņa" + "protocol_access": "Protokola piekļuves maiņa", + "pw_reset": "Ļaut atiestatīt mailcow lietotāja paroli", + "ratelimit": "Piekļuves biežuma ierobežojums", + "quarantine": "Karantīnas darbības", + "quarantine_attachments": "Karantīnas pielikumi", + "quarantine_category": "Mainīt karantīnas paziņojumu kategoriju", + "quarantine_notification": "Mainīt karantīnas paziņojumus", + "smtp_ip_access": "Mainīt SMTP atļautos saimniekdatorus", + "sogo_access": "Atļaut SOGo piekļuves pārvaldību", + "sogo_profile_reset": "Atiestatīt SOGo profilu" }, "add": { "activate_filter_warn": "Visi pārējie filtri tiks deaktivizēti, kad aktīvs ir atzīmēts.", @@ -24,8 +33,8 @@ "add": "Pievienot", "add_domain_only": "Tikai pievienot domēnu", "add_domain_restart": "Pievienot domēnu un restartēt SOGo", - "alias_address": "Aizstājaddrese/s", - "alias_address_info": "Pilna epasta addrese/s vai @piemērs.com, lai notvertu visas domēna ziņas (komatu atdalītas). tikai mailcow domēni.", + "alias_address": "Aizstājadrese/s", + "alias_address_info": "Pilna epasta adrese/s vai @example.com, lai notvertu visus domēna ziņojumus (atdalītas ar komatu). Tikai mailcow domēni.", "alias_domain": "Aizstājdomēni", "alias_domain_info": "Tikai derīgi domēna vārdi (komatu atdalīti).", "automap": "Mēģiniet automatizēt mapes (\"Nosūtītie vienumi\", \"Nosūtītie\" => \"Nosūtītie\" etc.)", @@ -63,15 +72,17 @@ "skipcrossduplicates": "Izlaist dublētus ziņojumus pa mapēm (pirmais nāk, pirmais kalpo)", "syncjob": "Pievienot sinhronizācijas darbu", "syncjob_hint": "Ņemiet vērā, ka parole ir jāuzglabā vienkāršā tekstā!", - "target_address": "Iet uz adresēm", - "target_address_info": "Pilna epasta addrese/s (comma-separated).", + "target_address": "Mērķa adreses", + "target_address_info": "Pilna epasta adrese/s (atdalītas ar komatu).", "target_domain": "Mērķa domēns", "username": "Lietotājvārds", "validate": "Apstiprināt", "validation_success": "Apstiprināts veiksmīgi", "bcc_dest_format": "BCC galamērķim ir jābūt vienai derīgai e-pasta adresei.
    Ja ir nepieciešams nosūtīt kopiju vairākām adresēm, jāizveido aizstājvārds un jāizmanto tas šeit.", "domain_matches_hostname": "Domēns %s atbilst saimniekdatora nosaukumam", - "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)" + "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)", + "app_password": "Pievienot lietotnes paroli", + "app_passwd_protocols": "Atļautie lietotnes paroles protokoli" }, "admin": { "access": "Pieeja", @@ -114,7 +125,7 @@ "f2b_whitelist": "Baltā saraksta tīkls/hosts", "filter_table": "Filtru tabula", "forwarding_hosts": "Hostu pārsūtīšana", - "forwarding_hosts_add_hint": "Var norādīt vai nu IPv4/IPv6 addreses, tīklu ar CIDR apzīmējumu, saimniekdatoru nosaukumus (kas tiks atrisināti IP adresēs) vai arī domēna vārdus (kas tiks atrisināti IP adresēs, vaicājot SPF ierakstus, vai, ja tādu nav, MX ierakstus).", + "forwarding_hosts_add_hint": "Var norādīt vai nu IPv4/IPv6 adreses, tīklu ar CIDR apzīmējumu, saimniekdatoru nosaukumus (kas tiks atrisināti IP adresēs) vai arī domēna vārdus (kas tiks atrisināti IP adresēs, vaicājot SPF ierakstus, vai, ja tādu nav, MX ierakstus).", "forwarding_hosts_hint": "Ienākošie ziņojumi tiek bez nosacījumiem pieņemti no visiem šeit norādītajiem saimniekdatoriem. Tie tad netiek pārbaudīti pret DNSBL vai pakļauti ievietošanai pelēkajā sarakstā. No tiem saņemtās mēstules nekad netiek noraidītas, bet pēc izvēles tās var pārvietot mapē \"Nevēlams\". Visbiežāk to izmanto, lai norādītu pasta serverus, kuros ir uzstādīts nosacījums, kas pārsūta ienākošās e-pasta vēstules uz Tavu mailcow serveri.", "help_text": "Pārrakstīt palīdzības tekstu zem pieteikšanās maskas (var izmantot HTML)", "host": "Hosts", @@ -142,7 +153,7 @@ "recipients": "Adresāts", "refresh": "Atsvaidzināt", "regen_api_key": "Reģenerēt API atslēgu", - "relay_from": "\"No:\" addrese", + "relay_from": "\"No:\" adrese", "relay_run": "Palaist testu", "relayhosts_hint": "Norādīt no sūtītāja atkarīgas piegādes, lai varētu tos atlasīt domēnu konfigurācijas uzvednē.
    \n Piegādes pakalpojums vienmēr ir \"smtp\", tādējādi tiks mēģināts TLS, kad piedāvāts. Iekļautais TLS (SMTPS) netiek atbalstīts. Tiek ņemts vērā lietotāja atsevišķais izejošā TLS nosacījuma iestatījums.
    \n Ietekmē atlasītos domēnus, tajā skaitā aizstājdomēnus.", "remove": "Noņemt", @@ -222,7 +233,8 @@ "targetd_not_found": "Mērķa domēns nav atrasts", "username_invalid": "Lietotājvārds nevar tikt izmantots", "validity_missing": "Lūdzu piešķiriet derīguma termiņu", - "domain_cannot_match_hostname": "Domēns nevar atbilst saimniekdatora nosaukumam" + "domain_cannot_match_hostname": "Domēns nevar atbilst saimniekdatora nosaukumam", + "app_passwd_id_invalid": "Lietotnes paroles Id %s ir nederīgs" }, "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.", @@ -257,7 +269,7 @@ "hostname": "Saimniekdatora nosaukums", "inactive": "Neaktīvs", "kind": "Veids", - "mailbox": "Rediģēt pastkasti", + "mailbox": "Labot pastkasti", "max_aliases": "Lielākais aizstājvārdu skaits", "max_mailboxes": "Maks. iespējamās pastkastes", "max_quota": "Maks. kvota uz pastkasti (MiB)", @@ -283,8 +295,8 @@ "spam_policy": "Pievienot vai noņemt vienumus baltajā-/melnajā sarakstā", "spam_score": "Iestatīt pielāgotu surogātpasta vērtējumu", "subfolder2": "Sinhronizēt galamērķa apakšmapē
    (tukšs = neizmantot apakšmapi)", - "syncjob": "Rediģēt sinhronizācijas darbu", - "target_address": "Iet uz adresi/ēm (komatu atdalītas)", + "syncjob": "Labot sinhronizācijas darbu", + "target_address": "Mērķa adrese/s (atdalītas ar komatu)", "target_domain": "Mērķa domēns", "title": "Labot priekšmetu", "unchanged_if_empty": "Ja neizmainīts atstājiet tukšu", @@ -300,8 +312,11 @@ "sogo_visible": "Aizstājvārds ir redzams SOGo", "sogo_visible_info": "Šī iespēja ietekmē tikai tos objektus, kurus var parādīt SOGo (koplietojamās vai nekoplietojamās aizstājadreses, kas norāda uz vismaz vienu vietējo pastkasti). Ja paslēpts, netiks parādīts SOGo kā atlasāms sūtītājs.", "mbox_rl_info": "Šis pieprasījumu ierobežojums tiek piemērots SASL pieteikšanās vārdam, tas atbilst jebkurai \"from\" adresei, ko izmanto lietotājs, kurš ir pieteicies. Pastkastes pieprasījumu ierobežojums pārraksta domēna mēroga pieprasījumu ierobežojumu.", - "sogo_access": "Nodrošināt tiešu pieteikšanās piekļuvi SOGo", - "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)" + "sogo_access": "Tieša pārvirzīšana uz SOGo", + "disable_login": "Neļaut pieteikšanos (ienākošais pasts joprojām tiks pieņemts)", + "app_passwd_protocols": "Atļautie lietotnes paroles protokoli", + "allowed_protocols": "Atļautie protokoli tiešai lietotāja piekļuvei (neietekmē lietotnes paroles protokolus)", + "app_passwd": "Lietotnes parole" }, "footer": { "cancel": "Atcelt", @@ -508,7 +523,9 @@ "verified_fido2_login": "Apliecināta FIDO2 pieteikšanās", "verified_webauthn_login": "Apliecināta WebAuthn pieteikšanās", "verified_totp_login": "Apliecināta TOTP pieteikšanās", - "verified_yotp_login": "Apliecināta Yubico OTP pieteikšanās" + "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" }, "tfa": { "api_register": "%s izmanto Yubico Cloud API. Lūdzu iegūstiet API atslēgu priekš Jūsu atslēgashere", @@ -523,7 +540,7 @@ "scan_qr_code": "Lūdzu, skenējiet šo kodu ar savu autentifikācijas lietojumprogrammu vai ievadiet kodu manuāli.", "select": "Lūdzu izvēlaties", "set_tfa": "Uzstādīt difi faktoru autentifik;acijas metodi", - "tfa": "Divu faktoru autentifikācija", + "tfa": "Divpakāpju pieteikšanās", "totp": "Uz laiku bāzēta vienreizēja parole (Google Autentifikātors utt.)", "webauthn": "WebAuthn autentifikācija", "waiting_usb_auth": "Gaida USB ierīci...

    Lūdzu, tagad nospiežiet pogu uz Jūsu WebAuthn USB ierīces.", @@ -618,12 +635,18 @@ "apple_connection_profile_mailonly": "Šis savienojuma profils iekļauj IMAP un SMTP konfigurācijas parametrus Apple ierīcei.", "pushover_info": "Pašpiegādes paziņojumu iestatījumi attieksies uz visu tīro (ne surogātpasta) pastu, kas piegādāts uz %s, ieskaitot aizstājvārdus (kopīgotus, nekopīgotus, ar birkām).", "app_hint": "Lietotņu paroles ir aizstājējparoles, lai pieteiktos IMAP, SMTP, CalDAV, CardDAV un EAS. Lietotājvārds paliek nemainīgs. SOGo tīmekļa pasts nav pieejams ar lietotņu parolēm.", - "direct_protocol_access": "Šim pastkastes lietotājam ir tieša, ārēja piekļuve zemāk uzskaitītajiem protokoliem un lietotnēm. Šo iestatījumu pārrauga pārvaldītājs. Lietotņu paroles var izveidot, lai nodrošinātu piekļuvi atsevišķiem protokoliem un lietotnēm.
    Poga \"Pieteikties tīmekļa pastā\" nodrošina vienotu pieteikšanos SOGo un vienmēr ir pieejama.", + "direct_protocol_access": "Šim pastkastes lietotājam ir tieša, ārēja piekļuve zemāk uzskaitītajiem protokoliem un lietotnēm. Šo iestatījumu pārrauga pārvaldītājs. Lietotņu paroles var izveidot, lai nodrošinātu piekļuvi atsevišķiem protokoliem un lietotnēm.
    Poga \"Tīmekļa pasts\" nodrošina vienotu pieteikšanos SOGo un vienmēr ir pieejama.", "last_ui_login": "Pēdējā pieteikšanās saskarnē", "login_history": "Pieteikšanās vēsture", "no_last_login": "Nav informācijas par pēdējām pieteikšanās saskarnē reizēm", - "open_webmail_sso": "Pieteikšanās tīmekļa pastā", - "last_mail_login": "Pēdējā pasta pieteikšanās" + "open_webmail_sso": "Tīmekļa pasts", + "last_mail_login": "Pēdējā pasta pieteikšanās", + "change_password_hint_app_passwords": "Kontā ir %d lietotņu paroles, kas netiks mainītas. Lai pārvaldītu tās, jādodas uz cilni \"Lietotņu paroles\".", + "with_app_password": "ar lietotnes paroli", + "apple_connection_profile_with_app_password": "Jauna lietotnes parole ir izveidota un pievienota profilam, lai ierīces iestatīšanas laikā nebūtu nepieciešams ievadīt paroli. Lūgums nekopīgot datni, jo tā nodrošina pilnu piekļuvi pastkastei.", + "tfa_info": "Divpakāpju autentificēšanās palīdz aizsargāt kontu.Ja tā ir iespējota, var būt nepieciešamas lietotņu paroles, lai pieteiktos lietotnēs vai pakalpojumos, kas nenodrošina divpakāpju autentificēšanos (piem., e-pasta klienti).", + "app_passwds": "Lietotņu paroles", + "create_app_passwd": "Izveidot lietotnes paroli" }, "datatables": { "paginate": { @@ -657,4 +680,4 @@ "fido2": { "fido2_auth": "Pieteikties ar FIDO2" } -} \ No newline at end of file +} diff --git a/data/web/lang/lang.tr-tr.json b/data/web/lang/lang.tr-tr.json index aa1b90df1..bc1da77e7 100644 --- a/data/web/lang/lang.tr-tr.json +++ b/data/web/lang/lang.tr-tr.json @@ -1309,4 +1309,4 @@ "q_reject": "Reddedildi", "week": "Hafta" } -} \ No newline at end of file +} diff --git a/data/web/lang/lang.uk-ua.json b/data/web/lang/lang.uk-ua.json index c6ded247b..61c401a07 100644 --- a/data/web/lang/lang.uk-ua.json +++ b/data/web/lang/lang.uk-ua.json @@ -1307,4 +1307,4 @@ }, "collapse_all": "Згорнути все" } -} \ No newline at end of file +} diff --git a/data/web/lang/lang.zh-cn.json b/data/web/lang/lang.zh-cn.json index 70a48a91a..f9bb481da 100644 --- a/data/web/lang/lang.zh-cn.json +++ b/data/web/lang/lang.zh-cn.json @@ -358,7 +358,43 @@ "ip_check_disabled": "IP 检查已禁用。你可透过以下路径启用
    系统 > 配置 > 选项 > 页面自定义", "queue_unban": "解除封禁", "allowed_methods": "访问控制允许方式", - "allowed_origins": "访问控制允许原" + "allowed_origins": "访问控制允许原", + "iam": "身份识别提供者", + "iam_attribute_field": "Attribute 域", + "iam_authorize_url": "Authorization endpoint", + "iam_auth_flow": "认证流程", + "iam_basedn": "Base DN", + "iam_client_id": "客户端 ID", + "iam_client_secret": "客户端凭据", + "iam_client_scopes": "客户端 Scopes", + "iam_default_template": "默认模板", + "iam_default_template_description": "如果未为用户分配模板,则在创建邮箱时将使用默认模板,但在更新邮箱时不会使用默认模板。", + "iam_description": "配置外部认证提供者
    如果已设置好属性映射,用户在首次登录时将会自动创建其 Mailbox。", + "iam_host": "Host", + "iam_host_info": "请输入一个或多个 LDAP 主机,使用英文逗号分隔。", + "iam_import_users": "导入用户", + "iam_mapping": "属性映射", + "iam_bindpass": "密码绑定(Bind Password)", + "iam_periodic_full_sync": "周期性全量同步", + "iam_port": "端口", + "iam_realm": "Realm", + "iam_redirect_url": "重定向 Url", + "iam_rest_flow": "Mailpassword 流程", + "iam_server_url": "服务器 Url", + "iam_sso": "单点登录(SSO)", + "iam_sync_interval": "同步/导入周期(min)", + "iam_test_connection": "测试连接", + "iam_token_url": "Token endpoint", + "iam_userinfo_url": "User info endpoint", + "iam_username_field": "Username 域", + "iam_binddn": "Bind DN", + "iam_use_ssl": "使用 SSL", + "iam_use_tls": "使用 TLS", + "iam_version": "版本", + "ignore_ssl_error": "忽略 SSL 错误", + "iam_auth_flow_info": "除了在单点登录(SSO)中使用的 Authorization Code 流程(在 Keycloak 中是标准流程)之外,mailcow 还支持使用 Credentials 的身份认证流程。Mailpassword 流程尝试通过 Keycloak 的 Admin REST API 验证用户凭据,mailcow 会从 Keycloak 中的 mailcow_password 属性中获取哈希后的密码。", + "filter": "过滤", + "iam_extra_permission": "要使以下设置生效,Keycloak 中的 mailcow 客户端需要一个 服务账户(Service account) 以及 查看用户(view-users) 的权限。" }, "danger": { "access_denied": "访问被拒绝或者表单数据无效", @@ -495,7 +531,11 @@ "webauthn_authenticator_failed": "找不到所选的 authenticator", "webauthn_publickey_failed": "没有为选定的身份验证器保存公钥", "webauthn_username_failed": "所选的 authenticator 属于另一个账户", - "demo_mode_enabled": "演示模式已开启" + "demo_mode_enabled": "演示模式已开启", + "generic_server_error": "服务器错误。请联系您的管理员。", + "authsource_in_use": "由于当前有一个或多个用户正在使用该身份提供者(IDP),因此无法更改或删除。", + "iam_test_connection": "连接失败", + "required_data_missing": "缺少需要的 %s 数据" }, "debug": { "chart_this_server": "图表 (此服务器)", @@ -744,7 +784,10 @@ "new_password_confirm": "确认新密码", "reset_password": "重置密码", "request_reset_password": "请求重置密码", - "invalid_pass_reset_token": "密码重置 token 无效或已过期。
    请重新获取新的密码重置链接。" + "invalid_pass_reset_token": "密码重置 token 无效或已过期。
    请重新获取新的密码重置链接。", + "login_user": "用户登录", + "login_dadmin": "域管理员登录", + "login_admin": "管理员登录" }, "mailbox": { "action": "操作", @@ -919,7 +962,8 @@ "max_quota": "每个信箱的最大容量配额", "relay_unknown": "转发未知信箱", "templates": "模板", - "template": "模板" + "template": "模板", + "iam": "身份提供者(IDP)" }, "oauth2": { "access_denied": "请作为邮箱所有者登录以使用 OAuth2 授权。", @@ -1097,7 +1141,8 @@ "recovery_email_sent": "重置邮件已发送至 %s", "template_added": "新增了模板 %s", "template_modified": "模板 %s 的修改已保存", - "template_removed": "模板 ID %s 已删除" + "template_removed": "模板 ID %s 已删除", + "iam_test_connection": "连接成功" }, "tfa": { "api_register": "%s 使用了 Yubico Cloud API,请在此为你的密钥获取 API 密钥", @@ -1292,7 +1337,11 @@ "password_reset_info": "如果不提供密码重置邮箱,此功能将无法使用。", "pushover_sound": "声音", "value": "值", - "attribute": "属性" + "attribute": "属性", + "protocols": "协议", + "authentication": "认证", + "tfa_info": "两步验证有助于保护您的账户安全。启用后,对于不支持两步验证的应用程序或服务(例如邮件客户端),需要使用应用专用密码进行登录。", + "overview": "概览" }, "warning": { "cannot_delete_self": "不能删除已登录的用户", @@ -1332,4 +1381,4 @@ "loadingRecords": "加载中...", "zeroRecords": "未找到符合条件的记录" } -} \ No newline at end of file +} From 84f67d66082b79e2e9c495ca7944bd55a3497104 Mon Sep 17 00:00:00 2001 From: milkmaker Date: Thu, 10 Apr 2025 17:16:44 +0200 Subject: [PATCH 08/24] Translations update from Weblate (#6478) * [Web] Updated lang.de-de.json Co-authored-by: Jonas Leiner * [Web] Updated lang.fr-fr.json Co-authored-by: Neuronnexion --------- Co-authored-by: Jonas Leiner Co-authored-by: Neuronnexion --- data/web/lang/lang.de-de.json | 12 +++++++----- data/web/lang/lang.fr-fr.json | 5 ++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 747f7cf84..911ebfc67 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -398,7 +398,8 @@ "allowed_methods": "Access-Control-Allow-Methods", "allowed_origins": "Access-Control-Allow-Origin", "logo_dark_label": "Invertiert für den Darkmode", - "logo_normal_label": "Normal" + "logo_normal_label": "Normal", + "user_link": "Nutzer-Link" }, "danger": { "access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten", @@ -806,9 +807,9 @@ "forgot_password": "> Passwort vergessen?", "invalid_pass_reset_token": "Der Rücksetz-Token für das Passwort ist ungültig oder abgelaufen.
    Bitte fordern Sie einen neuen Link zur Passwortwiederherstellung an.", "login": "Anmelden", - "login_user": "Benutzer Anmelden", - "login_dadmin": "Domain-Administrator Anmelden", - "login_admin": "Administrator Anmelden", + "login_user": "Anmeldung als Benutzer", + "login_dadmin": "Anmeldung als Domain-Administrator", + "login_admin": "Anmeldung als Administrator", "mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.", "new_password": "Neues Passwort", "new_password_confirm": "Neues Passwort bestätigen", @@ -991,7 +992,8 @@ "syncjob_EXIT_TLS_FAILURE": "Problem mit verschlüsselter Verbindung", "syncjob_EXIT_AUTHENTICATION_FAILURE": "Authentifizierungsproblem", "syncjob_EXIT_OVERQUOTA": "Ziel Mailbox ist über dem Limit", - "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen" + "syncjob_EXIT_CONNECTION_FAILURE_HOST1": "Kann keine Verbindung zum Zielserver herstellen", + "iam": "Identitätsanbieter" }, "oauth2": { "access_denied": "Bitte als Mailbox-Nutzer einloggen, um den Zugriff via OAuth2 zu erlauben.", diff --git a/data/web/lang/lang.fr-fr.json b/data/web/lang/lang.fr-fr.json index e9bff05d5..58300c0d4 100644 --- a/data/web/lang/lang.fr-fr.json +++ b/data/web/lang/lang.fr-fr.json @@ -730,7 +730,10 @@ "new_password": "Nouveau mot de passe", "new_password_confirm": "Confirmer le nouveau mot de passe", "reset_password": "Réinitialiser le mot de passe", - "request_reset_password": "Demander le changement du mot de passe" + "request_reset_password": "Demander le changement du mot de passe", + "login_user": "Connexion Utilisateur", + "login_dadmin": "Connexion Administrateur de domaine", + "login_admin": "Connexion Administrateur" }, "mailbox": { "action": "Action", From a370499aaafade9527ebb2a8282033a4f42a2fc1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 11 Apr 2025 08:30:54 +0200 Subject: [PATCH 09/24] Update dependency Imagick/imagick to v3.8.0 (#6480) --- 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 4ba8349f9..ff333f5b3 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.24 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?.*)$ -ARG IMAGICK_PECL_VERSION=3.7.0 +ARG IMAGICK_PECL_VERSION=3.8.0 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?.*)$ ARG MAILPARSE_PECL_VERSION=3.1.8 # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?.*)$ From 692355a08ad935b147ef8e877635d247a0e5aac3 Mon Sep 17 00:00:00 2001 From: PseudoResonance <75700a85-1205-4740-aebf-4413a352e81b@otake.pw> Date: Sat, 12 Apr 2025 06:24:37 -0700 Subject: [PATCH 10/24] Allow additional domains in OAuth2 redirect URLs --- data/web/api/openapi.yaml | 6 ++ data/web/inc/functions.inc.php | 27 ++++++- data/web/js/site/admin.js | 30 ++++++++ .../admin/tab-config-identity-provider.twig | 72 +++++++++++++++++-- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml index afab5fb0c..f207ee6a1 100644 --- a/data/web/api/openapi.yaml +++ b/data/web/api/openapi.yaml @@ -5847,6 +5847,7 @@ paths: client_id: "mailcow_client" client_secret: "*" redirect_url: "https://mail.mailcow.tld" + redirect_url_extra: ["https://extramail.mailcow.tld"] version: "26.1.3" default_template: "Default" mappers: @@ -5900,6 +5901,9 @@ paths: redirect_url: description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc. type: string + redirect_url_extra: + description: Additional redirect URLs that OIDC Provider can use after authentication if valid. + type: array version: description: Specifies the Keycloak version. Required if `authsource` is keycloak. type: string @@ -5990,6 +5994,7 @@ paths: client_id: "mailcow_client" client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf" redirect_url: "https://mail.mailcow.tld" + redirect_url_extra: ["https://extramail.mailcow.tld"] version: "26.1.3" default_template: "Default" mappers: ["small_mbox", "medium_mbox"] @@ -6034,6 +6039,7 @@ paths: client_id: "mailcow_client" client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf" redirect_url: "https://mail.mailcow.tld" + redirect_url_extra: ["https://extramail.mailcow.tld"] client_scopes: "openid profile email mailcow_template" default_template: "Default" mappers: ["small_mbox", "medium_mbox"] diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 49e26b978..5f9c772d0 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -2286,6 +2286,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach($rows as $row){ switch ($row["key"]) { + case "redirect_url_extra": case "mappers": case "templates": $settings[$row["key"]] = json_decode($row["value"]); @@ -2418,6 +2419,18 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { } $pdo->commit(); + // add redirect_url_extra + if (isset($_data['redirect_url_extra'])){ + $_data['redirect_url_extra'] = (!is_array($_data['redirect_url_extra'])) ? array($_data['redirect_url_extra']) : $_data['redirect_url_extra']; + + $redirect_url_extra = array_filter($_data['redirect_url_extra']); + $redirect_url_extra = json_encode($redirect_url_extra); + + $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('redirect_url_extra', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);"); + $stmt->bindParam(':value', $redirect_url_extra); + $stmt->execute(); + } + // add default template if (isset($_data['default_template'])) { $_data['default_template'] = (empty($_data['default_template'])) ? "" : $_data['default_template']; @@ -2851,7 +2864,19 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { case "get-redirect": if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc') return false; - $authUrl = $iam_provider->getAuthorizationUrl(); + $options = []; + if (isset($iam_settings['redirect_url_extra'])) { + // check if the current domain is used in an extra redirect URL + $targetDomain = strtolower($_SERVER['HTTP_HOST']); + foreach ($iam_settings['redirect_url_extra'] as $testUrl) { + $testUrlParsed = parse_url($testUrl); + if (isset($testUrlParsed['host']) && strtolower($testUrlParsed['host']) == $targetDomain) { + $options['redirect_uri'] = $testUrl; + break; + } + } + } + $authUrl = $iam_provider->getAuthorizationUrl($options); $_SESSION['oauth2state'] = $iam_provider->getState(); return $authUrl; break; diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js index b6edf6f80..cf1efd823 100644 --- a/data/web/js/site/admin.js +++ b/data/web/js/site/admin.js @@ -789,6 +789,18 @@ jQuery(function($){ $('.iam_ldap_rolemap_del').click(async function(e){ deleteAttributeMappingRow(this, e); }); + $('.iam_redirect_add_keycloak').click(async function(e){ + addRedirectUrlRow('#iam_keycloak_redirect_list', '.iam_keycloak_redirect_del', e); + }); + $('.iam_redirect_add_generic').click(async function(e){ + addRedirectUrlRow('#iam_generic_redirect_list', '.iam_generic_redirect_del', e); + }); + $('.iam_keycloak_redirect_del').click(async function(e){ + deleteRedirectUrlRow(this, e); + }); + $('.iam_generic_redirect_del').click(async function(e){ + deleteRedirectUrlRow(this, e); + }); // selecting identity provider $('#iam_provider').on('change', function(){ // toggle password fields @@ -833,4 +845,22 @@ jQuery(function($){ if ($(elem).parent().parent().parent().parent().children().length > 1) $(elem).parent().parent().parent().remove(); } + function addRedirectUrlRow(list_id, del_class, e) { + e.preventDefault(); + + var parent = $(list_id) + $(parent).children().last().clone().appendTo(parent); + var newChild = $(parent).children().last(); + $(newChild).find('input').val(''); + + $(del_class).off('click'); + $(del_class).click(async function(e){ + deleteRedirectUrlRow(this, e); + }); + } + function deleteRedirectUrlRow(elem, e) { + e.preventDefault(); + if ($(elem).parent().parent().parent().parent().children().length > 2) + $(elem).parent().parent().parent().remove(); + } }); diff --git a/data/web/templates/admin/tab-config-identity-provider.twig b/data/web/templates/admin/tab-config-identity-provider.twig index dce26f8c3..a93002257 100644 --- a/data/web/templates/admin/tab-config-identity-provider.twig +++ b/data/web/templates/admin/tab-config-identity-provider.twig @@ -64,10 +64,42 @@

    - +
    - +
    + + + +
    + +
    +
    +
    +
    +
    + + {% for key, url in iam_settings.redirect_url_extra %} +
    +
    +
    + +
    +
    + +
    +
    +
    + {% endfor %} +
    +
    +
    + +
    +
    + +
    +
    @@ -274,10 +306,42 @@
    - +
    - +
    + + + +
    + +
    +
    +
    +
    +
    + + {% for key, url in iam_settings.redirect_url_extra %} +
    +
    +
    + +
    +
    + +
    +
    +
    + {% endfor %} +
    +
    +
    + +
    +
    + +
    +
    From 0d3e8dd73896dfe87a4a652f56a37a74df28bdc6 Mon Sep 17 00:00:00 2001 From: Habetdin <15926758+Habetdin@users.noreply.github.com> Date: Sun, 13 Apr 2025 16:09:47 +0300 Subject: [PATCH 11/24] Update legacy admin dashboard links --- data/web/autoconfig.php | 2 +- data/web/js/site/admin.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/web/autoconfig.php b/data/web/autoconfig.php index 95952df0d..6a528d4a2 100644 --- a/data/web/autoconfig.php +++ b/data/web/autoconfig.php @@ -85,7 +85,7 @@ if (count($records) == 0 || $records[0]['target'] != '') { ?> password-cleartext - + If you didn't change the password given to you by the administrator or if you didn't change it in a long time, please consider doing that now. Sollten Sie das Ihnen durch den Administrator vergebene Passwort noch nicht geändert haben, empfehlen wir dies nun zu tun. Auch ein altes Passwort sollte aus Sicherheitsgründen geändert werden. diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js index b6edf6f80..0195a9c3c 100644 --- a/data/web/js/site/admin.js +++ b/data/web/js/site/admin.js @@ -558,7 +558,7 @@ jQuery(function($){ } else if (table == 'oauth2clientstable') { $.each(data, function (i, item) { item.action = ''; item.scope = "profile"; @@ -573,7 +573,7 @@ jQuery(function($){ item.action = ''; }); } else if (table == 'adminstable') { From c4d0f35008b9dfa1f8f2e87e55d308cc220bf181 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 15 Apr 2025 10:49:56 +0200 Subject: [PATCH 12/24] [Dovecot] Fix EAS login and improve logging --- data/conf/dovecot/auth/mailcowauth.php | 2 +- data/conf/dovecot/auth/passwd-verify.lua | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php index 4eda382b7..c625522ba 100644 --- a/data/conf/dovecot/auth/mailcowauth.php +++ b/data/conf/dovecot/auth/mailcowauth.php @@ -80,7 +80,7 @@ if ($isSOGoRequest) { } if ($result === false){ // If it's a SOGo Request, don't check for protocol access - $service = (isSOGoRequest) ? false : array($post['service'] => true); + $service = ($isSOGoRequest) ? false : array($post['service'] => true); $result = apppass_login($post['username'], $post['password'], $service, array( 'is_internal' => true, 'remote_addr' => $post['real_rip'] diff --git a/data/conf/dovecot/auth/passwd-verify.lua b/data/conf/dovecot/auth/passwd-verify.lua index 19dcc4bd6..b8843c996 100644 --- a/data/conf/dovecot/auth/passwd-verify.lua +++ b/data/conf/dovecot/auth/passwd-verify.lua @@ -34,8 +34,15 @@ function auth_password_verify(request, password) return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, "Upstream error" end - local api_response = json.decode(table.concat(res)) - if api_response.success == true then + local response_str = table.concat(res) + local is_response_valid, response_json = pcall(json.decode, response_str) + + if not is_response_valid then + dovecot.i_info("Invalid JSON received: " .. response_str) + return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, "Invalid response format" + end + + if response_json.success == true then return dovecot.auth.PASSDB_RESULT_OK, "" end From cb47fa406f10ce929543e3e918c11b985a500ab2 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 15 Apr 2025 13:48:13 +0200 Subject: [PATCH 13/24] [Web] Fix force password update at next login --- data/web/inc/functions.auth.inc.php | 14 ++++++++++++++ data/web/inc/functions.inc.php | 1 + data/web/inc/triggers.user.inc.php | 8 ++++++-- data/web/sogo-auth.php | 3 ++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 994915efc..57dec808d 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -242,6 +242,7 @@ function user_login($user, $pass, $extra = null){ return false; } + $row['attributes'] = json_decode($row['attributes'], true); switch ($row['authsource']) { case 'keycloak': // user authsource is keycloak, try using via rest flow @@ -261,6 +262,10 @@ function user_login($user, $pass, $extra = null){ return false; } + if (intval($row['attributes']['force_pw_update']) == 1) { + $_SESSION['pending_pw_update'] = true; + } + // check for tfa authenticators $authenticators = get_tfa($user); if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { @@ -313,6 +318,10 @@ function user_login($user, $pass, $extra = null){ return false; } + if (intval($row['attributes']['force_pw_update']) == 1) { + $_SESSION['pending_pw_update'] = true; + } + // check for tfa authenticators $authenticators = get_tfa($user); if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { @@ -351,6 +360,11 @@ function user_login($user, $pass, $extra = null){ } // verify password if (verify_hash($row['password'], $pass) !== false) { + + if (intval($row['attributes']['force_pw_update']) == 1) { + $_SESSION['pending_pw_update'] = true; + } + // check for tfa authenticators $authenticators = get_tfa($user); if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 49e26b978..334b99e64 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -1001,6 +1001,7 @@ function edit_user_account($_data) { ':password_hashed' => $password_hashed, ':username' => $username )); + $_SESSION['pending_pw_update'] = false; update_sogo_static_view(); } diff --git a/data/web/inc/triggers.user.inc.php b/data/web/inc/triggers.user.inc.php index 33eb83e7b..30bf0fe64 100644 --- a/data/web/inc/triggers.user.inc.php +++ b/data/web/inc/triggers.user.inc.php @@ -76,7 +76,9 @@ if (isset($_POST["verify_tfa_login"])) { $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; - if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) { + if (intval($user_details['attributes']['sogo_access']) == 1 && + intval($user_details['attributes']['force_pw_update']) != 1 && + !$is_dual) { header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}"); die(); } else { @@ -139,7 +141,9 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { $user_details = mailbox("get", "mailbox_details", $login_user); $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; - if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) { + if (intval($user_details['attributes']['sogo_access']) == 1 && + intval($user_details['attributes']['force_pw_update']) != 1 && + !$is_dual) { header("Location: /SOGo/so/{$login_user}"); die(); } else { diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 5e0f3c39b..00709fe5f 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -94,7 +94,8 @@ elseif (isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HT !empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL) && is_array($_SESSION[$session_var_user_allowed]) && - in_array($email, $_SESSION[$session_var_user_allowed]) + in_array($email, $_SESSION[$session_var_user_allowed]) && + !$_SESSION['pending_pw_update'] ) { $username = $email; $password = file_get_contents("/etc/sogo-sso/sogo-sso.pass"); From d8c6ed919144b0b97eaa9a329fe3366a04c422e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=C3=BChn?= Date: Tue, 22 Apr 2025 14:23:33 +0200 Subject: [PATCH 14/24] Check if skip_sogo is not set before redirecting to SOGo --- data/web/inc/triggers.user.inc.php | 4 ++-- data/web/index.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/web/inc/triggers.user.inc.php b/data/web/inc/triggers.user.inc.php index 33eb83e7b..07dc0372e 100644 --- a/data/web/inc/triggers.user.inc.php +++ b/data/web/inc/triggers.user.inc.php @@ -76,7 +76,7 @@ if (isset($_POST["verify_tfa_login"])) { $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; - if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) { + if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") { header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}"); die(); } else { @@ -139,7 +139,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) { $user_details = mailbox("get", "mailbox_details", $login_user); $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; - if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) { + if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") { header("Location: /SOGo/so/{$login_user}"); die(); } else { diff --git a/data/web/index.php b/data/web/index.php index 1e91cb785..f306f1aed 100644 --- a/data/web/index.php +++ b/data/web/index.php @@ -11,7 +11,7 @@ if (isset($_SESSION['mailcow_cc_role']) && isset($_SESSION['oauth2_request'])) { elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { $user_details = mailbox("get", "mailbox_details", $_SESSION['mailcow_cc_username']); $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? true : false; - if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual) { + if (intval($user_details['attributes']['sogo_access']) == 1 && !$is_dual && getenv('SKIP_SOGO') != "y") { header("Location: /SOGo/so/{$_SESSION['mailcow_cc_username']}"); } else { header("Location: /user"); From aa4125fe62d3a0e7ebfe5899ab614fb9f3ba01ec Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 23 Apr 2025 15:13:52 +0200 Subject: [PATCH 15/24] sogo: enabled SOGoEnableMailCleaning per default --- data/conf/sogo/sogo.conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 8455f0cf3..2c8d80a12 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -16,6 +16,9 @@ SOGoFoldersSendEMailNotifications = YES; SOGoForwardEnabled = YES; + // Added with SOGo 5.12 - Allows users to cleanup there maildirectories by deleting mails oder than X + SOGoEnableMailCleaning = YES; + // Fixes "MODIFICATION_FAILED" error (HTTP 412) in Clients when accepting invitations from external services SOGoDisableOrganizerEventCheck = YES; @@ -91,7 +94,7 @@ //SoDebugBaseURL = YES; //ImapDebugEnabled = YES; //SOGoEASDebugEnabled = YES; - SOGoEASSearchInBody = YES; // Experimental. Enabled since 2023-10 + SOGoEASSearchInBody = YES; //LDAPDebugEnabled = YES; //PGDebugEnabled = YES; //MySQL4DebugEnabled = YES; From 06b3ba91a045330daaa786203c897c4df7174653 Mon Sep 17 00:00:00 2001 From: milkmaker Date: Sun, 27 Apr 2025 18:47:08 +0200 Subject: [PATCH 16/24] [Web] Updated lang.zh-cn.json (#6502) Co-authored-by: Easton Man --- data/web/lang/lang.zh-cn.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/web/lang/lang.zh-cn.json b/data/web/lang/lang.zh-cn.json index f9bb481da..486e8dcc3 100644 --- a/data/web/lang/lang.zh-cn.json +++ b/data/web/lang/lang.zh-cn.json @@ -108,7 +108,8 @@ "timeout2": "本地主机连接超时时间", "username": "用户名", "validate": "验证", - "validation_success": "验证成功" + "validation_success": "验证成功", + "dry": "模拟同步(Dry run)" }, "admin": { "access": "权限管理", @@ -994,7 +995,7 @@ "neutral_danger": "无危险等级", "notified": "已发送通知", "qhandler_success": "已成功向系统发送请求,现在你可以关闭这个窗口了。", - "qid": "Rspamd QID", + "qid": "Rspamd 队列ID(QID)", "qinfo": "隔离系统会把已被拒绝接收的邮件以及作为拷贝发送到垃圾箱的邮件保存到数据库中 (发件人会知道)。\r\n
    \"学习为垃圾并删除\" 会根据贝叶斯定理将消息作为垃圾学习并计算其模糊特征以拒绝未来收到相似消息。\r\n
    请注意,这取决于你的系统资源,学习多个消息可能会花费较长时间。
    黑名单中项目会被隔离系统排除。", "qitem": "隔离项目", "quarantine": "隔离", From d55f0fc36618a41ae68a9475826d003ee11a89e7 Mon Sep 17 00:00:00 2001 From: Marcel Schuster Date: Tue, 29 Apr 2025 22:14:43 +0200 Subject: [PATCH 17/24] Update functions.quarantine.inc.php Fix regex for quarantine release functions --- data/web/inc/functions.quarantine.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/inc/functions.quarantine.inc.php b/data/web/inc/functions.quarantine.inc.php index f4f49de49..39606f271 100644 --- a/data/web/inc/functions.quarantine.inc.php +++ b/data/web/inc/functions.quarantine.inc.php @@ -169,7 +169,7 @@ function quarantine($_action, $_data = null) { } } elseif ($release_format == 'raw') { - $detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $detail_row['msg']); + $detail_row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $detail_row['msg']); $postfix_talk = array( array('220', 'HELO quarantine' . chr(10)), array('250', 'MAIL FROM: ' . $sender . chr(10)), @@ -464,7 +464,7 @@ function quarantine($_action, $_data = null) { } } elseif ($release_format == 'raw') { - $row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/', 'X-Pre-Release-Spam-Flag $1', $row['msg']); + $row['msg'] = preg_replace('/^X-Spam-Flag: (.*)/m', 'X-Pre-Release-Spam-Flag: $1', $row['msg']); $postfix_talk = array( array('220', 'HELO quarantine' . chr(10)), array('250', 'MAIL FROM: ' . $sender . chr(10)), From 0c83255573914e91a047e6238c69e8cc9a480e8f Mon Sep 17 00:00:00 2001 From: milkmaker Date: Thu, 1 May 2025 00:21:11 +0000 Subject: [PATCH 18/24] update postscreen_access.cidr --- data/conf/postfix/postscreen_access.cidr | 99 +++++++++++------------- 1 file changed, 45 insertions(+), 54 deletions(-) diff --git a/data/conf/postfix/postscreen_access.cidr b/data/conf/postfix/postscreen_access.cidr index 36c7e9fca..10c609097 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 Tue Apr 1 00:20:51 UTC 2025 +# Whitelist generated by Postwhite v3.4 on Thu May 1 00:21:10 UTC 2025 # https://github.com/stevejenkins/postwhite/ -# 2067 total rules +# 2058 total rules 2a00:1450:4000::/36 permit 2a01:111:f400::/48 permit 2a01:111:f403:8000::/50 permit @@ -18,6 +18,7 @@ 2a02:a60:0:5::/64 permit 2c0f:fb50:4000::/36 permit 2.207.151.53 permit +2.207.217.30 permit 3.70.123.177 permit 3.93.157.0/24 permit 3.94.40.108 permit @@ -26,10 +27,8 @@ 8.20.114.31 permit 8.25.194.0/23 permit 8.25.196.0/23 permit -8.39.54.0/23 permit -8.39.54.250/31 permit -8.40.222.0/23 permit -8.40.222.250/31 permit +8.36.116.0/24 permit +8.39.144.0/24 permit 12.130.86.238 permit 13.107.246.59 permit 13.110.208.0/21 permit @@ -103,6 +102,7 @@ 27.123.206.80/28 permit 31.25.48.222 permit 31.47.251.17 permit +31.186.239.0/24 permit 34.2.64.0/22 permit 34.2.68.0/23 permit 34.2.70.0/23 permit @@ -121,6 +121,10 @@ 34.2.90.0/23 permit 34.2.92.0/23 permit 34.2.94.0/23 permit +34.70.158.162 permit +34.74.74.140 permit +34.83.159.189 permit +34.141.160.224 permit 34.195.217.107 permit 34.212.163.75 permit 34.215.104.144 permit @@ -132,6 +136,7 @@ 35.190.247.0/24 permit 35.191.0.0/16 permit 35.205.92.9 permit +35.228.216.85 permit 35.242.169.159 permit 37.188.97.188 permit 37.218.248.47 permit @@ -233,6 +238,7 @@ 52.95.49.88/29 permit 52.96.91.34 permit 52.96.111.82 permit +52.96.172.98 permit 52.96.214.50 permit 52.96.222.194 permit 52.96.222.226 permit @@ -272,7 +278,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 62.13.128.0/24 permit 62.13.129.128/25 permit @@ -309,9 +314,6 @@ 64.207.219.13 permit 64.207.219.14 permit 64.207.219.15 permit -64.207.219.24 permit -64.207.219.25 permit -64.207.219.26 permit 64.207.219.71 permit 64.207.219.72 permit 64.207.219.73 permit @@ -321,9 +323,6 @@ 64.207.219.77 permit 64.207.219.78 permit 64.207.219.79 permit -64.207.219.88 permit -64.207.219.89 permit -64.207.219.90 permit 64.207.219.135 permit 64.207.219.136 permit 64.207.219.137 permit @@ -359,7 +358,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 @@ -1323,9 +1321,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 @@ -1388,21 +1383,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.147.128.0/20 permit 136.147.135.0/24 permit 136.147.176.0/20 permit @@ -1417,7 +1398,6 @@ 139.138.46.219 permit 139.138.57.55 permit 139.138.58.119 permit -139.167.79.86 permit 139.180.17.0/24 permit 140.238.148.191 permit 141.148.159.229 permit @@ -1452,6 +1432,7 @@ 146.20.215.0/24 permit 146.20.215.182 permit 146.88.28.0/24 permit +146.148.116.76 permit 147.154.32.0/25 permit 147.243.1.47 permit 147.243.1.48 permit @@ -1533,11 +1514,10 @@ 163.114.132.120 permit 163.114.134.16 permit 163.114.135.16 permit +163.116.128.0/17 permit 164.152.23.32 permit +164.152.25.241 permit 164.177.132.168/30 permit -165.173.128.0/24 permit -165.173.180.250/31 permit -165.173.182.250/31 permit 166.78.68.0/22 permit 166.78.68.221 permit 166.78.69.169 permit @@ -1566,12 +1546,6 @@ 168.245.12.252 permit 168.245.46.9 permit 168.245.127.231 permit -169.148.129.0/24 permit -169.148.131.0/24 permit -169.148.142.10 permit -169.148.144.0/25 permit -169.148.144.10 permit -169.148.146.0/23 permit 170.10.128.0/24 permit 170.10.129.0/24 permit 170.10.132.56/29 permit @@ -1697,6 +1671,21 @@ 193.123.56.63 permit 194.19.134.0/25 permit 194.64.234.129 permit +194.97.196.0/24 permit +194.97.196.3 permit +194.97.196.4 permit +194.97.196.11 permit +194.97.196.12 permit +194.97.204.0/24 permit +194.97.204.3 permit +194.97.204.4 permit +194.97.204.11 permit +194.97.204.12 permit +194.97.212.0/24 permit +194.97.212.3 permit +194.97.212.4 permit +194.97.212.11 permit +194.97.212.12 permit 194.106.220.0/23 permit 194.113.24.0/22 permit 194.154.193.192/27 permit @@ -1733,15 +1722,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.101.161.130 permit 199.101.162.0/25 permit 199.122.120.0/21 permit @@ -1798,8 +1779,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.220.160.0/21 permit 204.220.168.0/21 permit 204.220.176.0/20 permit @@ -2046,15 +2025,27 @@ 2001:0868:0100:0600::/64 permit 2001:4860:4000::/36 permit 2001:748:100:40::2:0/112 permit +2001:748:400:1300::3 permit +2001:748:400:1300::4 permit +2001:748:400:1301::0/64 permit +2001:748:400:1301::3 permit +2001:748:400:1301::4 permit +2001:748:400:2300::3 permit +2001:748:400:2300::4 permit +2001:748:400:2301::0/64 permit +2001:748:400:2301::3 permit +2001:748:400:2301::4 permit +2001:748:400:3300::3 permit +2001:748:400:3300::4 permit +2001:748:400:3301::0/64 permit +2001:748:400:3301::3 permit +2001:748:400:3301::4 permit 2404:6800:4000::/36 permit 2603:1010:3:3::5b permit 2603:1020:201:10::10f permit 2603:1030:20e:3::23c permit 2603:1030:b:3::152 permit 2603:1030:c02:8::14 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 2607:f8b0:4000::/36 permit 2620:109:c003:104::/64 permit 2620:109:c003:104::215 permit From 401b744808ff127b625001d8512d80d108d58a6a Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Thu, 8 May 2025 11:38:29 +0200 Subject: [PATCH 19/24] [Dovecot] return PASSDB_RESULT_PASSWORD_MISMATCH instead of PASSDB_RESULT_INTERNAL_FAILURE --- data/conf/dovecot/auth/passwd-verify.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data/conf/dovecot/auth/passwd-verify.lua b/data/conf/dovecot/auth/passwd-verify.lua index b8843c996..ea847932d 100644 --- a/data/conf/dovecot/auth/passwd-verify.lua +++ b/data/conf/dovecot/auth/passwd-verify.lua @@ -29,9 +29,12 @@ function auth_password_verify(request, password) insecure = true } + -- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry. + -- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry, + -- even if the TTL has expired. Useful to avoid cache eviction during backend issues. if c ~= 200 and c ~= 401 then dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user) - return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, "Upstream error" + return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error" end local response_str = table.concat(res) @@ -39,7 +42,7 @@ function auth_password_verify(request, password) if not is_response_valid then dovecot.i_info("Invalid JSON received: " .. response_str) - return dovecot.auth.PASSDB_RESULT_INTERNAL_FAILURE, "Invalid response format" + return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Invalid response format" end if response_json.success == true then From cb6ffe65c8fb0ec4b96abfd45c352f89a52bf40b Mon Sep 17 00:00:00 2001 From: Kai Biebel <38378574+seclution@users.noreply.github.com> Date: Fri, 9 May 2025 11:24:49 +0200 Subject: [PATCH 20/24] fix: typo in default_template --- data/web/inc/functions.auth.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 57dec808d..ec32e6610 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -669,8 +669,8 @@ function ldap_mbox_login($user, $pass, $extra = null){ // check if matching attribute exist if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) { - if (!empty($iam_settings['default_tempalte'])) { - $mbox_template = $iam_settings['default_tempalte']; + if (!empty($iam_settings['default_template'])) { + $mbox_template = $iam_settings['default_template']; } else { $_SESSION['return'][] = array( 'type' => 'danger', From ea0944d743c0f261cea57693dd1da9970206e99c Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 9 May 2025 15:13:44 +0200 Subject: [PATCH 21/24] [Web] Add quick links to other login pages and option to disable mailcow login form --- data/web/admin/index.php | 3 +- data/web/admin/system.php | 1 + data/web/domainadmin/index.php | 1 + data/web/inc/functions.customize.inc.php | 43 +++++++++++++ data/web/index.php | 8 ++- data/web/json_api.php | 3 + data/web/lang/lang.de-de.json | 12 ++++ data/web/lang/lang.en-gb.json | 12 ++++ .../templates/admin/tab-config-customize.twig | 36 ++++++++++- data/web/templates/admin_index.twig | 43 ++++++++----- data/web/templates/domainadmin_index.twig | 43 ++++++++----- data/web/templates/user_index.twig | 60 ++++++++++++------- 12 files changed, 206 insertions(+), 59 deletions(-) diff --git a/data/web/admin/index.php b/data/web/admin/index.php index 05ba70337..9ae4a0380 100644 --- a/data/web/admin/index.php +++ b/data/web/admin/index.php @@ -22,7 +22,8 @@ $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING']; $template = 'admin_index.twig'; $template_data = [ - 'login_delay' => @$_SESSION['ldelay'] + 'login_delay' => @$_SESSION['ldelay'], + 'custom_login' => customize('get', 'custom_login'), ]; $js_minifier->add('/web/js/site/index.js'); diff --git a/data/web/admin/system.php b/data/web/admin/system.php index c21d43f0d..9fd44e0d8 100644 --- a/data/web/admin/system.php +++ b/data/web/admin/system.php @@ -125,6 +125,7 @@ $template_data = [ 'logo_specs' => customize('get', 'main_logo_specs'), 'logo_dark_specs' => customize('get', 'main_logo_dark_specs'), 'ip_check' => customize('get', 'ip_check'), + 'custom_login' => customize('get', 'custom_login'), 'password_complexity' => password_complexity('get'), 'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'], 'cors_settings' => $cors_settings, diff --git a/data/web/domainadmin/index.php b/data/web/domainadmin/index.php index 2d909f97f..0d70ec3ae 100644 --- a/data/web/domainadmin/index.php +++ b/data/web/domainadmin/index.php @@ -22,6 +22,7 @@ $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING']; $template = 'domainadmin_index.twig'; $template_data = [ 'login_delay' => @$_SESSION['ldelay'], + 'custom_login' => customize('get', 'custom_login'), ]; $js_minifier->add('/web/js/site/index.js'); diff --git a/data/web/inc/functions.customize.inc.php b/data/web/inc/functions.customize.inc.php index 8c0feb69f..dc86bfe65 100644 --- a/data/web/inc/functions.customize.inc.php +++ b/data/web/inc/functions.customize.inc.php @@ -204,6 +204,35 @@ function customize($_action, $_item, $_data = null) { 'msg' => 'ip_check_opt_in_modified' ); break; + case 'custom_login': + $hide_user_quicklink = ($_data['hide_user_quicklink'] == "1") ? 1 : 0; + $hide_domainadmin_quicklink = ($_data['hide_domainadmin_quicklink'] == "1") ? 1 : 0; + $hide_admin_quicklink = ($_data['hide_admin_quicklink'] == "1") ? 1 : 0; + $force_sso = ($_data['force_sso'] == "1") ? 1 : 0; + + $custom_login = array( + "hide_user_quicklink" => $hide_user_quicklink, + "hide_domainadmin_quicklink" => $hide_domainadmin_quicklink, + "hide_admin_quicklink" => $hide_admin_quicklink, + "force_sso" => $force_sso, + ); + try { + $redis->set('CUSTOM_LOGIN', json_encode($custom_login)); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + $_SESSION['return'][] = array( + 'type' => 'success', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'custom_login_modified' + ); + break; } break; case 'delete': @@ -357,6 +386,20 @@ function customize($_action, $_item, $_data = null) { return false; } break; + case 'custom_login': + try { + $custom_login = ($custom_login = $redis->get('CUSTOM_LOGIN')) ? $custom_login : array(); + return json_decode($custom_login, true); + } + catch (RedisException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => array('redis_error', $e) + ); + return false; + } + break; } break; } diff --git a/data/web/index.php b/data/web/index.php index f306f1aed..d21e54364 100644 --- a/data/web/index.php +++ b/data/web/index.php @@ -33,16 +33,18 @@ $_SESSION['index_query_string'] = $_SERVER['QUERY_STRING']; $has_iam_sso = false; if ($iam_provider){ - $has_iam_sso = identity_provider("get-redirect") ? true : false; + $iam_redirect_url = identity_provider("get-redirect"); + $has_iam_sso = $iam_redirect_url ? true : false; } - +$custom_login = customize('get', 'custom_login'); $template = 'user_index.twig'; $template_data = [ 'oauth2_request' => @$_SESSION['oauth2_request'], 'is_mobileconfig' => str_contains($_SESSION['index_query_string'], 'mobileconfig'), 'login_delay' => @$_SESSION['ldelay'], - 'has_iam_sso' => $has_iam_sso + 'has_iam_sso' => $has_iam_sso, + 'custom_login' => $custom_login, ]; $js_minifier->add('/web/js/site/index.js'); diff --git a/data/web/json_api.php b/data/web/json_api.php index f2a222b85..a480b0ae0 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -1976,6 +1976,9 @@ if (isset($_GET['query'])) { case "ip_check": process_edit_return(customize('edit', 'ip_check', $attr)); break; + case "custom_login": + process_edit_return(customize('edit', 'custom_login', $attr)); + break; case "self": if ($_SESSION['mailcow_cc_role'] == "domainadmin") { process_edit_return(domain_admin('edit', $attr)); diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 911ebfc67..06d43b565 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -134,6 +134,7 @@ "admin_domains": "Domain-Zuweisungen", "admins": "Administratoren", "admins_ldap": "LDAP-Administratoren", + "admin_quicklink": "Quicklink zur Admin-Loginseite ausblenden", "advanced_settings": "Erweiterte Einstellungen", "api_allow_from": "IP-Adressen oder Netzwerke (CIDR Notation) für Zugriff auf API", "api_info": "Die API befindet sich noch in Entwicklung, die Dokumentation kann unter /api abgerufen werden.", @@ -155,6 +156,7 @@ "credentials_transport_warning": "Warnung: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.", "customer_id": "Kunde", "customize": "UI-Anpassung", + "login_page": "Login-Seite", "destination": "Ziel", "dkim_add_key": "ARC/DKIM-Key hinzufügen", "dkim_domains_selector": "Selector", @@ -173,6 +175,7 @@ "domain": "Domain", "domain_admin": "Administrator hinzufügen", "domain_admins": "Domain-Administratoren", + "domainadmin_quicklink": "Quicklink zur Domainadmin-Loginseite ausblenden", "domain_s": "Domain(s)", "duplicate": "Duplizieren", "duplicate_dkim": "DKIM duplizieren", @@ -195,6 +198,8 @@ "f2b_retry_window": "Wiederholungen im Zeitraum von (s)", "f2b_whitelist": "Whitelist für Netzwerke und Hosts", "filter_table": "Tabelle filtern", + "force_sso_text": "Wenn ein externer OIDC-Provider konfiguriert ist, blendet diese Option die mailcow Loginform aus und zeigt nur den Single Sign-On-Button an.", + "force_sso": "mailcow Login deaktivieren und nur Single Sign-On anzeigen", "forwarding_hosts": "Weiterleitungs-Hosts", "forwarding_hosts_add_hint": "Sie können entweder IPv4-/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.", "forwarding_hosts_hint": "Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die übliche Verwendung für diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.", @@ -308,6 +313,7 @@ "quarantine_release_format_att": "Als Anhang", "quarantine_release_format_raw": "Unverändertes Original", "quarantine_retention_size": "Rückhaltungen pro Mailbox:
    0 bedeutet inaktiv.", + "quicklink_text": "Quicklinks zu anderen Login-Seiten unter der Loginform ein- oder ausblenden", "quota_notification_html": "Benachrichtigungs-E-Mail Inhalt:
    Leer lassen, um Standard-Template wiederherzustellen.", "quota_notification_sender": "Benachrichtigungs-E-Mail Absender", "quota_notification_subject": "Benachrichtigungs-E-Mail Betreff", @@ -387,6 +393,7 @@ "unchanged_if_empty": "Unverändert, wenn leer", "upload": "Hochladen", "username": "Benutzername", + "user_quicklink": "Quicklink zur Benutzer-Loginseite ausblenden", "validate_license_now": "GUID erneut verifizieren", "verify": "Verifizieren", "yes": "✓", @@ -807,6 +814,10 @@ "forgot_password": "> Passwort vergessen?", "invalid_pass_reset_token": "Der Rücksetz-Token für das Passwort ist ungültig oder abgelaufen.
    Bitte fordern Sie einen neuen Link zur Passwortwiederherstellung an.", "login": "Anmelden", + "login_linkstext": "Nicht der richtige Login?", + "login_usertext": "Als Benutzer anmelden", + "login_domainadmintext": "Als Domainadmin anmelden", + "login_admintext": "Als Admin anmelden", "login_user": "Anmeldung als Benutzer", "login_dadmin": "Anmeldung als Domain-Administrator", "login_admin": "Anmeldung als Administrator", @@ -1096,6 +1107,7 @@ "bcc_edited": "BCC-Map-Eintrag %s wurde geändert", "bcc_saved": "BCC- Map-Eintrag wurde gespeichert", "cors_headers_edited": "CORS Einstellungen wurden erfolgreich gespeichert", + "custom_login_modified": "Login Anpassung wurde erfolgreich gespeichert", "db_init_complete": "Datenbankinitialisierung abgeschlossen", "delete_filter": "Filter-ID %s wurde gelöscht", "delete_filters": "Filter gelöscht: %s", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index ca8bb3adf..707e2a60e 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -134,6 +134,7 @@ "admin_domains": "Domain assignments", "admins": "Administrators", "admins_ldap": "LDAP Administrators", + "admin_quicklink": "Hide Quicklink to Admin Login Page", "advanced_settings": "Advanced settings", "allowed_methods": "Access-Control-Allow-Methods", "allowed_origins": "Access-Control-Allow-Origin", @@ -161,6 +162,7 @@ "credentials_transport_warning": "Warning: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.", "customer_id": "Customer ID", "customize": "Customize", + "login_page": "Login Page", "destination": "Destination", "dkim_add_key": "Add ARC/DKIM key", "dkim_domains_selector": "Selector", @@ -179,6 +181,7 @@ "domain": "Domain", "domain_admin": "Domain administrator", "domain_admins": "Domain administrators", + "domainadmin_quicklink": "Hide Quicklink to Domainadmin Login Page", "domain_s": "Domain/s", "duplicate": "Duplicate", "duplicate_dkim": "Duplicate DKIM record", @@ -202,6 +205,8 @@ "f2b_whitelist": "Whitelisted networks/hosts", "filter": "Filter", "filter_table": "Filter table", + "force_sso_text": "If an external OIDC provider is configured, this option hides the default mailcow login froms and only shows the Singe Sign-On button", + "force_sso": "Disable mailcow Login and show only Singe Sign-On", "forwarding_hosts": "Forwarding Hosts", "forwarding_hosts_add_hint": "You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).", "forwarding_hosts_hint": "Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.", @@ -317,6 +322,7 @@ "quarantine_release_format_att": "As attachment", "quarantine_release_format_raw": "Unmodified original", "quarantine_retention_size": "Retentions per mailbox:
    0 indicates inactive.", + "quicklink_text": "Show or hide quick links to other login pages under the login form", "quota_notification_html": "Notification email template:
    Leave empty to restore default template.", "quota_notification_sender": "Notification email sender", "quota_notification_subject": "Notification email subject", @@ -398,6 +404,7 @@ "upload": "Upload", "username": "Username", "user_link": "User-Link", + "user_quicklink": "Hide Quicklink to User Login Page", "validate_license_now": "Validate GUID against license server", "verify": "Verify", "yes": "✓" @@ -809,6 +816,10 @@ "forgot_password": "> Forgot Password?", "invalid_pass_reset_token": "The reset password token is invalid or has expired.
    Please request a new password reset link.", "login": "Login", + "login_linkstext": "Not the correct login?", + "login_usertext": "Log in as user", + "login_domainadmintext": "Log in as domain admin", + "login_admintext": "Log in as admin", "login_user": "User Login", "login_dadmin": "Domain-Administrator Login", "login_admin": "Administrator Login", @@ -1105,6 +1116,7 @@ "bcc_edited": "BCC map entry %s edited", "bcc_saved": "BCC map entry saved", "cors_headers_edited": "CORS settings have been saved", + "custom_login_modified": "Login customisation was saved successfully", "db_init_complete": "Database initialization completed", "delete_filter": "Deleted filters ID %s", "delete_filters": "Deleted filters: %s", diff --git a/data/web/templates/admin/tab-config-customize.twig b/data/web/templates/admin/tab-config-customize.twig index c2071c399..aecf1846e 100644 --- a/data/web/templates/admin/tab-config-customize.twig +++ b/data/web/templates/admin/tab-config-customize.twig @@ -51,7 +51,41 @@

    - {{ lang.admin.app_links }}
    + {{ lang.admin.login_page }}
    +
    +
    +

    {{ lang.admin.quicklink_text }}

    +
    + + +
    +
    + + +
    +
    + + +
    +

    {{ lang.admin.force_sso_text|raw }}

    +
    + + +
    +

    + +

    +
    +
    + {{ lang.admin.app_links }}

    {{ lang.admin.merged_vars_hint|raw }}

    diff --git a/data/web/templates/admin_index.twig b/data/web/templates/admin_index.twig index 6f223889e..0dcb9145e 100644 --- a/data/web/templates/admin_index.twig +++ b/data/web/templates/admin_index.twig @@ -5,13 +5,28 @@ {% block content %}
    +
    -
    +
    {{ lang.login.login_admin }}
    +
    + + +
    -
    - - -
    +
    -
    {{ lang.login.other_logins }}
    +
    {{ lang.login.other_logins }}
    @@ -86,6 +88,15 @@ {% endif %}
    + + {% if custom_login.hide_user_quicklink != 1 or custom_login.hide_domainadmin_quicklink != 1 %} +

    + {{ lang.login.login_linkstext }}
    + {% if custom_login.hide_user_quicklink != 1 %}{{ lang.login.login_usertext }}{% endif %} + {% if custom_login.hide_user_quicklink != 1 and custom_login.hide_domainadmin_quicklink != 1 %}|{% endif %} + {% if custom_login.hide_domainadmin_quicklink != 1 %}{{ lang.login.login_domainadmintext }}{% endif %} +

    + {% endif %}
    {% endblock %} diff --git a/data/web/templates/domainadmin_index.twig b/data/web/templates/domainadmin_index.twig index 003fdba1f..2fc7bad98 100644 --- a/data/web/templates/domainadmin_index.twig +++ b/data/web/templates/domainadmin_index.twig @@ -5,13 +5,28 @@ {% block content %}
    +
    -
    +
    {{ lang.login.login_dadmin }}
    +
    + + +
    -
    - - -
    +
    -
    {{ lang.login.other_logins }}
    +
    {{ lang.login.other_logins }}
    @@ -86,6 +88,15 @@ {% endif %}
    + + {% if custom_login.hide_user_quicklink != 1 or custom_login.hide_admin_quicklink != 1 %} +

    + {{ lang.login.login_linkstext }}
    + {% if custom_login.hide_user_quicklink != 1 %}{{ lang.login.login_usertext }}{% endif %} + {% if custom_login.hide_user_quicklink != 1 and custom_login.hide_admin_quicklink != 1 %}|{% endif %} + {% if custom_login.hide_admin_quicklink != 1 %}{{ lang.login.login_admintext }}{% endif %} +

    + {% endif %}
    {% endblock %} diff --git a/data/web/templates/user_index.twig b/data/web/templates/user_index.twig index 4ed942143..234aa01cb 100644 --- a/data/web/templates/user_index.twig +++ b/data/web/templates/user_index.twig @@ -5,13 +5,30 @@ {% block content %}
    +
    -
    +
    {{ lang.login.login_user }}
    + {% if not oauth2_request %} +
    + + +
    + {% endif %}
    +
    - - {% if not oauth2_request %} -
    - - -
    - {% endif %} +
    - -
    {{ lang.login.other_logins }}
    +
    {{ lang.login.other_logins }}
    + {% endif %}
    {% if has_iam_sso %} {{ lang.admin.iam_sso }} {% endif %} + {% if custom_login.force_sso != 1 %} {{ lang.login.fido2_webauthn }} + {% endif %}
    {% if login_delay %}

    {{ lang.login.delayed|format(login_delay) }}

    @@ -96,9 +101,20 @@ {% endfor %} {% endfor %}
    +
    +
    {% endif %}
    + + {% if custom_login.hide_admin_quicklink != 1 or custom_login.hide_domainadmin_quicklink != 1 %} +

    + {{ lang.login.login_linkstext }}
    + {% if custom_login.hide_admin_quicklink != 1 %}{{ lang.login.login_admintext }}{% endif %} + {% if custom_login.hide_admin_quicklink != 1 and custom_login.hide_domainadmin_quicklink != 1 %}|{% endif %} + {% if custom_login.hide_domainadmin_quicklink != 1 %}{{ lang.login.login_domainadmintext }}{% endif %} +

    + {% endif %}
    {% if not oauth2_request and ui_texts.help_text %} From 486b2974092dc5a42178204ff587650a8452c9c0 Mon Sep 17 00:00:00 2001 From: krzsztf1 <131195155+krzsztf1@users.noreply.github.com> Date: Fri, 9 May 2025 18:33:19 +0200 Subject: [PATCH 22/24] Fix typo in rspamd rule in composites.conf (#6515) Co-authored-by: Krzysztof Nowak --- data/conf/rspamd/local.d/composites.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/rspamd/local.d/composites.conf b/data/conf/rspamd/local.d/composites.conf index 9bb84424a..4ce042aab 100644 --- a/data/conf/rspamd/local.d/composites.conf +++ b/data/conf/rspamd/local.d/composites.conf @@ -8,7 +8,7 @@ VIRUS_FOUND { } # Bad policy from free mail providers FREEMAIL_POLICY_FAILURE { - expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST& !WHITELISTED_FWD_HOST & -g+:policies"; + expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST & !WHITELISTED_FWD_HOST & -g+:policies"; score = 16.0; } # Applies to freemail with undisclosed recipients From 1b2f424edc7b5f91c450b36b5cf64f012e7b821b Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 12 May 2025 12:20:23 +0200 Subject: [PATCH 23/24] [Web] Add identity_provider option to disable auto-creation of users on login --- data/web/inc/functions.auth.inc.php | 33 ++++++++++++++----- data/web/inc/functions.inc.php | 24 +++++++++++--- data/web/lang/lang.de-de.json | 1 + data/web/lang/lang.en-gb.json | 1 + .../admin/tab-config-identity-provider.twig | 32 +++++++++++++++++- 5 files changed, 78 insertions(+), 13 deletions(-) diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php index 57dec808d..ebc432a1f 100644 --- a/data/web/inc/functions.auth.inc.php +++ b/data/web/inc/functions.auth.inc.php @@ -262,10 +262,6 @@ function user_login($user, $pass, $extra = null){ return false; } - if (intval($row['attributes']['force_pw_update']) == 1) { - $_SESSION['pending_pw_update'] = true; - } - // check for tfa authenticators $authenticators = get_tfa($user); if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { @@ -318,10 +314,6 @@ function user_login($user, $pass, $extra = null){ return false; } - if (intval($row['attributes']['force_pw_update']) == 1) { - $_SESSION['pending_pw_update'] = true; - } - // check for tfa authenticators $authenticators = get_tfa($user); if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) { @@ -485,6 +477,9 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){ } return false; } + if (!$iam_provider) { + return false; + } // get access_token for service account of mailcow client $admin_token = identity_provider("get-keycloak-admin-token"); @@ -554,6 +549,17 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){ return 'user'; } + // check if login provisioning is enabled before creating user + if (!$iam_settings['login_provisioning']){ + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, "Auto-create users on login is deactivated"), + 'msg' => 'login_failed' + ); + } + return false; + } // check if matching attribute exist if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) { if (!empty($iam_settings['default_template'])) { @@ -667,6 +673,17 @@ function ldap_mbox_login($user, $pass, $extra = null){ return 'user'; } + // check if login provisioning is enabled before creating user + if (!$iam_settings['login_provisioning']){ + if (!$is_internal){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, "Auto-create users on login is deactivated"), + 'msg' => 'login_failed' + ); + } + return false; + } // check if matching attribute exist if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) { if (!empty($iam_settings['default_tempalte'])) { diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index aab579284..edf428d5a 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -2294,6 +2294,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { break; case "use_ssl": case "use_tls": + case "login_provisioning": case "ignore_ssl_errors": $settings[$row["key"]] = boolval($row["value"]); break; @@ -2302,6 +2303,10 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { break; } } + // set login_provisioning if not exists + if (!array_key_exists('login_provisioning', $settings)) { + $settings['login_provisioning'] = 1; + } // return default client_scopes for generic-oidc if none is set if ($settings["authsource"] == "generic-oidc" && empty($settings["client_scopes"])){ $settings["client_scopes"] = "openid profile email mailcow_template"; @@ -2366,7 +2371,8 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { return false; } - $_data['ignore_ssl_error'] = isset($_data['ignore_ssl_error']) ? boolval($_data['ignore_ssl_error']) : false; + $_data['ignore_ssl_error'] = isset($_data['ignore_ssl_error']) ? boolval($_data['ignore_ssl_error']) : false; + $_data['login_provisioning'] = isset($_data['login_provisioning']) ? boolval($_data['login_provisioning']) : false; switch ($_data['authsource']) { case "keycloak": $_data['server_url'] = (!empty($_data['server_url'])) ? rtrim($_data['server_url'], '/') : null; @@ -2375,14 +2381,14 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { $_data['import_users'] = isset($_data['import_users']) ? intval($_data['import_users']) : 0; $_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15; $_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval']; - $required_settings = array('authsource', 'server_url', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version', 'mailpassword_flow', 'periodic_sync', 'import_users', 'sync_interval', 'ignore_ssl_error'); + $required_settings = array('authsource', 'server_url', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version', 'mailpassword_flow', 'periodic_sync', 'import_users', 'sync_interval', 'ignore_ssl_error', 'login_provisioning'); break; case "generic-oidc": $_data['authorize_url'] = (!empty($_data['authorize_url'])) ? $_data['authorize_url'] : null; $_data['token_url'] = (!empty($_data['token_url'])) ? $_data['token_url'] : null; $_data['userinfo_url'] = (!empty($_data['userinfo_url'])) ? $_data['userinfo_url'] : null; $_data['client_scopes'] = (!empty($_data['client_scopes'])) ? $_data['client_scopes'] : "openid profile email mailcow_template"; - $required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes', 'ignore_ssl_error'); + $required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes', 'ignore_ssl_error', 'login_provisioning'); break; case "ldap": $_data['host'] = (!empty($_data['host'])) ? str_replace(" ", "", $_data['host']) : ""; @@ -2396,7 +2402,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { $_data['use_tls'] = isset($_data['use_tls']) && !$_data['use_ssl'] ? boolval($_data['use_tls']) : false; $_data['sync_interval'] = (!empty($_data['sync_interval'])) ? intval($_data['sync_interval']) : 15; $_data['sync_interval'] = $_data['sync_interval'] < 1 ? 1 : $_data['sync_interval']; - $required_settings = array('authsource', 'host', 'port', 'basedn', 'username_field', 'filter', 'attribute_field', 'binddn', 'bindpass', 'periodic_sync', 'import_users', 'sync_interval', 'use_ssl', 'use_tls', 'ignore_ssl_error'); + $required_settings = array('authsource', 'host', 'port', 'basedn', 'username_field', 'filter', 'attribute_field', 'binddn', 'bindpass', 'periodic_sync', 'import_users', 'sync_interval', 'use_ssl', 'use_tls', 'ignore_ssl_error', 'login_provisioning'); break; } @@ -2766,6 +2772,16 @@ function identity_provider($_action = null, $_data = null, $_extra = null) { return true; } + // user doesn't exist, check if login provisioning is enabled + if (!$iam_settings['login_provisioning']){ + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, "Auto-create users on login is deactivated"), + 'msg' => 'login_failed' + ); + return false; + } + if (empty($iam_settings['mappers']) || empty($user_template) || $mapper_key === false){ if (!empty($iam_settings['default_template'])) { $mbox_template = $iam_settings['default_template']; diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 06d43b565..6e1b4d4c2 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -227,6 +227,7 @@ "iam_host": "Host", "iam_host_info": "Gib einen oder mehrere LDAP-Hosts ein, getrennt durch Kommas.", "iam_import_users": "Importiere Benutzer", + "iam_login_provisioning": "Benutzer beim Login erstellen", "iam_mapping": "Attribut Mapping", "iam_bindpass": "Bind Passwort", "iam_periodic_full_sync": "Vollsynchronisation", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index 707e2a60e..fb8fbb6e4 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -234,6 +234,7 @@ "iam_host": "Host", "iam_host_info": "Enter one or more LDAP hosts, separated by commas.", "iam_import_users": "Import Users", + "iam_login_provisioning": "Auto-create users on login", "iam_mapping": "Attribute Mapping", "iam_bindpass": "Bind Password", "iam_periodic_full_sync": "Periodic Full Sync", diff --git a/data/web/templates/admin/tab-config-identity-provider.twig b/data/web/templates/admin/tab-config-identity-provider.twig index a93002257..4572d7fb5 100644 --- a/data/web/templates/admin/tab-config-identity-provider.twig +++ b/data/web/templates/admin/tab-config-identity-provider.twig @@ -219,6 +219,16 @@ +
    +
    + +
    +
    +
    + +
    +
    +
    @@ -430,7 +440,7 @@
    -
    +
    @@ -440,6 +450,16 @@
    +
    +
    + +
    +
    +
    + +
    +
    +
    @@ -646,6 +666,16 @@
    +
    +
    + +
    +
    +
    + +
    +
    +
    From ffa29338737069d7e15fe26933cbb3aa8b8fcc2b Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 13 May 2025 09:49:53 +0200 Subject: [PATCH 24/24] increase Olefy, Rspamd and Watchdog docker images --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3b27a09a9..8f31c69c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,7 +84,7 @@ services: - clamd rspamd-mailcow: - image: ghcr.io/mailcow/rspamd:2.1 + image: ghcr.io/mailcow/rspamd:2.2 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -498,7 +498,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: ghcr.io/mailcow/watchdog:2.07 + image: ghcr.io/mailcow/watchdog:2.08 dns: - ${IPV4_NETWORK:-172.22.1}.254 tmpfs: @@ -591,7 +591,7 @@ services: - dockerapi olefy-mailcow: - image: ghcr.io/mailcow/olefy:1.14 + image: ghcr.io/mailcow/olefy:1.15 restart: always environment: - TZ=${TZ}