diff options
Diffstat (limited to 'keyutils-1.5.6/key.dns_resolver.c')
-rw-r--r-- | keyutils-1.5.6/key.dns_resolver.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/keyutils-1.5.6/key.dns_resolver.c b/keyutils-1.5.6/key.dns_resolver.c new file mode 100644 index 0000000..249dcf3 --- /dev/null +++ b/keyutils-1.5.6/key.dns_resolver.c @@ -0,0 +1,752 @@ +/* + * DNS Resolver Module User-space Helper for AFSDB records + * + * Copyright (C) Wang Lei (wang840925@gmail.com) 2010 + * Authors: Wang Lei (wang840925@gmail.com) + * David Howells (dhowells@redhat.com) + * + * This is a userspace tool for querying AFSDB RR records in the DNS on behalf + * of the kernel, and converting the VL server addresses to IPv4 format so that + * they can be used by the kAFS filesystem. + * + * Compile with: + * + * cc -o key.dns_resolver key.dns_resolver.c -lresolv -lkeyutils + * + * As some function like res_init() should use the static liberary, which is a + * bug of libresolv, that is the reason for cifs.upcall to reimplement. + * + * To use this program, you must tell /sbin/request-key how to invoke it. You + * need to have the keyutils package installed and something like the following + * lines added to your /etc/request-key.conf file: + * + * #OP TYPE DESCRIPTION CALLOUT INFO PROGRAM ARG1 ARG2 ARG3 ... + * ====== ============ =========== ============ ========================== + * create dns_resolver afsdb:* * /sbin/key.dns_resolver %k + * + * + * 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 + */ +#define _GNU_SOURCE +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <arpa/inet.h> +#include <resolv.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <syslog.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdarg.h> +#include <keyutils.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> + +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 key_serial_t key; +static int verbose; +static int debug_mode; + + +#define MAX_VLS 15 /* Max Volume Location Servers Per-Cell */ +#define DNS_EXPIRY_PREFIX "expiry_time=" +#define DNS_EXPIRY_TIME_LEN 10 /* 2^32 - 1 = 4294967295 */ +#define AFSDB_MAX_DATA_LEN \ + ((MAX_VLS * (INET6_ADDRSTRLEN + 1)) + sizeof(DNS_EXPIRY_PREFIX) + \ + DNS_EXPIRY_TIME_LEN + 1 /* '#'*/ + 1 /* end 0 */) + +#define INET_IP4_ONLY 0x1 +#define INET_IP6_ONLY 0x2 +#define INET_ALL 0xFF +#define ONE_ADDR_ONLY 0x100 +#define LIST_MULTIPLE_ADDRS 0x200 + +/* + * segmental payload + */ +#define N_PAYLOAD 256 +struct iovec payload[N_PAYLOAD]; +int payload_index; + +/* + * Print an error to stderr or the syslog, negate the key being created and + * exit + */ +static __attribute__((format(printf, 1, 2), noreturn)) +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 + */ +static __attribute__((format(printf, 1, 2))) +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 status information + */ +static __attribute__((format(printf, 1, 2))) +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, +}; + +static __attribute__((noreturn)) +void nsError(int err, const char *domain) +{ + unsigned timeout = 1 * 60; + int ret; + + if (isatty(2)) + fprintf(stderr, "%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); + + if (err == EAGAIN) + timeout = 1; + else if (err == ECONNREFUSED) + timeout = 10; + + 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 + */ +static __attribute__((format(printf, 1, 2))) +void debug(const char *fmt, ...) +{ + va_list va; + + if (verbose < 2) + 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 + */ +static void append_address_to_payload(char *p, size_t sz) +{ + int loop; + + debug("append '%*.*s'", (int)sz, (int)sz, p); + + /* discard duplicates */ + for (loop = 0; loop < payload_index; loop++) + if (payload[loop].iov_len == sz && + memcmp(payload[loop].iov_base, p, sz) == 0) + return; + + if (payload_index != 0) { + if (payload_index + 2 > N_PAYLOAD - 1) + return; + payload[payload_index ].iov_base = ","; + payload[payload_index++].iov_len = 1; + } else { + if (payload_index + 1 > N_PAYLOAD - 1) + return; + } + + payload[payload_index ].iov_base = p; + payload[payload_index++].iov_len = sz; +} + +/* + * Dump the payload when debugging + */ +static 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); + free(buf); +} + +/* + * Perform address resolution on a hostname and add the resulting address as a + * string to the list of payload segments. + */ +static int +dns_resolver(const char *server_name, unsigned mask) +{ + struct addrinfo hints, *addr, *ai; + size_t slen; + char buf[INET6_ADDRSTRLEN + 1], *seg; + 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; + } + + debug("getaddrinfo = %d", ret); + + 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); + continue; + } + + if (!inet_ntop(ai->ai_family, sa, buf, len)) + error("%s: inet_ntop: %m", __func__); + + slen = strlen(buf); + seg = malloc(slen); + if (!seg) + error("%s: inet_ntop: %m", __func__); + memcpy(seg, buf, slen); + append_address_to_payload(seg, slen); + if (mask & ONE_ADDR_ONLY) + break; + } + + freeaddrinfo(addr); + return 0; +} + +/* + * + */ +static void afsdb_hosts_to_addrs(char *vllist[], + int *vlsnum, + ns_msg handle, + ns_sect section, + unsigned mask, + unsigned long *_ttl) +{ + 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], mask); + 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 += 1; + continue; + + next_one: + free(vllist[*vlsnum]); + } + } + + *_ttl = ttl; + info("ttl: %u", ttl); +} + +/* + * Look up an AFSDB record to get the VL server addresses. + * + * The callout_info is parsed for request options. For instance, "ipv4" to + * request only IPv4 addresses and "ipv6" to request only IPv6 addresses. + */ +static __attribute__((noreturn)) +int dns_query_afsdb(key_serial_t key, const char *cell, char *options) +{ + int ret; + char *vllist[MAX_VLS]; /* list of name servers */ + int vlsnum = 0; /* number of name servers in list */ + 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]; + } response; /* response buffers */ + + debug("Get AFSDB RR for cell name:'%s', options:'%s'", cell, options); + + /* query the dns for an AFSDB resource record */ + response_len = res_query(cell, + ns_c_in, + ns_t_afsdb, + response.buf, + sizeof(response)); + + if (response_len < 0) + /* negative result */ + nsError(h_errno, cell); + + if (ns_initparse(response.buf, response_len, &handle) < 0) + error("ns_initparse: %m"); + + /* 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; + + /* look up the hostnames we've obtained to get the actual addresses */ + afsdb_hosts_to_addrs(vllist, &vlsnum, handle, ns_s_an, mask, &ttl); + + info("DNS query AFSDB RR results:%u ttl:%lu", payload_index, ttl); + + /* set the key's expiry time from the minimum TTL encountered */ + if (!debug_mode) { + ret = keyctl_set_timeout(key, ttl); + if (ret == -1) + error("%s: keyctl_set_timeout: %m", __func__); + } + + /* 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(); + + /* 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__); + } + + exit(0); +} + +/* + * 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. + */ +static __attribute__((noreturn)) +int dns_query_a_or_aaaa(key_serial_t key, const char *hostname, char *options) +{ + unsigned mask; + int ret; + + debug("Get A/AAAA RR for hostname:'%s', options:'%s'", + hostname, options); + + if (!options[0]) { + /* legacy mode */ + mask = INET_IP4_ONLY | ONE_ADDR_ONLY; + } else { + char *key, *val; + + mask = INET_ALL | ONE_ADDR_ONLY; + + do { + key = options; + options = strchr(options, ' '); + if (!options) + options = key + strlen(key); + else + *options++ = '\0'; + if (!*key) + continue; + if (strchr(key, ',')) + error("Option name '%s' contains a comma", key); + + val = strchr(key, '='); + if (val) + *val++ = '\0'; + + debug("Opt %s", key); + + if (strcmp(key, "ipv4") == 0) { + mask &= ~INET_ALL; + mask |= INET_IP4_ONLY; + } else if (strcmp(key, "ipv6") == 0) { + mask &= ~INET_ALL; + mask |= INET_IP6_ONLY; + } else if (strcmp(key, "list") == 0) { + mask &= ~ONE_ADDR_ONLY; + mask |= LIST_MULTIPLE_ADDRS; + } + + } while (*options); + } + + /* Turn the hostname into IP addresses */ + ret = dns_resolver(hostname, mask); + if (ret) + nsError(NO_DATA, hostname); + + /* handle a lack of results */ + if (payload_index == 0) + nsError(NO_DATA, hostname); + + /* must include a NUL char at the end of the payload */ + payload[payload_index].iov_base = ""; + payload[payload_index++].iov_len = 1; + dump_payload(); + + /* 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__); + } + + exit(0); +} + +/* + * Print usage details, + */ +static __attribute__((noreturn)) +void usage(void) +{ + if (isatty(2)) { + fprintf(stderr, + "Usage: %s [-vv] key_serial\n", + prog); + fprintf(stderr, + "Usage: %s -D [-vv] <desc> <calloutinfo>\n", + prog); + } else { + info("Usage: %s [-vv] key_serial", prog); + } + if (!debug_mode) + keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT); + exit(2); +} + +const struct option long_options[] = { + { "debug", 0, NULL, 'D' }, + { "verbose", 0, NULL, 'v' }, + { "version", 0, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + +/* + * + */ +int main(int argc, char *argv[]) +{ + int ktlen, qtlen, ret; + char *keyend, *p; + char *callout_info = NULL; + char *buf = NULL, *name; + + openlog(prog, 0, LOG_DAEMON); + + while ((ret = getopt_long(argc, argv, "vD", long_options, NULL)) != -1) { + switch (ret) { + case 'D': + debug_mode = 1; + continue; + case 'V': + printf("version: %s from %s (%s)\n", + DNS_PARSE_VERSION, + keyutils_version_string, + keyutils_build_string); + exit(0); + case 'v': + verbose++; + continue; + default: + if (!isatty(2)) + syslog(LOG_ERR, "unknown option: %c", ret); + usage(); + } + } + + argc -= optind; + argv += optind; + + if (!debug_mode) { + if (argc != 1) + usage(); + + /* get the key ID */ + errno = 0; + key = strtol(*argv, NULL, 10); + if (errno != 0) + error("Invalid key ID format: %m"); + + /* get the key description (of the form "x;x;x;x;<query_type>:<name>") */ + if (!buf) { + ret = keyctl_describe_alloc(key, &buf); + if (ret == -1) + error("keyctl_describe_alloc failed: %m"); + } + + /* get the callout_info (which can supply options) */ + if (!callout_info) { + ret = keyctl_read_alloc(KEY_SPEC_REQKEY_AUTH_KEY, + (void **)&callout_info); + if (ret == -1) + error("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"); + callout_info = argv[1]; + } + + ret = 1; + info("Key description: '%s'", buf); + info("Callout info: '%s'", callout_info); + + p = strchr(buf, ';'); + if (!p) + error("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); + + keyend = buf + ktlen + 1; + + /* the actual key description follows the last semicolon */ + keyend = rindex(keyend, ';'); + if (!keyend) + error("Invalid key description: %s", buf); + keyend++; + + name = index(keyend, ':'); + if (!name) + dns_query_a_or_aaaa(key, keyend, callout_info); + + qtlen = name - keyend; + name++; + + if ((qtlen == sizeof(a_query_type) - 1 && + memcmp(keyend, a_query_type, sizeof(a_query_type) - 1) == 0) || + (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'", + name, callout_info); + dns_query_a_or_aaaa(key, name, callout_info); + } + + if (qtlen == sizeof(afsdb_query_type) - 1 && + memcmp(keyend, afsdb_query_type, sizeof(afsdb_query_type) - 1) == 0 + ) { + info("Do DNS query of AFSDB type for:'%s' mask:'%s'", + name, callout_info); + dns_query_afsdb(key, name, callout_info); + } + + error("Query type: \"%*.*s\" is not supported", qtlen, qtlen, keyend); +} |