This is an automated email from the ASF dual-hosted git repository. chibenwa pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-jspf.git
commit 62f174129545452d60ed88735c83a2e22645d2db Author: Quan Tran <[email protected]> AuthorDate: Thu Nov 5 10:13:22 2026 +0700 [FIX] DNSServiceXBillImpl.getRecords: temperror for an extended rcode An answer whose rcode has extended bits, in its OPT record (RFC 6891 6.1.3), as BADVERS (16), is an error, but dnsjava's Lookup reads only the 4 bits of the header: getRecords took such an answer for NOERROR, so no record, or the records it holds. The synchronous executor, the default one, and the staged one then gave none or the result of those records, where the asynchronous executor gives temperror (RFC 7208 4.4: "some other error (RCODE other than 0 or 3)"). Check the answers of getRecords as those of getRecordsAsync: an rcode other than NOERROR and NXDOMAIN, extended bits included, is a DNS error, a TimeoutException. --- .../james/jspf/impl/DNSServiceXBillImpl.java | 34 ++++++++++++++++------ .../apache/james/jspf/DNSServiceXBillImplTest.java | 14 +++++++++ .../org/apache/james/jspf/SPFExecutorBaseTest.java | 11 +++++++ .../james/jspf/helpers/AnsweringResolver.java | 8 +++-- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/resolver/src/main/java/org/apache/james/jspf/impl/DNSServiceXBillImpl.java b/resolver/src/main/java/org/apache/james/jspf/impl/DNSServiceXBillImpl.java index 76d2c20..2090467 100644 --- a/resolver/src/main/java/org/apache/james/jspf/impl/DNSServiceXBillImpl.java +++ b/resolver/src/main/java/org/apache/james/jspf/impl/DNSServiceXBillImpl.java @@ -80,7 +80,8 @@ public class DNSServiceXBillImpl implements DNSService { * an error rcode, as YXRRSET), or of another class. * It also fails, as Lookup does, an answer with an rcode other than NOERROR and NXDOMAIN (with its * extended bits, RFC 6891 6.1.3), or for another question: LookupSession would read an NXRRSET - * answer as no record. + * answer as no record. Its {@link #send(Message)}, of the synchronous Lookup, is not bounded but + * checks the answers the same way: Lookup reads only the 4 bits of the rcode in the header. */ private static final class BoundedResolver implements Resolver { /** @@ -94,17 +95,26 @@ public class DNSServiceXBillImpl implements DNSService { } /** - * @return the answer, or a failed lookup when it is a DNS error (RFC 7208 4.4) + * @return why the answer is a DNS error (RFC 7208 4.4), or null when it is none */ - private static CompletionStage<Message> checked(Message query, Message answer) { + private static String errorOf(Message query, Message answer) { int rcode = answer.getRcode(); if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) { - return CompletableFuture.failedFuture(new LookupFailedException( - "Answer " + Rcode.string(rcode) + " for " + query.getQuestion())); + return "Answer " + Rcode.string(rcode) + " for " + query.getQuestion(); } if (!query.getQuestion().equals(answer.getQuestion())) { - return CompletableFuture.failedFuture(new LookupFailedException( - "Answer for another question than " + query.getQuestion())); + return "Answer for another question than " + query.getQuestion(); + } + return null; + } + + /** + * @return the answer, or a failed lookup when it is a DNS error (RFC 7208 4.4) + */ + private static CompletionStage<Message> checked(Message query, Message answer) { + String error = errorOf(query, answer); + if (error != null) { + return CompletableFuture.failedFuture(new LookupFailedException(error)); } return CompletableFuture.completedFuture(answer); } @@ -167,9 +177,15 @@ public class DNSServiceXBillImpl implements DNSService { /** * @see org.xbill.DNS.Resolver#send(Message) + * @throws IOException also when the answer is a DNS error: Lookup then reports TRY_AGAIN */ public Message send(Message query) throws IOException { - return resolver.send(query); + Message answer = resolver.send(query); + String error = errorOf(query, answer); + if (error != null) { + throw new IOException(error); + } + return answer; } /** @@ -351,7 +367,7 @@ public class DNSServiceXBillImpl implements DNSService { // RFC 7208 4.3: a fully qualified name, not looked up under the search path of the host Name name = Name.fromString(request.getHostname(), Name.root); Lookup query = new Lookup(name, dnsJavaType); - query.setResolver(resolver); + query.setResolver(new BoundedResolver(resolver)); Record[] rr = run(query, name, dnsJavaType); int queryResult = query.getResult(); diff --git a/resolver/src/test/java/org/apache/james/jspf/DNSServiceXBillImplTest.java b/resolver/src/test/java/org/apache/james/jspf/DNSServiceXBillImplTest.java index 0bf0980..0f0e1ab 100644 --- a/resolver/src/test/java/org/apache/james/jspf/DNSServiceXBillImplTest.java +++ b/resolver/src/test/java/org/apache/james/jspf/DNSServiceXBillImplTest.java @@ -36,6 +36,7 @@ import org.xbill.DNS.Flags; import org.xbill.DNS.Lookup; import org.xbill.DNS.Message; import org.xbill.DNS.Name; +import org.xbill.DNS.Rcode; import org.xbill.DNS.Record; import org.xbill.DNS.SPFRecord; import org.xbill.DNS.Section; @@ -128,6 +129,19 @@ public class DNSServiceXBillImplTest { } } + @Test(timeout = 10000) + public void getRecordsShouldReportADnsErrorForAnExtendedErrorRcode() throws Exception { + // BADVERS, of which dnsjava's Lookup reads only the header bits: NOERROR + DNSServiceXBillImpl dns = new DNSServiceXBillImpl(AnsweringResolver.withRcode(Rcode.BADVERS)); + + try { + dns.getRecords(new DNSRequest("badvers.example.com", DNSRequest.TXT)); + fail("the lookup should fail"); + } catch (TimeoutException e) { + // an error rcode: a DNS error (RFC 7208 4.4) + } + } + @Test(timeout = 10000) public void getRecordsAsyncShouldReportADnsErrorForAnAnswerDnsjavaCannotProcess() throws Exception { // opcode UPDATE, and the answer "example.com CNAME" of RDLENGTH 0: dnsjava's LookupSession fails with diff --git a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java index 45626d4..0da1c37 100644 --- a/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java +++ b/resolver/src/test/java/org/apache/james/jspf/SPFExecutorBaseTest.java @@ -741,6 +741,17 @@ public abstract class SPFExecutorBaseTest { assertEquals(SPFErrorConstants.TEMP_ERROR_CONV, result.getResult()); } + @Test(timeout = 10000) + public void shouldReturnTempErrorWhenTheLookupGetsAnExtendedErrorRcode() { + // BADVERS (16): 0 in the header, the upper bits in the OPT record (RFC 6891 6.1.3) + SPF spf = createCustomSPF(new DNSServiceXBillImpl(AnsweringResolver.withRcode(Rcode.BADVERS))); + + SPFResult result = spf.checkSPF("192.0.2.10", "[email protected]", "badvers.spfexecutor.fake"); + + // RFC 7208 4.4: "some other error (RCODE other than 0 or 3)" + assertEquals(SPFErrorConstants.TEMP_ERROR_CONV, result.getResult()); + } + @Test(timeout = 10000) public void shouldReturnTempErrorWhenTheLookupGetsANetworkError() { SPF spf = createCustomSPF(new DNSServiceXBillImpl(AnsweringResolver.unreachable())); diff --git a/resolver/src/test/java/org/apache/james/jspf/helpers/AnsweringResolver.java b/resolver/src/test/java/org/apache/james/jspf/helpers/AnsweringResolver.java index 7d05fca..874a339 100644 --- a/resolver/src/test/java/org/apache/james/jspf/helpers/AnsweringResolver.java +++ b/resolver/src/test/java/org/apache/james/jspf/helpers/AnsweringResolver.java @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.xbill.DNS.EDNSOption; import org.xbill.DNS.Flags; import org.xbill.DNS.Message; +import org.xbill.DNS.OPTRecord; import org.xbill.DNS.Resolver; import org.xbill.DNS.Section; import org.xbill.DNS.TSIG; @@ -40,13 +41,16 @@ import org.xbill.DNS.TSIG; */ public final class AnsweringResolver implements Resolver { /** - * @param rcode an rcode + * @param rcode an rcode, of the header, or with extended bits, of an OPT record (RFC 6891 6.1.3) * @return a resolver answering every query with that rcode and no record */ public static AnsweringResolver withRcode(int rcode) { Message answer = new Message(); answer.getHeader().setFlag(Flags.QR); - answer.getHeader().setRcode(rcode); + answer.getHeader().setRcode(rcode & 0xF); + if (rcode > 0xF) { + answer.addRecord(new OPTRecord(1232, rcode >> 4, 0), Section.ADDITIONAL); + } return new AnsweringResolver(answer.toWire()); } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
