08c8fc
commit 75e7568dc516db698093b33ea273e1b4a30b70be
08c8fc
Author: David Howells <dhowells@redhat.com>
08c8fc
Date: Tue, 14 Apr 2020 16:07:26 +0100
08c8fc
08c8fc
    dns: Apply a default TTL to records obtained from getaddrinfo()
08c8fc
08c8fc
    Address records obtained from getaddrinfo() don't come with any TTL
08c8fc
    information, even if they're obtained from the DNS, with the result that
08c8fc
    key.dns_resolver upcall program doesn't set an expiry time on dns_resolver
08c8fc
    records unless they include a component obtained directly from the DNS,
08c8fc
    such as an SRV or AFSDB record.
08c8fc
08c8fc
    Fix this to apply a default TTL of 10mins in the event that we haven't got
08c8fc
    one.  This can be configured in /etc/keyutils/key.dns_resolver.conf by
08c8fc
    adding the line:
08c8fc
08c8fc
	    default_ttl = <number-of-seconds>
08c8fc
08c8fc
    to the file.
08c8fc
08c8fc
    Signed-off-by: David Howells <dhowells@redhat.com>
08c8fc
    Reviewed-by: Ben Boeckel <me@benboeckel.net>
08c8fc
    Reviewed-by: Jeff Layton <jlayton@kernel.org>
08c8fc
08c8fc
    [dhowells: Cut down to remove kafs-specific bits]
08c8fc
08c8fc
Signed-off-by: David Howells <dhowells@redhat.com>
08c8fc
---
08c8fc
 Makefile                    |    1 
08c8fc
 key.dns_resolver.c          |  209 +++++++++++++++++++++++++++++++++++++++++---
08c8fc
 man/key.dns_resolver.8      |   25 ++++-
08c8fc
 man/key.dns_resolver.conf.5 |   48 ++++++++++
08c8fc
 4 files changed, 267 insertions(+), 16 deletions(-)
08c8fc
08c8fc
diff --git a/Makefile b/Makefile
08c8fc
index 824bbbf..c2b2460 100644
08c8fc
--- a/Makefile
08c8fc
+++ b/Makefile
08c8fc
@@ -175,6 +175,7 @@ endif
08c8fc
 	$(INSTALL) -D key.dns_resolver $(DESTDIR)$(SBINDIR)/key.dns_resolver
08c8fc
 	$(INSTALL) -D -m 0644 request-key.conf $(DESTDIR)$(ETCDIR)/request-key.conf
08c8fc
 	mkdir -p $(DESTDIR)$(ETCDIR)/request-key.d
08c8fc
+	mkdir -p $(DESTDIR)$(ETCDIR)/keyutils
08c8fc
 	mkdir -p $(DESTDIR)$(MAN1)
08c8fc
 	$(INSTALL) -m 0644 $(wildcard man/*.1) $(DESTDIR)$(MAN1)
08c8fc
 	mkdir -p $(DESTDIR)$(MAN3)
08c8fc
diff --git a/key.dns_resolver.c b/key.dns_resolver.c
08c8fc
index 849c8fe..f3052e6 100644
08c8fc
--- a/key.dns_resolver.c
08c8fc
+++ b/key.dns_resolver.c
08c8fc
@@ -53,10 +53,12 @@
08c8fc
 #include <string.h>
08c8fc
 #include <stdio.h>
08c8fc
 #include <stdarg.h>
08c8fc
+#include <stdbool.h>
08c8fc
 #include <keyutils.h>
08c8fc
 #include <stdlib.h>
08c8fc
 #include <unistd.h>
08c8fc
 #include <time.h>
08c8fc
+#include <ctype.h>
08c8fc
 
08c8fc
 static const char *DNS_PARSE_VERSION = "1.0";
08c8fc
 static const char prog[] = "key.dns_resolver";
08c8fc
@@ -64,11 +66,13 @@ static const char key_type[] = "dns_resolver";
08c8fc
 static const char a_query_type[] = "a";
08c8fc
 static const char aaaa_query_type[] = "aaaa";
08c8fc
 static const char afsdb_query_type[] = "afsdb";
08c8fc
+static const char *config_file = "/etc/keyutils/key.dns_resolver.conf";
08c8fc
 static key_serial_t key;
08c8fc
+static unsigned int key_expiry = 5;
08c8fc
+static bool config_specified = false;
08c8fc
 static int verbose;
08c8fc
 static int debug_mode;
08c8fc
 
08c8fc
-
08c8fc
 #define	MAX_VLS			15	/* Max Volume Location Servers Per-Cell */
08c8fc
 #define	INET_IP4_ONLY		0x1
08c8fc
 #define	INET_IP6_ONLY		0x2
08c8fc
@@ -132,6 +136,23 @@ void _error(const char *fmt, ...)
08c8fc
 	va_end(va);
08c8fc
 }
