From 05dd8ff90a3eb38a5bb08638a4dc2a31c0344de3 Mon Sep 17 00:00:00 2001
From: Lubos Slovak <lubos.slovak@nic.cz>
Date: Thu, 23 Sep 2010 15:07:52 +0200
Subject: [PATCH] Rewrite of the answering algorithm (ns_answer()).

There are probably multiple bugs, needs a lot of testing and
  revision of the algorithm.
However, it should mirror the current state of the flowchart
  (230910-my).
---
 samples/example.com.zone |   2 +
 src/server/name-server.c | 238 +++++++++++++++++++--------------------
 2 files changed, 119 insertions(+), 121 deletions(-)

diff --git a/samples/example.com.zone b/samples/example.com.zone
index 9eb06114e..bb4ccabc2 100644
--- a/samples/example.com.zone
+++ b/samples/example.com.zone
@@ -50,6 +50,8 @@ sub4	NS	ns.example.com.
 
 d.example.com.	CNAME	non-existing.example.com.
 e.example.com.	DNAME	bogus25.com.
+f.example.com.	CNAME	e.example.com.
+g.example.com.	CNAME	www.bogus25.com.
 
 f.g	A	10.0.0.20
 h.i.j.k	A	10.0.0.21
diff --git a/src/server/name-server.c b/src/server/name-server.c
index b26303bad..d2f75ddb2 100644
--- a/src/server/name-server.c
+++ b/src/server/name-server.c
@@ -201,38 +201,11 @@ size_t ns_rrset_size( ldns_rr_list *rrset )
 	return size;
 }
 
-/*----------------------------------------------------------------------------*/
-
-//const zn_node *ns_find_node_in_zone( const zdb_zone *zone, ldns_rdf **qname,
-//									 uint *labels )
-//{
-//	const zn_node *node = NULL;
-//	// search for the name and strip labels until nothing left
-//	while ((*labels) > 0 &&
-//		   (node = zdb_find_name_in_zone(zone, *qname)) == NULL) {
-//		debug_ns("Name %s not found, stripping leftmost label.\n",
-//				 ldns_rdf2str(*qname));
-//		/* TODO: optimize!!!
-//		 *       1) do not copy the name!
-//		 *       2) implementation of ldns_dname_left_chop() is inefficient
-//		 */
-//		ldns_rdf *new_qname = ldns_dname_left_chop(*qname);
-//		ldns_rdf_deep_free(*qname);
-//		*qname = new_qname;
-//		--(*labels);
-//		assert(*qname != NULL || (*labels) == 0);
-//	}
-//	assert((ldns_dname_label_count(*qname) == 0 && (*labels) == 0)
-//		   || (ldns_dname_label_count(*qname) > 0 && (*labels) > 0));
-
-//	return node;
-//}
-
 /*----------------------------------------------------------------------------*/
 /*!
  * @todo Check return values from push functions!
  */
