From 75e7568dc516db698093b33ea273e1b4a30b70be Mon Sep 17 00:00:00 2001
From: David Howells <dhowells@redhat.com>
Date: Tue, 14 Apr 2020 16:07:26 +0100
Subject: [PATCH] dns: Apply a default TTL to records obtained from
 getaddrinfo()

[ ematsumiya: the patch has been modified from upstream version to
  accomodate the missing files/context in this older version ]

Address records obtained from getaddrinfo() don't come with any TTL
information, even if they're obtained from the DNS, with the result that
key.dns_resolver upcall program doesn't set an expiry time on dns_resolver
records unless they include a component obtained directly from the DNS,
such as an SRV or AFSDB record.

Fix this to apply a default TTL of 10mins in the event that we haven't got
one.  This can be configured in /etc/keyutils/key.dns_resolver.conf by
adding the line:

	default_ttl = <number-of-seconds>

to the file.

Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Ben Boeckel <me@benboeckel.net>
Reviewed-by: Jeff Layton <jlayton@kernel.org>

--- a/Makefile
+++ b/Makefile
@@ -168,6 +168,7 @@ endif
 	$(INSTALL) -D key.dns_resolver $(DESTDIR)$(SBINDIR)/key.dns_resolver
 	$(INSTALL) -D -m 0644 request-key.conf $(DESTDIR)$(ETCDIR)/request-key.conf
 	mkdir -p $(DESTDIR)$(ETCDIR)/request-key.d
+	mkdir -p $(DESTDIR)$(ETCDIR)/keyutils
 	mkdir -p $(DESTDIR)$(MAN1)
 	$(INSTALL) -m 0644 $(wildcard man/*.1) $(DESTDIR)$(MAN1)
 	mkdir -p $(DESTDIR)$(MAN3)
--- a/key.dns_resolver.c
+++ b/key.dns_resolver.c
@@ -56,6 +56,8 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <time.h>
+#include <stdbool.h>
+#include <ctype.h>
 
 static const char *DNS_PARSE_VERSION = "1.0";
 static const char prog[] = "key.dns_resolver";
@@ -63,9 +65,12 @@ static const char key_type[] = "dns_reso
 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;
 static key_serial_t key;
 static int verbose;
 static int debug_mode;
+unsigned int key_expiry = 5;
 
 
 #define	MAX_VLS			15	/* Max Volume Location Servers Per-Cell */
@@ -287,6 +292,7 @@ static void dump_payload(void)
 	}
 
 	info("The key instantiation data is '%s'", buf);
+	info("The key expiry time is %us", key_expiry);
 	free(buf);
 }
 
@@ -363,8 +369,7 @@ dns_resolver(const char *server_name, un
  */
 static void afsdb_hosts_to_addrs(ns_msg handle,
 				 ns_sect section,
-				 unsigned mask,
-				 unsigned long *_ttl)
+				 unsigned mask)
 {
 	char *vllist[MAX_VLS];	/* list of name servers	*/
 	int vlsnum = 0;		/* number of name servers in list */
@@ -436,8 +441,8 @@ static void afsdb_hosts_to_addrs(ns_msg
 		}
 	}
 
-	*_ttl = ttl;
-	info("ttl: %u", ttl);
+	key_expiry = ttl;
+	info("ttl: %u", key_expiry);
 }
 
 /*
@@ -453,7 +458,6 @@ int dns_query_afsdb(const char *cell, ch
 	unsigned mask = INET_ALL;
 	int	response_len;		/* buffer length */
 	ns_msg	handle;			/* handle for response message */
