All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] keyutils: create a common DNS interface
@ 2022-07-07 19:15 Enzo Matsumiya
  2022-07-07 19:15 ` [PATCH 1/3] key.dns: " Enzo Matsumiya
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Enzo Matsumiya @ 2022-07-07 19:15 UTC (permalink / raw)
  To: dhowells
  Cc: linux-cifs, smfrench, pc, ronniesahlberg, nspmangalore, Enzo Matsumiya

Hi David, list,

In the following patches, I moved all the common DNS functionality in
keyutils into key.dns.{h,c} with the aim to:

- Improve code readability
- ... by restructuring the DNS functions
- Use libresolv for any query, and not only AFSDB/SRV types. This allows
  A/AAAA queries to also have extra data fetched (such as TTL, for example)
- Add flexibility to add/remove/update any DNS query type

Patch 1/3: moves the common DNS parts into key.dns.{h.c}, rework on the
	   query part to have it as generic as possible, adapt callers
	   to the new structure.
Patch 2/3: refactor read_config() to make it more structured, hence more
	   flexible (idea is to have it ready for new config options).
Patch 3/3: adds a "ns=ADDR" callout option to allow callers to specify
	   custom nameservers for their queries. Custom as in, different
	   from the system's default (usually resolv.conf).

I've been using most of this code on my day-to-day to aid my
CIFS debugging activities and I thought it'd be good to have it
upstream.

Reviews, feedback, testing are all welcome.


Cheers,

Enzo Matsumiya (3):
  key.dns: create a common DNS interface
  key.dns_resolver: refactor read_config()
  key.dns: allow to use custom nameservers

 Makefile           |   6 +-
 dns.afsdb.c        | 358 +++++-------------
 key.dns.c          | 891 +++++++++++++++++++++++++++++++++++++++++++++
 key.dns.h          | 260 +++++++++++--
 key.dns_resolver.c | 683 +++++++++++++---------------------
 5 files changed, 1457 insertions(+), 741 deletions(-)
 create mode 100644 key.dns.c

-- 
2.35.3


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/3] key.dns: create a common DNS interface
  2022-07-07 19:15 [PATCH 0/3] keyutils: create a common DNS interface Enzo Matsumiya
@ 2022-07-07 19:15 ` Enzo Matsumiya
  2022-07-07 19:15 ` [PATCH 2/3] key.dns_resolver: refactor read_config() Enzo Matsumiya
  2022-07-07 19:15 ` [PATCH 3/3] key.dns: allow to use custom nameservers Enzo Matsumiya
  2 siblings, 0 replies; 4+ messages in thread
From: Enzo Matsumiya @ 2022-07-07 19:15 UTC (permalink / raw)
  To: dhowells
  Cc: linux-cifs, smfrench, pc, ronniesahlberg, nspmangalore, Enzo Matsumiya

This commit merges all the common DNS functionalities into
key.dns.{h,c}.

The main function exposed by this API is the dns_resolver() function,
that will resolve the desired hostname. There's a second exposed function
afs_lookup_VL_servers() for querying AFSDB records, but will use
dns_resolver() internally as well.

Some refactor to the imported exsiting code was also done:
- dns_resolver() now uses only libresolv functions, pulling away
  getaddrinfo() as it didn't provid no other information aside
  from the resolved IP address (e.g. it's now possible to get the
  TTL from the DNS record for any of query type)
- better error handling and rework on error logging
- reduced number of globals, such as key expiry, payload buffer, etc
- simplify the code from the caller modules

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 Makefile           |   6 +-
 dns.afsdb.c        | 348 +++++---------------
 key.dns.c          | 794 +++++++++++++++++++++++++++++++++++++++++++++
 key.dns.h          | 255 ++++++++++++---
 key.dns_resolver.c | 490 +++++++---------------------
 5 files changed, 1207 insertions(+), 686 deletions(-)
 create mode 100644 key.dns.c

diff --git a/Makefile b/Makefile
index 599b1452a05a..80d82e538a48 100644
--- a/Makefile
+++ b/Makefile
@@ -159,12 +159,14 @@ keyctl_watch.o: watch_queue.h
 request-key: request-key.o $(LIB_DEPENDENCY)
 	$(CC) -L. $(CFLAGS) $(LDFLAGS) $(RPATH) -o $@ $< -lkeyutils
 
-key.dns_resolver: key.dns_resolver.o dns.afsdb.o $(LIB_DEPENDENCY)
+key.dns_resolver: key.dns_resolver.o dns.afsdb.o key.dns.h key.dns.o $(LIB_DEPENDENCY)
 	$(CC) -L. $(CFLAGS) $(LDFLAGS) $(RPATH) -o $@ \
-		key.dns_resolver.o dns.afsdb.o -lkeyutils -lresolv
+		key.dns_resolver.o dns.afsdb.o key.dns.o \
+		-lkeyutils -lresolv
 
 key.dns_resolver.o: key.dns_resolver.c key.dns.h
 dns.afsdb.o: dns.afsdb.c key.dns.h
+key.dns.o: key.dns.c key.dns.h
 
 ###############################################################################
 #
diff --git a/dns.afsdb.c b/dns.afsdb.c
index 986c0f385a1a..2241cf58e55c 100644
--- a/dns.afsdb.c
+++ b/dns.afsdb.c
@@ -38,296 +38,100 @@
 #include "key.dns.h"
 
 /*
- *
- */
-static void afsdb_hosts_to_addrs(ns_msg handle, ns_sect section)
-{
-	char *vllist[MAX_VLS];	/* list of name servers	*/
-	int vlsnum = 0;		/* number of name servers in list */
-	int rrnum;
-	ns_rr rr;
-	int subtype, i, ret;
-	unsigned int ttl = UINT_MAX, rr_ttl;
-
-	debug("AFSDB RR count is %d", ns_msg_count(handle, section));
-
-	/* Look at all the resource records in this section. */
-	for (rrnum = 0; rrnum < ns_msg_count(handle, section); rrnum++) {
-		/* Expand the resource record number rrnum into rr. */
-		if (ns_parserr(&handle, section, rrnum, &rr)) {
-			_error("ns_parserr failed : %m");
-			continue;
-		}
-
-		/* We're only interested in AFSDB records */
-		if (ns_rr_type(rr) == ns_t_afsdb) {
-			vllist[vlsnum] = malloc(MAXDNAME);
-			if (!vllist[vlsnum])
-				error("Out of memory");
-
-			subtype = ns_get16(ns_rr_rdata(rr));
-
-			/* Expand the name server's domain name */
-			if (ns_name_uncompress(ns_msg_base(handle),
-					       ns_msg_end(handle),
-					       ns_rr_rdata(rr) + 2,
-					       vllist[vlsnum],
-					       MAXDNAME) < 0)
-				error("ns_name_uncompress failed");
-
-			rr_ttl = ns_rr_ttl(rr);
-			if (ttl > rr_ttl)
-				ttl = rr_ttl;
-
-			/* Check the domain name we've just unpacked and add it to
-			 * the list of VL servers if it is not a duplicate.
-			 * If it is a duplicate, just ignore it.
-			 */
-			for (i = 0; i < vlsnum; i++)
-				if (strcasecmp(vllist[i], vllist[vlsnum]) == 0)
-					goto next_one;
-
-			/* Turn the hostname into IP addresses */
-			ret = dns_resolver(vllist[vlsnum], NULL);
-			if (ret) {
-				debug("AFSDB RR can't resolve."
-				      "subtype:%d, server name:%s, netmask:%u",
-				      subtype, vllist[vlsnum], mask);
-				goto next_one;
-			}
-
-			info("AFSDB RR subtype:%d, server name:%s, ip:%*.*s, ttl:%u",
-			     subtype, vllist[vlsnum],
-			     (int)payload[payload_index - 1].iov_len,
-			     (int)payload[payload_index - 1].iov_len,
-			     (char *)payload[payload_index - 1].iov_base,
-			     ttl);
-
-			/* prepare for the next record */
-			vlsnum++;
-			continue;
-
-		next_one:
-			free(vllist[vlsnum]);
-		}
-	}
-
-	key_expiry = ttl;
-	info("ttl: %u", key_expiry);
-}
-
-/*
- *
- */
-static void srv_hosts_to_addrs(ns_msg handle, ns_sect section)
-{
-	char *vllist[MAX_VLS];	/* list of name servers	*/
-	int vlsnum = 0;		/* number of name servers in list */
-	int rrnum;
-	ns_rr rr;
-	int subtype, i, ret;
-	unsigned short pref, weight, port;
-	unsigned int ttl = UINT_MAX, rr_ttl;
-	char sport[8];
-
-	debug("SRV RR count is %d", ns_msg_count(handle, section));
-
-	/* Look at all the resource records in this section. */
-	for (rrnum = 0; rrnum < ns_msg_count(handle, section); rrnum++) {
-		/* Expand the resource record number rrnum into rr. */
-		if (ns_parserr(&handle, section, rrnum, &rr)) {
-			_error("ns_parserr failed : %m");
-			continue;
-		}
-
-		if (ns_rr_type(rr) == ns_t_srv) {
-			vllist[vlsnum] = malloc(MAXDNAME);
-			if (!vllist[vlsnum])
-				error("Out of memory");
-
-			subtype = ns_get16(ns_rr_rdata(rr));
-
-			/* Expand the name server's domain name */
-			if (ns_name_uncompress(ns_msg_base(handle),
-					       ns_msg_end(handle),
-					       ns_rr_rdata(rr) + 6,
-					       vllist[vlsnum],
-					       MAXDNAME) < 0) {
-				_error("ns_name_uncompress failed");
-				continue;
-			}
-
-			rr_ttl = ns_rr_ttl(rr);
-			if (ttl > rr_ttl)
-				ttl = rr_ttl;
-
-			pref   = ns_get16(ns_rr_rdata(rr));
-			weight = ns_get16(ns_rr_rdata(rr) + 2);
-			port   = ns_get16(ns_rr_rdata(rr) + 4);
-			info("rdata %u %u %u", pref, weight, port);
-
-			sprintf(sport, "+%hu", port);
-
-			/* Check the domain name we've just unpacked and add it to
-			 * the list of VL servers if it is not a duplicate.
-			 * If it is a duplicate, just ignore it.
-			 */
-			for (i = 0; i < vlsnum; i++)
-				if (strcasecmp(vllist[i], vllist[vlsnum]) == 0)
-					goto next_one;
-
-			/* Turn the hostname into IP addresses */
-			ret = dns_resolver(vllist[vlsnum], sport);
-			if (ret) {
-				debug("SRV RR can't resolve."
-				      "subtype:%d, server name:%s, netmask:%u",
-				      subtype, vllist[vlsnum], mask);
-				goto next_one;
-			}
-
-			info("SRV RR subtype:%d, server name:%s, ip:%*.*s, ttl:%u",
-			     subtype, vllist[vlsnum],
-			     (int)payload[payload_index - 1].iov_len,
-			     (int)payload[payload_index - 1].iov_len,
-			     (char *)payload[payload_index - 1].iov_base,
-			     ttl);
-
-			/* prepare for the next record */
-			vlsnum++;
-			continue;
-
-		next_one:
-			free(vllist[vlsnum]);
-		}
-	}
-
-	key_expiry = ttl;
-	info("ttl: %u", key_expiry);
-}
-
-/*
- * Look up an AFSDB record to get the VL server addresses.
+ * Instantiate the key.
  */