08c8fc
 
08c8fc
+/*
08c8fc
+ * Print a warning to stderr or the syslog
08c8fc
+ */
08c8fc
+void warning(const char *fmt, ...)
08c8fc
+{
08c8fc
+	va_list va;
08c8fc
+
08c8fc
+	va_start(va, fmt);
08c8fc
+	if (isatty(2)) {
08c8fc
+		vfprintf(stderr, fmt, va);
08c8fc
+		fputc('\n', stderr);
08c8fc
+	} else {
08c8fc
+		vsyslog(LOG_WARNING, fmt, va);
08c8fc
+	}
08c8fc
+	va_end(va);
08c8fc
+}
08c8fc
+
08c8fc
 /*
08c8fc
  * Print status information
08c8fc
  */
08c8fc
@@ -302,6 +323,7 @@ static void dump_payload(void)
08c8fc
 	}
08c8fc
 
08c8fc
 	info("The key instantiation data is '%s'", buf);
08c8fc
+	info("The expiry time is %us", key_expiry);
08c8fc
 	free(buf);
08c8fc
 }
08c8fc
 
08c8fc
@@ -597,6 +619,9 @@ int dns_query_a_or_aaaa(const char *hostname, char *options)
08c8fc
 
08c8fc
 	/* load the key with data key */
08c8fc
 	if (!debug_mode) {
08c8fc
+		ret = keyctl_set_timeout(key, key_expiry);
08c8fc
+		if (ret == -1)
08c8fc
+			error("%s: keyctl_set_timeout: %m", __func__);
08c8fc
 		ret = keyctl_instantiate_iov(key, payload, payload_index, 0);
08c8fc
 		if (ret == -1)
08c8fc
 			error("%s: keyctl_instantiate: %m", __func__);
08c8fc
@@ -605,6 +630,157 @@ int dns_query_a_or_aaaa(const char *hostname, char *options)
08c8fc
 	exit(0);
08c8fc
 }
08c8fc
 