-	unsigned long ttl = ULONG_MAX;
 	union {
 		HEADER hdr;
 		u_char buf[NS_PACKETSZ];
@@ -482,13 +486,13 @@ int dns_query_afsdb(const char *cell, ch
 		mask = INET_IP6_ONLY;
 
 	/* look up the hostnames we've obtained to get the actual addresses */
-	afsdb_hosts_to_addrs(handle, ns_s_an, mask, &ttl);
+	afsdb_hosts_to_addrs(handle, ns_s_an, mask);
 
-	info("DNS query AFSDB RR results:%u ttl:%lu", payload_index, ttl);
+	info("DNS query AFSDB RR results:%u ttl:%u", payload_index, key_expiry);
 
 	/* set the key's expiry time from the minimum TTL encountered */
 	if (!debug_mode) {
-		ret = keyctl_set_timeout(key, ttl);
+		ret = keyctl_set_timeout(key, key_expiry);
 		if (ret == -1)
 			error("%s: keyctl_set_timeout: %m", __func__);
 	}
@@ -583,6 +587,9 @@ int dns_query_a_or_aaaa(const char *host
 
 	/* load the key with data key */
 	if (!debug_mode) {
+		ret = keyctl_set_timeout(key, key_expiry);
+		if (ret == -1)
+			error("%s: keyctl_set_timeout: %m", __func__);
 		ret = keyctl_instantiate_iov(key, payload, payload_index, 0);
 		if (ret == -1)
 			error("%s: keyctl_instantiate: %m", __func__);
@@ -592,6 +599,157 @@ int dns_query_a_or_aaaa(const char *host
 }
 
 /*
+ * Read the config file.
+ */
+static void read_config(void)
+{
+	FILE *f;
+	char buf[4096], *b, *p, *k, *v;
+	unsigned int line = 0, u;
+	int n;
+
+	info("READ CONFIG %s", config_file);
+
+	f = fopen(config_file, "r");
+	if (!f) {
+		if (errno == ENOENT && !config_specified) {
+			debug("%s: %m", config_file);
+			return;
+		}
+		error("%s: %m", config_file);
+	}
+
+	while (fgets(buf, sizeof(buf) - 1, f)) {
+		line++;
+
+		/* Trim off leading and trailing spaces and discard whole-line
+		 * comments.
+		 */
+		b = buf;
+		while (isspace(*b))
+			b++;
+		if (!*b || *b == '#')
+			continue;
+		p = strchr(b, '\n');
+		if (!p)
+			error("%s:%u: line missing newline or too long", config_file, line);
+		while (p > buf && isspace(p[-1]))
+			p--;
+		*p = 0;
+
+		/* Split into key[=value] pairs and trim spaces. */
+		k = b;
+		v = NULL;
+		b = strchr(b, '=');
+		if (b) {
+			char quote = 0;
+			bool esc = false;
+
+			if (b == k)
+				error("%s:%u: Unspecified key",
+				      config_file, line);
+
+			/* NUL-terminate the key. */
+			for (p = b - 1; isspace(*p); p--)
+				;
+			p[1] = 0;
+
+			/* Strip leading spaces */
+			b++;
+			while (isspace(*b))
+				b++;
+			if (!*b)
+				goto missing_value;
+
+			if (*b == '"' || *b == '\'') {
+				quote = *b;
+				b++;
+			}
+			v = p = b;
+			while (*b) {
+				if (esc) {
+					switch (*b) {
+					case ' ':
+					case '\t':
+					case '"':
+					case '\'':
+					case '\\':
+						break;
+					default:
+						goto invalid_escape_char;
+					}
+					esc = false;
+					*p++ = *b++;
+					continue;
+				}
+				if (*b == '\\') {
+					esc = true;
+					b++;
+					continue;
+				}
+				if (*b == quote) {
+					b++;
+					if (*b)
+						goto post_quote_data;
+					quote = 0;
+					break;
+				}
+				if (!quote && *b == '#')
+					break; /* Terminal comment */
+				*p++ = *b++;
+			}
+
+			if (esc)
+				error("%s:%u: Incomplete escape", config_file, line);
+			if (quote)
+				error("%s:%u: Unclosed quotes", config_file, line);
+			*p = 0;
+		}
+
+		if (strcmp(k, "default_ttl") == 0) {
+			if (!v)
+				goto missing_value;
+			if (sscanf(v, "%u%n", &u, &n) != 1)
+				goto bad_value;
+			if (v[n])
+				goto extra_data;
+			if (u < 1 || u > INT_MAX)
+				goto out_of_range;
+			key_expiry = u;
+		} else {
+			error("%s:%u: Unknown option '%s'", config_file, line, k);
+		}
+	}
+
+	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);
+}
+
+/*
+ * Dump the configuration after parsing the config file.
+ */
+static __attribute__((noreturn))
+void config_dumper(void)
+{
+	printf("default_ttl = %u\n", key_expiry);
+	exit(0);
+}
+
+/*
  * Print usage details,
  */
 static __attribute__((noreturn))
@@ -599,22 +757,24 @@ void usage(void)
 {
 	if (isatty(2)) {
 		fprintf(stderr,
-			"Usage: %s [-vv] key_serial\n",
+			"Usage: %s [-vv] [-c config] key_serial\n",
 			prog);
 		fprintf(stderr,
-			"Usage: %s -D [-vv] <desc> <calloutinfo>\n",
+			"Usage: %s -D [-vv] [-c config] <desc> <calloutinfo>\n",
 			prog);
 	} else {
-		info("Usage: %s [-vv] key_serial", prog);
+		info("Usage: %s [-vv] [-c config] key_serial", prog);
 	}
 	exit(2);
 }
 
 const struct option long_options[] = {
-	{ "debug",	0, NULL, 'D' },
-	{ "verbose",	0, NULL, 'v' },
-	{ "version",	0, NULL, 'V' },
-	{ NULL,		0, NULL, 0 }
+	{ "config",		0, NULL, 'c' },
+	{ "debug",		0, NULL, 'D' },
+	{ "dump-config",	0, NULL, 2   },
+	{ "verbose",		0, NULL, 'v' },
+	{ "version",		0, NULL, 'V' },
+	{ NULL,			0, NULL, 0 }
 };
 
 /*
@@ -626,11 +786,19 @@ int main(int argc, char *argv[])
 	char *keyend, *p;
 	char *callout_info = NULL;
 	char *buf = NULL, *name;
+	bool dump_config = false;
 
 	openlog(prog, 0, LOG_DAEMON);
 
-	while ((ret = getopt_long(argc, argv, "vD", long_options, NULL)) != -1) {
+	while ((ret = getopt_long(argc, argv, "c:vD", long_options, NULL)) != -1) {
 		switch (ret) {
+		case 'c':
+			config_file = optarg;
+			config_specified = true;
+			continue;
+		case 2:
+			dump_config = true;
+			continue;
 		case 'D':
 			debug_mode = 1;
 			continue;
@@ -652,6 +820,9 @@ int main(int argc, char *argv[])
 
 	argc -= optind;
 	argv += optind;
+	read_config();
+	if (dump_config)
+		config_dumper();
 
 	if (!debug_mode) {
 		if (argc != 1)
--- a/man/key.dns_resolver.8
+++ b/man/key.dns_resolver.8
@@ -13,18 +13,29 @@ key.dns_resolver - Upcall for request-ke
 .SH SYNOPSIS
 \fB/sbin/key.dns_resolver \fR<key>
 .br
-\fB/sbin/key.dns_resolver \fR-D [-v] [-v] <keydesc> <calloutinfo>
+\fB/sbin/key.dns_resolver \fR--dump-config [\-c <configfile>]
+\fB/sbin/key.dns_resolver \fR\-D [-v] [-v] [\-c <configfile>] <keydesc>
+.br
+<calloutinfo>
 .SH DESCRIPTION
 This program is invoked by request-key on behalf of the kernel when kernel
 services (such as NFS, CIFS and AFS) want to perform a hostname lookup and the
 kernel does not have the key cached.  It is not ordinarily intended to be
 called directly.
 .P
-It can be called in debugging mode to test its functionality by passing a
-\fB-D\fR flag on the command line.  For this to work, the key description and
-the callout information must be supplied.  Verbosity can be increased by
-supplying one or more \fB-v\fR flags.
+There program has internal parameters that can be changed with a configuration
+file (see key.dns_resolver.conf(5) for more information).  The default
+configuration file is in /etc, but this can be overridden with the \fB-c\fR
+flag.
+.P
+The program can be called in debugging mode to test its functionality by
+passing a \fB\-D\fR or \fB\--debug\fR flag on the command line.  For this to
+work, the key description and the callout information must be supplied.
+Verbosity can be increased by supplying one or more \fB\-v\fR flags.
+.P
+The program may also be called with \fB--dump-config\fR to show the values that
+configurable parameters will have after parsing the config file.
 .SH ERRORS
 All errors will be logged to the syslog.
 .SH SEE ALSO
-\fBrequest-key\fR(8), \fBrequest-key.conf\fR(5)
+\fBrequest-key\fR(8), \fBrequest-key.conf\fR(5), \fBkey.dns_resolver.conf\fR(5)
--- /dev/null
+++ b/man/key.dns_resolver.conf.5
@@ -0,0 +1,48 @@
+.\" -*- nroff -*-
+.\" Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
+.\" Written by David Howells (dhowells@redhat.com)
+.\"
+.\" 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.
+.\"
+.TH KEY.DNS_RESOLVER.CONF 5 "18 May 2020" Linux "Linux Key Management Utilities"
+.SH NAME
+key.dns_resolver.conf \- Kernel DNS resolver config
+.SH DESCRIPTION
+This file is used by the key.dns_resolver(5) program to set parameters.
+Unless otherwise overridden with the \fB\-c\fR flag, the program reads:
+.IP
+/etc/key.dns_resolver.conf
+.P
+Configuration options are given in \fBkey[=value]\fR form, where \fBvalue\fR is
+optional.  If present, the value may be surrounded by a pair of single ('') or
+double quotes ("") which will be stripped off.  The special characters in the
+value may be escaped with a backslash to turn them into ordinary characters.
+.P
+Lines beginning with a '#' are considered comments and ignored.  A '#' symbol
+anywhere after the '=' makes the rest of the line into a comment unless the '#'
+is inside a quoted section or is escaped.
+.P
+Leading and trailing spaces and spaces around the '=' symbol will be stripped
+off.
+.P
+Available options include:
+.TP
+.B default_ttl=<number>
+The number of seconds to set as the expiration on a cached record.  This will
+be overridden if the program manages to retrieve TTL information along with
+the addresses (if, for example, it accesses the DNS directly).  The default is
+5 seconds.  The value must be in the range 1 to INT_MAX.
+.P
+The file can also include comments beginning with a '#' character unless
+otherwise suppressed by being inside a quoted value or being escaped with a
+backslash.
+
+.SH FILES
+.ul
+/etc/key.dns_resolver.conf
+.ul 0
+.SH SEE ALSO
+\fBkey.dns_resolver\fR(8)
