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