From 6b894a41294ee4f965e861ed5ca6fb828e13f2c5 Mon Sep 17 00:00:00 2001
From: Jan Vcelak <jan.vcelak@nic.cz>
Date: Fri, 24 May 2013 13:28:11 +0200
Subject: [PATCH] implement server identification using TXT/CH

refs #2463

Change-Id: I7a686f92bf9879e248f7878974f1840d179d84d0
---
 Knot.files                           |   2 +
 src/Makefile.am                      |   2 +
 src/knot/server/zones.c              |  78 +++++++--------
 src/libknot/nameserver/chaos.c       | 138 +++++++++++++++++++++++++++
 src/libknot/nameserver/chaos.h       |  50 ++++++++++
 src/libknot/nameserver/name-server.c |  14 ++-
 src/libknot/nameserver/name-server.h |   4 +
 7 files changed, 241 insertions(+), 47 deletions(-)
 create mode 100644 src/libknot/nameserver/chaos.c
 create mode 100644 src/libknot/nameserver/chaos.h

diff --git a/Knot.files b/Knot.files
index 3561585561..4190a02155 100644
--- a/Knot.files
+++ b/Knot.files
@@ -147,6 +147,8 @@ src/libknot/dname.h
 src/libknot/edns.c
 src/libknot/edns.h
 src/libknot/libknot.h
+src/libknot/nameserver/chaos.c
+src/libknot/nameserver/chaos.h
 src/libknot/nameserver/name-server.c
 src/libknot/nameserver/name-server.h
 src/libknot/nsec3.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 7053302097..5cafe23637 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -168,6 +168,8 @@ libknot_la_SOURCES =				\
 	libknot/zone/zone-diff.c		\
 	libknot/nameserver/name-server.h	\
 	libknot/nameserver/name-server.c	\
+	libknot/nameserver/chaos.h		\
+	libknot/nameserver/chaos.c		\
 	libknot/updates/changesets.h		\
 	libknot/updates/changesets.c		\
 	libknot/updates/xfr-in.h		\
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index a9931bedd5..4bf4a20be9 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -34,6 +34,7 @@
 #include "libknot/updates/xfr-in.h"
 #include "knot/server/zones.h"
 #include "libknot/nameserver/name-server.h"
+#include "libknot/nameserver/chaos.h"
 #include "libknot/updates/changesets.h"
 #include "libknot/tsig-op.h"
 #include "common/descriptor.h"
