Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > 482997498f9f95c2819169698d08f0c7 > files > 7

bind-9.3.6-25.P1.el5_11.8.src.rpm

diff -up bind-9.3.6-P1/bin/nsupdate/nsupdate.c.nsupdate-gssapi bind-9.3.6-P1/bin/nsupdate/nsupdate.c
--- bind-9.3.6-P1/bin/nsupdate/nsupdate.c.nsupdate-gssapi	2008-01-18 00:45:27.000000000 +0100
+++ bind-9.3.6-P1/bin/nsupdate/nsupdate.c	2009-02-23 15:14:33.000000000 +0100
@@ -35,6 +35,7 @@
 #include <isc/lex.h>
 #include <isc/mem.h>
 #include <isc/parseint.h>
+#include <isc/random.h>
 #include <isc/region.h>
 #include <isc/sockaddr.h>
 #include <isc/socket.h>
@@ -62,6 +63,7 @@
 #include <dns/rdatatype.h>
 #include <dns/request.h>
 #include <dns/result.h>
+#include <dns/tkey.h>
 #include <dns/tsig.h>
 
 #include <dst/dst.h>
@@ -69,6 +71,8 @@
 #include <lwres/lwres.h>
 #include <lwres/net.h>
 
+#include <dst/gssapi.h>
+
 #include <bind9/getaddresses.h>
 
 #ifdef HAVE_ADDRINFO
@@ -105,6 +109,9 @@ static isc_boolean_t have_ipv4 = ISC_FAL
 static isc_boolean_t have_ipv6 = ISC_FALSE;
 static isc_boolean_t is_dst_up = ISC_FALSE;
 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_taskmgr_t *taskmgr = NULL;
 static isc_task_t *global_task = NULL;
 static isc_event_t *global_event = NULL;
@@ -118,6 +125,10 @@ static dns_dispatch_t *dispatchv6 = NULL
 static dns_message_t *updatemsg = NULL;
 static dns_fixedname_t fuserzone;
 static dns_name_t *userzone = NULL;
+static dns_name_t *zonename = NULL;
+static dns_name_t tmpzonename;
+static dns_name_t restart_master;
+static dns_tsig_keyring_t *gssring = NULL;
 static dns_tsigkey_t *tsigkey = NULL;
 static dst_key_t *sig0key;
 static lwres_context_t *lwctx = NULL;
@@ -127,6 +138,8 @@ static int ns_inuse = 0;
 static int ns_total = 0;
 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 isc_entropy_t *entp = NULL;
 static isc_boolean_t shuttingdown = ISC_FALSE;
@@ -167,6 +180,25 @@ error(const char *format, ...) ISC_FORMA
 #define STATUS_QUIT	(isc_uint16_t)2
 #define STATUS_SYNTAX	(isc_uint16_t)3
 
+static dns_fixedname_t fkname;
+static isc_sockaddr_t *kserver = NULL;
+static char servicename[DNS_NAME_FORMATSIZE];
+static dns_name_t *keyname;
+typedef struct nsu_gssinfo {
+	dns_message_t *msg;
+	isc_sockaddr_t *addr;
+	gss_ctx_id_t context;
+} nsu_gssinfo_t;
+
+static void
+start_gssrequest(dns_name_t *master);
+static void
+send_gssrequest(isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr,
+		dns_message_t *msg, dns_request_t **request, 
+		gss_ctx_id_t context);
+static void
+recvgss(isc_task_t *task, isc_event_t *event);
+
 static dns_rdataclass_t
 getzoneclass(void) {
 	if (zoneclass == dns_rdataclass_none)
@@ -293,6 +325,13 @@ reset_system(void) {
 		check_result(result, "dns_message_create");
 	}
 	updatemsg->opcode = dns_opcode_update;
+	if (usegsstsig) {
+		if (tsigkey != NULL)
+			dns_tsigkey_detach(&tsigkey);
+		if (gssring != NULL)
+			dns_tsigkeyring_destroy(&gssring);
+		tried_other_gsstsig = ISC_FALSE;
+	}
 }
 
 static void
@@ -606,7 +645,7 @@ parse_args(int argc, char **argv) {
 	isc_result_t result;
 
 	debug("parse_args");
-	while ((ch = isc_commandline_parse(argc, argv, "dDMy:vk:r:t:u:")) != -1)
+	while ((ch = isc_commandline_parse(argc, argv, "dDMy:govk:r:t:u:")) != -1)
 	{
 		switch (ch) {
 		case 'd':
@@ -632,6 +671,14 @@ parse_args(int argc, char **argv) {
 		case 'k':
 			keyfile = isc_commandline_argument;
 			break;
+		case 'g':
+			usegsstsig = ISC_TRUE;
+			use_win2k_gsstsig = ISC_FALSE;
+			break;
+		case 'o':
+			usegsstsig = ISC_TRUE;
+			use_win2k_gsstsig = ISC_TRUE;
+			break;
 		case 't':
 			result = isc_parse_uint32(&timeout,
 						  isc_commandline_argument, 10);
@@ -674,6 +721,11 @@ parse_args(int argc, char **argv) {
 			argv[0]);
 		exit(1);
 	}
+	if (usegsstsig && (keyfile != NULL || keystr != NULL)) {
+		fprintf(stderr, "%s: cannot specify -g with -k or -y\n",
+			argv[0]);
+		exit(1);
+	}
 
 	if (argv[isc_commandline_index] != NULL) {
 		if (strcmp(argv[isc_commandline_index], "-") == 0) {
@@ -1397,8 +1449,20 @@ get_next_command(void) {
 			show_message(answer);
 		return (STATUS_MORE);
 	}
-	if (strcasecmp(word, "key") == 0)
+	if (strcasecmp(word, "key") == 0) {
+		usegsstsig = ISC_FALSE;
 		return (evaluate_key(cmdline));
+	}
+	if (strcasecmp(word, "gsstsig") == 0) {
+		usegsstsig = ISC_TRUE;
+		use_win2k_gsstsig = ISC_FALSE;
+		return (STATUS_MORE);
+	}
+	if (strcasecmp(word, "oldgsstsig") == 0) {
+		usegsstsig = ISC_TRUE;
+		use_win2k_gsstsig = ISC_TRUE;
+		return (STATUS_MORE);
+	}
 	fprintf(stderr, "incorrect section name: %s\n", word);
 	return (STATUS_SYNTAX);
 }
@@ -1542,6 +1606,10 @@ update_completed(isc_task_t *task, isc_e
 	}
  done:
 	dns_request_destroy(&request);
+	if (usegsstsig) {
+		dns_name_free(&tmpzonename, mctx);
+		dns_name_free(&restart_master, mctx);
+	}
 	isc_event_free(&event);
 	done_update();
 }
@@ -1606,8 +1674,6 @@ recvsoa(isc_task_t *task, isc_event_t *e
 	dns_rdata_t soarr = DNS_RDATA_INIT;
 	int pass = 0;
 	dns_name_t master;
-	isc_sockaddr_t *serveraddr, tempaddr;
-	dns_name_t *zonename;
 	nsu_requestinfo_t *reqinfo;
 	dns_message_t *soaquery = NULL;
 	isc_sockaddr_t *addr;
@@ -1821,8 +1887,16 @@ recvsoa(isc_task_t *task, isc_event_t *e
 	}
 	dns_rdata_freestruct(&soa);
 
-	send_update(zonename, serveraddr, localaddr);
-	setzoneclass(dns_rdataclass_none);
+	if (usegsstsig) {
+		dns_name_init(&tmpzonename, NULL);
+		dns_name_dup(zonename, mctx, &tmpzonename);
+		dns_name_init(&restart_master, NULL);
+		dns_name_dup(&master, mctx, &restart_master);
+		start_gssrequest(&master);
+	} else {
+		send_update(zonename, serveraddr, localaddr);
+		setzoneclass(dns_rdataclass_none);
+	}
 
 	dns_message_destroy(&soaquery);
 	dns_request_destroy(&request);
@@ -1875,6 +1949,286 @@ sendrequest(isc_sockaddr_t *srcaddr, isc
 }
 
 static void
+start_gssrequest(dns_name_t *master)
+{
+	gss_ctx_id_t context;
+	isc_buffer_t buf;
+	isc_result_t result;
+	isc_uint32_t val = 0;
+	dns_message_t *rmsg;
+	dns_request_t *request = NULL;
+	dns_name_t *servname;
+	dns_fixedname_t fname;
+	char namestr[DNS_NAME_FORMATSIZE];
+	char keystr[DNS_NAME_FORMATSIZE];
+
+	debug("start_gssrequest");
+	usevc = ISC_TRUE;
+
+	if (gssring != NULL)
+		dns_tsigkeyring_destroy(&gssring);
+	gssring = NULL;
+	result = dns_tsigkeyring_create(mctx, &gssring);
+
+	if (result != ISC_R_SUCCESS)
+		fatal("dns_tsigkeyring_create failed: %s",
+		      isc_result_totext(result));
+
+	dns_name_format(master, namestr, sizeof(namestr));
+	if (kserver == NULL) {
+		kserver = isc_mem_get(mctx, sizeof(isc_sockaddr_t));
+		if (kserver == NULL)
+			fatal("out of memory");
+	}
+	if (userserver == NULL)
+		get_address(namestr, DNSDEFAULTPORT, kserver);
+	else
+		(void)memcpy(kserver, userserver, sizeof(isc_sockaddr_t));
+
+	dns_fixedname_init(&fname);
+	servname = dns_fixedname_name(&fname);
+
+	result = isc_string_printf(servicename, sizeof(servicename),
+				   "DNS/%s", namestr);
+	if (result != ISC_R_SUCCESS)
+		fatal("isc_string_printf(servicename) failed: %s",
+		      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);
+	if (result != ISC_R_SUCCESS)
+		fatal("dns_name_fromtext(servname) failed: %s",
+		      isc_result_totext(result));
+
+	dns_fixedname_init(&fkname);
+	keyname = dns_fixedname_name(&fkname);
+
+	isc_random_get(&val);
+	result = isc_string_printf(keystr, sizeof(keystr), "%u.sig-%s",
+				   val, namestr);
+	if (result != ISC_R_SUCCESS)
+		fatal("isc_string_printf(keystr) failed: %s",
+		      isc_result_totext(result));
+	isc_buffer_init(&buf, keystr, strlen(keystr));
+	isc_buffer_add(&buf, strlen(keystr));
+
+	result = dns_name_fromtext(keyname, &buf, dns_rootname,
+				   ISC_FALSE, NULL);
+	if (result != ISC_R_SUCCESS)
+		fatal("dns_name_fromtext(keyname) failed: %s",
+		      isc_result_totext(result));
+
+	/* Windows doesn't recognize name compression in the key name. */
+	keyname->attributes |= DNS_NAMEATTR_NOCOMPRESS;
+
+	rmsg = NULL;
+	result = dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &rmsg);
+	if (result != ISC_R_SUCCESS)
+		fatal("dns_message_create failed: %s",
+		      isc_result_totext(result));
+
+	/* Build first request. */
+
+	context = GSS_C_NO_CONTEXT;
+	result = dns_tkey_buildgssquery(rmsg, keyname, servname, NULL, 0,
+					&context, use_win2k_gsstsig);
+	if (result == ISC_R_FAILURE)
+		fatal("Check your Kerberos ticket, it may have expired.");
+	if (result != ISC_R_SUCCESS)
+		fatal("dns_tkey_buildgssquery failed: %s",
+		      isc_result_totext(result));
+
+	send_gssrequest(localaddr, kserver, rmsg, &request, context);
+}
+
+static void
+send_gssrequest(isc_sockaddr_t *srcaddr, isc_sockaddr_t *destaddr,
+		dns_message_t *msg, dns_request_t **request,
+		gss_ctx_id_t context)
+{
+	isc_result_t result;
+	nsu_gssinfo_t *reqinfo;
+	unsigned int options = 0;
+
+	debug("send_gssrequest");
+	reqinfo = isc_mem_get(mctx, sizeof(nsu_gssinfo_t));
+	if (reqinfo == NULL)
+		fatal("out of memory");
+	reqinfo->msg = msg;
+	reqinfo->addr = destaddr;
+	reqinfo->context = context;
+
+	options |= DNS_REQUESTOPT_TCP;
+	result = dns_request_createvia3(requestmgr, msg, srcaddr, destaddr,
+					options, tsigkey, FIND_TIMEOUT * 20,
+					FIND_TIMEOUT, 3, global_task, recvgss,
+					reqinfo, request);
+	check_result(result, "dns_request_createvia3");
+	if (debugging) {
+		ddebug("Outgoing update query:");
+		show_message(msg);
+	}
+	requests++;
+}
+
+static void
+recvgss(isc_task_t *task, isc_event_t *event) {
+	dns_requestevent_t *reqev = NULL;
+	dns_request_t *request = NULL;
+	isc_result_t result, eresult;
+	dns_message_t *rcvmsg = NULL;
+	nsu_gssinfo_t *reqinfo;
+	dns_message_t *tsigquery = NULL;
+	isc_sockaddr_t *addr;
+	gss_ctx_id_t context;
+	isc_buffer_t buf;
+	dns_name_t *servname;
+	dns_fixedname_t fname;
+
+	UNUSED(task);
+
+	ddebug("recvgss()");
+
+	requests--;
+
+	REQUIRE(event->ev_type == DNS_EVENT_REQUESTDONE);
+	reqev = (dns_requestevent_t *)event;
+	request = reqev->request;
+	eresult = reqev->result;
+	reqinfo = reqev->ev_arg;
+	tsigquery = reqinfo->msg;
+	context = reqinfo->context;
+	addr = reqinfo->addr;
+
+	if (shuttingdown) {
+		dns_request_destroy(&request);
+		dns_message_destroy(&tsigquery);
+		isc_mem_put(mctx, reqinfo, sizeof(nsu_gssinfo_t));
+		isc_event_free(&event);
+		maybeshutdown();
+		return;
+	}
+
+	if (eresult != ISC_R_SUCCESS) {
+		char addrbuf[ISC_SOCKADDR_FORMATSIZE];
+
+		isc_sockaddr_format(addr, addrbuf, sizeof(addrbuf));
+		fprintf(stderr, "; Communication with %s failed: %s\n",
+			addrbuf, isc_result_totext(eresult));
+		if (userserver != NULL)
+			fatal("could not talk to specified name server");
+		else if (++ns_inuse >= lwconf->nsnext)
+			fatal("could not talk to any default name server");
+		ddebug("Destroying request [%p]", request);
+		dns_request_destroy(&request);
+		dns_message_renderreset(tsigquery);
+		sendrequest(localaddr, &servers[ns_inuse], tsigquery,
+			    &request);
+		isc_mem_put(mctx, reqinfo, sizeof(nsu_gssinfo_t));
+		isc_event_free(&event);
+		return;
+	}
+	isc_mem_put(mctx, reqinfo, sizeof(nsu_gssinfo_t));
+
+	isc_event_free(&event);
+	reqev = NULL;
+
+	ddebug("recvgss creating rcvmsg");
+	result = dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &rcvmsg);
+	check_result(result, "dns_message_create");
+
+	result = dns_request_getresponse(request, rcvmsg,
+					 DNS_MESSAGEPARSE_PRESERVEORDER);
+	check_result(result, "dns_request_getresponse");
+
+	if (debugging) {
+		ddebug("recvmsg reply from GSS-TSIG query");
+		show_message(rcvmsg);
+	}
+
+	if (rcvmsg->rcode == dns_rcode_formerr && !tried_other_gsstsig) {
+		ddebug("recvgss trying %s GSS-TSIG",
+		       use_win2k_gsstsig ? "Standard" : "Win2k");
+		if (use_win2k_gsstsig)
+			use_win2k_gsstsig = ISC_FALSE;
+		else
+			use_win2k_gsstsig = ISC_TRUE;
+		tried_other_gsstsig = ISC_TRUE;
+		start_gssrequest(&restart_master);
+		goto done;
+	}
+
+	if (rcvmsg->rcode != dns_rcode_noerror &&
+	    rcvmsg->rcode != dns_rcode_nxdomain)
+		fatal("response to GSS-TSIG query was unsuccessful");
+
+	dns_fixedname_init(&fname);
+	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);
+	check_result(result, "dns_name_fromtext");
+
+	tsigkey = NULL;
+	result = dns_tkey_gssnegotiate(tsigquery, rcvmsg, servname,
+				       &context, &tsigkey, gssring, 
+				       use_win2k_gsstsig);
+	switch (result) {
+
+	case DNS_R_CONTINUE:
+		send_gssrequest(localaddr, kserver, tsigquery, &request,
+				context);
+		break;
+
+	case ISC_R_SUCCESS:
+		/*
+		 * XXXSRA Waaay too much fun here.  There's no good
+		 * reason why we need a TSIG here (the people who put
+		 * it into the spec admitted at the time that it was
+		 * not a security issue), and Windows clients don't
+		 * seem to work if named complies with the spec and
+		 * includes the gratuitous TSIG.  So we're in the
+		 * bizzare situation of having to choose between
+		 * complying with a useless requirement in the spec
+		 * and interoperating.  This is nuts.  If we can
+		 * confirm this behavior, we should ask the WG to
+		 * consider removing the requirement for the
+		 * gratuitous TSIG here.  For the moment, we ignore
+		 * the TSIG -- this too is a spec violation, but it's
+		 * the least insane thing to do.
+		 */
+#if 0
+		/*
+		 * Verify the signature. 
+		 */
+		rcvmsg->state = DNS_SECTION_ANY;
+		dns_message_setquerytsig(rcvmsg, NULL);
+		result = dns_message_settsigkey(rcvmsg, tsigkey);
+		check_result(result, "dns_message_settsigkey");
+		result = dns_message_checksig(rcvmsg, NULL);
+		ddebug("tsig verification: %s", dns_result_totext(result));
+		check_result(result, "dns_message_checksig");
+#endif /* 0 */
+
+		send_update(&tmpzonename, serveraddr, localaddr);
+		setzoneclass(dns_rdataclass_none);
+		break;
+
+	default:
+		fatal("dns_tkey_negotiategss: %s", isc_result_totext(result));
+	}
+
+done:
+	dns_request_destroy(&request);
+	dns_message_destroy(&tsigquery);
+
+	dns_message_destroy(&rcvmsg);
+	ddebug("Out of recvgss");
+}
+
+static void
 start_update(void) {
 	isc_result_t result;
 	dns_rdataset_t *rdataset = NULL;
@@ -1889,7 +2243,7 @@ start_update(void) {
 	if (answer != NULL)
 		dns_message_destroy(&answer);
 
-	if (userzone != NULL && userserver != NULL) {
+	if (userzone != NULL && userserver != NULL && !usegsstsig) {
 		send_update(userzone, userserver, localaddr);
 		setzoneclass(dns_rdataclass_none);
 		return;
@@ -1947,6 +2301,20 @@ cleanup(void) {
 
 	if (answer != NULL)
 		dns_message_destroy(&answer);
+
+	if (tsigkey != NULL) {
+		ddebug("detach tsigkey x%p", tsigkey);
+		dns_tsigkey_detach(&tsigkey);
+	}
+	if (gssring != NULL) {
+		ddebug("Destroying GSS-TSIG keyring");
+		dns_tsigkeyring_destroy(&gssring);
+	}
+	if (kserver != NULL) {
+		isc_mem_put(mctx, kserver, sizeof(isc_sockaddr_t));
+		kserver = NULL;
+	}
+
 	ddebug("Shutting down task manager");
 	isc_taskmgr_destroy(&taskmgr);
 
diff -up bind-9.3.6-P1/lib/dns/include/dns/name.h.nsupdate-gssapi bind-9.3.6-P1/lib/dns/include/dns/name.h
--- bind-9.3.6-P1/lib/dns/include/dns/name.h.nsupdate-gssapi	2006-03-02 01:37:20.000000000 +0100
+++ bind-9.3.6-P1/lib/dns/include/dns/name.h	2009-02-23 15:09:29.000000000 +0100
@@ -132,6 +132,7 @@ struct dns_name {
 #define DNS_NAMEATTR_READONLY		0x0002
 #define DNS_NAMEATTR_DYNAMIC		0x0004
 #define DNS_NAMEATTR_DYNOFFSETS		0x0008
+#define DNS_NAMEATTR_NOCOMPRESS		0x0010
 /*
  * Attributes below 0x0100 reserved for name.c usage.
  */
diff -up bind-9.3.6-P1/lib/dns/name.c.nsupdate-gssapi bind-9.3.6-P1/lib/dns/name.c
--- bind-9.3.6-P1/lib/dns/name.c.nsupdate-gssapi	2006-12-07 08:02:45.000000000 +0100
+++ bind-9.3.6-P1/lib/dns/name.c	2009-02-23 15:09:29.000000000 +0100
@@ -1781,7 +1781,8 @@ dns_name_towire(const dns_name_t *name, 
 
 	methods = dns_compress_getmethods(cctx);
 
-	if ((methods & DNS_COMPRESS_GLOBAL14) != 0)
+	if ((name->attributes & DNS_NAMEATTR_NOCOMPRESS) == 0 &&
+	    (methods & DNS_COMPRESS_GLOBAL14) != 0)
 		gf = dns_compress_findglobal(cctx, name, &gp, &go);
 	else
 		gf = ISC_FALSE;
diff -up bind-9.3.6-P1/lib/isc/include/isc/string.h.nsupdate-gssapi bind-9.3.6-P1/lib/isc/include/isc/string.h
--- bind-9.3.6-P1/lib/isc/include/isc/string.h.nsupdate-gssapi	2007-09-14 01:45:58.000000000 +0200
+++ bind-9.3.6-P1/lib/isc/include/isc/string.h	2009-02-23 15:11:00.000000000 +0100
@@ -20,9 +20,12 @@
 #ifndef ISC_STRING_H
 #define ISC_STRING_H 1
 
+#include <isc/formatcheck.h>
 #include <isc/int.h>
 #include <isc/lang.h>
 #include <isc/platform.h>
+#include <isc/types.h>
+#define ISC_STRING_MAGIC 0x5e
 
 #include <string.h>
  
@@ -47,6 +50,32 @@ isc_string_touint64(char *source, char *
  * On error 'endp' points to 'source'.
  */
 
+isc_result_t
+isc_string_printf(char *target, size_t size, const char *format, ...)
+	ISC_FORMAT_PRINTF(3, 4);
+/*
+ * Print 'format' to 'target' which is a pointer to a string of at least
+ * 'size' bytes.
+ *
+ * Requires:
+ *      'target' is a pointer to a char[] of at least 'size' bytes.
+ *      'size' an integer > 0.
+ *      'format' == NULL or points to a NUL terminated string.
+ *
+ * Ensures:
+ *      If result == ISC_R_SUCCESS
+ *              'target' will be a NUL terminated string of no more
+ *              than 'size' bytes (including NUL).
+ *
+ *      If result == ISC_R_NOSPACE
+ *              'target' is undefined.
+ *
+ * Returns:
+ *      ISC_R_SUCCESS  -- 'format' was successfully printed to 'target'.
+ *      ISC_R_NOSPACE  -- 'format' could not be printed to 'target' since it
+ *                        is too small.
+ */
+
 
 char *
 isc_string_separate(char **stringp, const char *delim);
diff -up bind-9.3.6-P1/lib/isc/string.c.nsupdate-gssapi bind-9.3.6-P1/lib/isc/string.c
--- bind-9.3.6-P1/lib/isc/string.c.nsupdate-gssapi	2004-09-16 03:00:58.000000000 +0200
+++ bind-9.3.6-P1/lib/isc/string.c	2009-02-23 15:09:29.000000000 +0100
@@ -22,6 +22,9 @@
 #include <ctype.h>
 
 #include <isc/string.h>
+#include <isc/util.h>
+#include <isc/print.h>
+#include <stdio.h>
 
 static char digits[] = "0123456789abcdefghijklmnoprstuvwxyz";
 
@@ -89,6 +92,27 @@ isc_string_touint64(char *source, char *
 	return (tmp);
 }
 
+isc_result_t
+isc_string_printf(char *target, size_t size, const char *format, ...) {
+	va_list args;
+	size_t n;
+
+	REQUIRE(size > 0U);
+
+	va_start(args, format);
+	n = vsnprintf(target, size, format, args);
+	va_end(args);
+
+	if (n >= size) {
+		memset(target, ISC_STRING_MAGIC, size);
+		return (ISC_R_NOSPACE);
+	}
+
+	ENSURE(strlen(target) < size);
+
+	return (ISC_R_SUCCESS);
+}
+
 char *
 isc_string_separate(char **stringp, const char *delim) {
 	char *string = *stringp;