-static int dns_query_AFSDB(const char *cell)
+static int afs_instantiate(payload_t *payload, unsigned int ttl)
 {
-	int	response_len;		/* buffer length */
-	ns_msg	handle;			/* handle for response message */
-	union {
-		HEADER hdr;
-		u_char buf[NS_PACKETSZ];
-	} response;		/* response buffers */
-
-	debug("Get AFSDB RR for cell name:'%s'", cell);
-
-	/* query the dns for an AFSDB resource record */
-	response_len = res_query(cell,
-				 ns_c_in,
-				 ns_t_afsdb,
-				 response.buf,
-				 sizeof(response));
+	int ret = 0;
 
-	if (response_len < 0) {
-		/* negative result */
-		_nsError(h_errno, cell);
-		return -1;
+	/* set the key's expiry time from the minimum TTL encountered */
+	ret = keyctl_set_timeout(key, ttl);
+	if (ret) {
+		error("%s: keyctl_set_timeout: %m", __func__);
+		return ret;
 	}
 
-	if (ns_initparse(response.buf, response_len, &handle) < 0)
-		error("ns_initparse: %m");
-
-	/* look up the hostnames we've obtained to get the actual addresses */
-	afsdb_hosts_to_addrs(handle, ns_s_an);
+	/* instantiate the key */
+	ret = keyctl_instantiate_iov(key, payload->data, payload->index, 0);
+	if (ret)
+		error("%s: keyctl_instantiate: %m", __func__);
 
-	info("DNS query AFSDB RR results:%u ttl:%u", payload_index, key_expiry);
-	return 0;
+	return ret;
 }
 
 /*
- * Look up an SRV record to get the VL server addresses [RFC 5864].
+ * Lookup VL servers for AFS.
  */
-static int dns_query_VL_SRV(const char *cell)
+__attribute__((noreturn))
+void afs_lookup_VL_servers(const char *cell, char *options)
 {
-	int	response_len;		/* buffer length */
-	ns_msg	handle;			/* handle for response message */
-	union {
-		HEADER hdr;
-		u_char buf[NS_PACKETSZ];
-	} response;
-	char name[1024];
-
-	snprintf(name, sizeof(name), "_afs3-vlserver._udp.%s", cell);
-
-	debug("Get VL SRV RR for name:'%s'", name);
-
-	response_len = res_query(name,
-				 ns_c_in,
-				 ns_t_srv,
-				 response.buf,
-				 sizeof(response));
-
-	if (response_len < 0) {
-		/* negative result */
-		_nsError(h_errno, cell);
-		return -1;
+	payload_t *payload = NULL;
+	struct hostinfo host = { 0 };
+	char *vlsrv_name = NULL;
+	int ret = 0;
+
+	if (!cell)
+		error_ex("%s: missing hostname", __func__);
+
+	CALLOC_CHECK(vlsrv_name, 1, MAXDNAME);
+	snprintf(vlsrv_name, MAXDNAME, "_afs3-vlserver._udp.%s", cell);
+	STRNDUP_CHECK(host.hostname, vlsrv_name, strlen(vlsrv_name));
+	CALLOC_CHECK(payload, 1, sizeof(payload_t));
+
+	free(vlsrv_name);
+
+	host.af = AF_UNSPEC;
+	host.single_addr = true;
+
+	parse_opts(&host, options);
+
+	/*
+	 * Look up an SRV record to get the VL server addresses [RFC 5864].
+	 */
+	host.type = ns_t_srv;
+	ret = dns_resolver(&host, payload);
+	if (ret) {
+		free(host.hostname);
+		STRNDUP_CHECK(host.hostname, cell, strlen(cell));
+
+		/*
+		 * Look up an AFSDB record to get the VL server addresses.
+		 */
+		host.type = ns_t_afsdb;
+		ret = dns_resolver(&host, payload);
 	}
 
-	if (ns_initparse(response.buf, response_len, &handle) < 0)
-		error("ns_initparse: %m");
-
-	/* look up the hostnames we've obtained to get the actual addresses */
-	srv_hosts_to_addrs(handle, ns_s_an);
-
-	info("DNS query VL SRV RR results:%u ttl:%u", payload_index, key_expiry);
-	return 0;
-}
-
-/*
- * Instantiate the key.
- */
-static __attribute__((noreturn))
-void afs_instantiate(const char *cell)
-{
-	int ret;
-
-	/* set the key's expiry time from the minimum TTL encountered */
-	if (!debug_mode) {
-		ret = keyctl_set_timeout(key, key_expiry);
-		if (ret == -1)
-			error("%s: keyctl_set_timeout: %m", __func__);
+	if (ret || payload->index == 0) {
+		ret = get_err(0);
+		goto out_free;
 	}
 
-	/* handle a lack of results */
-	if (payload_index == 0)
-		nsError(NO_DATA, cell);
-
-	/* must include a NUL char at the end of the payload */
-	payload[payload_index].iov_base = "";
-	payload[payload_index++].iov_len = 1;
-	dump_payload();
+	dump_payload(payload);
+	info("Key timeout will be %ld seconds", host.ttl);
 
-	/* load the key with data key */
 	if (!debug_mode) {
-		ret = keyctl_instantiate_iov(key, payload, payload_index, 0);
-		if (ret == -1)
-			error("%s: keyctl_instantiate: %m", __func__);
+		unsigned int ttl;
+
+		/*
+		 * If TTL was set through the config file (key_expiry),
+		 * it takes precedence over the one from the DNS record (stored
+		 * in host.ttl).
+		 */
+		if (key_expiry > -1)
+			ttl = (unsigned int)key_expiry;
+		else if (host.ttl > -1)
+			ttl = (unsigned int)host.ttl;
+		else
+			/* Fallback to default value if dns_resolver() couldn't
+			 * get TTL for some reason */
+			ttl = DEFAULT_KEY_TTL;
+
+		ret = afs_instantiate(payload, ttl);
 	}
 
-	exit(0);
-}
-
-/*
- * Look up VL servers for AFS.
- */
-void afs_look_up_VL_servers(const char *cell, char *options)
-{
-	/* Is the IP address family limited? */
-	if (strcmp(options, "ipv4") == 0)
-		mask = INET_IP4_ONLY;
-	else if (strcmp(options, "ipv6") == 0)
-		mask = INET_IP6_ONLY;
-
-	if (dns_query_VL_SRV(cell) != 0)
-		dns_query_AFSDB(cell);
+out_free:
+	free_hostinfo(&host);
+	free(payload);
 
-	afs_instantiate(cell);
+	exit(ret);
 }
diff --git a/key.dns.c b/key.dns.c
new file mode 100644
index 000000000000..6390f3118a6e
--- /dev/null
+++ b/key.dns.c
@@ -0,0 +1,794 @@
+/*
+ * Common DNS resolving code for keyutils
+ *
+ * Copyright (c) 2022, SUSE LLC
+ * Author: Enzo Matsumiya <ematsumiya@suse.de>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "key.dns.h"
+
+#define clamp(x, lo, hi) (MIN(hi, MAX(x, lo)))
+
+static const int ns_errno_map[] = {
+	[0]			= ECONNREFUSED,
+	[HOST_NOT_FOUND]	= ENODATA,
+	[TRY_AGAIN]		= EAGAIN,
+	[NO_RECOVERY]		= ECONNREFUSED,
+	[NO_DATA]		= ENODATA,
+};
+static const int ns_errno_max = sizeof(ns_errno_map) / sizeof(ns_errno_map[0]);
+
+/*
+ * Returns -errno, @optval, -h_errno, or -1, in that order.
+ *
+ * Resets errno and h_errno to 0 unconditionally.
+ */
+int get_err(int optval)
+{
+	int ret = -1;
+
+	if (errno)
+		ret = -errno;
+	else if (optval)
+		ret = optval;
+	else if (h_errno >= ns_errno_max)
+		ret = -ECONNREFUSED;
+	else if (h_errno)
+		ret = -(ns_errno_map[h_errno]);
+
+	errno = h_errno = 0;
+	return ret;
+}
+
+const char *get_strerr(int optval)
+{
+	int opt = optval < 0 ? -optval : optval;
+
+	/* begin with h_errno here because it can be more descriptive for us */
+	if (h_errno)
+		return hstrerror(h_errno);
+	else if (errno)
+		return strerror(errno);
+	else if (opt)
+		return strerror(opt);
+
+	return "Unknown error";
+}
+
+void _log(FILE *f, int level, const char *fmt, ...)
+{
+	va_list va;
+
+	va_start(va, fmt);
+	if (isatty(2)) {
+		vfprintf(f, fmt, va);
+		fputc('\n', f);
+	} else {
+		vsyslog(level, fmt, va);
+	}
+	va_end(va);
+}
+
+/*
+ * Print nameserver error
+ *
+ * Error code is always h_errno (netdb.h) for these.
+ */
+void nsError(const char *hostname)
+{
+	if (isatty(2))
+		error("NS: %s: %s.", hostname, hstrerror(h_errno));
+	else
+		error("%s: %s", hostname, hstrerror(h_errno));
+}
+
+/*
+ * Print nameserver error and exit
+ */
+void nsError_ex(const char *hostname)
+{
+	unsigned timeout;
+	int ret, err;
+
+	nsError(hostname);
+
+	if (!debug_mode) {
+		if (h_errno >= ns_errno_max)
+			err = ECONNREFUSED;
+		else
+			err = ns_errno_map[h_errno];
+
+		switch (h_errno) {
+		case TRY_AGAIN:
+			timeout = 1;
+			break;
+		case 0:
+		case NO_RECOVERY:
+			timeout = 10;
+			break;
+		default:
+			timeout = 1 * 60;
+			break;
+		}
+
+		error("Reject the key with error %d", err);
+
+		ret = keyctl_reject(key, timeout, err, KEY_REQKEY_DEFL_DEFAULT);
+		if (ret == -1)
+			error_ex("keyctl_reject: %m");
+	}
+
+	exit(h_errno);
+}
+
+/*
+ * Dump the payload when debugging
+ */
+void dump_payload(payload_t *payload)
+{
+	size_t plen, n;
+	unsigned char *buf, *p;
+	int i;
+
+	plen = 0;
+	for (i = 0; i < payload->index; i++) {
+		n = payload->data[i].iov_len;
+		debug("seg[%d] size %zu", i, n);
+		plen += n;
+	}
+	if (plen == 0) {
+		info("Key instantiation data is empty");
+		return;
+	}
+
+	debug("payload len: %zu", plen);
+
+	CALLOC_CHECK(buf, 1, plen + 1);
+
+	p = buf;
+	for (i = 0; i < payload->index; i++) {
+		n = payload->data[i].iov_len;
+		memcpy(p, payload->data[i].iov_base, n);
+		p += n;
+	}
+
+	info("The key instantiation data is '%s'", buf);
+	free(buf);
+}
+
+static char *get_addr_str(int af, char *s, const void *data)
+{
+	switch (af) {
+	case AF_INET:
+		inet_ntop(AF_INET, data, s, INET_ADDRSTRLEN);
+		break;
+	case AF_INET6:
+		inet_ntop(AF_INET6, data, s, INET6_ADDRSTRLEN);
+		break;
+	default:
+		error("Invalid address family '%d'", af);
+		s = NULL;
+	}
+
+	return s;
+}
+
+/*
+ * "valid" for our cases
+ * ns_t_any is used to represent an "A/AAAA" query, i.e. both IPv4 and IPv6
+ * addresses must be queried
+ */
+static bool is_type_valid(ns_type type)
+{
+	switch(type) {
+	case ns_t_a:
+	case ns_t_aaaa:
+	case ns_t_any:
+	case ns_t_afsdb:
+	case ns_t_srv:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static inline bool is_type_resolvable(ns_type type)
+{
+	return (type == ns_t_a || type == ns_t_aaaa);
+}
+
+static bool can_resolve_host(hostinfo_t *host)
+{
+	bool afok = false, typeok = false;
+
+	if (!host)
+		return false;
+
+	switch (host->af) {
+	case AF_INET:
+	case AF_INET6:
+	case AF_UNSPEC:
+		afok = true;
+		break;
+	default:
+		afok = false;
+		break;
+	}
+
+	switch(host->type) {
+	case ns_t_a:
+	case ns_t_aaaa:
+	case ns_t_any:
+		typeok = true;
+		break;
+	default:
+		typeok = false;
+	}
+
+	return (afok && typeok);
+}
+
+static inline int ns2af(ns_type type)
+{
+	if (type == ns_t_a)
+		return AF_INET;
+	if (type == ns_t_aaaa)
+		return AF_INET6;
+
+	return AF_UNSPEC;
+}
+
+static inline void add_to_payload(payload_t *payload, void *data, size_t len)
+{
+	payload->data[payload->index].iov_base = data;
+	payload->data[payload->index].iov_len = len;
+	payload->index++;
+}
+
+/*
+ * Append an address to the payload segment list
+ */
+static void append_addr(char *addr, payload_t *payload)
+{
+	size_t len;
+	int i;
+
+	if (!addr) {
+		error("no address to append to payload");
+		return;
+	}
+
+	if (!payload) {
+		error("payload buffer is NULL, can't append addr '%s'", addr);
+		return;
+	}
+
+	len = strlen(addr);
+
+	debug("append '%s'", addr);
+
+	if (payload->index + 2 > MAX_PAYLOAD - 1) {
+		info("payload buffer is full, can't append addr '%s'", addr);
+		return;
+	}
+
+	/* do not append duplicate entry */
+	for (i = 0; i < payload->index; i++)
+		if (payload->data[i].iov_len == len &&
+		    memcmp(payload->data[i].iov_base, addr, len) == 0)
+			return;
+
+	if (payload->index != 0)
+		add_to_payload(payload, ",", 1);
+
+	add_to_payload(payload, (void *)strndup(addr, len), len);
+}
+
+/*
+ * Returns the smallest TTL from all targets.
+ */
+static long append_addrs(hostinfo_t **targets, int ntgts, payload_t *payload)
+{
+	int i;
+	long ttl = LONG_MAX;
+
+	for (i = 0; i < ntgts; i++) {
+		hostinfo_t *t = targets[i];
+		int n;
+
+		for (n = 0; n < t->naddrs; n++) {
+			if (t->port[0] != '\0')
+				strcat(t->addrs[n], t->port);
+			append_addr(t->addrs[n], payload);
+		}
+
+		dump_host(t);
+		ttl = MIN(ttl, t->ttl);
+	}
+
+	/* must include a NUL char at the end of the payload */
+	add_to_payload(payload, "", 1);
+
+	return ttl;
+}
+
+static inline bool is_host_dup(hostinfo_t *h1, hostinfo_t *h2)
+{
+	int len;
+
+	if (!h1 || !h2)
+		return false;
+
+	if (!h1->hostname || !h2->hostname)
+		return false;
+
+	len = MAX(strlen(h1->hostname), strlen(h2->hostname));
+	if (strncasecmp(h1->hostname, h2->hostname, len) != 0)
+		return false;
+
+	debug("dup host '%s'", h1->hostname);
+	return true;
+}
+
+/*
+ * Get maximum number of targets a record might have
+ */
+static int get_rr_count(ns_msg *handle)
+{
+	return ns_msg_count(*handle, ns_s_an);
+}
+
+static int get_rr_name(hostinfo_t *host, const unsigned char *rdata)
+{
+	int ret = 0;
+
+	CALLOC_CHECK(host->hostname, 1, MAXDNAME);
+
+	/* Expand the name server's domain name */
+	ret = ns_name_uncompress(ns_msg_base(*(host->handle)),
+				 ns_msg_end(*(host->handle)),
+				 rdata, host->hostname, MAXDNAME);
+	if (ret < 0) {
+		warn("ns_name_uncompress() failed: %m");
+		ret = get_err(-ENODATA);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Parses a resource record
+ */
+static int parse_rr(hostinfo_t *host, ns_rr rr)
+{
+	const unsigned char *rdata;
+	unsigned short prio, weight, port; /* for ns_t_srv */
+	int subtype = 0; /* for ns_t_afsdb */
+	ns_type rrtype;
+	int ret = 0;
+
+	if (!host)
+		return -EINVAL;
+
+	rrtype = ns_rr_type(rr);
+	rdata = ns_rr_rdata(rr);
+
+	/* increment rdata after reading each field, based on type */
+	switch (rrtype) {
+	case ns_t_afsdb:
+		NS_GET16(subtype, rdata);
+		debug("rdata: subtype=%d", subtype);
+		break;
+	case ns_t_srv:
+		NS_GET16(prio, rdata);
+		NS_GET16(weight, rdata);
+		NS_GET16(port, rdata);
+
+		snprintf(host->port, 10, "+%hu", port); // '+' + 8 + NUL
+
+		debug("rdata: prio=%u, weight=%u, port=%u",
+		      prio, weight, port);
+		break;
+	case ns_t_a:
+	case ns_t_aaaa:
+		if (host->naddrs == MAX_ADDRS) {
+			warn("Can't add more IP addresses (max '%d' reached)",
+			     MAX_ADDRS);
+			break;
+		}
+
+		CALLOC_CHECK(host->addrs[host->naddrs], 1, MAX_ADDR_LEN);
+		get_addr_str(ns2af(rrtype), host->addrs[host->naddrs], rdata);
+
+		debug("rdata: addr=%s", host->addrs[host->naddrs]);
+
+		host->naddrs++;
+		break;
+	default:
+		debug("invalid type '%s' (%d)", str_type(rrtype), rrtype);
+		return -EINVAL;
+	}
+
+	debug("rdata: type='%s' (%d), ttl=%d", str_type(rrtype), rrtype,
+	      ns_rr_ttl(rr));
+
+	host->type = rrtype;
+	host->ttl = ns_rr_ttl(rr);
+
+	if (!is_type_resolvable(rrtype))
+		ret = get_rr_name(host, rdata);
+
+	debug("rdata: hostname='%s'", host->hostname);
+
+	return ret;
+}
+
+/*
+ * Get host targets/cells for AFSDB and SRV records
+ *
+ * @host: host with queried handle
+ * @targets: array to store targets' information. Must be initialized and
+ *	     freed by caller.
+ * @maxn: Maximum number of targets we have available to process. It gets updated
+ *        when done parsing to the actual number of targets available.
+ *
+ * Non-unique info is copied from @host to each target. Targets with duplicate
+ * hostnames are discarded.
+ *
+ * Returns 0 on success, or -error otherwise.
+ */
+static int get_targets(hostinfo_t *host, hostinfo_t **targets, int *maxn)
+{
+	int n = 0; /* list index */
+	int i, ret;
+	ns_rr rr;
+
+	debug("Resource record count is %d", *maxn);
+
+	for (i = 0; i < *maxn; i++) {
+		hostinfo_t *target = NULL;
+
+		if (ns_parserr(host->handle, ns_s_an, i, &rr)) {
+			error("ns_parserr failed: %m");
+			continue;
+		}
+
+		CALLOC_CHECK(targets[n], 1, sizeof(hostinfo_t));
+		target = targets[n];
+
+		target->handle = host->handle; /* temp */
+		ret = parse_rr(target, rr);
+		if (ret)
+			goto out;
+
+		/* discard if duplicate */
+		for (i = 0; i < n; i++)
+			if (is_host_dup(targets[i], target))
+				goto next;
+
+		target->handle = NULL;
+		target->af = host->af;
+		target->single_addr = host->single_addr;
+
+		n++;
+		continue;
+next:
+		free_host(target);
+		target = NULL;
+	}
+
+	ret = 0;
+
+	if (n == 0) {
+		error("Failed to parse all records");
+		ret = -ENODATA;
+	}
+
+	*maxn = n;
+out:
+	return ret;
+}
+
+/*
+ * "resolves" a host as in: parses a host containing A/AAAA resource
+ * records, i.e. assumes @host::handle is allocated and is valid.
+ */
+static int resolve_host(hostinfo_t *host, int n)
+{
+	int i, ret = 0, err = 0;
+	ns_rr rr;
+
+	if (!host->handle)
+		return -ENODATA;
+
+	for (i = 0; i < n; i++) {
+		ns_type type;
+
+		if (ns_parserr(host->handle, ns_s_an, i, &rr)) {
+			error("ns_parserr failed: %m");
+			continue;
+		}
+
+		type = ns_rr_type(rr);
+		/* ignore resource records that doesn't contain IP addresses */
+		if (!is_type_resolvable(type)) {
+			debug("skipping unresolvable type '%s' (%d)",
+			      str_type(type), type);
+			err++;
+			continue;
+		}
+
+		ret = parse_rr(host, rr);
+		if (ret) {
+			warn("Failed to parse host '%s': %s", host->hostname,
+			     get_strerr(ret));
+			err++;
+			/* continue */
+		}
+	}
+
+	if (err >= n)
+		ret = -ENODATA;
+
+	return ret;
+}
+
+/*
+ * Makes the actual query
+ *
+ * @host: host info to query for
+ *
+ * Returns 0 on success and sets host->handle for resource record processing. 
+ * Returns -error otherwise.
+ *
+ * Caller is responsible for freeing the allocated handle in both cases.
+ */
+static int dns_query(hostinfo_t *host)
+{
+	res_state sp;
+	int ret, len;
+
+	if (!host)
+		return -EINVAL;
+
+	if (!is_type_valid(host->type))
+		return -EINVAL;
+
+	union {
+		HEADER hdr;
+		u_char buf[NS_PACKETSZ];
+	} answer; /* answer buffers */
+
+	CALLOC_CHECK(sp, 1, sizeof(*sp));
+
+	if (res_ninit(sp) < 0) {
+		error("Can't initialize sp");
+		return -ENODEV;
+	}
+
+	h_errno = 0;
+	/* query the dns for a @type resource record */
+	len = res_nquery(sp, host->hostname, ns_c_in, host->type, answer.buf,
+			 sizeof(answer));
+
+	if (len < 0 || len > NS_MAXMSG) {
+		ret = get_err(-ENODATA);
+		goto out;
+	}
+
+	CALLOC_CHECK(host->handle, 1, sizeof(ns_msg));
+
+	ret = 0;
+
+	if (ns_initparse(answer.buf, len, host->handle) < 0) {
+		error("ns_initparse: %m");
+		ret = get_err(-ENODATA);
+	}
+out:
+	/* frees sp */
+	res_nclose(sp);
+	return ret;
+}
+
+/*
+ * Queries the host and returns the number of targets (resource records)
+ * for it. Returns -error in case of errors.
+ */
+static int query_host(hostinfo_t *host)
+{
+	int n, ret;
+
+	ret = dns_query(host);
+	if (ret) {
+		if (host->handle) {
+			free(host->handle);
+			host->handle = NULL;
+		}
+		return ret;
+	}
+
+	n = get_rr_count(host->handle);
+	if (n == 0)
+		return -ENODATA;
+
+	if (!is_type_resolvable(host->type) && n > MAX_VLS) {
+		info("Processing only '%d' records.", MAX_VLS);
+		return MAX_VLS;
+	}
+
+	if (n > 1 && is_type_resolvable(host->type) && host->single_addr)
+		return 1;
+
+	return n;
+}
+
+static int __resolve_targets(hostinfo_t **targets, int ntgts, ns_type type)
+{
+	int n = 0;
+	int i, ret, err = 0;
+
+	if (!is_type_resolvable(type))
+		return -EINVAL;
+
+	for (i = 0; i < ntgts; i++) {
+		targets[i]->type = type;
+		targets[i]->af = ns2af(type);
+
+		n = query_host(targets[i]);
+		if (!n) {
+			debug("can't query '%s', error %d", targets[i]->hostname, n);
+			err++;
+			continue;
+		}
+
+		ret = resolve_host(targets[i], n);
+		if (ret) {
+			debug("failed to parse '%s' resource records for target '%s': %s",
+			      str_type(type), targets[i]->hostname, get_strerr(ret));
+			err++;
+		}
+	}
+
+	if (err >= ntgts)
+		ret = -ENODATA;
+
+	return ret;
+}
+
+static int resolve_targets4(hostinfo_t **targets, int ntgts)
+{
+	return __resolve_targets(targets, ntgts, ns_t_a);
+}
+
+static int resolve_targets6(hostinfo_t **targets, int ntgts)
+{
+	return __resolve_targets(targets, ntgts, ns_t_aaaa);
+}
+
+/*
+ * This function resolves the targets in @targets, based on @af address family
+ * (requested by the "ipv4" or "ipv6" options).
+ *
+ * On success, each target will have at least one IP address in its addrs list,
+ * ready to be appended to the payload. Returns 0.
+ * On errors:
+ * - if @af is AF_INET or AF_INET6, returns -ENODATA
+ * - if @af is AF_UNSPEC, returns -AF_INET if only ipv4 failed, -AF_INET6 if
+ *   only ipv6 failed, and -ENODATA if both failed
+ */
+static int resolve_targets(hostinfo_t **targets, int ntgts, int af)
+{
+	int ret4 = 0, ret6 = 0, ret = 0;
+
+	if (IS_IP4(af) || IS_IP_ANY(af))
+		ret4 = resolve_targets4(targets, ntgts);
+	if (ret4) {
+		warn("Failed to resolve IPv4 targets: %s", get_strerr(ret4));
+		ret = IS_IP_ANY(af) ? -AF_INET : -ENODATA;
+	}
+
+	if (IS_IP6(af) || IS_IP_ANY(af))
+		ret6 = resolve_targets6(targets, ntgts);
+	if (ret6) {
+		warn("Failed to resolve IPv6 targets: %s", get_strerr(ret6));
+		ret = IS_IP_ANY(af) ? -AF_INET6 : -ENODATA;
+	}
+
+	if (ret4 && ret6)
+		ret = -ENODATA;
+
+	return ret;
+}
+
+/*
+ * Perform address resolution for a hostname and add the resulting addresses as
+ * strings to the list of payload segments
+ *
+ * @host: host information to query for
+ * @payload: payload buffer to append results to. Initialized and freed by caller.
+ *
+ * Returns 0 on success, -error otherwise.
+ */
+int dns_resolver(hostinfo_t *host, payload_t *payload)
+{
+	hostinfo_t *targets[MAX_TARGETS];
+	int ntgts = 0, i;
+	int ret = 0;
+	long ttl;
+
+	if (!host || !host->hostname || !payload)
+		return -EINVAL;
+
+	if (!is_type_valid(host->type)) {
+		warn("Invalid query type '%s' (%d)", str_type(host->type),
+		     host->type);
+		return -EINVAL;
+	}
+
+	debug("Querying hostname %s with query type '%s' (%d)",
+	      host->hostname, str_type(host->type), host->type);
+
+	for (i = 0; i < MAX_TARGETS; i++)
+		targets[i] = NULL;
+
+	host->handle = NULL;
+	host->naddrs = 0;
+
+	if (can_resolve_host(host)) {
+		targets[0] = host;
+		ntgts = 1;
+
+		goto resolve;
+	}
+
+	/* else, can't straight resolve, must query+fetch targets first */
+	ntgts = query_host(host);
+	if (ntgts < 1) {
+		ret = ntgts;
+		error("Failed to query host '%s': %s", host->hostname,
+		      get_strerr(ret));
+		goto out;
+	}
+
+	ret = get_targets(host, targets, &ntgts);
+	if (ret || ntgts == 0) {
+		error("Failed to get targets for '%s': %s", host->hostname,
+		      get_strerr(ret));
+		goto out;
+	}
+
+resolve:
+	ret = resolve_targets(targets, ntgts, host->af);
+	if (ret == -ENODATA) {
+		error("Failed to resolve targets for '%s': %s", host->hostname,
+		      get_strerr(ret));
+		goto out;
+	} /* else, we got at least *some* data */
+
+	/* append resolved addresses to payload */
+	ttl = append_addrs(targets, ntgts, payload);
+
+	host->ttl = clamp(ttl, -1, LONG_MAX);
+
+	/* success if reached here */
+	ret = errno = h_errno = 0;
+out:
+	i = 0;
+	while (targets[i] && targets[i] != host)
+		free_host(targets[i++]);
+
+	return ret;
+}
diff --git a/key.dns.h b/key.dns.h
index 33d0ab3b05b1..f522a51b92ce 100644
--- a/key.dns.h
+++ b/key.dns.h
@@ -1,14 +1,25 @@
 /*
+ * Common DNS resolving code for keyutils
+ *
+ * Copyright (c) 2022, SUSE LLC
+ * Author: Enzo Matsumiya <ematsumiya@suse.de>
+ *
  * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public Licence as published by
- * the Free Software Foundation; either version 2 of the Licence, or
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 Licence for more details.
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  */
+#ifndef _KEY_DNS_H
+#define _KEY_DNS_H 
+
 #define _GNU_SOURCE
 #include <netinet/in.h>
 #include <arpa/nameser.h>
@@ -25,50 +36,216 @@
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdarg.h>
-#include <keyutils.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <time.h>
 #include <ctype.h>
+#include <sys/param.h>
+#include <sys/uio.h> // struct iovec
+
+#include "keyutils.h"
+
+extern int verbose;
+extern int debug_mode;
+extern key_serial_t key;
+extern long key_expiry;
+
+#define DEFAULT_KEY_TTL 5
+
+#define MAX_VLS		15	/* Max Volume Location Servers Per-Cell (AFSDB) */
+#define MAX_PAYLOAD	256	/* Max number of payload vectors (iovecs) */
 
-#define	MAX_VLS			15	/* Max Volume Location Servers Per-Cell */
-#define	INET_IP4_ONLY		0x1
-#define	INET_IP6_ONLY		0x2
-#define	INET_ALL		0xFF
-#define ONE_ADDR_ONLY		0x100
+#define MAX_ADDRS	MAX_PAYLOAD	/* Max number of IP addresses for a hostname */
+#define MAX_ADDR_LEN	(INET6_ADDRSTRLEN + 8 + 1)
+#define MAX_TARGETS	128
+
+#define IS_IP4(af)	((af) == AF_INET)
+#define IS_IP6(af)	((af) == AF_INET6)
+#define IS_IP_ANY(af)	((af) == AF_UNSPEC)
+
+/* For DNS/libresolv-specific messages */
+void nsError(const char *hostname);
+void nsError_ex(const char *hostname);
+int get_err(int optval);
+/* For keyutils-specific messages */
+extern __attribute__((format(printf, 3, 4)))
+void _log(FILE *f, int level, const char *fmt, ...);
 
 /*
- * key.dns_resolver.c
+ * Just print an error to stderr or the syslog
  */
-extern key_serial_t key;
-extern int debug_mode;
-extern unsigned mask;
-extern unsigned int key_expiry;
-
-#define N_PAYLOAD 256
-extern struct iovec payload[N_PAYLOAD];
-extern int payload_index;
-
-extern __attribute__((format(printf, 1, 2), noreturn))
-void error(const char *fmt, ...);
-extern __attribute__((format(printf, 1, 2)))
-void _error(const char *fmt, ...);
-extern __attribute__((format(printf, 1, 2)))
-void warning(const char *fmt, ...);
-extern __attribute__((format(printf, 1, 2)))
-void info(const char *fmt, ...);
-extern __attribute__((noreturn))
-void nsError(int err, const char *domain);
-extern void _nsError(int err, const char *domain);
-extern __attribute__((format(printf, 1, 2)))
-void debug(const char *fmt, ...);
-
-extern void append_address_to_payload(const char *addr);
-extern void dump_payload(void);
-extern int dns_resolver(const char *server_name, const char *port);
+#define error(fmt, ...) \
+	do { \
+		_log(stderr, LOG_ERR, "E: " fmt, ##__VA_ARGS__); \
+	} while (0)
+
+/*
+ * Print an error to stderr or the syslog, negate the key being created, and
+ * exit with a generic -1 error code.
+ *
+ * On error, negatively instantiate the key ourselves so that we can
+ * make sure the kernel doesn't hang it off of a searchable keyring
+ * and interfere with the next attempt to instantiate the key.
+ */
+#define error_ex(fmt, ...) \
+	do { \
+		_log(stderr, LOG_ERR, fmt, ##__VA_ARGS__); \
+		if (!debug_mode) \
+			keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT); \
+		exit(-1); \
+	} while (0)
+
+#define error_oom() error_ex("%s: out of memory", __func__)
+
+/*
+ * Print a warning to stderr or the syslog
+ */
+#define warn(fmt, ...) \
+	do { \
+		_log(stderr, LOG_WARNING, "W: " fmt, ##__VA_ARGS__); \
+	} while (0)
+
+/*
+ * Print status information
+ */
+#define info(fmt, ...) \
+	do { \
+		if (verbose >= 1) \
+			_log(stdout, LOG_INFO, "I: " fmt, ##__VA_ARGS__); \
+	} while (0)
 
 /*
- * dns.afsdb.c
+ * Print debugging information
  */
-extern __attribute__((noreturn))
-void afs_look_up_VL_servers(const char *cell, char *options);
+#define debug(fmt, ...) \
+	do { \
+		if (verbose >= 2) \
+			_log(stdout, LOG_DEBUG, "D: " fmt, ##__VA_ARGS__); \
+	} while (0)
+
+/*
+ * Print nameserver warning
+ */
+#define nsWarn(fmt, ...) \
+	do { \
+		if (isatty(2)) \
+			_log(stderr, LOG_WARNING, "NS: W: " fmt, ##__VA_ARGS__); \
+		else \
+			_log(stderr, LOG_WARNING, fmt, ##__VA_ARGS__); \
+	} while (0)
+
+#define STRNDUP_CHECK(dst, src, len) \
+	do { \
+		(dst) = strndup(src, len); \
+		if (!(dst)) \
+			error_oom(); \
+	} while (0)
+
+#define CALLOC_CHECK(dst, count, size) \
+	do { \
+		(dst) = calloc(count, size); \
+		if (!(dst)) \
+			error_oom(); \
+	} while (0)
+
+static inline const char *str_type(ns_type type)
+{
+	if (type == ns_t_invalid)
+		return "invalid";
+
+	if (type == ns_t_any)
+		return "A/AAAA";
+
+	/* p_type is deprecated in glibc >2.34 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+	return p_type(type);
+#pragma GCC diagnostic pop
+}
+
+/* data to be sent through keyctl */
+struct payload {
+	struct iovec data[MAX_PAYLOAD];
+	int index;
+};
+
+typedef struct payload payload_t;
+
+/* Structure to easily pass around host information */
+struct hostinfo {
+	char *hostname;
+	ns_type type;
+	char *addrs[MAX_ADDRS];
+	int naddrs;
+	char port[10];
+	int af;
+	long ttl;
+	ns_msg *handle;
+	bool single_addr;
+};
+
+typedef struct hostinfo hostinfo_t;
+
+static inline void dump_host(hostinfo_t *host)
+{
+	int i = 0;
+
+	if (!host)
+		return;
+
+	debug("Host info:");
+	debug("  hostname: %s", host->hostname);
+	debug("  type: %s", (host->type == ns_t_a || host->type == ns_t_aaaa) ?
+			    "A/AAAA" : str_type(host->type));
+	for (i = 0; i < host->naddrs; i++)
+		debug("  addr[%d]: %s", i, host->addrs[i]);
+	if (host->port[0] != '\0')
+		debug("  port: %s", host->port+1);
+	/* skip af; it's used only internally for resolving and could contain
+	 * incoherent information */
+	debug("  ttl: %ld", host->ttl);
+}
+
+static inline void free_hostinfo(hostinfo_t *host)
+{
+	int i;
+
+	if (!host)
+		return;
+
+	if (host->hostname) {
+		free(host->hostname);
+		host->hostname = NULL;
+	}
+	for (i = 0; i < host->naddrs; i++) {
+		if (host->addrs[i]) {
+			free(host->addrs[i]);
+			host->addrs[i] = NULL;
+		}
+	}
+	if (host->handle) {
+		free(host->handle);
+		host->handle = NULL;
+	}
+}
+
+static inline void free_host(hostinfo_t *host)
+{
+	if (!host)
+		return;
+
+	free_hostinfo(host);
+	free(host);
+	host = NULL;
+}
+
+extern void parse_opts(hostinfo_t *host, char *options);
+void dump_payload(payload_t *payload);
+
+/* Main function for DNS module */
+int dns_resolver(hostinfo_t *host, payload_t *payload);
+
+/* AFS-specific DNS query function */
+void afs_lookup_VL_servers(const char *cell, char *options);
+
+#endif /* _KEY_DNS_H */
diff --git a/key.dns_resolver.c b/key.dns_resolver.c
index 7a7ec4241ea0..0c680caf98cc 100644
--- a/key.dns_resolver.c
+++ b/key.dns_resolver.c
@@ -49,399 +49,138 @@ static const char afsdb_query_type[] = "afsdb";
 static const char *config_file = "/etc/keyutils/key.dns_resolver.conf";
 static bool config_specified = false;
 key_serial_t key;
-static int verbose;
+int verbose;
 int debug_mode;
-unsigned mask = INET_ALL;
-unsigned int key_expiry = 5;
+long key_expiry = DEFAULT_KEY_TTL;
 
-
-/*
- * segmental payload
- */
-struct iovec payload[N_PAYLOAD];
-int payload_index;
-
-/*
- * Print an error to stderr or the syslog, negate the key being created and
- * exit
- */
-void error(const char *fmt, ...)
-{
-	va_list va;
-
-	va_start(va, fmt);
-	if (isatty(2)) {
-		vfprintf(stderr, fmt, va);
-		fputc('\n', stderr);
-	} else {
-		vsyslog(LOG_ERR, fmt, va);
-	}
-	va_end(va);
-
-	/*
-	 * on error, negatively instantiate the key ourselves so that we can
-	 * make sure the kernel doesn't hang it off of a searchable keyring
-	 * and interfere with the next attempt to instantiate the key.
-	 */
-	if (!debug_mode)
-		keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT);
-
-	exit(1);
-}
-
-#define error(FMT, ...) error("Error: " FMT, ##__VA_ARGS__);
-
-/*
- * Just print an error to stderr or the syslog
- */
-void _error(const char *fmt, ...)
-{
-	va_list va;
-
-	va_start(va, fmt);
-	if (isatty(2)) {
-		vfprintf(stderr, fmt, va);
-		fputc('\n', stderr);
-	} else {
-		vsyslog(LOG_ERR, fmt, va);
-	}
-	va_end(va);
-}
-
-/*
- * Print a warning to stderr or the syslog
- */
-void warning(const char *fmt, ...)
-{
-	va_list va;
-
-	va_start(va, fmt);
-	if (isatty(2)) {
-		vfprintf(stderr, fmt, va);
-		fputc('\n', stderr);
-	} else {
-		vsyslog(LOG_WARNING, fmt, va);
-	}
-	va_end(va);
-}
-
-/*
- * Print status information
- */
-void info(const char *fmt, ...)
-{
-	va_list va;
-
-	if (verbose < 1)
-		return;
-
-	va_start(va, fmt);
-	if (isatty(1)) {
-		fputs("I: ", stdout);
-		vfprintf(stdout, fmt, va);
-		fputc('\n', stdout);
-	} else {
-		vsyslog(LOG_INFO, fmt, va);
-	}
-	va_end(va);
-}
-
-/*
- * Print a nameserver error and exit
- */
-static const int ns_errno_map[] = {
-	[0]			= ECONNREFUSED,
-	[HOST_NOT_FOUND]	= ENODATA,
-	[TRY_AGAIN]		= EAGAIN,
-	[NO_RECOVERY]		= ECONNREFUSED,
-	[NO_DATA]		= ENODATA,
-};
-
-void _nsError(int err, const char *domain)
-{
-	if (isatty(2))
-		fprintf(stderr, "NS:%s: %s.\n", domain, hstrerror(err));
-	else
-		syslog(LOG_INFO, "%s: %s", domain, hstrerror(err));
-
-	if (err >= sizeof(ns_errno_map) / sizeof(ns_errno_map[0]))
-		err = ECONNREFUSED;
-	else
-		err = ns_errno_map[err];
-
-	info("Reject the key with error %d", err);
-}
-
-void nsError(int err, const char *domain)
-{
-	unsigned timeout;
-	int ret;
-
-	_nsError(err, domain);
-
-	switch (err) {
-	case TRY_AGAIN:
-		timeout = 1;
-		break;
-	case 0:
-	case NO_RECOVERY:
-		timeout = 10;
-		break;
-	default:
-		timeout = 1 * 60;
-		break;
-	}
-
-	if (!debug_mode) {
-		ret = keyctl_reject(key, timeout, err, KEY_REQKEY_DEFL_DEFAULT);
-		if (ret == -1)
-			error("%s: keyctl_reject: %m", __func__);
-	}
-	exit(0);
-}
-
-/*
- * Print debugging information
- */
-void debug(const char *fmt, ...)
+void parse_opts(hostinfo_t *host, char *options)
 {
-	va_list va;
+	char *k, *val;
+	bool invalid;
 
-	if (verbose < 2)
+	if (!host || !options)
 		return;
 
-	va_start(va, fmt);
-	if (isatty(1)) {
-		fputs("D: ", stdout);
-		vfprintf(stdout, fmt, va);
-		fputc('\n', stdout);
-	} else {
-		vsyslog(LOG_DEBUG, fmt, va);
-	}
-	va_end(va);
-}
-
-/*
- * Append an address to the payload segment list
- */
-void append_address_to_payload(const char *addr)
-{
-	size_t sz = strlen(addr);
-	char *copy;
-	int loop;
-
-	debug("append '%s'", addr);
-
-	if (payload_index + 2 > N_PAYLOAD - 1)
-		return;
-
-	/* discard duplicates */
-	for (loop = 0; loop < payload_index; loop++)
-		if (payload[loop].iov_len == sz &&
-		    memcmp(payload[loop].iov_base, addr, sz) == 0)
-			return;
-
-	copy = malloc(sz);
-	if (!copy)
-		error("%s: malloc: %m", __func__);
-	memcpy(copy, addr, sz);
-
-	if (payload_index != 0) {
-		payload[payload_index  ].iov_base = ",";
-		payload[payload_index++].iov_len = 1;
-	}
-	payload[payload_index  ].iov_base = copy;
-	payload[payload_index++].iov_len = sz;
-}
-
-/*
- * Dump the payload when debugging
- */
-void dump_payload(void)
-{
-	size_t plen, n;
-	char *buf, *p;
-	int loop;
-
-	if (debug_mode)
-		verbose = 1;
-	if (verbose < 1)
-		return;
-
-	plen = 0;
-	for (loop = 0; loop < payload_index; loop++) {
-		n = payload[loop].iov_len;
-		debug("seg[%d]: %zu", loop, n);
-		plen += n;
-	}
-	if (plen == 0) {
-		info("The key instantiation data is empty");
-		return;
-	}
-
-	debug("total: %zu", plen);
-	buf = malloc(plen + 1);
-	if (!buf)
-		return;
-
-	p = buf;
-	for (loop = 0; loop < payload_index; loop++) {
-		n = payload[loop].iov_len;
-		memcpy(p, payload[loop].iov_base, n);
-		p += n;
-	}
-
-	info("The key instantiation data is '%s'", buf);
-	info("The expiry time is %us", key_expiry);
-	free(buf);
-}
-
-/*
- * Perform address resolution on a hostname and add the resulting address as a
- * string to the list of payload segments.
- */
-int dns_resolver(const char *server_name, const char *port)
-{
-	struct addrinfo hints, *addr, *ai;
-	char buf[INET6_ADDRSTRLEN + 8 + 1];
-	int ret, len;
-	void *sa;
-
-	debug("Resolve '%s' with %x", server_name, mask);
-
-	memset(&hints, 0, sizeof(hints));
-	switch (mask & INET_ALL) {
-	case INET_IP4_ONLY:	hints.ai_family = AF_INET;	debug("IPv4"); break;
-	case INET_IP6_ONLY:	hints.ai_family = AF_INET6;	debug("IPv6"); break;
-	default: break;
-	}
-
-	/* resolve name to ip */
-	ret = getaddrinfo(server_name, NULL, &hints, &addr);
-	if (ret) {
-		info("unable to resolve hostname: %s [%s]",
-		     server_name, gai_strerror(ret));
-		return -1;
-	}
-
-	for (ai = addr; ai; ai = ai->ai_next) {
-		debug("RR: %x,%x,%x,%x,%x,%s",
-		      ai->ai_flags, ai->ai_family,
-		      ai->ai_socktype, ai->ai_protocol,
-		      ai->ai_addrlen, ai->ai_canonname);
-
-		/* convert address to string */
-		switch (ai->ai_family) {
-		case AF_INET:
-			if (!(mask & INET_IP4_ONLY))
-				continue;
-			sa = &(((struct sockaddr_in *)ai->ai_addr)->sin_addr);
-			len = INET_ADDRSTRLEN;
-			break;
-		case AF_INET6:
-			if (!(mask & INET_IP6_ONLY))
-				continue;
-			sa = &(((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr);
-			len = INET6_ADDRSTRLEN;
-			break;
-		default:
-			debug("Address of unknown family %u", addr->ai_family);
+	do {
+		invalid = false;
+		k = options;
+		options = strchr(options, ' ');
+		if (!options)
+			options = k + strlen(k);
+		else
+			*options++ = '\0';
+		if (!*k)
 			continue;
+		if (strchr(k, ','))
+			error_ex("Option name '%s' contains a comma", k);
+
+		val = strchr(k, '=');
+		if (val)
+			*val++ = '\0';
+
+		if (strcmp(k, "ipv4") == 0) {
+			host->af = AF_INET;
+			host->type = ns_t_a;
+		} else if (strcmp(k, "ipv6") == 0) {
+			host->af = AF_INET6;
+			host->type = ns_t_aaaa;
+		} else if (strcmp(k, "list") == 0) {
+			host->single_addr = false;
+		} else {
+			invalid = true;
 		}
 
-		if (!inet_ntop(ai->ai_family, sa, buf, len))
-			error("%s: inet_ntop: %m", __func__);
-
-		if (port)
-			strcat(buf, port);
-		append_address_to_payload(buf);
-		if (mask & ONE_ADDR_ONLY)
-			break;
-	}
-
-	freeaddrinfo(addr);
-	return 0;
+		if (invalid && !val)
+			warn("Skipping invalid opt %s", k);
+		else if (invalid && val)
+			warn("Skipping invalid opt %s=%s", k, val);
+		else if (val)
+			debug("Opt %s=%s", k, val);
+		else
+			debug("Opt %s", k);
+	} while (*options);
 }
 
 /*
  * Look up a A and/or AAAA records to get host addresses
  *
- * The callout_info is parsed for request options.  For instance, "ipv4" to
- * request only IPv4 addresses, "ipv6" to request only IPv6 addresses and
- * "list" to get multiple addresses.
+ * @hostname: hostname to query for
+ * @options is parsed for request options:
+ *   "ipv4": to request only IPv4 addresses
+ *   "ipv6": to request only IPv6 addresses
+ *   "list": to get multiple addresses
  */
 static __attribute__((noreturn))
 int dns_query_a_or_aaaa(const char *hostname, char *options)
 {
-	int ret;
+	payload_t *payload = NULL;
+	hostinfo_t host = { 0 };
+	int ret = 0;
 
-	debug("Get A/AAAA RR for hostname:'%s', options:'%s'",
-	      hostname, options);
+	if (!hostname)
+		error_ex("%s: missing hostname", __func__);
 
-	if (!options[0]) {
-		/* legacy mode */
-		mask = INET_IP4_ONLY | ONE_ADDR_ONLY;
-	} else {
-		char *k, *val;
-
-		mask = INET_ALL | ONE_ADDR_ONLY;
-
-		do {
-			k = options;
-			options = strchr(options, ' ');
-			if (!options)
-				options = k + strlen(k);
-			else
-				*options++ = '\0';
-			if (!*k)
-				continue;
-			if (strchr(k, ','))
-				error("Option name '%s' contains a comma", k);
-
-			val = strchr(k, '=');
-			if (val)
-				*val++ = '\0';
+	debug("Query A/AAAA records for hostname:'%s', options:'%s'",
+	      hostname, options);
 
-			debug("Opt %s", k);
+	host.af = AF_UNSPEC;
+	host.single_addr = true;
+	host.type = ns_t_any;
 
-			if (strcmp(k, "ipv4") == 0) {
-				mask &= ~INET_ALL;
-				mask |= INET_IP4_ONLY;
-			} else if (strcmp(k, "ipv6") == 0) {
-				mask &= ~INET_ALL;
-				mask |= INET_IP6_ONLY;
-			} else if (strcmp(k, "list") == 0) {
-				mask &= ~ONE_ADDR_ONLY;
-			}
+	parse_opts(&host, options);
 
-		} while (*options);
-	}
+	CALLOC_CHECK(payload, 1, sizeof(payload_t));
+	STRNDUP_CHECK(host.hostname, hostname, strlen(hostname));
+	ret = h_errno = 0;
 
 	/* Turn the hostname into IP addresses */
-	ret = dns_resolver(hostname, NULL);
-	if (ret)
-		nsError(NO_DATA, hostname);
+	ret = dns_resolver(&host, payload);
 
 	/* handle a lack of results */
-	if (payload_index == 0)
-		nsError(NO_DATA, hostname);
+	if (ret || payload->index == 0) {
+		ret = get_err(0);
+		goto out_free;
+	}
 
-	/* must include a NUL char at the end of the payload */
-	payload[payload_index].iov_base = "";
-	payload[payload_index++].iov_len = 1;
-	dump_payload();
+	dump_payload(payload);
+	info("Key timeout will be %ld seconds", host.ttl);
 
 	/* load the key with data key */
 	if (!debug_mode) {
-		ret = keyctl_set_timeout(key, key_expiry);
-		if (ret == -1)
+		unsigned int ttl;
+
+		/*
+		 * If TTL was set through the config file (key_expiry),
+		 * it takes precedence over the one from the DNS record (stored
+		 * in host.ttl).
+		 */
+		if (key_expiry > -1)
+			ttl = (unsigned int)key_expiry;
+		else if (host.ttl > -1)
+			ttl = (unsigned int)host.ttl;
+		else
+			/* Fallback to default value if dns_resolver() couldn't
+			 * get TTL for some reason */
+			ttl = DEFAULT_KEY_TTL;
+
+		ret = keyctl_set_timeout(key, ttl);
+		if (ret) {
 			error("%s: keyctl_set_timeout: %m", __func__);
-		ret = keyctl_instantiate_iov(key, payload, payload_index, 0);
+			goto out_free;
+		}
+
+		info("Key set to timeout in %u seconds", ttl);
+
+		ret = keyctl_instantiate_iov(key, payload->data, payload->index, 0);
 		if (ret == -1)
 			error("%s: keyctl_instantiate: %m", __func__);
 	}
 
-	exit(0);
+out_free:
+	free_hostinfo(&host);
+	free(payload);
+
+	exit(ret);
 }
 
 /*
@@ -451,7 +190,8 @@ static void read_config(void)
 {
 	FILE *f;
 	char buf[4096], *b, *p, *k, *v;
-	unsigned int line = 0, u;
+	unsigned int line = 0;
+	long u;
 	int n;
 
 	info("READ CONFIG %s", config_file);
@@ -555,15 +295,15 @@ static void read_config(void)
 		if (strcmp(k, "default_ttl") == 0) {
 			if (!v)
 				goto missing_value;
-			if (sscanf(v, "%u%n", &u, &n) != 1)
+			if (sscanf(v, "%ld%n", &u, &n) != 1)
 				goto bad_value;
 			if (v[n])
 				goto extra_data;
-			if (u < 1 || u > INT_MAX)
+			if (u < 1 || u > LONG_MAX)
 				goto out_of_range;
 			key_expiry = u;
 		} else {
-			warning("%s:%u: Unknown option '%s'", config_file, line, k);
+			warn("%s:%u: Unknown option '%s'", config_file, line, k);
 		}
 	}
 
@@ -591,7 +331,7 @@ out_of_range:
 static __attribute__((noreturn))
 void config_dumper(void)
 {
-	printf("default_ttl = %u\n", key_expiry);
+	printf("default_ttl = %ld\n", key_expiry);
 	exit(0);
 }
 
@@ -634,6 +374,8 @@ int main(int argc, char *argv[])
 	char *buf = NULL, *name;
 	bool dump_config = false;
 
+	key_expiry = -1;
+
 	openlog(prog, 0, LOG_DAEMON);
 
 	while ((ret = getopt_long(argc, argv, "c:vDV", long_options, NULL)) != -1) {
@@ -667,6 +409,7 @@ int main(int argc, char *argv[])
 	argc -= optind;
 	argv += optind;
 	read_config();
+
 	if (dump_config)
 		config_dumper();
 
@@ -676,27 +419,27 @@ int main(int argc, char *argv[])
 
 		/* get the key ID */
 		if (!**argv)
-			error("Invalid blank key ID");
+			error_ex("Invalid blank key ID");
 		key = strtol(*argv, &p, 10);
 		if (*p)
-			error("Invalid key ID format");
+			error_ex("Invalid key ID format");
 
 		/* get the key description (of the form "x;x;x;x;<query_type>:<name>") */
 		ret = keyctl_describe_alloc(key, &buf);
 		if (ret == -1)
-			error("keyctl_describe_alloc failed: %m");
+			error_ex("keyctl_describe_alloc failed: %m");
 
 		/* get the callout_info (which can supply options) */
 		ret = keyctl_read_alloc(KEY_SPEC_REQKEY_AUTH_KEY, (void **)&callout_info);
 		if (ret == -1)
-			error("Invalid key callout_info read: %m");
+			error_ex("Invalid key callout_info read: %m");
 	} else {
 		if (argc != 2)
 			usage();
 
 		ret = asprintf(&buf, "%s;-1;-1;0;%s", key_type, argv[0]);
 		if (ret < 0)
-			error("Error %m");
+			error_ex("Error %m");
 		callout_info = argv[1];
 	}
 
@@ -706,20 +449,20 @@ int main(int argc, char *argv[])
 
 	p = strchr(buf, ';');
 	if (!p)
-		error("Badly formatted key description '%s'", buf);
+		error_ex("Badly formatted key description '%s'", buf);
 	ktlen = p - buf;
 
 	/* make sure it's the type we are expecting */
 	if (ktlen != sizeof(key_type) - 1 ||
 	    memcmp(buf, key_type, ktlen) != 0)
-		error("Key type is not supported: '%*.*s'", ktlen, ktlen, buf);
+		error_ex("Key type is not supported: '%*.*s'", ktlen, ktlen, buf);
 
 	keyend = buf + ktlen + 1;
 
 	/* the actual key description follows the last semicolon */
 	keyend = rindex(keyend, ';');
 	if (!keyend)
-		error("Invalid key description: %s", buf);
+		error_ex("Invalid key description: %s", buf);
 	keyend++;
 
 	name = index(keyend, ':');
@@ -736,7 +479,7 @@ int main(int argc, char *argv[])
 	    (qtlen == sizeof(aaaa_query_type) - 1 &&
 	     memcmp(keyend, aaaa_query_type, sizeof(aaaa_query_type) - 1) == 0)
 	    ) {
-		info("Do DNS query of A/AAAA type for:'%s' mask:'%s'",
+		info("Do DNS query of A/AAAA type for '%s', with options '%s'",
 		     name, callout_info);
 		dns_query_a_or_aaaa(name, callout_info);
 	}
@@ -744,10 +487,11 @@ int main(int argc, char *argv[])
 	if (qtlen == sizeof(afsdb_query_type) - 1 &&
 	    memcmp(keyend, afsdb_query_type, sizeof(afsdb_query_type) - 1) == 0
 	    ) {
-		info("Do AFS VL server query for:'%s' mask:'%s'",
+		info("Do AFS VL server query for '%s', with options '%s'",
 		     name, callout_info);
-		afs_look_up_VL_servers(name, callout_info);
+		afs_lookup_VL_servers(name, callout_info);
 	}
 
 	error("Query type: \"%*.*s\" is not supported", qtlen, qtlen, keyend);
+	exit(-EINVAL);
 }
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/3] key.dns_resolver: refactor read_config()
  2022-07-07 19:15 [PATCH 0/3] keyutils: create a common DNS interface Enzo Matsumiya
  2022-07-07 19:15 ` [PATCH 1/3] key.dns: " Enzo Matsumiya
@ 2022-07-07 19:15 ` Enzo Matsumiya
  2022-07-07 19:15 ` [PATCH 3/3] key.dns: allow to use custom nameservers Enzo Matsumiya
  2 siblings, 0 replies; 4+ messages in thread
From: Enzo Matsumiya @ 2022-07-07 19:15 UTC (permalink / raw)
  To: dhowells
  Cc: linux-cifs, smfrench, pc, ronniesahlberg, nspmangalore, Enzo Matsumiya

Create a key_dns_conf_t struct to hold config values (*).
Rework read_config() to take a config file path and return a
key_dns_conf_t. This avoids globals and create a structured way to map
between config file values and code.

(*) The only config option is still default_ttl

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 dns.afsdb.c        |  40 ++++----
 key.dns.h          |   2 +-
 key.dns_resolver.c | 222 ++++++++++++++++++++++++++++-----------------
 3 files changed, 161 insertions(+), 103 deletions(-)

diff --git a/dns.afsdb.c b/dns.afsdb.c
index 2241cf58e55c..806ae85c5632 100644
--- a/dns.afsdb.c
+++ b/dns.afsdb.c
@@ -63,11 +63,12 @@ static int afs_instantiate(payload_t *payload, unsigned int ttl)
  * Lookup VL servers for AFS.
  */
 __attribute__((noreturn))
-void afs_lookup_VL_servers(const char *cell, char *options)
+void afs_lookup_VL_servers(const char *cell, char *options, long config_ttl)
 {
 	payload_t *payload = NULL;
 	struct hostinfo host = { 0 };
 	char *vlsrv_name = NULL;
+	unsigned int ttl;
 	int ret = 0;
 
 	if (!cell)
@@ -107,28 +108,25 @@ void afs_lookup_VL_servers(const char *cell, char *options)
 	}
 
 	dump_payload(payload);
-	info("Key timeout will be %ld seconds", host.ttl);
-
-	if (!debug_mode) {
-		unsigned int ttl;
-
-		/*
-		 * If TTL was set through the config file (key_expiry),
-		 * it takes precedence over the one from the DNS record (stored
-		 * in host.ttl).
-		 */
-		if (key_expiry > -1)
-			ttl = (unsigned int)key_expiry;
-		else if (host.ttl > -1)
-			ttl = (unsigned int)host.ttl;
-		else
-			/* Fallback to default value if dns_resolver() couldn't
-			 * get TTL for some reason */
-			ttl = DEFAULT_KEY_TTL;
 
+	/*
+	 * If TTL was set through the config file (@config_ttl),
+	 * it takes precedence over the one from the DNS record (stored
+	 * in host.ttl).
+	 */
+	if (config_ttl > 0)
+		ttl = (unsigned int)config_ttl;
+	else if (host.ttl > 0)
+		ttl = (unsigned int)host.ttl;
+	else
+		/* Fallback to default value if dns_resolver() couldn't
+		 * get TTL for some reason */
+		ttl = DEFAULT_KEY_TTL;
+
+	info("Key timeout will be %u seconds", ttl);
+
+	if (!debug_mode)
 		ret = afs_instantiate(payload, ttl);
-	}
-
 out_free:
 	free_hostinfo(&host);
 	free(payload);
diff --git a/key.dns.h b/key.dns.h
index f522a51b92ce..25a815b82159 100644
--- a/key.dns.h
+++ b/key.dns.h
@@ -246,6 +246,6 @@ void dump_payload(payload_t *payload);
 int dns_resolver(hostinfo_t *host, payload_t *payload);
 
 /* AFS-specific DNS query function */
-void afs_lookup_VL_servers(const char *cell, char *options);
+void afs_lookup_VL_servers(const char *cell, char *options, long config_ttl);
 
 #endif /* _KEY_DNS_H */
diff --git a/key.dns_resolver.c b/key.dns_resolver.c
index 0c680caf98cc..8da7da6858a0 100644
--- a/key.dns_resolver.c
+++ b/key.dns_resolver.c
@@ -40,18 +40,27 @@
  */
 #include "key.dns.h"
 
+#define DEFAULT_CONFIG_FILE "/etc/keyutils/key.dns_resolver.conf"
+
 static const char *DNS_PARSE_VERSION = "1.0";
 static const char prog[] = "key.dns_resolver";
 static const char key_type[] = "dns_resolver";
 static const char a_query_type[] = "a";
 static const char aaaa_query_type[] = "aaaa";
 static const char afsdb_query_type[] = "afsdb";
-static const char *config_file = "/etc/keyutils/key.dns_resolver.conf";
-static bool config_specified = false;
 key_serial_t key;
 int verbose;
 int debug_mode;
-long key_expiry = DEFAULT_KEY_TTL;
+
+/*
+ * key.dns_resolver.conf struct
+ *
+ * XXX: if this ever grows too big, move to another file
+ */
+typedef struct _key_dns_conf {
+	long default_ttl;
+} key_dns_conf_t;
+
 
 void parse_opts(hostinfo_t *host, char *options)
 {
@@ -109,12 +118,17 @@ void parse_opts(hostinfo_t *host, char *options)
  *   "ipv4": to request only IPv4 addresses
  *   "ipv6": to request only IPv6 addresses
  *   "list": to get multiple addresses
+ * @config_ttl: TTL gotten from key.dns_resolver.conf (callers must set this to
+ *		-1 if no config)
+ *		XXX: might have to change this to a key_dns_conf_t if config
+ *		options increase.
  */
 static __attribute__((noreturn))
-int dns_query_a_or_aaaa(const char *hostname, char *options)
+int dns_query_a_or_aaaa(const char *hostname, char *options, long config_ttl)
 {
 	payload_t *payload = NULL;
 	hostinfo_t host = { 0 };
+	unsigned int ttl;
 	int ret = 0;
 
 	if (!hostname)
@@ -143,34 +157,31 @@ int dns_query_a_or_aaaa(const char *hostname, char *options)
 	}
 
 	dump_payload(payload);
-	info("Key timeout will be %ld seconds", host.ttl);
+
+	/*
+	 * If TTL was set through the config file (@config_ttl),
+	 * it takes precedence over the one from the DNS record (stored
+	 * in host.ttl).
+	 */
+	if (config_ttl > 0)
+		ttl = (unsigned int)config_ttl;
+	else if (host.ttl > 0)
+		ttl = (unsigned int)host.ttl;
+	else
+		/* Fallback to default value if dns_resolver() couldn't
+		 * get TTL for some reason */
+		ttl = DEFAULT_KEY_TTL;
+
+	info("Key timeout will be %u seconds", ttl);
 
 	/* load the key with data key */
 	if (!debug_mode) {
-		unsigned int ttl;
-
-		/*
-		 * If TTL was set through the config file (key_expiry),
-		 * it takes precedence over the one from the DNS record (stored
-		 * in host.ttl).
-		 */
-		if (key_expiry > -1)
-			ttl = (unsigned int)key_expiry;
-		else if (host.ttl > -1)
-			ttl = (unsigned int)host.ttl;
-		else
-			/* Fallback to default value if dns_resolver() couldn't
-			 * get TTL for some reason */
-			ttl = DEFAULT_KEY_TTL;
-
 		ret = keyctl_set_timeout(key, ttl);
 		if (ret) {
 			error("%s: keyctl_set_timeout: %m", __func__);
 			goto out_free;
 		}
 
-		info("Key set to timeout in %u seconds", ttl);
-
 		ret = keyctl_instantiate_iov(key, payload->data, payload->index, 0);
 		if (ret == -1)
 			error("%s: keyctl_instantiate: %m", __func__);
@@ -185,26 +196,42 @@ out_free:
 
 /*
  * Read the config file.
+ *
+ * @config_file: absolute path to the config file to use
+ *
+ * Returns a key_dns_conf_t on success, NULL otherwise (errno is set to
+ * something meaningful). Must be freed by the caller.
  */
-static void read_config(void)
+static key_dns_conf_t *read_config(const char *config_file)
 {
+	key_dns_conf_t *config = NULL;
 	FILE *f;
 	char buf[4096], *b, *p, *k, *v;
 	unsigned int line = 0;
 	long u;
 	int n;
 
-	info("READ CONFIG %s", config_file);
+	if (!config_file) {
+		error("Missing config file");
+		errno = -EINVAL;
+		return NULL;
+	}
+
+	info("Reading config %s", config_file);
+
+	CALLOC_CHECK(config, 1, sizeof(key_dns_conf_t));
 
 	f = fopen(config_file, "r");
 	if (!f) {
-		if (errno == ENOENT && !config_specified) {
-			debug("%s: %m", config_file);
-			return;
-		}
 		error("%s: %m", config_file);
+		goto out;
 	}
 
+#define cfgerr(msg) error("%s:%u: " msg, config_file, line)
+#define cfgerr_k(msg) error("%s:%u: %s: " msg, config_file, line, k)
+#define cfgerr_v(msg) error("%s:%u: %s: " msg " '%s'", config_file, line, k, v)
+
+	errno = -EINVAL;
 	while (fgets(buf, sizeof(buf) - 1, f)) {
 		line++;
 
@@ -217,8 +244,10 @@ static void read_config(void)
 		if (!*b || *b == '#')
 			continue;
 		p = strchr(b, '\n');
-		if (!p)
-			error("%s:%u: line missing newline or too long", config_file, line);
+		if (!p) {
+			cfgerr("line missing newline or too long");
+			goto out;
+		}
 		while (p > buf && isspace(p[-1]))
 			p--;
 		*p = 0;
@@ -231,9 +260,11 @@ static void read_config(void)
 			char quote = 0;
 			bool esc = false;
 
-			if (b == k)
-				error("%s:%u: Unspecified key",
-				      config_file, line);
+			if (b == k) {
+				errno = -EINVAL;
+				cfgerr("Unspecified key");
+				goto out;
+			}
 
 			/* NUL-terminate the key. */
 			for (p = b - 1; isspace(*p); p--)
@@ -244,8 +275,10 @@ static void read_config(void)
 			b++;
 			while (isspace(*b))
 				b++;
-			if (!*b)
-				goto missing_value;
+			if (!*b) {
+				cfgerr_k("Missing value");
+				goto out;
+			}
 
 			if (*b == '"' || *b == '\'') {
 				quote = *b;
@@ -262,7 +295,8 @@ static void read_config(void)
 					case '\\':
 						break;
 					default:
-						goto invalid_escape_char;
+						cfgerr_k("Invalid char in escape");
+						goto out;
 					}
 					esc = false;
 					*p++ = *b++;
@@ -275,8 +309,10 @@ static void read_config(void)
 				}
 				if (*b == quote) {
 					b++;
-					if (*b)
-						goto post_quote_data;
+					if (*b) {
+						cfgerr_k("Data after closing quote");
+						goto out;
+					}
 					quote = 0;
 					break;
 				}
@@ -285,53 +321,64 @@ static void read_config(void)
 				*p++ = *b++;
 			}
 
-			if (esc)
-				error("%s:%u: Incomplete escape", config_file, line);
-			if (quote)
-				error("%s:%u: Unclosed quotes", config_file, line);
+			if (esc) {
+				cfgerr("Incomplete escape");
+				goto out;
+			}
+			if (quote) {
+				cfgerr("Unclosed quotes");
+				goto out;
+			}
 			*p = 0;
 		}
 
 		if (strcmp(k, "default_ttl") == 0) {
-			if (!v)
-				goto missing_value;
-			if (sscanf(v, "%ld%n", &u, &n) != 1)
-				goto bad_value;
-			if (v[n])
-				goto extra_data;
-			if (u < 1 || u > LONG_MAX)
-				goto out_of_range;
-			key_expiry = u;
+			if (!v) {
+				cfgerr_k("Missing value");
+				goto out;
+			}
+			if (sscanf(v, "%ld%n", &u, &n) != 1) {
+				cfgerr_v("Bad value");
+				goto out;
+			}
+			if (v[n]) {
+				cfgerr_k("Extra data supplied");
+				goto out;
+			}
+			if (u < 1 || u > LONG_MAX) {
+				cfgerr_k("Value out of range");
+				goto out;
+			}
+			config->default_ttl = u;
 		} else {
 			warn("%s:%u: Unknown option '%s'", config_file, line, k);
 		}
 	}
 
-	if (ferror(f) || fclose(f) == EOF)
+	if (ferror(f) || fclose(f) == EOF) {
 		error("%s: %m", config_file);
-	return;
-
-missing_value:
-	error("%s:%u: %s: Missing value", config_file, line, k);
-invalid_escape_char:
-	error("%s:%u: %s: Invalid char in escape", config_file, line, k);
-post_quote_data:
-	error("%s:%u: %s: Data after closing quote", config_file, line, k);
-bad_value:
-	error("%s:%u: %s: Bad value", config_file, line, k);
-extra_data:
-	error("%s:%u: %s: Extra data supplied", config_file, line, k);
-out_of_range:
-	error("%s:%u: %s: Value out of range", config_file, line, k);
+		goto out;
+	}
+
+	errno = 0;
+	return config;
+out:
+	free(config);
+	return NULL;
 }
 
 /*
- * Dump the configuration after parsing the config file.
+ * Dump the configuration to stdout after parsing the config file.
  */
 static __attribute__((noreturn))
-void config_dumper(void)
+void dump_config(key_dns_conf_t *config)
 {
-	printf("default_ttl = %ld\n", key_expiry);
+	if (!config) {
+		error("No config loaded");
+		exit(-EINVAL);
+	}
+
+	printf("default_ttl = %ld\n", config->default_ttl);
 	exit(0);
 }
 
@@ -372,9 +419,10 @@ int main(int argc, char *argv[])
 	char *keyend, *p;
 	char *callout_info = NULL;
 	char *buf = NULL, *name;
-	bool dump_config = false;
-
-	key_expiry = -1;
+	bool opt_dump_config = false;
+	key_dns_conf_t *config = NULL;
+	char *config_file = NULL;
+	long ttl = -1;
 
 	openlog(prog, 0, LOG_DAEMON);
 
@@ -382,10 +430,9 @@ int main(int argc, char *argv[])
 		switch (ret) {
 		case 'c':
 			config_file = optarg;
-			config_specified = true;
 			continue;
 		case 2:
-			dump_config = true;
+			opt_dump_config = true;
 			continue;
 		case 'D':
 			debug_mode = 1;
@@ -408,10 +455,23 @@ int main(int argc, char *argv[])
 
 	argc -= optind;
 	argv += optind;
-	read_config();
 
-	if (dump_config)
-		config_dumper();
+	if (config_file)
+		config = read_config(config_file);
+	else
+		/* try to open the default config only if there wasn't one explicitly specified */
+		config = read_config(DEFAULT_CONFIG_FILE);
+
+	if (opt_dump_config)
+		dump_config(config);
+
+	if (config) {
+		ttl = config->default_ttl;
+		free(config);
+	} else {
+		info("No config file loaded. Using default values and/or from "
+		     "DNS records.");
+	}
 
 	if (!debug_mode) {
 		if (argc != 1)
@@ -467,7 +527,7 @@ int main(int argc, char *argv[])
 
 	name = index(keyend, ':');
 	if (!name)
-		dns_query_a_or_aaaa(keyend, callout_info);
+		dns_query_a_or_aaaa(keyend, callout_info, ttl);
 
 	qtlen = name - keyend;
 	name++;
@@ -481,7 +541,7 @@ int main(int argc, char *argv[])
 	    ) {
 		info("Do DNS query of A/AAAA type for '%s', with options '%s'",
 		     name, callout_info);
-		dns_query_a_or_aaaa(name, callout_info);
+		dns_query_a_or_aaaa(name, callout_info, ttl);
 	}
 
 	if (qtlen == sizeof(afsdb_query_type) - 1 &&
@@ -489,7 +549,7 @@ int main(int argc, char *argv[])
 	    ) {
 		info("Do AFS VL server query for '%s', with options '%s'",
 		     name, callout_info);
-		afs_lookup_VL_servers(name, callout_info);
+		afs_lookup_VL_servers(name, callout_info, ttl);
 	}
 
 	error("Query type: \"%*.*s\" is not supported", qtlen, qtlen, keyend);
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 3/3] key.dns: allow to use custom nameservers
  2022-07-07 19:15 [PATCH 0/3] keyutils: create a common DNS interface Enzo Matsumiya
  2022-07-07 19:15 ` [PATCH 1/3] key.dns: " Enzo Matsumiya
  2022-07-07 19:15 ` [PATCH 2/3] key.dns_resolver: refactor read_config() Enzo Matsumiya
@ 2022-07-07 19:15 ` Enzo Matsumiya
  2 siblings, 0 replies; 4+ messages in thread
From: Enzo Matsumiya @ 2022-07-07 19:15 UTC (permalink / raw)
  To: dhowells
  Cc: linux-cifs, smfrench, pc, ronniesahlberg, nspmangalore, Enzo Matsumiya

This commit allows the user to use custom nameservers for their
key.dns_resolver queries.

It can be achieved by passing the "ns=ADDR" option as the callout info
for dns_resolver(). Both IPv4 and IPv6 are supported. A maximum of 3
nameservers is allowed, as a libresolv limitation.

Each nameserver will be queried only once for the hostname specified.
Upon success (hostname was resolved), the information is returned and
no further queries are made to the remaining nameservers.

If all specified nameservers fail (either unreachable or can't resolve
the hostname), there's no fallback to using system's default. Callers
must adapt their calls to remove the failing nameservers from their
callout info and query again.

If no nameservers are passed, the system's default will be used (just
like before this patch).

Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de>
---
 dns.afsdb.c        |   2 +
 key.dns.c          | 107 ++++++++++++++++++++++++++++++++++++++++++---
 key.dns.h          |   5 +++
 key.dns_resolver.c |  41 +++++++++++++++--
 4 files changed, 146 insertions(+), 9 deletions(-)

diff --git a/dns.afsdb.c b/dns.afsdb.c
index 806ae85c5632..7e18b969bc7f 100644
--- a/dns.afsdb.c
+++ b/dns.afsdb.c
@@ -129,6 +129,8 @@ void afs_lookup_VL_servers(const char *cell, char *options, long config_ttl)
 		ret = afs_instantiate(payload, ttl);
 out_free:
 	free_hostinfo(&host);
+	while (host.nslen-- > 0)
+		free(host.nameservers[host.nslen]);
 	free(payload);
 
 	exit(ret);
diff --git a/key.dns.c b/key.dns.c
index 6390f3118a6e..39ef62fa4847 100644
--- a/key.dns.c
+++ b/key.dns.c
@@ -168,6 +168,19 @@ void dump_payload(payload_t *payload)
 	free(buf);
 }
 
+static int get_af_from_addr(const char *addr)
+{
+	char buf[MAX_ADDR_LEN];
+	int ret;
+
+	if ((ret =inet_pton(AF_INET, addr, buf)))
+		return AF_INET;
+	else if ((ret = inet_pton(AF_INET6, addr, buf)))
+		return AF_INET6;
+
+	return (!ret) ? -EINVAL : -EAFNOSUPPORT;
+}
+
 static char *get_addr_str(int af, char *s, const void *data)
 {
 	switch (af) {
@@ -377,6 +390,7 @@ static int parse_rr(hostinfo_t *host, ns_rr rr)
 	const unsigned char *rdata;
 	unsigned short prio, weight, port; /* for ns_t_srv */
 	int subtype = 0; /* for ns_t_afsdb */
+	char *addrbuf = NULL; /* for ns_t_a/aaaa */
 	ns_type rrtype;
 	int ret = 0;
 
@@ -405,16 +419,23 @@ static int parse_rr(hostinfo_t *host, ns_rr rr)
 	case ns_t_a:
 	case ns_t_aaaa:
 		if (host->naddrs == MAX_ADDRS) {
-			warn("Can't add more IP addresses (max '%d' reached)",
+			warn("Can't add more IP addresses, max '%d' reached",
 			     MAX_ADDRS);
-			break;
+			return -ENOMEM;
 		}
 
-		CALLOC_CHECK(host->addrs[host->naddrs], 1, MAX_ADDR_LEN);
-		get_addr_str(ns2af(rrtype), host->addrs[host->naddrs], rdata);
+		CALLOC_CHECK(addrbuf, 1, MAX_ADDR_LEN);
+		get_addr_str(ns2af(rrtype), addrbuf, rdata);
 
-		debug("rdata: addr=%s", host->addrs[host->naddrs]);
+		if (!strcmp(addrbuf, "0.0.0.0") || !strcmp(addrbuf, "::")) {
+			info("Discarding invalid address '%s'", addrbuf);
+			free(addrbuf);
+			return -EINVAL;
+		}
+
+		debug("rdata: addr=%s", addrbuf);
 
+		host->addrs[host->naddrs] = addrbuf;
 		host->naddrs++;
 		break;
 	default:
@@ -460,6 +481,7 @@ static int get_targets(hostinfo_t *host, hostinfo_t **targets, int *maxn)
 
 	for (i = 0; i < *maxn; i++) {
 		hostinfo_t *target = NULL;
+		int ns;
 
 		if (ns_parserr(host->handle, ns_s_an, i, &rr)) {
 			error("ns_parserr failed: %m");
@@ -482,6 +504,9 @@ static int get_targets(hostinfo_t *host, hostinfo_t **targets, int *maxn)
 		target->handle = NULL;
 		target->af = host->af;
 		target->single_addr = host->single_addr;
+		for (ns = 0; ns < host->nslen; ns++)
+			target->nameservers[ns] = host->nameservers[ns];
+		target->nslen = host->nslen;
 
 		n++;
 		continue;
@@ -546,6 +571,43 @@ static int resolve_host(hostinfo_t *host, int n)
 	return ret;
 }
 
+/* Sets a nameserver to use in @sp */
+static int set_ns(res_state sp, char *addr)
+{
+	int ret, af;
+
+	if (!sp || !addr || !strlen(addr))
+		return -EINVAL;
+
+	af = get_af_from_addr(addr);
+	if (af < 0) {
+		nsWarn("Can't convert nameserver address '%s'", addr);
+		return -EINVAL;
+	}
+
+	ret = inet_pton(af, addr, &sp->nsaddr_list[0].sin_addr.s_addr);
+	if (!ret) {
+		nsWarn("Invalid nameserver address '%s'", addr);
+		return -EINVAL;
+	} else if (ret == -1) {
+		nsWarn("Invalid address family '%d'", af);
+		return -EAFNOSUPPORT;
+	}
+
+	sp->nscount = 1;
+	sp->nsaddr_list[0].sin_family = af;
+	sp->nsaddr_list[0].sin_port = htons(53);
+
+	sp->options = RES_DEFAULT;
+	/* RES_DFLRETRY is 2 by default (resolv.h), but the retry logic there
+	 * is different from what we do here; see more below */
+	sp->retry = RES_DFLRETRY;
+
+	debug("using nameserver: %s", addr);
+
+	return 0;
+}
+
 /*
  * Makes the actual query
  *
@@ -559,6 +621,7 @@ static int resolve_host(hostinfo_t *host, int n)
 static int dns_query(hostinfo_t *host)
 {
 	res_state sp;
+	int maxns, ns;
 	int ret, len;
 
 	if (!host)
@@ -579,12 +642,46 @@ static int dns_query(hostinfo_t *host)
 		return -ENODEV;
 	}
 
+	ns = 0;
+	ret = 0;
+	maxns = clamp(host->nslen, 0, MAXNS);
+
+	/*
+	 * The retry logic here for nameserver rotation is based on the answer
+	 * containing 1 or more valid resource records, instead of the NS
+	 * server being down/unreachable (as libresolv does internally).
+	 * So the RES_ROTATE option flag doesn't apply here since we'll be
+	 * trying a single NS a time. Ditto for sp->retry (used for DNS over
+	 * TCP (RES_USEVC)).
+	 */
+retry:
+	/* Find the next suitable nameserver in @host::nameservers */
+	while (ns < maxns && (ret = set_ns(sp, host->nameservers[ns])))
+		ns++;
+
+	if (ret && ns == maxns) {
+		error("Can't use any of the nameservers provided.");
+		ret = get_err(-ECONNREFUSED);
+		goto out;
+	}
+
 	h_errno = 0;
 	/* query the dns for a @type resource record */
 	len = res_nquery(sp, host->hostname, ns_c_in, host->type, answer.buf,
 			 sizeof(answer));
 
 	if (len < 0 || len > NS_MAXMSG) {
+		if (host->nslen > 0) {
+			if (ret == -ECONNREFUSED)
+				error("Can't reach nameserver '%s'",
+				      host->nameservers[ns]);
+			else
+				error("Nameserver '%s' can't resolve '%s'",
+				     host->nameservers[ns], host->hostname);
+			ns++;
+			if (ns < maxns)
+				goto retry;
+		}
 		ret = get_err(-ENODATA);
 		goto out;
 	}
diff --git a/key.dns.h b/key.dns.h
index 25a815b82159..295c7b2241e3 100644
--- a/key.dns.h
+++ b/key.dns.h
@@ -182,6 +182,8 @@ struct hostinfo {
 	long ttl;
 	ns_msg *handle;
 	bool single_addr;
+	char *nameservers[MAXNS];
+	int nslen;
 };
 
 typedef struct hostinfo hostinfo_t;
@@ -204,6 +206,9 @@ static inline void dump_host(hostinfo_t *host)
 	/* skip af; it's used only internally for resolving and could contain
 	 * incoherent information */
 	debug("  ttl: %ld", host->ttl);
+	if (host->nslen > 0)
+		for (i = 0; i < host->nslen; i++)
+			debug("  custom ns[%d]: %s", i, host->nameservers[i]);
 }
 
 static inline void free_hostinfo(hostinfo_t *host)
diff --git a/key.dns_resolver.c b/key.dns_resolver.c
index 8da7da6858a0..325683bdb584 100644
--- a/key.dns_resolver.c
+++ b/key.dns_resolver.c
@@ -65,11 +65,16 @@ typedef struct _key_dns_conf {
 void parse_opts(hostinfo_t *host, char *options)
 {
 	char *k, *val;
+	int ns;
 	bool invalid;
 
 	if (!host || !options)
 		return;
 
+	for (ns = 0; ns < MAXNS; ns++)
+		host->nameservers[ns] = NULL;
+	ns = 0;
+
 	do {
 		invalid = false;
 		k = options;
@@ -95,14 +100,38 @@ void parse_opts(hostinfo_t *host, char *options)
 			host->type = ns_t_aaaa;
 		} else if (strcmp(k, "list") == 0) {
 			host->single_addr = false;
+		} else if (strcmp(k, "ns") == 0) {
+			if (ns < MAXNS) {
+				size_t vlen = strlen(val);
+				if (vlen >= INET6_ADDRSTRLEN) {
+					debug("%s: value too long for ns= key: %s",
+					      __func__, val);
+					invalid = true;
+					goto invalid;
+				}
+				/*
+				 * Blindly copy the specified address here,
+				 * it'll be checked later in dns_query().
+				 * Must be freed by callers.
+				 */
+				STRNDUP_CHECK(host->nameservers[ns], val, vlen);
+				ns++;
+				host->nslen = ns;
+			} else {
+				warn("Max of %d nameservers allowed. Skipping %s=%s",
+				     MAXNS, k, val);
+				continue;
+			}
 		} else {
 			invalid = true;
 		}
 
-		if (invalid && !val)
-			warn("Skipping invalid opt %s", k);
-		else if (invalid && val)
-			warn("Skipping invalid opt %s=%s", k, val);
+invalid:
+		if (invalid)
+			if (val)
+				warn("Skipping invalid opt %s=%s", k, val);
+			else
+				warn("Skipping invalid opt %s", k);
 		else if (val)
 			debug("Opt %s=%s", k, val);
 		else
@@ -118,6 +147,8 @@ void parse_opts(hostinfo_t *host, char *options)
  *   "ipv4": to request only IPv4 addresses
  *   "ipv6": to request only IPv6 addresses
  *   "list": to get multiple addresses
+ *   "ns=ADDR": use ADDR as a custom nameserver. Max of 3 nameservers is
+ *              allowed (MAXNS from libresolv).
  * @config_ttl: TTL gotten from key.dns_resolver.conf (callers must set this to
  *		-1 if no config)
  *		XXX: might have to change this to a key_dns_conf_t if config
@@ -189,6 +220,8 @@ int dns_query_a_or_aaaa(const char *hostname, char *options, long config_ttl)
 
 out_free:
 	free_hostinfo(&host);
+	while (host.nslen-- > 0)
+		free(host.nameservers[host.nslen]);
 	free(payload);
 
 	exit(ret);
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2022-07-07 19:15 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-07 19:15 [PATCH 0/3] keyutils: create a common DNS interface Enzo Matsumiya
2022-07-07 19:15 ` [PATCH 1/3] key.dns: " Enzo Matsumiya
2022-07-07 19:15 ` [PATCH 2/3] key.dns_resolver: refactor read_config() Enzo Matsumiya
2022-07-07 19:15 ` [PATCH 3/3] key.dns: allow to use custom nameservers Enzo Matsumiya

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.