@@ -2051,8 +2052,10 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver,
 {
 	rcu_read_lock();
 
+	knot_rcode_t rcode = 0;
 	knot_packet_t *resp = NULL;
 	const knot_zone_t *zone = NULL;
+	const uint16_t qclass = knot_packet_qclass(query);
 
 	dbg_zones_verb("Preparing response structure.\n");
 	int ret = knot_ns_prep_normal_response(nameserver, query, &resp, &zone,
@@ -2060,20 +2063,6 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver,
 	                                       ? *rsize : 0);
 	query->zone = zone;
 
-	// check for TSIG in the query
-	// not required, TSIG is already found if it is there
-//	if (knot_packet_additional_rrset_count(query) > 0) {
-//		/*! \todo warning */
-//		const knot_rrset_t *tsig = knot_packet_additional_rrset(query,
-//		                 knot_packet_additional_rrset_count(query) - 1);
-//		if (knot_rrset_type(tsig) == KNOT_RRTYPE_TSIG) {
-//			dbg_zones_verb("found TSIG in normal query\n");
-//			knot_packet_set_tsig(query, tsig);
-//		}
-//	}
-
-	knot_rcode_t rcode = 0;
-
 	switch (ret) {
 	case KNOT_EOK:
 		rcode = KNOT_RCODE_NOERROR;
@@ -2088,14 +2077,15 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver,
 		break;
 	}
 
-	if (rcode == KNOT_RCODE_NOERROR
-	    && ((zone == NULL && knot_packet_tsig(query) == NULL)
-	        || (knot_packet_qclass(query) != KNOT_CLASS_IN
-	            && knot_packet_qclass(query) != KNOT_CLASS_ANY))) {
-		/*! \todo If there is TSIG, this should be probably handled
-		 *        as a key error.
-		 */
-		rcode = KNOT_RCODE_REFUSED;
+	if (rcode == KNOT_RCODE_NOERROR) {
+		switch (qclass) {
+		case KNOT_CLASS_IN:
+		case KNOT_CLASS_CH:
+		case KNOT_CLASS_ANY:
+			break;
+		default:
+			rcode = KNOT_RCODE_REFUSED;
+		}
 	}
 
 	if (rcode != KNOT_RCODE_NOERROR) {
@@ -2124,25 +2114,19 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver,
 		size_t answer_size = *rsize;
 		int ret = KNOT_EOK;
 
-		if (zone == NULL) {
-			assert(knot_packet_tsig(query) != NULL);
-			// treat as BADKEY error
-			/*! \todo Is this OK?? */
-			rcode = KNOT_RCODE_NOTAUTH;
-			tsig_rcode = KNOT_RCODE_BADKEY;
-			ret = KNOT_TSIG_EBADKEY;
-		} else {
+		const knot_rrset_t *tsig = knot_packet_tsig(query);
+		if (tsig != NULL) {
 			dbg_zones_verb("Checking TSIG in query.\n");
-			const knot_rrset_t *tsig = knot_packet_tsig(query);
-			if (tsig == NULL) {
-				// no TSIG, this is completely valid
-				tsig_rcode = 0;
-				ret = KNOT_EOK;
+			if (zone == NULL) {
+				// treat as BADKEY error
+				/*! \todo Is this OK?? */
+				rcode = KNOT_RCODE_NOTAUTH;
+				tsig_rcode = KNOT_RCODE_BADKEY;
+				ret = KNOT_TSIG_EBADKEY;
 			} else {
 				ret = zones_check_tsig_query(zone, query, addr,
-				                             &rcode, &tsig_rcode,
-				                             &tsig_key_zone,
-				                             &tsig_prev_time_signed);
+				      &rcode, &tsig_rcode, &tsig_key_zone,
+				      &tsig_prev_time_signed);
 			}
 		}
 
@@ -2165,11 +2149,14 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver,
 				                              resp, resp_wire,
 				                              &answer_size);
 			} else {
-				ret = knot_ns_answer_normal(nameserver, zone,
-				                            resp, resp_wire,
-				                            &answer_size,
-				                            transport ==
-				                            NS_TRANSPORT_UDP);
+				if (qclass == KNOT_CLASS_CH) {
+					ret = knot_ns_answer_chaos(nameserver,
+					      resp, resp_wire, &answer_size);
+				} else {
+					ret = knot_ns_answer_normal(nameserver,
+					      zone, resp, resp_wire, &answer_size,
+					      transport == NS_TRANSPORT_UDP);
+				}
 				query->flags = resp->flags; /* Copy markers. */
 			}
 
@@ -2693,6 +2680,11 @@ int zones_ns_conf_hook(const struct conf_t *conf, void *data)
 	/* Set NSID. */
 	knot_ns_set_nsid(ns, conf->nsid, conf->nsid_len);
 
+	/* Server identification, RFC 4892. */
+	ns->identity = conf->identity;
+	ns->version = conf->version;
+	ns->hostname = conf->hostname;
+
 	knot_zonedb_t *old_db = 0;
 
 	int ret = zones_update_db_from_config(conf, ns, &old_db);
diff --git a/src/libknot/nameserver/chaos.c b/src/libknot/nameserver/chaos.c
new file mode 100644
index 0000000000..389da12c3d
--- /dev/null
+++ b/src/libknot/nameserver/chaos.c
@@ -0,0 +1,138 @@
+/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <strings.h>
+
+#include "chaos.h"
+#include "common/descriptor.h"
+#include "packet/packet.h"
+#include "packet/response.h"
+
+/*!
+ * \brief Get a string result for a given TXT query.
+ */
+static const char *get_txt_response_string(const knot_nameserver_t *nameserver,
+                                           const knot_dname_t *qname)
+{
+	char *qname_str = knot_dname_to_str(qname);
+	const char *response = NULL;
+
+	if (strcasecmp("id.server.", qname_str) == 0) {
+		response = nameserver->identity;
+	} else if (strcasecmp("version.server.", qname_str) == 0) {
+		response = nameserver->version;
+	} else if (strcasecmp("hostname.server.", qname_str) == 0) {
+		response = nameserver->hostname;
+	}
+
+	free(qname_str);
+
+	return response;
+}
+
+/*!
+ * \brief Create TXT RR with a given string content.
+ *
+ * \param owner     RR owner name.
+ * \param response  String to be saved in RDATA. Truncated to 255 chars.
+ *
+ * \return Allocated RRset or NULL in case of error.
+ */
+static knot_rrset_t *create_txt_rrset(const knot_dname_t *owner,
+                                      const char *response)
+{
+	// truncate response to one TXT label
+	size_t response_len = strlen(response);
+	if (response_len > 255)
+		response_len = 255;
+
+	knot_dname_t *rowner = knot_dname_deep_copy(owner);
+	if (!rowner)
+		return NULL;
+
+	knot_rrset_t *rrset;
+	rrset = knot_rrset_new(rowner, KNOT_RRTYPE_TXT, KNOT_CLASS_CH, 0);
+	knot_dname_release(rowner);
+	if (!rrset)
+		return NULL;
+
+	uint8_t *rdata = knot_rrset_create_rdata(rrset, response_len + 1);
+	if (!rdata) {
+		knot_rrset_deep_free(&rrset, 1, 0);
+		return NULL;
+	}
+
+	rdata[0] = response_len;
+	memcpy(&rdata[1], response, response_len);
+
+	return rrset;
+}
+
+/*!
+ * \brief Create a response for a TXT CHAOS query.
+ *
+ * \param return KNOT_RCODE_NOERROR if the response was succesfully created,
+ *               otherwise an RCODE representing the failure.
+ */
+static int answer_txt(knot_nameserver_t *nameserver, knot_packet_t *response,
+                      uint8_t *response_wire, size_t *response_size)
+{
+	const knot_dname_t *qname = knot_packet_qname(response);
+	const char *response_str = get_txt_response_string(nameserver, qname);
+	if (response_str == NULL || response_str[0] == '\0')
+		return KNOT_RCODE_REFUSED;
+
+	knot_rrset_t *rrset = create_txt_rrset(qname, response_str);
+	if (!rrset)
+		return KNOT_RCODE_SERVFAIL;
+
+	int result = knot_response_add_rrset_answer(response, rrset, 1, 0, 0);
+	if (result != KNOT_EOK) {
+		knot_rrset_deep_free(&rrset, 1, 0);
+		return KNOT_RCODE_SERVFAIL;
+	}
+
+	result = ns_response_to_wire(response, response_wire, response_size);
+	if (result != KNOT_EOK) {
+		knot_rrset_deep_free(&rrset, 1, 0);
+		return KNOT_RCODE_SERVFAIL;
+	}
+
+	knot_rrset_deep_free(&rrset, 1, 0);
+	knot_response_set_rcode(response, KNOT_RCODE_NOERROR);
+
+	return KNOT_RCODE_NOERROR;
+}
+
+/*!
+ * \brief Create a response for a given query in the CHAOS class.
+ */
+int knot_ns_answer_chaos(knot_nameserver_t *nameserver, knot_packet_t *resp,
+                         uint8_t *resp_wire, size_t *resp_size)
+{
+	int rcode = KNOT_RCODE_REFUSED;
+
+	if (knot_packet_qtype(resp) == KNOT_RRTYPE_TXT) {
+		rcode = answer_txt(nameserver, resp, resp_wire, resp_size);
+	}
+
+	if (rcode != KNOT_RCODE_NOERROR) {
+		knot_ns_error_response_full(nameserver, resp, rcode,
+		                            resp_wire, resp_size);
+	}
+
+	return KNOT_EOK;
+}
diff --git a/src/libknot/nameserver/chaos.h b/src/libknot/nameserver/chaos.h
new file mode 100644
index 0000000000..2c1e55432f
--- /dev/null
+++ b/src/libknot/nameserver/chaos.h
@@ -0,0 +1,50 @@
+/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file chaos.h
+ *
+ * \author Jan Vcelak <jvcelak@nic.cz>
+ *
+ * \addtogroup query_processing
+ * @{
+ */
+
+#ifndef _KNOT_CHAOS_H_
+#define _KNOT_CHAOS_H_
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "name-server.h"
+#include "packet/packet.h"
+
+/*!
+ * \brief Create a response for a given query in the CHAOS class.
+ *
+ * \param nameserver     Name server structure.
+ * \param response       Response structure with parsed query.
+ * \param response_wire  Output for response in wire format.
+ * \param response_size  IN: maximum acceptable size of input, OUT: real size.
+ *
+ * \return Always KNOT_EOK.
+ */
+int knot_ns_answer_chaos(knot_nameserver_t *nameserver, knot_packet_t *response,
+                         uint8_t *response_wire, size_t *response_size);
+
+#endif // _KNOT_CHAOS_H_
+
+/*! @} */
diff --git a/src/libknot/nameserver/name-server.c b/src/libknot/nameserver/name-server.c
index 40625bd77b..1646bad5af 100644
--- a/src/libknot/nameserver/name-server.c
+++ b/src/libknot/nameserver/name-server.c
@@ -3040,6 +3040,10 @@ knot_nameserver_t *knot_ns_create()
 		ns->opt_rr = NULL;
 	}
 
+	ns->identity = NULL;
+	ns->version = NULL;
+	ns->hostname = NULL;
+
 	knot_packet_free(&err);
 
 	return ns;
@@ -3404,10 +3408,6 @@ int knot_ns_prep_normal_response(knot_nameserver_t *nameserver,
 	            query->parsed, query->size);
 	dbg_ns_detail("Opt RR: version: %d, payload: %d\n",
 	              query->opt_rr.version, query->opt_rr.payload);
-
-	// get the answer for the query
-	knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db);
-
 	dbg_ns_detail("EDNS supported in query: %d\n",
 	              knot_query_edns_supported(query));
 
@@ -3428,6 +3428,11 @@ int knot_ns_prep_normal_response(knot_nameserver_t *nameserver,
 
 	dbg_ns_verb("Response max size: %zu\n", (*resp)->max_size);
 
+	// search for zone only for IN and ANY classes
+	uint16_t qclass = knot_packet_qclass(*resp);
+	if (qclass != KNOT_CLASS_IN && qclass != KNOT_CLASS_ANY)
+		return KNOT_EOK;
+
 	const knot_dname_t *qname = knot_packet_qname(*resp);
 	assert(qname != NULL);
 
@@ -3438,6 +3443,7 @@ dbg_ns_exec_verb(
 	free(name_str);
 );
 	// find zone in which to search for the name
+	knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db);
 	*zone = ns_get_zone_for_qname(zonedb, qname, qtype);
 
 	return KNOT_EOK;
diff --git a/src/libknot/nameserver/name-server.h b/src/libknot/nameserver/name-server.h
index 7f36ed5189..58ea212b9c 100644
--- a/src/libknot/nameserver/name-server.h
+++ b/src/libknot/nameserver/name-server.h
@@ -68,6 +68,10 @@ typedef struct knot_nameserver {
 	size_t err_resp_size;     /*!< Size of the prepared error response. */
 	knot_opt_rr_t *opt_rr;  /*!< OPT RR with the server's EDNS0 info. */
 
+	const char *identity; //!< RFC 4892, server identity (id.server).
+	const char *version;  //!< RFC 4892, server version (version.server).
+	const char *hostname; //!< RFC 4892, server host name (hostname.server).
+
 	void *data;
 } knot_nameserver_t;
 
-- 
GitLab