From: David Howells on

The userspace upcall program is the attached.

David
---
/*
* DNS Resolver Module User-space Helper for AFSDB records
*
* Copyright (C) Wang Lei (wang840925(a)gmail.com) 2010
* Authors: Wang Lei (wang840925(a)gmail.com)
* David Howells (dhowells(a)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 dns.afsdb dns_query_afsdb.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:* * /usr/sbin/dns.afsdb %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[] = "dns.upcall";
static const char key_type[] = "dns_resolver";
static const char afsdb_query_type[] = "afsdb";
static key_serial_t key = 0;
static int verbose = 0;


#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 DNS_ERR_PREFIX "#dnserror="

/*
* 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.
*/
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 __attribute__((noreturn))
void nsError(int err, const char *domain)
{
char buf[AFSDB_MAX_DATA_LEN];
int ret;

if (isatty(2))
fprintf(stderr, "%s: %s.\n", domain, hstrerror(err));
else
syslog(LOG_INFO, "%s: %s", domain, hstrerror(err));

sprintf(buf, "%s%d", DNS_ERR_PREFIX, err);

info("The key instantiation ERROR data is '%s'", buf);

ret = keyctl_instantiate(key, buf, strlen(buf) + 1, 0);
if (ret == -1)
error("%s: keyctl_instantiate: %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);
}

/*
* Perform address resolution on a hostname
*/
static int
dns_resolver(char *server_name, char *ip, short mask)
{
struct addrinfo hints, *addr;
int ret, len;
void *p;

memset(&hints, 0, sizeof(hints));
switch (mask) {
case INET_IP4_ONLY: hints.ai_family = AF_INET; break;
case INET_IP6_ONLY: hints.ai_family = AF_INET6; 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;
}

/* convert ip to string form */
if (addr->ai_family == AF_INET && (mask & INET_IP4_ONLY)) {
p = &(((struct sockaddr_in *)addr->ai_addr)->sin_addr);
len = INET_ADDRSTRLEN;
} else if (addr->ai_family == AF_INET6 && (mask & INET_IP6_ONLY)) {
p = &(((struct sockaddr_in6 *)addr->ai_addr)->sin6_addr);
len = INET6_ADDRSTRLEN;
} else {
freeaddrinfo(addr);
return -1;
}

if (!inet_ntop(addr->ai_family, p, ip, len))
error("%s: inet_ntop: %m", __func__);

freeaddrinfo(addr);
return 0;
}

/*
*
*/
static void
addVLServers(char *VLlist[],
int *vlsnum,
ns_msg handle,
ns_sect section,
char *result,
short mask,
unsigned long *_ttl)
{
int rrnum; /* resource record number */
ns_rr rr; /* expanded resource record */
char ip[INET6_ADDRSTRLEN];
char *p = result;
int subtype, i, ret, alen;
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),/* Start of the message */
ns_msg_end(handle), /* End of the message */
ns_rr_rdata(rr) + 2, /* Position in the message*/
VLlist[*vlsnum], /* Result */
MAXDNAME /* Size of VLlist buffer*/
) < 0) /* Negative: error */
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 name 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], ip, mask);
if (ret) {
debug("AFSDB RR can't resolve."
"subtype:%d, server name:%s, netmask:%d",
subtype, VLlist[*vlsnum], mask);
goto next_one;
}

info("AFSDB RR subtype:%d, server name:%s, ip:%s, ttl:%u",
subtype, VLlist[*vlsnum], ip, ttl);

/* colons are used in IPv6 addresses, so we use commas
* to separate IP addresses
*/
if (p > result)
*p++ = ',';
alen = strlen(ip);
memcpy(p, ip, alen);
p += alen;
p[0] = '\0';

/* prepare for the next record */
(*vlsnum)++;
continue;

next_one:
free(VLlist[*vlsnum]);
}
}

*_ttl = ttl;
info("ttl: %u", ttl);
}

/*
* Look up the 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_get_vlserver(key_serial_t key, const char *cell, char *options)
{
int ret;
char *VLlist[MAX_VLS]; /* list of name servers */
char ip[AFSDB_MAX_DATA_LEN];
int vlsnum = 0; /* number of name servers in list */
short mask = INET_ALL;
int responseLen, 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 RR */
responseLen = res_query(cell, /* the query to make */
ns_c_in, /* record class */
ns_t_afsdb, /* record type */
response.buf,
sizeof(response));
if (responseLen < 0) {
/* negative result; set an arbitrary timeout on the cache of 1
* minute */
ret = keyctl_set_timeout(key, 1 * 60);
if (ret == -1)
error("%s: keyctl_set_timeout: %m", __func__);
nsError(h_errno, cell);
}

if (ns_initparse(response.buf, responseLen, &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 a list of VL servers */
addVLServers(VLlist, &vlsnum, handle, ns_s_an, ip, mask, &ttl);

info("DNS query AFSDB RR results:'%s' ttl:%lu", ip, ttl);

len = strlen(ip);

/* set the key's expiry time from the minimum TTL encountered */
ret = keyctl_set_timeout(key, ttl);
if (ret == -1)
error("%s: keyctl_set_timeout: %m", __func__);

/* handle a lack of results */
if (len == 0)
nsError(NO_DATA, cell);

info("The key instantiation data is '%s'", ip);

/* load the key with data key */
ret = keyctl_instantiate(key, ip, strlen(ip) + 1, 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] [-C <callout_info>] [-D <desc>]"
" key_serial\n",
prog);
else
info("Usage: %s [-vv] key_serial", prog);
keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT);
exit(2);
}

const struct option long_options[] = {
{ "call-out", 1, NULL, 'C' },
{ "desc", 1, 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;
char hostbuf[NI_MAXHOST];

hostbuf[0] = '\0';

openlog(prog, 0, LOG_DAEMON);

while ((ret = getopt_long(argc, argv, "vD:C:", long_options, NULL)) != -1) {
switch (ret) {
case 'C':
callout_info = optarg;
continue;
case 'D':
ret = asprintf(&buf, "%s;-1;-1;0;%s", key_type, optarg);
if (ret < 0)
error("Error %m");
continue;
case 'V':
printf("version: %s\n", DNS_PARSE_VERSION);
exit(0);
case 'v':
verbose++;
continue;
default:
if (!isatty(2))
syslog(LOG_ERR, "unknown option: %c", ret);
usage();
}
}

argc -= optind;
if (argc != 1)
usage();
argv += optind;

/* 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");
}

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;

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 is after the last semicolon */
keyend = rindex(keyend, ';');
if (!keyend)
error("Invalid key description: %s", buf);
keyend++;

name = index(keyend, ':');
if (!name)
error("Missing query type: '%s'", keyend);
qtlen = name - keyend;
name++;

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_get_vlserver(key, name, callout_info);
}

error("Query type:\"%*.*s\" is not supported", qtlen, qtlen, keyend);
}

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo(a)vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/