Hi

Setting Null MX is a way for domainowners to indicate that the domain
does not accept mail. Currently a Null MX causes a tempfail and the
mail will be queued and tried to resubmitted till a timeout. With the
attached patch a Null MX causes a permfail. This way the sender will
directly get a bounce with the message "Domain does not accept mail".

Because some domains set the MX record to "localhost." to get a similar
efect the secound patch ignores "localhost." MX entries and handles a MX
containing only "localhost." records like a Null MX.

Philipp
From 2970019967e967d98ec30f86549f38788bff6081 Mon Sep 17 00:00:00 2001
From: Philipp <philipp+open...@bureaucracy.de>
Date: Sun, 2 Jul 2023 01:27:35 +0200
Subject: [PATCH 1/2] implement rfc 7505 (Null MX)

Null MX is to indicate that a domain does not accept mail.
---
 usr.sbin/smtpd/dns.c   | 28 +++++++++++++++++++++++-----
 usr.sbin/smtpd/mta.c   |  4 ++++
 usr.sbin/smtpd/smtpd.h |  2 ++
 3 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/usr.sbin/smtpd/dns.c b/usr.sbin/smtpd/dns.c
index 4cf5d23d1d1..d510fa2b5aa 100644
--- a/usr.sbin/smtpd/dns.c
+++ b/usr.sbin/smtpd/dns.c
@@ -44,6 +44,7 @@ struct dns_session {
 	size_t			 mxfound;
 	int			 error;
 	int			 refcount;
+	int			 nullmx;
 };
 
 static void dns_lookup_host(struct dns_session *, const char *, int);
@@ -195,7 +196,7 @@ dns_dispatch_host(struct asr_result *ar, void *arg)
 
 	s = lookup->session;
 
-	for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) {
+	for (ai = ar->ar_addrinfo; s->nullmx == 0 && ai; ai = ai->ai_next) {
 		s->mxfound++;
 		m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1);
 		m_add_id(s->p, s->reqid);
@@ -215,10 +216,12 @@ dns_dispatch_host(struct asr_result *ar, void *arg)
 	if (--s->refcount)
 		return;
 
-	m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1);
-	m_add_id(s->p, s->reqid);
-	m_add_int(s->p, s->mxfound ? DNS_OK : DNS_ENOTFOUND);
-	m_close(s->p);
+	if (s->nullmx == 0) {
+		m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1);
+		m_add_id(s->p, s->reqid);
+		m_add_int(s->p, s->mxfound ? DNS_OK : DNS_ENOTFOUND);
+		m_close(s->p);
+	}
 	free(s);
 }
 
@@ -259,6 +262,21 @@ dns_dispatch_mx(struct asr_result *ar, void *arg)
 		unpack_rr(&pack, &rr);
 		if (rr.rr_type != T_MX)
 			continue;
+
+		/* Null MX */
+		if (rr.rr.mx.preference == 0 && rr.rr.mx.exchange[0] == 0) {
+			m_create(s->p,  IMSG_MTA_DNS_HOST_END, 0, 0, -1);
+			m_add_id(s->p, s->reqid);
+			m_add_int(s->p, DNS_NULLMX);
+			m_close(s->p);
+			if (found == 0)
+				free(s);
+			else
+				s->nullmx = 1;
+			found++;
+			break;
+		}
+
 		print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
 		buf[strlen(buf) - 1] = '\0';
 		dns_lookup_host(s, buf, rr.rr.mx.preference);
diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c
index c0d0fc02931..25e158b68a8 100644
--- a/usr.sbin/smtpd/mta.c
+++ b/usr.sbin/smtpd/mta.c
@@ -1088,6 +1088,10 @@ mta_on_mx(void *tag, void *arg, void *data)
 		else
 			relay->failstr = "No MX found for domain";
 		break;
+	case DNS_NULLMX:
+		relay->fail = IMSG_MTA_DELIVERY_PERMFAIL;
+		relay->failstr = "Domain does not accept mail";
+		break;
 	default:
 		fatalx("bad DNS lookup error code");
 		break;
diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h
index 6781286928d..9f4732d1c27 100644
--- a/usr.sbin/smtpd/smtpd.h
+++ b/usr.sbin/smtpd/smtpd.h
@@ -1026,6 +1026,8 @@ enum dns_error {
 	DNS_EINVAL,
 	DNS_ENONAME,
 	DNS_ENOTFOUND,
+	/* rfc 7505 */
+	DNS_NULLMX,
 };
 
 enum lka_resp_status {
-- 
2.39.2

From ace283bbedc1e7594c850e0ae6f3b6d9d456ba77 Mon Sep 17 00:00:00 2001
From: Philipp <philipp+open...@bureaucracy.de>
Date: Sun, 2 Jul 2023 01:50:20 +0200
Subject: [PATCH 2/2] filter localhost MX

A localhost MX only cause a loop. Also handle only a localhost
like a Null MX.
---
 usr.sbin/smtpd/dns.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/usr.sbin/smtpd/dns.c b/usr.sbin/smtpd/dns.c
index d510fa2b5aa..0df221f3755 100644
--- a/usr.sbin/smtpd/dns.c
+++ b/usr.sbin/smtpd/dns.c
@@ -235,6 +235,7 @@ dns_dispatch_mx(struct asr_result *ar, void *arg)
 	struct dns_rr		 rr;
 	char			 buf[512];
 	size_t			 found;
+	int			 localhost;
 
 	if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA &&
 	    ar->ar_h_errno != NOTIMP) {
@@ -258,6 +259,7 @@ dns_dispatch_mx(struct asr_result *ar, void *arg)
 	unpack_query(&pack, &q);
 
 	found = 0;
+	localhost = 0;
 	for (; h.ancount; h.ancount--) {
 		unpack_rr(&pack, &rr);
 		if (rr.rr_type != T_MX)
@@ -279,11 +281,27 @@ dns_dispatch_mx(struct asr_result *ar, void *arg)
 
 		print_dname(rr.rr.mx.exchange, buf, sizeof(buf));
 		buf[strlen(buf) - 1] = '\0';
+		if (strcasecmp("localhost", buf) == 0) {
+			log_warnx("ignore localhost MX-entry for domain <%s>",
+			    s->name);
+			localhost++;
+			continue;
+		}
 		dns_lookup_host(s, buf, rr.rr.mx.preference);
 		found++;
 	}
 	free(ar->ar_data);
 
+	/* handle only localhost as null mx */
+	if (found == 0 && localhost > 0) {
+		m_create(s->p,  IMSG_MTA_DNS_HOST_END, 0, 0, -1);
+		m_add_id(s->p, s->reqid);
+		m_add_int(s->p, DNS_NULLMX);
+		m_close(s->p);
+		free(s);
+		return;
+	}
+
 	/* fallback to host if no MX is found. */
 	if (found == 0)
 		dns_lookup_host(s, s->name, 0);
-- 
2.39.2

Reply via email to