1/*	$NetBSD: tsig-keygen.c,v 1.2 2024/02/21 22:51:00 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16/*! \file */
17
18/**
19 * tsig-keygen generates TSIG keys that can be used in named configuration
20 * files for dynamic DNS.
21 */
22
23#include <stdarg.h>
24#include <stdbool.h>
25#include <stdlib.h>
26
27#include <isc/assertions.h>
28#include <isc/attributes.h>
29#include <isc/base64.h>
30#include <isc/buffer.h>
31#include <isc/commandline.h>
32#include <isc/file.h>
33#include <isc/mem.h>
34#include <isc/net.h>
35#include <isc/print.h>
36#include <isc/result.h>
37#include <isc/string.h>
38#include <isc/time.h>
39#include <isc/util.h>
40
41#include <dns/keyvalues.h>
42#include <dns/name.h>
43
44#include <dst/dst.h>
45
46#include <confgen/os.h>
47
48#include "keygen.h"
49#include "util.h"
50
51#define KEYGEN_DEFAULT	"tsig-key"
52#define CONFGEN_DEFAULT "ddns-key"
53
54static char program[256];
55const char *progname;
56static enum { progmode_keygen, progmode_confgen } progmode;
57bool verbose = false; /* needed by util.c but not used here */
58
59noreturn static void
60usage(int status);
61
62static void
63usage(int status) {
64	if (progmode == progmode_confgen) {
65		fprintf(stderr, "\
66Usage:\n\
67 %s [-a alg] [-k keyname] [-q] [-s name | -z zone]\n\
68  -a alg:        algorithm (default hmac-sha256)\n\
69  -k keyname:    name of the key as it will be used in named.conf\n\
70  -s name:       domain name to be updated using the created key\n\
71  -z zone:       name of the zone as it will be used in named.conf\n\
72  -q:            quiet mode: print the key, with no explanatory text\n",
73			progname);
74	} else {
75		fprintf(stderr, "\
76Usage:\n\
77 %s [-a alg] [keyname]\n\
78  -a alg:        algorithm (default hmac-sha256)\n\n",
79			progname);
80	}
81
82	exit(status);
83}
84
85int
86main(int argc, char **argv) {
87	isc_result_t result = ISC_R_SUCCESS;
88	bool show_final_mem = false;
89	bool quiet = false;
90	isc_buffer_t key_txtbuffer;
91	char key_txtsecret[256];
92	isc_mem_t *mctx = NULL;
93	const char *keyname = NULL;
94	const char *zone = NULL;
95	const char *self_domain = NULL;
96	char *keybuf = NULL;
97	dns_secalg_t alg = DST_ALG_HMACSHA256;
98	const char *algname;
99	int keysize = 256;
100	int len = 0;
101	int ch;
102
103	result = isc_file_progname(*argv, program, sizeof(program));
104	if (result != ISC_R_SUCCESS) {
105		memmove(program, "tsig-keygen", 11);
106	}
107	progname = program;
108
109	/*
110	 * Libtool doesn't preserve the program name prior to final
111	 * installation.  Remove the libtool prefix ("lt-").
112	 */
113	if (strncmp(progname, "lt-", 3) == 0) {
114		progname += 3;
115	}
116
117#define PROGCMP(X) \
118	(strcasecmp(progname, X) == 0 || strcasecmp(progname, X ".exe") == 0)
119
120	if (PROGCMP("tsig-keygen")) {
121		progmode = progmode_keygen;
122		quiet = true;
123	} else if (PROGCMP("ddns-confgen")) {
124		progmode = progmode_confgen;
125	} else {
126		UNREACHABLE();
127	}
128
129	isc_commandline_errprint = false;
130
131	while ((ch = isc_commandline_parse(argc, argv, "a:hk:Mmr:qs:y:z:")) !=
132	       -1)
133	{
134		switch (ch) {
135		case 'a':
136			algname = isc_commandline_argument;
137			alg = alg_fromtext(algname);
138			if (alg == DST_ALG_UNKNOWN) {
139				fatal("Unsupported algorithm '%s'", algname);
140			}
141			keysize = alg_bits(alg);
142			break;
143		case 'h':
144			usage(0);
145		case 'k':
146		case 'y':
147			if (progmode == progmode_confgen) {
148				keyname = isc_commandline_argument;
149			} else {
150				usage(1);
151			}
152			break;
153		case 'M':
154			isc_mem_debugging = ISC_MEM_DEBUGTRACE;
155			break;
156		case 'm':
157			show_final_mem = true;
158			break;
159		case 'q':
160			if (progmode == progmode_confgen) {
161				quiet = true;
162			} else {
163				usage(1);
164			}
165			break;
166		case 'r':
167			fatal("The -r option has been deprecated.");
168			break;
169		case 's':
170			if (progmode == progmode_confgen) {
171				self_domain = isc_commandline_argument;
172			} else {
173				usage(1);
174			}
175			break;
176		case 'z':
177			if (progmode == progmode_confgen) {
178				zone = isc_commandline_argument;
179			} else {
180				usage(1);
181			}
182			break;
183		case '?':
184			if (isc_commandline_option != '?') {
185				fprintf(stderr, "%s: invalid argument -%c\n",
186					program, isc_commandline_option);
187				usage(1);
188			} else {
189				usage(0);
190			}
191			break;
192		default:
193			fprintf(stderr, "%s: unhandled option -%c\n", program,
194				isc_commandline_option);
195			exit(1);
196		}
197	}
198
199	if (progmode == progmode_keygen) {
200		keyname = argv[isc_commandline_index++];
201	}
202
203	POST(argv);
204
205	if (self_domain != NULL && zone != NULL) {
206		usage(1); /* -s and -z cannot coexist */
207	}
208
209	if (argc > isc_commandline_index) {
210		usage(1);
211	}
212
213	/* Use canonical algorithm name */
214	algname = dst_hmac_algorithm_totext(alg);
215
216	isc_mem_create(&mctx);
217
218	if (keyname == NULL) {
219		const char *suffix = NULL;
220
221		keyname = ((progmode == progmode_keygen) ? KEYGEN_DEFAULT
222							 : CONFGEN_DEFAULT);
223		if (self_domain != NULL) {
224			suffix = self_domain;
225		} else if (zone != NULL) {
226			suffix = zone;
227		}
228		if (suffix != NULL) {
229			len = strlen(keyname) + strlen(suffix) + 2;
230			keybuf = isc_mem_get(mctx, len);
231			snprintf(keybuf, len, "%s.%s", keyname, suffix);
232			keyname = (const char *)keybuf;
233		}
234	}
235
236	isc_buffer_init(&key_txtbuffer, &key_txtsecret, sizeof(key_txtsecret));
237
238	generate_key(mctx, alg, keysize, &key_txtbuffer);
239
240	if (!quiet) {
241		printf("\
242# To activate this key, place the following in named.conf, and\n\
243# in a separate keyfile on the system or systems from which nsupdate\n\
244# will be run:\n");
245	}
246
247	printf("\
248key \"%s\" {\n\
249	algorithm %s;\n\
250	secret \"%.*s\";\n\
251};\n",
252	       keyname, algname, (int)isc_buffer_usedlength(&key_txtbuffer),
253	       (char *)isc_buffer_base(&key_txtbuffer));
254
255	if (!quiet) {
256		if (self_domain != NULL) {
257			printf("\n\
258# Then, in the \"zone\" statement for the zone containing the\n\
259# name \"%s\", place an \"update-policy\" statement\n\
260# like this one, adjusted as needed for your preferred permissions:\n\
261update-policy {\n\
262	  grant %s name %s ANY;\n\
263};\n",
264			       self_domain, keyname, self_domain);
265		} else if (zone != NULL) {
266			printf("\n\
267# Then, in the \"zone\" definition statement for \"%s\",\n\
268# place an \"update-policy\" statement like this one, adjusted as \n\
269# needed for your preferred permissions:\n\
270update-policy {\n\
271	  grant %s zonesub ANY;\n\
272};\n",
273			       zone, keyname);
274		} else {
275			printf("\n\
276# Then, in the \"zone\" statement for each zone you wish to dynamically\n\
277# update, place an \"update-policy\" statement granting update permission\n\
278# to this key.  For example, the following statement grants this key\n\
279# permission to update any name within the zone:\n\
280update-policy {\n\
281	grant %s zonesub ANY;\n\
282};\n",
283			       keyname);
284		}
285
286		printf("\n\
287# After the keyfile has been placed, the following command will\n\
288# execute nsupdate using this key:\n\
289nsupdate -k <keyfile>\n");
290	}
291
292	if (keybuf != NULL) {
293		isc_mem_put(mctx, keybuf, len);
294	}
295
296	if (show_final_mem) {
297		isc_mem_stats(mctx, stderr);
298	}
299
300	isc_mem_destroy(&mctx);
301
302	return (0);
303}
304