diff --git a/data/Dockerfiles/acme/acme.sh b/data/Dockerfiles/acme/acme.sh
index c09569cd..271de4fc 100755
--- a/data/Dockerfiles/acme/acme.sh
+++ b/data/Dockerfiles/acme/acme.sh
@@ -253,10 +253,20 @@ while true; do
unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS
for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do
- if [[ "${SUBDOMAIN}.${SQL_DOMAIN}" != "${MAILCOW_HOSTNAME}" ]]; then
- if check_domain "${SUBDOMAIN}.${SQL_DOMAIN}"; then
- VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}")
- fi
+ FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}"
+
+ # Skip if subdomain matches MAILCOW_HOSTNAME
+ if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then
+ continue
+ fi
+ # Skip if subdomain is covered by a wildcard in ADDITIONAL_SAN
+ if is_covered_by_wildcard "${FULL_SUBDOMAIN}"; then
+ log_f "Subdomain '${FULL_SUBDOMAIN}' is covered by wildcard - skipping explicit subdomain"
+ continue
+ fi
+ # Validate and add subdomain
+ if check_domain "${FULL_SUBDOMAIN}"; then
+ VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${FULL_SUBDOMAIN}")
fi
done
VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}")
@@ -273,7 +283,10 @@ while true; do
fi
# Only add mta-sts subdomain for alias domains
if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then
- if check_domain "mta-sts.${alias_domain}"; then
+ # Skip if mta-sts subdomain is covered by a wildcard
+ if is_covered_by_wildcard "mta-sts.${alias_domain}"; then
+ log_f "Alias domain mta-sts subdomain 'mta-sts.${alias_domain}' is covered by wildcard - skipping"
+ elif check_domain "mta-sts.${alias_domain}"; then
VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}")
fi
fi
@@ -308,13 +321,31 @@ while true; do
done
fi
+ # Check if MAILCOW_HOSTNAME is covered by a wildcard in ADDITIONAL_SAN
+ MAILCOW_HOSTNAME_COVERED=0
+ if [[ ! -z ${VALIDATED_MAILCOW_HOSTNAME} ]]; then
+ if is_covered_by_wildcard "${VALIDATED_MAILCOW_HOSTNAME}"; then
+ MAILCOW_PARENT_DOMAIN=$(echo ${VALIDATED_MAILCOW_HOSTNAME} | cut -d. -f2-)
+ log_f "MAILCOW_HOSTNAME '${VALIDATED_MAILCOW_HOSTNAME}' is covered by wildcard '*.${MAILCOW_PARENT_DOMAIN}' - skipping explicit hostname"
+ MAILCOW_HOSTNAME_COVERED=1
+ fi
+ fi
+
# Unique domains for server certificate
if [[ ${ENABLE_SSL_SNI} == "y" ]]; then
# create certificate for server name and fqdn SANs only
- SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
+ if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then
+ SERVER_SAN_VALIDATED=($(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
+ else
+ SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
+ fi
else
# create certificate for all domains, including all subdomains from other domains [*]
- SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
+ if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then
+ SERVER_SAN_VALIDATED=($(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
+ else
+ SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
+ fi
fi
if [[ ! -z ${SERVER_SAN_VALIDATED[*]} ]]; then
CERT_NAME=${SERVER_SAN_VALIDATED[0]}
diff --git a/data/Dockerfiles/acme/functions.sh b/data/Dockerfiles/acme/functions.sh
index 9db83291..707f4695 100644
--- a/data/Dockerfiles/acme/functions.sh
+++ b/data/Dockerfiles/acme/functions.sh
@@ -135,3 +135,32 @@ verify_challenge_path(){
return 1
fi
}
+
+# Check if a domain is covered by a wildcard (*.example.com) in ADDITIONAL_SAN
+# Usage: is_covered_by_wildcard "subdomain.example.com"
+# Returns: 0 if covered, 1 if not covered
+# Note: Only returns 0 (covered) when DNS-01 challenge is enabled,
+# as wildcards cannot be validated with HTTP-01 challenge
+is_covered_by_wildcard() {
+ local DOMAIN=$1
+
+ # Only skip if DNS challenge is enabled (wildcards require DNS-01)
+ if [[ ${ACME_DNS_CHALLENGE} != "y" ]]; then
+ return 1
+ fi
+
+ # Return early if no ADDITIONAL_SAN is set
+ if [[ -z ${ADDITIONAL_SAN} ]]; then
+ return 1
+ fi
+
+ # Extract parent domain (e.g., mail.example.com -> example.com)
+ local PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-)
+
+ # Check if ADDITIONAL_SAN contains a wildcard for this parent domain
+ if [[ "${ADDITIONAL_SAN}" == *"*.${PARENT_DOMAIN}"* ]]; then
+ return 0 # Covered by wildcard
+ fi
+
+ return 1 # Not covered
+}
diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php
index 91a8c55f..e5a303f9 100644
--- a/data/web/inc/functions.auth.inc.php
+++ b/data/web/inc/functions.auth.inc.php
@@ -287,6 +287,8 @@ function user_login($user, $pass, $extra = null){
return false;
}
+ $row['attributes'] = json_decode($row['attributes'], true);
+
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
@@ -343,6 +345,8 @@ function user_login($user, $pass, $extra = null){
return false;
}
+ $row['attributes'] = json_decode($row['attributes'], 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/js/build/013-mailcow.js b/data/web/js/build/013-mailcow.js
index d897f23e..e4893d7a 100644
--- a/data/web/js/build/013-mailcow.js
+++ b/data/web/js/build/013-mailcow.js
@@ -345,7 +345,7 @@ $(document).ready(function() {
$('.main-logo-dark').addClass('d-none');
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_dark.png');
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_dark.png');
- localStorage.setItem('theme', 'light');
+ localStorage.setItem('mailcow_theme', 'light');
}else{
$('head').append('');
$('#dark-mode-toggle').prop('checked', true);
@@ -353,7 +353,7 @@ $(document).ready(function() {
$('.main-logo-dark').removeClass('d-none');
if ($('#rspamd_logo').length) $('#rspamd_logo').attr('src', '/img/rspamd_logo_light.png');
if ($('#rspamd_logo_sm').length) $('#rspamd_logo_sm').attr('src', '/img/rspamd_logo_light.png');
- localStorage.setItem('theme', 'dark');
+ localStorage.setItem('mailcow_theme', 'dark');
}
}
diff --git a/data/web/js/site/index.js b/data/web/js/site/index.js
index 4c812a37..29b72f0d 100644
--- a/data/web/js/site/index.js
+++ b/data/web/js/site/index.js
@@ -1,5 +1,6 @@
$(document).ready(function() {
- var theme = localStorage.getItem("theme");
- localStorage.clear();
- localStorage.setItem("theme", theme);
+ var theme = localStorage.getItem("mailcow_theme");
+ if (theme !== null) {
+ localStorage.setItem("mailcow_theme", theme);
+ }
});
diff --git a/data/web/lang/lang.hu-hu.json b/data/web/lang/lang.hu-hu.json
index e51748c4..ff49bc5e 100644
--- a/data/web/lang/lang.hu-hu.json
+++ b/data/web/lang/lang.hu-hu.json
@@ -1144,7 +1144,8 @@
"subscribeall": "Feliratkozás minden mappára",
"syncjob": "Szinkronizálási feladat hozzáadása",
"internal": "Belső",
- "internal_info": "Belső álnevek csak a saját domain vagy domain álnév számára elérhető."
+ "internal_info": "Belső álnevek csak a saját domain vagy domain álnév számára elérhető.",
+ "sender_allowed": "Küldés engedélyezése ezzel az aliasszal"
},
"danger": {
"access_denied": "Hozzáférés megtagatva vagy nem megfelelő űrlap adat",
@@ -1245,6 +1246,21 @@
"pushover_key": "A pushover kulcs rossz formátumú",
"pushover_token": "A Pushover token rossz formátumú",
"quota_not_0_not_numeric": "A kvótának numerikusnak és >= 0-nak kell lennie.",
- "recipient_map_entry_exists": "Létezik egy \"%s\" címzett-térkép bejegyzés"
+ "recipient_map_entry_exists": "Létezik egy \"%s\" címzett-térkép bejegyzés",
+ "redis_error": "Redis hiba lépett fel: %s",
+ "relayhost_invalid": "A(z) %s elem érvénytelen a leképezésben.",
+ "release_send_failed": "Az üzenet felszabadítása sikertelen: %s",
+ "reset_f2b_regex": "A regex-szűrő időtúllépés miatt nem állt le. Próbálja újra, vagy várjon egy kicsit, és töltse újra az oldalt.",
+ "resource_invalid": "A(z) %s erőforrásnév érvénytelen",
+ "rl_timeframe": "Érvénytelen időkeret a lekérdezési korláthoz",
+ "rspamd_ui_pw_length": "A Rspamd UI jelszónak legalább 6 karakter hosszúnak kell lennie.",
+ "script_empty": "A szkript nem lehet üres",
+ "sender_acl_invalid": "A küldőhöz tartozó ACL-érték (%s) érvénytelen",
+ "set_acl_failed": "Az ACL beállítása meghiúsult",
+ "settings_map_invalid": "Érvénytelen beállítás-leképezési azonosító: %s",
+ "recovery_email_failed": "A helyreállítási email kiküldése sikertelen. Kérlek, lépj kapcsolatba az adminisztrátorral!",
+ "reset_token_limit_exceeded": "Túl sok visszaállítási kísérlet. Kérjük, várjon, mielőtt újra próbálkozna.",
+ "required_data_missing": "Hiányzik a(z) szükséges %s adat",
+ "tfa_removal_blocked": "A kétfaktoros hitelesítés nem távolítható el, mert elengedhetetlen a fiókod használatához."
}
}
diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig
index 4290edae..e1708950 100644
--- a/data/web/templates/base.twig
+++ b/data/web/templates/base.twig
@@ -11,8 +11,8 @@