From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 663071FF0FC for ; Tue, 27 Oct 2026 10:31:30 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 3670F21654; Tue, 27 Oct 2026 10:31:30 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=straightec.de; s=dkim; t=1793008739; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=qYVWX8QWDr+UraaK8UMosP6TJsjRTCg6EFrVHiAh1H8=; b=kk/7hfZw2alRExisXbjnlQvzbVUnru+5auizKQ3fcZknLv/d8GQR/K7W2qMXqvrJAabXtE qvmWbV879YQZ/kWhjHDorVbNUZBtas7/Z2DAJfW/6QGHs3b/FYN0Ypy0lHnGFO0dWfcyrh iSFyFV6/c3zQxrhHMWwHQVZTAJ0IdVZTV5prpthTRyWVgfVNGgLGtkOYeyKc5JNhyoryRx UOf314LX4NAc23Q9Pl1GN4x7K6DCtfRoWah/XDahtPssOEEz7PFMJJ/80kgolFJa76kuCc PLj5CH9R6gXmbRehIUm5Bed4U0mhRO6+wCgi+B4/wNMw1m+t+ttMP56TzBDmRQ== From: Carsten Haerle To: pmg-devel@lists.proxmox.com Subject: [PATCH pmg-api 4/6] fix #8231: config: resolve relay and transport host names for mynetworks Date: Mon, 26 Oct 2026 10:58:49 +0100 Message-ID: <20261026095852.3243241-5-carsten.haerle@straightec.de> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20261026095852.3243241-1-carsten.haerle@straightec.de> References: <20261026095852.3243241-1-carsten.haerle@straightec.de> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-SPAM-LEVEL: Spam detection results: 0 DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record X-MailFrom: carsten.haerle@straightec.de X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation Message-ID-Hash: UD2AQVT7KBNGGJ7H7IZRTS2VJBXC2HC7 X-Message-ID-Hash: UD2AQVT7KBNGGJ7H7IZRTS2VJBXC2HC7 X-Mailman-Approved-At: Tue, 27 Oct 2026 10:31:21 +0100 CC: Carsten Haerle X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Mail Gateway development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The default relay and the transport hosts are added to mynetworks, as they are the mail servers that send outgoing mail through the internal port. This only happened if they were configured as IP address. For a host name nothing was added, so the same mail server was trusted or not depending on whether it was entered by address or by name. Resolve host names when the configuration is generated and add all their addresses. If the address of such a host changes, this takes effect with the next regeneration (configuration change, sync, boot). If a name cannot be resolved, it is skipped with a warning, which is the same as before. As the template variables are computed for every rendered template, resolved names are cached for 60 seconds, so that a config sync looks up (and warns) once instead of once per template. Note that this extends the trusted networks on installations that use host names for the default relay or transports: these mail servers can now relay through the internal port, as they could if configured by address. The Networks list shows the host name the entry was resolved from. Signed-off-by: Carsten Haerle --- src/PMG/API2/MyNetworks.pm | 3 ++ src/PMG/Config.pm | 65 +++++++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/PMG/API2/MyNetworks.pm b/src/PMG/API2/MyNetworks.pm index 30bf448..bfb6183 100644 --- a/src/PMG/API2/MyNetworks.pm +++ b/src/PMG/API2/MyNetworks.pm @@ -81,6 +81,9 @@ __PACKAGE__->register_method({ if (my $domains =3D $origin->{transport}) { push @why, "transport for " . join(', ', $domains->@*) . "= (Transports)"; } + if (my $names =3D $origin->{names}) { + push @why, "resolved from '" . join("', '", sort keys $nam= es->%*) . "'"; + } =20 my $entry =3D { cidr =3D> $cidr, diff --git a/src/PMG/Config.pm b/src/PMG/Config.pm index e01d4d7..e579334 100644 --- a/src/PMG/Config.pm +++ b/src/PMG/Config.pm @@ -913,6 +913,7 @@ use Template; =20 use PVE::SafeSyslog; use PVE::Tools qw($IPV4RE $IPV6RE); +use Socket qw(AF_INET6 SOCK_STREAM); use PVE::INotify; use PVE::JSONSchema; =20 @@ -1555,8 +1556,51 @@ sub get_host_dns_info { return $dnsinfo; } =20 +# The template variables are computed for every rendered template, so cach= e +# resolved host names for a short time to look up (and warn) once per run. +my $resolved_hosts =3D {}; + +# Returns the networks to trust for a relay or transport host: the address= itself +# or all addresses of a host name. Host names are resolved now, so a chang= ed address +# only takes effect with the next regeneration of the configuration. +sub mynetworks_for_host { + my ($host) =3D @_; + + return ("$host/32") if $host =3D~ m/^$IPV4RE$/; + return ("[$1]/128") if $host =3D~ m/^(?:ipv6:)?($IPV6RE)$/i; + + my $cached =3D $resolved_hosts->{$host}; + return $cached->{cidrs}->@* if $cached && time() - $cached->{time} < 6= 0; + + my $cidrs =3D {}; + $resolved_hosts->{$host} =3D { time =3D> time(), cidrs =3D> [] }; + + my @addrinfo =3D eval { PVE::Tools::getaddrinfo_all($host, socktype = =3D> SOCK_STREAM) }; + if (my $err =3D $@) { + warn "not adding '$host' to trusted networks - $err"; + return (); + } + + for my $ai (@addrinfo) { + my ($family, undef, $addr) =3D PVE::Tools::unpack_sockaddr_in46($a= i->{addr}); + my $ip =3D Socket::inet_ntop($family, $addr); + if ($family !=3D AF_INET6) { + $cidrs->{"$ip/32"} =3D 1; + } elsif ($ip =3D~ m/^::ffff:($IPV4RE)$/i) { + $cidrs->{"$1/32"} =3D 1; + } else { + $cidrs->{"[$ip]/128"} =3D 1; + } + } + + $resolved_hosts->{$host}->{cidrs} =3D [sort keys %$cidrs]; + + return $resolved_hosts->{$host}->{cidrs}->@*; +} + # Networks trusted automatically, in addition to loopback and the configur= ed -# networks. Returns a hash: cidr =3D> { local =3D> 1, relay =3D> 1, transp= ort =3D> [domains] } +# networks. Returns a hash: cidr =3D> { local =3D> 1, relay =3D> 1, transp= ort =3D> [domains], +# names =3D> { host name =3D> 1 } }, 'names' only for hosts configured by = host name. sub get_automatic_mynetworks { my ($self, $int_ip) =3D @_; =20 @@ -1565,13 +1609,11 @@ sub get_automatic_mynetworks { if (my $tmap =3D PVE::INotify::read_file('transport')) { foreach my $domain (sort keys %$tmap) { my $host =3D $tmap->{$domain}->{host}; - my $cidr; - if ($host =3D~ m/^$IPV4RE$/) { - $cidr =3D "$host/32"; - } elsif ($host =3D~ m/^(?:ipv6:)?($IPV6RE)$/i) { - $cidr =3D "[$1]/128"; + my $is_name =3D $host !~ m/^(?:$IPV4RE|(?:ipv6:)?$IPV6RE)$/i; + for my $cidr (mynetworks_for_host($host)) { + push $res->{$cidr}->{transport}->@*, $domain; + $res->{$cidr}->{names}->{$host} =3D 1 if $is_name; } - push $res->{$cidr}->{transport}->@*, $domain if defined($cidr)= ; } } =20 @@ -1592,12 +1634,11 @@ sub get_automatic_mynetworks { $res->{$cidr}->{local} =3D 1; } =20 - # host names are not resolved, they are not trusted automatically if (my $relay =3D $self->get('mail', 'relay')) { - if ($relay =3D~ m/^$IPV4RE$/) { - $res->{"$relay/32"}->{relay} =3D 1; - } elsif ($relay =3D~ m/^$IPV6RE$/) { - $res->{"[$relay]/128"}->{relay} =3D 1; + my $is_name =3D $relay !~ m/^(?:$IPV4RE|$IPV6RE)$/; + for my $cidr (mynetworks_for_host($relay)) { + $res->{$cidr}->{relay} =3D 1; + $res->{$cidr}->{names}->{$relay} =3D 1 if $is_name; } } =20 --=20 2.53.0