From 038b2efb759e6d011b35beee53e3e534d266341f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:29:21 +0100 Subject: [PATCH] Add MTA-STS support for alias domains (#6972) * Initial plan * Add MTA-STS support for alias domains Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com> * Improve domain normalization and code style in mta-sts.php Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com> * Add error handling for idn_to_ascii in mta-sts.php Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com> * Add database error handling for alias domain query Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com> * Add ACME certificate support for MTA-STS on alias domains Query alias_domain table to find aliases with MTA-STS enabled target domains and request certificates for mta-sts. subdomains. Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com> * compose: bump image tag to 1.95 * Add MTA-STS DNS records display for alias domains in UI When viewing an alias domain's DNS diagnostics, check if the target domain has MTA-STS enabled and display the required DNS records for the alias domain. Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DerLinkman <62480600+DerLinkman@users.noreply.github.com> Co-authored-by: DerLinkman --- data/Dockerfiles/acme/acme.sh | 19 +++++++++++++++++++ data/web/inc/ajax/dns_diagnostics.php | 11 ++++++++++- data/web/mta-sts.php | 25 ++++++++++++++++++++++++- docker-compose.yml | 2 +- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/acme/acme.sh b/data/Dockerfiles/acme/acme.sh index 69b18bc1f..2aab8e7e3 100755 --- a/data/Dockerfiles/acme/acme.sh +++ b/data/Dockerfiles/acme/acme.sh @@ -246,6 +246,25 @@ while true; do done VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}") done + + # Fetch alias domains where target domain has MTA-STS enabled + if [[ ${AUTODISCOVER_SAN} == "y" ]]; then + SQL_ALIAS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT ad.alias_domain FROM alias_domain ad INNER JOIN mta_sts m ON ad.target_domain = m.domain WHERE ad.active = 1 AND m.active = 1" -Bs) + if [[ $? -eq 0 ]]; then + while read alias_domain; do + if [[ -z "${alias_domain}" ]]; then + # ignore empty lines + continue + 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 + VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}") + fi + fi + done <<< "${SQL_ALIAS_DOMAINS}" + fi + fi fi if check_domain ${MAILCOW_HOSTNAME}; then diff --git a/data/web/inc/ajax/dns_diagnostics.php b/data/web/inc/ajax/dns_diagnostics.php index b48239e10..95e34e886 100644 --- a/data/web/inc/ajax/dns_diagnostics.php +++ b/data/web/inc/ajax/dns_diagnostics.php @@ -129,7 +129,16 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm ); } - $mta_sts = mailbox('get', 'mta_sts', $domain); + // Check if domain is an alias domain and get target domain's MTA-STS + $alias_domain_details = mailbox('get', 'alias_domain_details', $domain); + $mta_sts_domain = $domain; + + if ($alias_domain_details !== false && !empty($alias_domain_details['target_domain'])) { + // This is an alias domain, check target domain for MTA-STS + $mta_sts_domain = $alias_domain_details['target_domain']; + } + + $mta_sts = mailbox('get', 'mta_sts', $mta_sts_domain); if (count($mta_sts) > 0 && $mta_sts['active'] == 1) { if (!in_array($domain, $alias_domains)) { $records[] = array( diff --git a/data/web/mta-sts.php b/data/web/mta-sts.php index 650b8b583..0c6f1a248 100644 --- a/data/web/mta-sts.php +++ b/data/web/mta-sts.php @@ -7,7 +7,30 @@ if (!isset($_SERVER['HTTP_HOST']) || strpos($_SERVER['HTTP_HOST'], 'mta-sts.') ! } $host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']); -$domain = str_replace('mta-sts.', '', $host); +$domain = idn_to_ascii(strtolower(str_replace('mta-sts.', '', $host)), 0, INTL_IDNA_VARIANT_UTS46); + +// Validate domain or return 404 on error +if ($domain === false || empty($domain)) { + http_response_code(404); + exit; +} + +// Check if domain is an alias domain and resolve to target domain +try { + $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain"); + $stmt->execute(array(':domain' => $domain)); + $alias_row = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($alias_row !== false && !empty($alias_row['target_domain'])) { + // This is an alias domain, use the target domain for MTA-STS lookup + $domain = $alias_row['target_domain']; + } +} catch (PDOException $e) { + // On database error, return 404 + http_response_code(404); + exit; +} + $mta_sts = mailbox('get', 'mta_sts', $domain); if (count($mta_sts) == 0 || diff --git a/docker-compose.yml b/docker-compose.yml index fccd9ee4e..f09afca2a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -465,7 +465,7 @@ services: condition: service_started unbound-mailcow: condition: service_healthy - image: ghcr.io/mailcow/acme:1.94 + image: ghcr.io/mailcow/acme:1.95 dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: