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