08c8fc
+/*
08c8fc
+ * Read the config file.
08c8fc
+ */
08c8fc
+static void read_config(void)
08c8fc
+{
08c8fc
+	FILE *f;
08c8fc
+	char buf[4096], *b, *p, *k, *v;
08c8fc
+	unsigned int line = 0, u;
08c8fc
+	int n;
08c8fc
+
08c8fc
+	info("READ CONFIG %s", config_file);
08c8fc
+
08c8fc
+	f = fopen(config_file, "r");
08c8fc
+	if (!f) {
08c8fc
+		if (errno == ENOENT && !config_specified) {
08c8fc
+			debug("%s: %m", config_file);
08c8fc
+			return;
08c8fc
+		}
08c8fc
+		error("%s: %m", config_file);
08c8fc
+	}
08c8fc
+
08c8fc
+	while (fgets(buf, sizeof(buf) - 1, f)) {
08c8fc
+		line++;
08c8fc
+
08c8fc
+		/* Trim off leading and trailing spaces and discard whole-line
08c8fc
+		 * comments.
08c8fc
+		 */
08c8fc
+		b = buf;
08c8fc
+		while (isspace(*b))
08c8fc
+			b++;
08c8fc
+		if (!*b || *b == '#')
08c8fc
+			continue;
08c8fc
+		p = strchr(b, '\n');
08c8fc
+		if (!p)
08c8fc
+			error("%s:%u: line missing newline or too long", config_file, line);
08c8fc
+		while (p > buf && isspace(p[-1]))
08c8fc
+			p--;
08c8fc
+		*p = 0;
08c8fc
+
08c8fc
+		/* Split into key[=value] pairs and trim spaces. */
08c8fc
+		k = b;
08c8fc
+		v = NULL;
08c8fc
+		b = strchr(b, '=');
08c8fc
+		if (b) {
08c8fc
+			char quote = 0;
08c8fc
+			bool esc = false;
08c8fc
+
08c8fc
+			if (b == k)
08c8fc
+				error("%s:%u: Unspecified key",
08c8fc
+				      config_file, line);
08c8fc
+
08c8fc
+			/* NUL-terminate the key. */
08c8fc
+			for (p = b - 1; isspace(*p); p--)
08c8fc
+				;
08c8fc
+			p[1] = 0;
08c8fc
+
08c8fc
+			/* Strip leading spaces */
08c8fc
+			b++;
08c8fc
+			while (isspace(*b))
08c8fc
+				b++;
08c8fc
+			if (!*b)
08c8fc
+				goto missing_value;
08c8fc
+
08c8fc
+			if (*b == '"' || *b == '\'') {
08c8fc
+				quote = *b;
08c8fc
+				b++;
08c8fc
+			}
08c8fc
+			v = p = b;
08c8fc
+			while (*b) {
08c8fc
+				if (esc) {
08c8fc
+					switch (*b) {
08c8fc
+					case ' ':
08c8fc
+					case '\t':
08c8fc
+					case '"':
08c8fc
+					case '\'':
08c8fc
+					case '\\':
08c8fc
+						break;
08c8fc
+					default:
08c8fc
+						goto invalid_escape_char;
08c8fc
+					}
08c8fc
+					esc = false;
08c8fc
+					*p++ = *b++;
08c8fc
+					continue;
08c8fc
+				}
08c8fc
+				if (*b == '\\') {
08c8fc
+					esc = true;
08c8fc
+					b++;
08c8fc
+					continue;
08c8fc
+				}
08c8fc
+				if (*b == quote) {
08c8fc
+					b++;
08c8fc
+					if (*b)
08c8fc
+						goto post_quote_data;
08c8fc
+					quote = 0;
08c8fc
+					break;
08c8fc
+				}
08c8fc
+				if (!quote && *b == '#')
08c8fc
+					break; /* Terminal comment */
08c8fc
+				*p++ = *b++;
08c8fc
+			}
08c8fc
+
08c8fc
+			if (esc)
08c8fc
+				error("%s:%u: Incomplete escape", config_file, line);
08c8fc
+			if (quote)
08c8fc
+				error("%s:%u: Unclosed quotes", config_file, line);
08c8fc
+			*p = 0;
08c8fc
+		}
08c8fc
+
08c8fc
+		if (strcmp(k, "default_ttl") == 0) {
08c8fc
+			if (!v)
08c8fc
+				goto missing_value;
08c8fc
+			if (sscanf(v, "%u%n", &u, &n) != 1)
08c8fc
+				goto bad_value;
08c8fc
+			if (v[n])
08c8fc
+				goto extra_data;
08c8fc
+			if (u < 1 || u > INT_MAX)
08c8fc
+				goto out_of_range;
08c8fc
+			key_expiry = u;
08c8fc
+		} else {
08c8fc
+			warning("%s:%u: Unknown option '%s'", config_file, line, k);
08c8fc
+		}
08c8fc
+	}
08c8fc
+
08c8fc
+	if (ferror(f) || fclose(f) == EOF)
08c8fc
+		error("%s: %m", config_file);
08c8fc
+	return;
08c8fc
+
08c8fc
+missing_value:
08c8fc
+	error("%s:%u: %s: Missing value", config_file, line, k);
08c8fc
+invalid_escape_char:
08c8fc
+	error("%s:%u: %s: Invalid char in escape", config_file, line, k);
08c8fc
+post_quote_data:
08c8fc
+	error("%s:%u: %s: Data after closing quote", config_file, line, k);
08c8fc
+bad_value:
08c8fc
+	error("%s:%u: %s: Bad value", config_file, line, k);
08c8fc
+extra_data:
08c8fc
+	error("%s:%u: %s: Extra data supplied", config_file, line, k);
08c8fc
+out_of_range:
08c8fc
+	error("%s:%u: %s: Value out of range", config_file, line, k);
08c8fc
+}
08c8fc
+
08c8fc
+/*
08c8fc
+ * Dump the configuration after parsing the config file.
08c8fc
+ */
08c8fc
+static __attribute__((noreturn))
08c8fc
+void config_dumper(void)
08c8fc
+{
08c8fc
+	printf("default_ttl = %u\n", key_expiry);
08c8fc
+	exit(0);
08c8fc
+}
08c8fc
+
08c8fc
 /*
08c8fc
  * Print usage details,
08c8fc
  */
08c8fc
@@ -613,22 +789,24 @@ void usage(void)
08c8fc
 {
08c8fc
 	if (isatty(2)) {
08c8fc
 		fprintf(stderr,
08c8fc
-			"Usage: %s [-vv] key_serial\n",
08c8fc
+			"Usage: %s [-vv] [-c config] key_serial\n",
08c8fc
 			prog);
08c8fc
 		fprintf(stderr,
08c8fc
-			"Usage: %s -D [-vv] <desc> <calloutinfo>\n",
08c8fc
+			"Usage: %s -D [-vv] [-c config] <desc> <calloutinfo>\n",
08c8fc
 			prog);
08c8fc
 	} else {
08c8fc
-		info("Usage: %s [-vv] key_serial", prog);
08c8fc
+		info("Usage: %s [-vv] [-c config] key_serial", prog);
08c8fc
 	}
08c8fc
 	exit(2);
08c8fc
 }
