From 3029363bc87e109ee8f9d4794b213fc6294934cf Mon Sep 17 00:00:00 2001
From: Ryan Sundberg <ryan@arctype.co>
Date: Tue, 23 Feb 2021 19:57:02 -0800
Subject: [PATCH] libunbound: add interface for reading authority response
 sections

Adds support to libunbound for accessing the AUTHORITY section of a DNS response.
This allows the client to read NS records from non-authoritative, non-recursive nameservers (such as a TLD.)
---
 libunbound/libworker.c | 55 ++++++++++++++++++++++++++++++++++++++++++
 libunbound/unbound.h   | 17 +++++++++++++
 2 files changed, 72 insertions(+)

diff --git a/libunbound/libworker.c b/libunbound/libworker.c
index 7f46df38..8ddb4d06 100644
--- a/libunbound/libworker.c
+++ b/libunbound/libworker.c
@@ -493,6 +493,57 @@ fill_res(struct ub_result* res, struct ub_packed_rrset_key* answer,
 	return 1;
 }
 
+/** fill authority_data into result */
+static int
+fill_res_authority(struct ub_result* res, struct ub_packed_rrset_key* answer,
+	struct query_info* rq, struct reply_info* rep)
+{
+	size_t i;
+	struct packed_rrset_data* authority_data;
+	res->authority_ttl = 0;
+	if(!answer) {
+		if(rep->rrset_count != 0)
+			res->authority_ttl = (int)rep->ttl;
+		res->authority_data = (char**)calloc(1, sizeof(char*));
+		res->authority_len = (int*)calloc(1, sizeof(int));
+		return (res->authority_data && res->authority_len);
+	}
+	authority_data = (struct packed_rrset_data*)answer->entry.data;
+	if(query_dname_compare(rq->qname, answer->rk.dname) != 0) {
+		if(!fill_canon(res, answer->rk.dname))
+			return 0; /* out of memory */
+	} else	res->canonname = NULL;
+	res->authority_data = (char**)calloc(authority_data->count+1, sizeof(char*));
+	res->authority_len = (int*)calloc(authority_data->count+1, sizeof(int));
+	if(!res->authority_data || !res->authority_len)
+		return 0; /* out of memory */
+	for(i=0; i<authority_data->count; i++) {
+		/* remove rdauthority_length from rauthority_data */
+		res->authority_len[i] = (int)(authority_data->rr_len[i] - 2);
+		res->authority_data[i] = memdup(authority_data->rr_data[i]+2, (size_t)res->authority_len[i]);
+		if(!res->authority_data[i])
+			return 0; /* out of memory */
+	}
+	/* authority_ttl for positive answers, from CNAME and answer RRs */
+	if(authority_data->count != 0) {
+		size_t j;
+		res->authority_ttl = (int)authority_data->ttl;
+		for(j=0; j<rep->ns_numrrsets; j++) {
+			struct packed_rrset_data* d =
+				(struct packed_rrset_data*)rep->rrsets[rep->an_numrrsets + j]->
+				entry.data;
+			if((int)d->ttl < res->authority_ttl)
+				res->authority_ttl = (int)d->ttl;
+		}
+	}
+	/* authority_ttl for negative answers */
+	if(authority_data->count == 0 && rep->rrset_count != 0)
+		res->authority_ttl = (int)rep->ttl;
+	res->authority_data[authority_data->count] = NULL;
+	res->authority_len[authority_data->count] = 0;
+	return 1;
+}
+
 /** fill result from parsed message, on error fills servfail */
 void
 libworker_enter_result(struct ub_result* res, sldns_buffer* buf,
@@ -506,6 +557,10 @@ libworker_enter_result(struct ub_result* res, sldns_buffer* buf,
 		log_err("cannot parse buf");
 		return; /* error parsing buf, or out of memory */
 	}
+	struct ub_packed_rrset_key *authority_rrset = reply_find_rrset_section_ns(rep, rq.qname, rq.qname_len, rq.qtype, rq.qclass);
+	if(!fill_res_authority(res, authority_rrset, &rq, rep))
+		return; /* out of memory */
+
 	if(!fill_res(res, reply_find_answer_rrset(&rq, rep), 
 		reply_find_final_cname_target(&rq, rep), &rq, rep))
 		return; /* out of memory */
diff --git a/libunbound/unbound.h b/libunbound/unbound.h
index 945c17a8..067f0442 100644
--- a/libunbound/unbound.h
+++ b/libunbound/unbound.h
@@ -136,9 +136,20 @@ struct ub_result {
 	 */
 	char** data;
 
+	/**
+	 * a list of network order DNS rdata items, terminated with a
+	 * NULL pointer, so that authority_data[0] is the first result entry,
+	 * data[1] the second, and the last entry is NULL.
+	 * If there was no data, authority_data[0] is NULL.
+	 */
+	char** authority_data;
+
 	/** the length in bytes of the data items, len[i] for data[i] */
 	int* len;
 
+	/** the length in bytes of the authority_data items, authority_len[i] for autority_data[i] */
+	int* authority_len;
+
 	/** 
 	 * canonical name for the result (the final cname). 
 	 * zero terminated string.
@@ -215,6 +226,12 @@ struct ub_result {
 	 * you also cannot trust this value.
 	 */
 	int ttl;
+
+	/**
+	 * TTL for the authority results, in seconds.  If the security is bogus, then
+	 * you also cannot trust this value.
+	 */
+	int authority_ttl;
 };
 
 /**
-- 
2.30.0