-void ns_follow_cname( const zn_node **node, const ldns_rdf **qname,
+void ns_follow_cname( const zn_node **node, ldns_rdf **qname,
 					  ldns_pkt *pkt, ldns_pkt_section section,
 					  ldns_rr_list *copied_rrs )
 {
@@ -268,7 +241,7 @@ void ns_follow_cname( const zn_node **node, const ldns_rdf **qname,
 
 		(*node) = zn_get_ref_cname(*node);
 		// save the new name which should be used for replacing wildcard
-		*qname = ldns_rr_rdf(cname_rr, 0);
+		*qname = ldns_rdf_clone(ldns_rr_rdf(cname_rr, 0));
 		assert(ldns_rdf_get_type(*qname) == LDNS_RDF_TYPE_DNAME);
 	} while (*node != NULL && zn_has_cname(*node));
 }
@@ -357,7 +330,7 @@ void ns_put_additional( const zn_node *node, ldns_pkt *response,
 	int count = ldns_pkt_ancount(response);
 	for (int i = 0; i < count; ++i) {
 		ldns_rr *rr = ldns_rr_list_rr(ldns_pkt_answer(response), i);
-		const ldns_rdf *name;
+		ldns_rdf *name;
 
 		switch (ldns_rr_get_type(rr)) {
 		case LDNS_RR_TYPE_MX:
@@ -512,19 +485,6 @@ void ns_answer_from_node( const zn_node *node, const zdb_zone *zone,
 						  ldns_pkt *response, ldns_rr_list *copied_rrs )
 {
 	debug_ns("Putting answers from found node to the response...\n");
-	if (zn_has_cname(node) > 0) {
-		// resolve the cname chain and copy all CNAME records to the answer
-		ns_follow_cname(&node, &qname, response, LDNS_SECTION_ANSWER,
-						copied_rrs);
-		// node is now set to the canonical name node (if found)
-		if (node == NULL) {
-			// TODO: add SOA
-			//ns_put_authority_soa(zone, response);
-			//ldns_pkt_set_rcode(response, LDNS_RCODE_NXDOMAIN);
-			return;
-		}
-	}
-	//assert(ldns_dname_compare(node->owner, qname) == 0);
 	ns_put_answer(node, qname, qtype, response, copied_rrs);
 	if (ldns_pkt_ancount(response) == 0) {	// if NODATA response, put SOA
 		ns_put_authority_soa(zone, response);
@@ -620,12 +580,39 @@ void ns_process_dname( ldns_rr_list *dname_rrset, const ldns_rdf *qname,
 													copied_rrs);
 	ns_try_put_rrset(synth_cname, LDNS_SECTION_ANSWER, 1, response);
 	ldns_rr_list_free(synth_cname);
-	ldns_pkt_set_rcode(response, LDNS_RCODE_NOERROR);
 	// do not search for the name in new zone (out-of-bailiwick)
 }
 
 /*----------------------------------------------------------------------------*/
 
+const zn_node *ns_strip_and_find( const zdb_zone *zone, ldns_rdf **qname,
+								  uint *labels )
+{
+	const zn_node *node = NULL;
+	// search for the name and strip labels until nothing left
+	do {
+		debug_ns("Name %s not found, stripping leftmost label.\n",
+				 ldns_rdf2str(*qname));
+		/* TODO: optimize!!!
+		 *       1) do not copy the name!
+		 *       2) implementation of ldns_dname_left_chop() is inefficient
+		 */
+		ldns_rdf *new_qname = ldns_dname_left_chop(*qname);
+		ldns_rdf_deep_free(*qname);
+		*qname = new_qname;
+		--(*labels);
+		assert(*qname != NULL || (*labels) == 0);
+		node = zdb_find_name_in_zone(zone, *qname);
+	} while ((*labels) > 0 && node == NULL);
+
+	assert((ldns_dname_label_count(*qname) == 0 && (*labels) == 0)
+		   || (ldns_dname_label_count(*qname) > 0 && (*labels) > 0));
+
+	return node;
+}
+
+/*----------------------------------------------------------------------------*/
+
 void ns_answer( zdb_database *zdb, const ldns_rr *question, ldns_pkt *response,
 				ldns_rr_list *copied_rrs )
 {
@@ -648,92 +635,101 @@ void ns_answer( zdb_database *zdb, const ldns_rr *question, ldns_pkt *response,
 	debug_ns("Found zone for QNAME %s\n", ldns_rdf2str(zone->zone_name));
 	debug_ns("Size of response packet: %u\n", ldns_pkt_size(response));
 
-	// find proper zone node
-	uint labels = ldns_dname_label_count(qname);
-	uint labels_found = labels;
-	const zn_node *node;
+	//const zn_node *node = ns_find_node_in_zone(zone, &qname, &labels_found);
+	const zn_node *node = zdb_find_name_in_zone(zone, qname);
+	int cname = 0;
 
-	while (labels_found > 0 &&
-		   (node = zdb_find_name_in_zone(zone, qname)) == NULL) {
-search:
-		debug_ns("Name %s not found, stripping leftmost label.\n",
-				 ldns_rdf2str(qname));
-		/* TODO: optimize!!!
-		 *       1) do not copy the name!
-		 *       2) implementation of ldns_dname_left_chop() is inefficient
-		 */
-		ldns_rdf *new_qname = ldns_dname_left_chop(qname);
-		ldns_rdf_deep_free(qname);
-		qname = new_qname;
-		--labels_found;
-		assert(qname != NULL || labels_found == 0);
-	}
-	assert((ldns_dname_label_count(qname) == 0 && labels_found == 0)
-		   || (ldns_dname_label_count(qname) > 0 && labels_found > 0));
-
-	// if the name was not found in the zone something is wrong (SERVFAIL)
-	if (labels_found == 0) {
-		log_error("Name %s not found in zone %s! Returning SERVFAIL\n",
-				  ldns_rdf2str(ldns_rr_owner(question)),
-				  ldns_rdf2str(zone->zone_name));
-		ldns_pkt_set_rcode(response, LDNS_RCODE_SERVFAIL);
-		ldns_rdf_deep_free(qname);
-		return;
-	}
-	assert(node != NULL);
+	while (1) {
+		uint labels = ldns_dname_label_count(qname);
+		uint labels_found = labels;
 
-	// if the node is delegation point (no matter if whole QNAME was found)
-	if (zn_is_delegation_point(node)) {
-		ns_referral(node, response, copied_rrs);
-		debug_ns("Size of response packet: %u\n", ldns_pkt_size(response));
-		ldns_rdf_deep_free(qname);
-		return;
-	}
+		// whole QNAME not found
+		if (node == NULL) {
+			node = ns_strip_and_find(zone, &qname, &labels_found);
+
+			if (labels_found == 0) {
+				if (cname == 0) {
+					log_error("Name %s not found in zone %s! SERVFAIL.\n",
+							  ldns_rdf2str(ldns_rr_owner(question)),
+							  ldns_rdf2str(zone->zone_name));
+					ldns_pkt_set_rcode(response, LDNS_RCODE_SERVFAIL);
+					break;
+				} else {
+					ldns_pkt_set_rcode(response, LDNS_RCODE_NOERROR);
+					break;
+				}
+			}
+			assert(node != NULL);
+		}
 
-	if (labels_found == labels) {	// whole QNAME found
-		debug_ns("All labels matched.\n");
-		// if an empty non-terminal, continue the search
-		if (zn_is_empty(node) == 0) {
-			goto search;
+		// if the node is delegation point (no matter if whole QNAME was found)
+		if (zn_is_delegation_point(node)) {
+			ns_referral(node, response, copied_rrs);
+			debug_ns("Size of response packet: %u\n", ldns_pkt_size(response));
+			break;
 		}
-		ns_answer_from_node(node, zone, qname, ldns_rr_get_type(question),
-							response, copied_rrs);
-	} else {	// only part of QNAME found
-		debug_ns("Found node with name: %s (rest of QNAME: %s).\n",
-				 ldns_rdf2str(node->owner), ldns_rdf2str(qname));
-
-		// check if DNAME is present
-		ldns_rr_list *dname_rrset = NULL;
-		if ((dname_rrset = zn_find_rrset(node, LDNS_RR_TYPE_DNAME)) != NULL) {
-			ns_process_dname(dname_rrset, ldns_rr_owner(question), response,
-							 copied_rrs);
-		} else {
-			// try to find a wildcard child
-			ldns_rdf *wildcard = ldns_dname_new_frm_str("*");
-			if (ldns_dname_cat(wildcard, qname) != LDNS_STATUS_OK) {
-				log_error("Unknown error occured.\n");
-				ldns_pkt_set_rcode(response, LDNS_RCODE_SERVFAIL);
-				ldns_rdf_deep_free(wildcard);
-				ldns_rdf_deep_free(qname);
-				return;	// need some return value??
-			}
 
-			const zn_node *wildcard_node =
-					zdb_find_name_in_zone(zone, wildcard);
-			if (wildcard_node != NULL) {
-				debug_ns("Found wildcard node %s, answering.\n", ldns_rdf2str(
-						wildcard_node->owner));
-				ns_answer_from_node(
-					wildcard_node, zone, ldns_rr_owner(question),
-					ldns_rr_get_type(question), response, copied_rrs);
+		if (labels_found < labels) {
+			// check if DNAME is present
+			ldns_rr_list *dname_rrset = NULL;
+			if ((dname_rrset = zn_find_rrset(node, LDNS_RR_TYPE_DNAME))
+				!= NULL) {
+				ns_process_dname(dname_rrset, ldns_rr_owner(question), response,
+								 copied_rrs);
 			} else {
-				// return NXDOMAIN
-				ldns_pkt_set_rcode(response, LDNS_RCODE_NXDOMAIN);
+				debug_ns("Trying to find wildcard child of node %s.\n",
+						 ldns_rdf2str(qname));
+				// try to find a wildcard child
+				ldns_rdf *wildcard = ldns_dname_new_frm_str("*");
+				if (ldns_dname_cat(wildcard, qname) != LDNS_STATUS_OK) {
+					log_error("Unknown error occured.\n");
+					ldns_pkt_set_rcode(response, LDNS_RCODE_SERVFAIL);
+					ldns_rdf_deep_free(wildcard);
+					break;	// need some return value??
+				}
+
+				const zn_node *wildcard_node =
+						zdb_find_name_in_zone(zone, wildcard);
 				ldns_rdf_deep_free(wildcard);
+
+				debug_ns("Found node: %p\n", wildcard_node);
+
+				if (wildcard_node == NULL) {
+					if (cname == 0) {
+						// return NXDOMAIN
+						ldns_pkt_set_rcode(response, LDNS_RCODE_NXDOMAIN);
+					} else {
+						ldns_pkt_set_rcode(response, LDNS_RCODE_NOERROR);
+					}
+					break;
+				} else {
+					node = wildcard_node;
+					debug_ns("Node's owner: %s\n", ldns_rdf2str(node->owner));
+				}
+			}
+		}
+
+		if (zn_has_cname(node)) {
+			ldns_rdf *act_name = qname;
+			ns_follow_cname(&node, &act_name, response, LDNS_SECTION_ANSWER,
+							copied_rrs);
+			if (act_name != qname) {
 				ldns_rdf_deep_free(qname);
-				return;
+				qname = act_name;
+			}
+			cname = 1;
+			if (node == NULL) {
+				continue;	// hm, infinite loop better than goto? :)
+			}
+		} else {
+			if (ldns_dname_is_wildcard(node->owner)) {
+				ldns_rdf_deep_free(qname);
+				qname = ldns_rdf_clone(ldns_rr_owner(question));
 			}
-			ldns_rdf_deep_free(wildcard);
+			ns_answer_from_node(node, zone, qname,
+						ldns_rr_get_type(question), response, copied_rrs);
+			ldns_pkt_set_rcode(response, LDNS_RCODE_NOERROR);
+			break;
 		}
 	}
 
-- 
GitLab