08c8fc
 
08c8fc
-const struct option long_options[] = {
08c8fc
-	{ "debug",	0, NULL, 'D' },
08c8fc
-	{ "verbose",	0, NULL, 'v' },
08c8fc
-	{ "version",	0, NULL, 'V' },
08c8fc
-	{ NULL,		0, NULL, 0 }
08c8fc
+static const struct option long_options[] = {
08c8fc
+	{ "config",		0, NULL, 'c' },
08c8fc
+	{ "debug",		0, NULL, 'D' },
08c8fc
+	{ "dump-config",	0, NULL, 2   },
08c8fc
+	{ "verbose",		0, NULL, 'v' },
08c8fc
+	{ "version",		0, NULL, 'V' },
08c8fc
+	{ NULL,			0, NULL, 0 }
08c8fc
 };
08c8fc
 
08c8fc
 /*
08c8fc
@@ -640,11 +818,19 @@ int main(int argc, char *argv[])
08c8fc
 	char *keyend, *p;
08c8fc
 	char *callout_info = NULL;
08c8fc
 	char *buf = NULL, *name;
08c8fc
+	bool dump_config = false;
08c8fc
 
08c8fc
 	openlog(prog, 0, LOG_DAEMON);
08c8fc
 
08c8fc
-	while ((ret = getopt_long(argc, argv, "vDV", long_options, NULL)) != -1) {
08c8fc
+	while ((ret = getopt_long(argc, argv, "c:vDV", long_options, NULL)) != -1) {
08c8fc
 		switch (ret) {
08c8fc
+		case 'c':
08c8fc
+			config_file = optarg;
08c8fc
+			config_specified = true;
08c8fc
+			continue;
08c8fc
+		case 2:
08c8fc
+			dump_config = true;
08c8fc
+			continue;
08c8fc
 		case 'D':
08c8fc
 			debug_mode = 1;
08c8fc
 			continue;
08c8fc
@@ -666,6 +852,9 @@ int main(int argc, char *argv[])
08c8fc
 
08c8fc
 	argc -= optind;
08c8fc
 	argv += optind;
08c8fc
+	read_config();
08c8fc
+	if (dump_config)
08c8fc
+		config_dumper();
08c8fc
 
08c8fc
 	if (!debug_mode) {
08c8fc
 		if (argc != 1)
08c8fc
diff --git a/man/key.dns_resolver.8 b/man/key.dns_resolver.8
08c8fc
index e1882e0..0b17edd 100644
08c8fc
--- a/man/key.dns_resolver.8
08c8fc
+++ b/man/key.dns_resolver.8
08c8fc
@@ -7,28 +7,41 @@
08c8fc
 .\" as published by the Free Software Foundation; either version
08c8fc
 .\" 2 of the License, or (at your option) any later version.
08c8fc
 .\"
08c8fc
-.TH KEY.DNS_RESOLVER 8 "04 Mar 2011" Linux "Linux Key Management Utilities"
08c8fc
+.TH KEY.DNS_RESOLVER 8 "18 May 2020" Linux "Linux Key Management Utilities"
08c8fc
 .SH NAME
08c8fc
 key.dns_resolver \- upcall for request\-key to handle dns_resolver keys
08c8fc
 .SH SYNOPSIS
08c8fc
 \fB/sbin/key.dns_resolver \fR<key>
08c8fc
 .br
08c8fc
-\fB/sbin/key.dns_resolver \fR\-D [\-v] [\-v] <keydesc> <calloutinfo>
08c8fc
+\fB/sbin/key.dns_resolver \fR--dump-config [\-c <configfile>]
08c8fc
+.br
08c8fc
+\fB/sbin/key.dns_resolver \fR\-D [\-v] [\-v] [\-c <configfile>] <desc>
08c8fc
+.br
08c8fc
+<calloutinfo>
08c8fc
 .SH DESCRIPTION
08c8fc
 This program is invoked by request\-key on behalf of the kernel when kernel
08c8fc
 services (such as NFS, CIFS and AFS) want to perform a hostname lookup and the
08c8fc
 kernel does not have the key cached.  It is not ordinarily intended to be
08c8fc
 called directly.
08c8fc
 .P
08c8fc
-It can be called in debugging mode to test its functionality by passing a
08c8fc
-\fB\-D\fR flag on the command line.  For this to work, the key description and
08c8fc
-the callout information must be supplied.  Verbosity can be increased by
08c8fc
-supplying one or more \fB\-v\fR flags.
08c8fc
+There program has internal parameters that can be changed with a configuration
08c8fc
+file (see key.dns_resolver.conf(5) for more information).  The default
08c8fc
+configuration file is in /etc, but this can be overridden with the \fB-c\fR
08c8fc
+flag.
08c8fc
+.P
08c8fc
+The program can be called in debugging mode to test its functionality by
08c8fc
+passing a \fB\-D\fR or \fB\--debug\fR flag on the command line.  For this to
08c8fc
+work, the key description and the callout information must be supplied.
08c8fc
+Verbosity can be increased by supplying one or more \fB\-v\fR flags.
08c8fc
+.P
08c8fc
+The program may also be called with \fB--dump-config\fR to show the values that
08c8fc
+configurable parameters will have after parsing the config file.
08c8fc
 .SH ERRORS
08c8fc
 All errors will be logged to the syslog.
08c8fc
 .SH SEE ALSO
08c8fc
 .ad l
08c8fc
 .nh
08c8fc
+.BR key.dns_resolver.conf (5),
08c8fc
 .BR request\-key.conf (5),
08c8fc
 .BR keyrings (7),
08c8fc
 .BR request\-key (8)
08c8fc
diff --git a/man/key.dns_resolver.conf.5 b/man/key.dns_resolver.conf.5
08c8fc
new file mode 100644
08c8fc
index 0000000..c944ad5
08c8fc
--- /dev/null
08c8fc
+++ b/man/key.dns_resolver.conf.5
08c8fc
@@ -0,0 +1,48 @@
08c8fc
+.\" -*- nroff -*-
08c8fc
+.\" Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
08c8fc
+.\" Written by David Howells (dhowells@redhat.com)
08c8fc
+.\"
08c8fc
+.\" This program is free software; you can redistribute it and/or
08c8fc
+.\" modify it under the terms of the GNU General Public License
08c8fc
+.\" as published by the Free Software Foundation; either version
08c8fc
+.\" 2 of the License, or (at your option) any later version.
08c8fc
+.\"
08c8fc
+.TH KEY.DNS_RESOLVER.CONF 5 "18 May 2020" Linux "Linux Key Management Utilities"
08c8fc
+.SH NAME
08c8fc
+key.dns_resolver.conf \- Kernel DNS resolver config
08c8fc
+.SH DESCRIPTION
08c8fc
+This file is used by the key.dns_resolver(5) program to set parameters.
08c8fc
+Unless otherwise overridden with the \fB\-c\fR flag, the program reads:
08c8fc
+.IP
08c8fc
+/etc/key.dns_resolver.conf
08c8fc
+.P
08c8fc
+Configuration options are given in \fBkey[=value]\fR form, where \fBvalue\fR is
08c8fc
+optional.  If present, the value may be surrounded by a pair of single ('') or
08c8fc
+double quotes ("") which will be stripped off.  The special characters in the
08c8fc
+value may be escaped with a backslash to turn them into ordinary characters.
08c8fc
+.P
08c8fc
+Lines beginning with a '#' are considered comments and ignored.  A '#' symbol
08c8fc
+anywhere after the '=' makes the rest of the line into a comment unless the '#'
08c8fc
+is inside a quoted section or is escaped.
08c8fc
+.P
08c8fc
+Leading and trailing spaces and spaces around the '=' symbol will be stripped
08c8fc
+off.
08c8fc
+.P
08c8fc
+Available options include:
08c8fc
+.TP
08c8fc
+.B default_ttl=<number>
08c8fc
+The number of seconds to set as the expiration on a cached record.  This will
08c8fc
+be overridden if the program manages to retrieve TTL information along with
08c8fc
+the addresses (if, for example, it accesses the DNS directly).  The default is
08c8fc
+5 seconds.  The value must be in the range 1 to INT_MAX.
08c8fc
+.P
08c8fc
+The file can also include comments beginning with a '#' character unless
08c8fc
+otherwise suppressed by being inside a quoted value or being escaped with a
08c8fc
+backslash.
08c8fc
+
08c8fc
+.SH FILES
08c8fc
+.ul
08c8fc
+/etc/key.dns_resolver.conf
08c8fc
+.ul 0
08c8fc
+.SH SEE ALSO
08c8fc
+\fBkey.dns_resolver\fR(8)