From 8daa2dfbfdc6713b81fa4310fcc9d22e6ad4b284 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Tue, 28 Jul 2026 13:30:11 +0200 Subject: [PATCH] smtp: deliver IPv4 address literals directly --- src/agents/smtp/smtpc.c | 51 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/agents/smtp/smtpc.c b/src/agents/smtp/smtpc.c index f5016c5..37185a6 100755 --- a/src/agents/smtp/smtpc.c +++ b/src/agents/smtp/smtpc.c @@ -499,6 +499,8 @@ Connection *LookupRemoteMX(RecipStruct *Recipient, char *hostname, XplDns_MxLookup *mx = NULL; XplDns_IpList *list = NULL; Connection *Result=NULL; + struct in_addr literalAddress; + BOOL addressLiteral = FALSE; if (hostname == NULL || hostnameSize == 0 || canonicalHostname == NULL || canonicalHostnameSize == 0 || nextHopDomain == NULL || @@ -533,7 +535,13 @@ Connection *LookupRemoteMX(RecipStruct *Recipient, char *hostname, *(Recipient->localPart-1) = '@'; return Result; } - // FIXME: assumes our DNS routines will accept IP address. + if (inet_pton(AF_INET, Host, &literalAddress) != 1) { + Recipient->Result = DELIVER_BOGUS_NAME; + *end = ']'; + *(Recipient->localPart-1) = '@'; + return Result; + } + addressLiteral = TRUE; *end = ']'; } else { size_t hostLength = strlen(Recipient->SortField); @@ -549,6 +557,47 @@ Connection *LookupRemoteMX(RecipStruct *Recipient, char *hostname, /* make sure to reset the SortField as it was previously */ *(Recipient->localPart-1) = '@'; + /* + * RFC 5321 address literals bypass MX and address lookups. Besides + * avoiding a meaningless DNS query, this preserves the literal as the + * next-hop identity and prevents DNSSEC state from being inferred for it. + */ + if (addressLiteral) { + int status; + + snprintf(hostname, hostnameSize, "%s", Host); + snprintf(canonicalHostname, canonicalHostnameSize, "%s", Host); + snprintf(nextHopDomain, nextHopDomainSize, "%s", Host); + Result = ConnAlloc(TRUE); + if (Result == NULL) { + Recipient->Result = DELIVER_INTERNAL_ERROR; + goto finish; + } + Result->socketAddress.sin_addr = literalAddress; + Result->socketAddress.sin_family = AF_INET; + Result->socketAddress.sin_port = htons(25); + status = ConnConnect(Result, NULL, 0, NULL); + if (status <= 0) { + switch (errno) { + case ETIMEDOUT: + Recipient->Result = DELIVER_TIMEOUT; + break; + case ECONNREFUSED: + Recipient->Result = DELIVER_REFUSED; + break; + case ENETUNREACH: + Recipient->Result = DELIVER_UNREACHABLE; + break; + default: + Recipient->Result = DELIVER_TRY_LATER; + break; + } + ConnFree(Result); + Result = NULL; + } + goto finish; + } + /* DNS and the normalized SMTP envelope both use the same IDNA2008 * A-label. This also validates ASCII domains before lookup. */ if (!BongoDomainToASCII(Host, asciiHost, sizeof(asciiHost))) {