summaryrefslogtreecommitdiff
path: root/bin/nsupdate/nsupdate.c
diff options
context:
space:
mode:
Diffstat (limited to 'bin/nsupdate/nsupdate.c')
-rw-r--r--bin/nsupdate/nsupdate.c279
1 files changed, 226 insertions, 53 deletions
diff --git a/bin/nsupdate/nsupdate.c b/bin/nsupdate/nsupdate.c
index d9ee4884a6043..9bbea4bc937c7 100644
--- a/bin/nsupdate/nsupdate.c
+++ b/bin/nsupdate/nsupdate.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2004-2010 Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (C) 2004-2011 Internet Systems Consortium, Inc. ("ISC")
* Copyright (C) 2000-2003 Internet Software Consortium.
*
* Permission to use, copy, modify, and/or distribute this software for any
@@ -15,7 +15,7 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id: nsupdate.c,v 1.163.48.15 2010-12-09 04:30:57 tbox Exp $ */
+/* $Id: nsupdate.c,v 1.193 2011-01-10 05:32:03 marka Exp $ */
/*! \file */
@@ -33,6 +33,7 @@
#include <isc/commandline.h>
#include <isc/entropy.h>
#include <isc/event.h>
+#include <isc/file.h>
#include <isc/hash.h>
#include <isc/lex.h>
#include <isc/log.h>
@@ -50,6 +51,8 @@
#include <isc/types.h>
#include <isc/util.h>
+#include <isccfg/namedconf.h>
+
#include <dns/callbacks.h>
#include <dns/dispatch.h>
#include <dns/dnssec.h>
@@ -78,6 +81,7 @@
#ifdef GSSAPI
#include <dst/gssapi.h>
+#include ISC_PLATFORM_KRB5HEADER
#endif
#include <bind9/getaddresses.h>
@@ -106,6 +110,8 @@ extern int h_errno;
#define DNSDEFAULTPORT 53
+static isc_uint16_t dnsport = DNSDEFAULTPORT;
+
#ifndef RESOLV_CONF
#define RESOLV_CONF "/etc/resolv.conf"
#endif
@@ -119,6 +125,7 @@ static isc_boolean_t usevc = ISC_FALSE;
static isc_boolean_t usegsstsig = ISC_FALSE;
static isc_boolean_t use_win2k_gsstsig = ISC_FALSE;
static isc_boolean_t tried_other_gsstsig = ISC_FALSE;
+static isc_boolean_t local_only = ISC_FALSE;
static isc_taskmgr_t *taskmgr = NULL;
static isc_task_t *global_task = NULL;
static isc_event_t *global_event = NULL;
@@ -148,7 +155,8 @@ static isc_sockaddr_t *userserver = NULL;
static isc_sockaddr_t *localaddr = NULL;
static isc_sockaddr_t *serveraddr = NULL;
static isc_sockaddr_t tempaddr;
-static char *keystr = NULL, *keyfile = NULL;
+static const char *keyfile = NULL;
+static char *keystr = NULL;
static isc_entropy_t *entropy = NULL;
static isc_boolean_t shuttingdown = ISC_FALSE;
static FILE *input;
@@ -174,8 +182,10 @@ typedef struct nsu_requestinfo {
static void
sendrequest(isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr,
dns_message_t *msg, dns_request_t **request);
-static void
-fatal(const char *format, ...) ISC_FORMAT_PRINTF(1, 2);
+
+ISC_PLATFORM_NORETURN_PRE static void
+fatal(const char *format, ...)
+ISC_FORMAT_PRINTF(1, 2) ISC_PLATFORM_NORETURN_POST;
static void
debug(const char *format, ...) ISC_FORMAT_PRINTF(1, 2);
@@ -406,7 +416,7 @@ reset_system(void) {
if (tsigkey != NULL)
dns_tsigkey_detach(&tsigkey);
if (gssring != NULL)
- dns_tsigkeyring_destroy(&gssring);
+ dns_tsigkeyring_detach(&gssring);
tried_other_gsstsig = ISC_FALSE;
}
}
@@ -479,6 +489,19 @@ parse_hmac(dns_name_t **hmac, const char *hmacstr, size_t len) {
return (digestbits);
}
+static int
+basenamelen(const char *file) {
+ int len = strlen(file);
+
+ if (len > 1 && file[len - 1] == '.')
+ len -= 1;
+ else if (len > 8 && strcmp(file + len - 8, ".private") == 0)
+ len -= 8;
+ else if (len > 4 && strcmp(file + len - 4, ".key") == 0)
+ len -= 4;
+ return (len);
+}
+
static void
setup_keystr(void) {
unsigned char *secret = NULL;
@@ -520,8 +543,7 @@ setup_keystr(void) {
isc_buffer_add(&keynamesrc, n - name);
debug("namefromtext");
- result = dns_name_fromtext(keyname, &keynamesrc, dns_rootname,
- ISC_FALSE, NULL);
+ result = dns_name_fromtext(keyname, &keynamesrc, dns_rootname, 0, NULL);
check_result(result, "dns_name_fromtext");
secretlen = strlen(secretstr) * 3 / 4;
@@ -553,21 +575,67 @@ setup_keystr(void) {
isc_mem_free(mctx, secret);
}
-static int
-basenamelen(const char *file) {
- int len = strlen(file);
+/*
+ * Get a key from a named.conf format keyfile
+ */
+static isc_result_t
+read_sessionkey(isc_mem_t *mctx, isc_log_t *lctx) {
+ cfg_parser_t *pctx = NULL;
+ cfg_obj_t *sessionkey = NULL;
+ const cfg_obj_t *key = NULL;
+ const cfg_obj_t *secretobj = NULL;
+ const cfg_obj_t *algorithmobj = NULL;
+ const char *keyname;
+ const char *secretstr;
+ const char *algorithm;
+ isc_result_t result;
+ int len;
- if (len > 1 && file[len - 1] == '.')
- len -= 1;
- else if (len > 8 && strcmp(file + len - 8, ".private") == 0)
- len -= 8;
- else if (len > 4 && strcmp(file + len - 4, ".key") == 0)
- len -= 4;
- return (len);
+ if (! isc_file_exists(keyfile))
+ return (ISC_R_FILENOTFOUND);
+
+ result = cfg_parser_create(mctx, lctx, &pctx);
+ if (result != ISC_R_SUCCESS)
+ goto cleanup;
+
+ result = cfg_parse_file(pctx, keyfile, &cfg_type_sessionkey,
+ &sessionkey);
+ if (result != ISC_R_SUCCESS)
+ goto cleanup;
+
+ result = cfg_map_get(sessionkey, "key", &key);
+ if (result != ISC_R_SUCCESS)
+ goto cleanup;
+
+ (void) cfg_map_get(key, "secret", &secretobj);
+ (void) cfg_map_get(key, "algorithm", &algorithmobj);
+ if (secretobj == NULL || algorithmobj == NULL)
+ fatal("key must have algorithm and secret");
+
+ keyname = cfg_obj_asstring(cfg_map_getname(key));
+ secretstr = cfg_obj_asstring(secretobj);
+ algorithm = cfg_obj_asstring(algorithmobj);
+
+ len = strlen(algorithm) + strlen(keyname) + strlen(secretstr) + 3;
+ keystr = isc_mem_allocate(mctx, len);
+ snprintf(keystr, len, "%s:%s:%s", algorithm, keyname, secretstr);
+ setup_keystr();
+
+ cleanup:
+ if (pctx != NULL) {
+ if (sessionkey != NULL)
+ cfg_obj_destroy(pctx, &sessionkey);
+ cfg_parser_destroy(&pctx);
+ }
+
+ if (keystr != NULL)
+ isc_mem_free(mctx, keystr);
+
+ return (result);
}
static void
-setup_keyfile(void) {
+setup_keyfile(isc_mem_t *mctx, isc_log_t *lctx) {
dst_key_t *dstkey = NULL;
isc_result_t result;
dns_name_t *hmacname = NULL;
@@ -577,15 +645,25 @@ setup_keyfile(void) {
if (sig0key != NULL)
dst_key_free(&sig0key);
- result = dst_key_fromnamedfile(keyfile,
+ /* Try reading the key from a K* pair */
+ result = dst_key_fromnamedfile(keyfile, NULL,
DST_TYPE_PRIVATE | DST_TYPE_KEY, mctx,
&dstkey);
+
+ /* If that didn't work, try reading it as a session.key keyfile */
+ if (result != ISC_R_SUCCESS) {
+ result = read_sessionkey(mctx, lctx);
+ if (result == ISC_R_SUCCESS)
+ return;
+ }
+
if (result != ISC_R_SUCCESS) {
fprintf(stderr, "could not read key from %.*s.{private,key}: "
"%s\n", basenamelen(keyfile), keyfile,
isc_result_totext(result));
return;
}
+
switch (dst_key_alg(dstkey)) {
case DST_ALG_HMACMD5:
hmacname = DNS_TSIG_HMACMD5_NAME;
@@ -746,7 +824,7 @@ setup_system(void) {
if (servers == NULL)
fatal("out of memory");
localhost.s_addr = htonl(INADDR_LOOPBACK);
- isc_sockaddr_fromin(&servers[0], &localhost, DNSDEFAULTPORT);
+ isc_sockaddr_fromin(&servers[0], &localhost, dnsport);
} else {
servers = isc_mem_get(mctx, ns_total * sizeof(isc_sockaddr_t));
if (servers == NULL)
@@ -755,12 +833,12 @@ setup_system(void) {
if (lwconf->nameservers[i].family == LWRES_ADDRTYPE_V4) {
struct in_addr in4;
memcpy(&in4, lwconf->nameservers[i].address, 4);
- isc_sockaddr_fromin(&servers[i], &in4, DNSDEFAULTPORT);
+ isc_sockaddr_fromin(&servers[i], &in4, dnsport);
} else {
struct in6_addr in6;
memcpy(&in6, lwconf->nameservers[i].address, 16);
isc_sockaddr_fromin6(&servers[i], &in6,
- DNSDEFAULTPORT);
+ dnsport);
}
}
}
@@ -827,8 +905,13 @@ setup_system(void) {
if (keystr != NULL)
setup_keystr();
- else if (keyfile != NULL)
- setup_keyfile();
+ else if (local_only) {
+ result = read_sessionkey(mctx, lctx);
+ if (result != ISC_R_SUCCESS)
+ fatal("can't read key from %s: %s\n",
+ keyfile, isc_result_totext(result));
+ } else if (keyfile != NULL)
+ setup_keyfile(mctx, lctx);
}
static void
@@ -845,7 +928,7 @@ get_address(char *host, in_port_t port, isc_sockaddr_t *sockaddr) {
INSIST(count == 1);
}
-#define PARSE_ARGS_FMT "dDMl:y:govk:rR::t:u:"
+#define PARSE_ARGS_FMT "dDML:y:ghlovk:p:rR::t:u:"
static void
pre_parse_args(int argc, char **argv) {
@@ -862,10 +945,11 @@ pre_parse_args(int argc, char **argv) {
break;
case '?':
+ case 'h':
if (isc_commandline_option != '?')
fprintf(stderr, "%s: invalid argument -%c\n",
argv[0], isc_commandline_option);
- fprintf(stderr, "usage: nsupdate [-d] "
+ fprintf(stderr, "usage: nsupdate [-dD] [-L level] [-l]"
"[-g | -o | -y keyname:secret | -k keyfile] "
"[-v] [filename]\n");
exit(1);
@@ -897,6 +981,9 @@ parse_args(int argc, char **argv, isc_mem_t *mctx, isc_entropy_t **ectx) {
case 'M':
break;
case 'l':
+ local_only = ISC_TRUE;
+ break;
+ case 'L':
result = isc_parse_uint32(&i, isc_commandline_argument,
10);
if (result != ISC_R_SUCCESS) {
@@ -923,6 +1010,15 @@ parse_args(int argc, char **argv, isc_mem_t *mctx, isc_entropy_t **ectx) {
usegsstsig = ISC_TRUE;
use_win2k_gsstsig = ISC_TRUE;
break;
+ case 'p':
+ result = isc_parse_uint16(&dnsport,
+ isc_commandline_argument, 10);
+ if (result != ISC_R_SUCCESS) {
+ fprintf(stderr, "bad port number "
+ "'%s'\n", isc_commandline_argument);
+ exit(1);
+ }
+ break;
case 't':
result = isc_parse_uint32(&timeout,
isc_commandline_argument, 10);
@@ -968,6 +1064,22 @@ parse_args(int argc, char **argv, isc_mem_t *mctx, isc_entropy_t **ectx) {
exit(1);
}
+ if (local_only) {
+ struct in_addr localhost;
+
+ if (keyfile == NULL)
+ keyfile = SESSION_KEYFILE;
+
+ if (userserver == NULL) {
+ userserver = isc_mem_get(mctx, sizeof(isc_sockaddr_t));
+ if (userserver == NULL)
+ fatal("out of memory");
+ }
+
+ localhost.s_addr = htonl(INADDR_LOOPBACK);
+ isc_sockaddr_fromin(userserver, &localhost, dnsport);
+ }
+
#ifdef GSSAPI
if (usegsstsig && (keyfile != NULL || keystr != NULL)) {
fprintf(stderr, "%s: cannot specify -g with -k or -y\n",
@@ -976,7 +1088,7 @@ parse_args(int argc, char **argv, isc_mem_t *mctx, isc_entropy_t **ectx) {
}
#else
if (usegsstsig) {
- fprintf(stderr, "%s: cannot specify -g or -o, " \
+ fprintf(stderr, "%s: cannot specify -g or -o, " \
"program not linked with GSS API Library\n",
argv[0]);
exit(1);
@@ -1022,8 +1134,7 @@ parse_name(char **cmdlinep, dns_message_t *msg, dns_name_t **namep) {
dns_message_takebuffer(msg, &namebuf);
isc_buffer_init(&source, word, strlen(word));
isc_buffer_add(&source, strlen(word));
- result = dns_name_fromtext(*namep, &source, dns_rootname,
- ISC_FALSE, NULL);
+ result = dns_name_fromtext(*namep, &source, dns_rootname, 0, NULL);
check_result(result, "dns_name_fromtext");
isc_buffer_invalidate(&source);
return (STATUS_MORE);
@@ -1225,6 +1336,11 @@ evaluate_server(char *cmdline) {
char *word, *server;
long port;
+ if (local_only) {
+ fprintf(stderr, "cannot reset server in localhost-only mode\n");
+ return (STATUS_SYNTAX);
+ }
+
word = nsu_strsep(&cmdline, " \t\r\n");
if (*word == 0) {
fprintf(stderr, "could not read server name\n");
@@ -1234,7 +1350,7 @@ evaluate_server(char *cmdline) {
word = nsu_strsep(&cmdline, " \t\r\n");
if (*word == 0)
- port = DNSDEFAULTPORT;
+ port = dnsport;
else {
char *endp;
port = strtol(word, &endp, 10);
@@ -1340,7 +1456,7 @@ evaluate_key(char *cmdline) {
isc_buffer_init(&b, namestr, strlen(namestr));
isc_buffer_add(&b, strlen(namestr));
- result = dns_name_fromtext(keyname, &b, dns_rootname, ISC_FALSE, NULL);
+ result = dns_name_fromtext(keyname, &b, dns_rootname, 0, NULL);
if (result != ISC_R_SUCCESS) {
fprintf(stderr, "could not parse key name\n");
return (STATUS_SYNTAX);
@@ -1397,8 +1513,7 @@ evaluate_zone(char *cmdline) {
userzone = dns_fixedname_name(&fuserzone);
isc_buffer_init(&b, word, strlen(word));
isc_buffer_add(&b, strlen(word));
- result = dns_name_fromtext(userzone, &b, dns_rootname, ISC_FALSE,
- NULL);
+ result = dns_name_fromtext(userzone, &b, dns_rootname, 0, NULL);
if (result != ISC_R_SUCCESS) {
userzone = NULL; /* Lest it point to an invalid name */
fprintf(stderr, "could not parse zone name\n");
@@ -1850,9 +1965,9 @@ get_next_command(void) {
"server address [port] (set master server for zone)\n"
"send (send the update request)\n"
"show (show the update request)\n"
-"answer (show the answer to the last request)\n"
+"answer (show the answer to the last request)\n"
"quit (quit, any pending update is not sent\n"
-"help (display this message_\n"
+"help (display this message_\n"
"key [hmac:]keyname secret (use TSIG to sign the request)\n"
"gsstsig (use GSS_TSIG to sign the request)\n"
"oldgsstsig (use Microsoft's GSS_TSIG to sign the request)\n"
@@ -2013,7 +2128,7 @@ send_update(dns_name_t *zonename, isc_sockaddr_t *master,
{
isc_result_t result;
dns_request_t *request = NULL;
- unsigned int options = 0;
+ unsigned int options = DNS_REQUESTOPT_CASE;
ddebug("send_update()");
@@ -2246,7 +2361,7 @@ recvsoa(isc_task_t *task, isc_event_t *event) {
result = dns_name_totext(&master, ISC_TRUE, &buf);
check_result(result, "dns_name_totext");
serverstr[isc_buffer_usedlength(&buf)] = 0;
- get_address(serverstr, DNSDEFAULTPORT, &tempaddr);
+ get_address(serverstr, dnsport, &tempaddr);
serveraddr = &tempaddr;
}
dns_rdata_freestruct(&soa);
@@ -2317,9 +2432,60 @@ sendrequest(isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr,
}
#ifdef GSSAPI
+
+/*
+ * Get the realm from the users kerberos ticket if possible
+ */
static void
-start_gssrequest(dns_name_t *master)
+get_ticket_realm(isc_mem_t *mctx)
{
+ krb5_context ctx;
+ krb5_error_code rc;
+ krb5_ccache ccache;
+ krb5_principal princ;
+ char *name, *ticket_realm;
+
+ rc = krb5_init_context(&ctx);
+ if (rc != 0)
+ return;
+
+ rc = krb5_cc_default(ctx, &ccache);
+ if (rc != 0) {
+ krb5_free_context(ctx);
+ return;
+ }
+
+ rc = krb5_cc_get_principal(ctx, ccache, &princ);
+ if (rc != 0) {
+ krb5_cc_close(ctx, ccache);
+ krb5_free_context(ctx);
+ return;
+ }
+
+ rc = krb5_unparse_name(ctx, princ, &name);
+ if (rc != 0) {
+ krb5_free_principal(ctx, princ);
+ krb5_cc_close(ctx, ccache);
+ krb5_free_context(ctx);
+ return;
+ }
+
+ ticket_realm = strrchr(name, '@');
+ if (ticket_realm != NULL) {
+ realm = isc_mem_strdup(mctx, ticket_realm);
+ }
+
+ free(name);
+ krb5_free_principal(ctx, princ);
+ krb5_cc_close(ctx, ccache);
+ krb5_free_context(ctx);
+ if (realm != NULL && debugging)
+ fprintf(stderr, "Found realm from ticket: %s\n", realm+1);
+}
+
+
+static void
+start_gssrequest(dns_name_t *master) {
gss_ctx_id_t context;
isc_buffer_t buf;
isc_result_t result;
@@ -2330,12 +2496,13 @@ start_gssrequest(dns_name_t *master)
dns_fixedname_t fname;
char namestr[DNS_NAME_FORMATSIZE];
char keystr[DNS_NAME_FORMATSIZE];
+ char *err_message = NULL;
debug("start_gssrequest");
usevc = ISC_TRUE;
if (gssring != NULL)
- dns_tsigkeyring_destroy(&gssring);
+ dns_tsigkeyring_detach(&gssring);
gssring = NULL;
result = dns_tsigkeyring_create(mctx, &gssring);
@@ -2350,13 +2517,16 @@ start_gssrequest(dns_name_t *master)
fatal("out of memory");
}
if (userserver == NULL)
- get_address(namestr, DNSDEFAULTPORT, kserver);
+ get_address(namestr, dnsport, kserver);
else
(void)memcpy(kserver, userserver, sizeof(isc_sockaddr_t));
dns_fixedname_init(&fname);
servname = dns_fixedname_name(&fname);
+ if (realm == NULL)
+ get_ticket_realm(mctx);
+
result = isc_string_printf(servicename, sizeof(servicename),
"DNS/%s%s", namestr, realm ? realm : "");
if (result != ISC_R_SUCCESS)
@@ -2364,8 +2534,7 @@ start_gssrequest(dns_name_t *master)
isc_result_totext(result));
isc_buffer_init(&buf, servicename, strlen(servicename));
isc_buffer_add(&buf, strlen(servicename));
- result = dns_name_fromtext(servname, &buf, dns_rootname,
- ISC_FALSE, NULL);
+ result = dns_name_fromtext(servname, &buf, dns_rootname, 0, NULL);
if (result != ISC_R_SUCCESS)
fatal("dns_name_fromtext(servname) failed: %s",
isc_result_totext(result));
@@ -2382,8 +2551,7 @@ start_gssrequest(dns_name_t *master)
isc_buffer_init(&buf, keystr, strlen(keystr));
isc_buffer_add(&buf, strlen(keystr));
- result = dns_name_fromtext(keyname, &buf, dns_rootname,
- ISC_FALSE, NULL);
+ result = dns_name_fromtext(keyname, &buf, dns_rootname, 0, NULL);
if (result != ISC_R_SUCCESS)
fatal("dns_name_fromtext(keyname) failed: %s",
isc_result_totext(result));
@@ -2400,9 +2568,11 @@ start_gssrequest(dns_name_t *master)
/* Build first request. */
context = GSS_C_NO_CONTEXT;
result = dns_tkey_buildgssquery(rmsg, keyname, servname, NULL, 0,
- &context, use_win2k_gsstsig);
+ &context, use_win2k_gsstsig,
+ mctx, &err_message);
if (result == ISC_R_FAILURE)
- fatal("Check your Kerberos ticket, it may have expired.");
+ fatal("tkey query failed: %s",
+ err_message != NULL ? err_message : "unknown error");
if (result != ISC_R_SUCCESS)
fatal("dns_tkey_buildgssquery failed: %s",
isc_result_totext(result));
@@ -2451,6 +2621,7 @@ recvgss(isc_task_t *task, isc_event_t *event) {
isc_buffer_t buf;
dns_name_t *servname;
dns_fixedname_t fname;
+ char *err_message = NULL;
UNUSED(task);
@@ -2533,14 +2704,14 @@ recvgss(isc_task_t *task, isc_event_t *event) {
servname = dns_fixedname_name(&fname);
isc_buffer_init(&buf, servicename, strlen(servicename));
isc_buffer_add(&buf, strlen(servicename));
- result = dns_name_fromtext(servname, &buf, dns_rootname,
- ISC_FALSE, NULL);
+ result = dns_name_fromtext(servname, &buf, dns_rootname, 0, NULL);
check_result(result, "dns_name_fromtext");
tsigkey = NULL;
result = dns_tkey_gssnegotiate(tsigquery, rcvmsg, servname,
&context, &tsigkey, gssring,
- use_win2k_gsstsig);
+ use_win2k_gsstsig,
+ &err_message);
switch (result) {
case DNS_R_CONTINUE:
@@ -2583,7 +2754,9 @@ recvgss(isc_task_t *task, isc_event_t *event) {
break;
default:
- fatal("dns_tkey_negotiategss: %s", isc_result_totext(result));
+ fatal("dns_tkey_negotiategss: %s %s",
+ isc_result_totext(result),
+ err_message != NULL ? err_message : "");
}
done:
@@ -2693,8 +2866,8 @@ cleanup(void) {
dns_tsigkey_detach(&tsigkey);
}
if (gssring != NULL) {
- ddebug("Destroying GSS-TSIG keyring");
- dns_tsigkeyring_destroy(&gssring);
+ ddebug("Detaching GSS-TSIG keyring");
+ dns_tsigkeyring_detach(&gssring);
}
if (kserver != NULL) {
isc_mem_put(mctx, kserver, sizeof(isc_sockaddr_t));