1 /*
2 * Copyright (c) 2014 Stichting NLnet Labs
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *
26 */
27
28 #include "config.h"
29
30 #include <sys/stat.h>
31 #include <getopt.h>
32
33 #include "cmdhandler.h"
34 #include "daemon/engine.h"
35 #include "enforcer/enforce_task.h"
36 #include "file.h"
37 #include "log.h"
38 #include "str.h"
39 #include "clientpipe.h"
40 #include "duration.h"
41 #include "db/key_data.h"
42 #include "db/zone_db.h"
43 #include "db/db_error.h"
44 #include "db/hsm_key.h"
45 #include "libhsm.h"
46 #include "libhsmdns.h"
47
48 #include "keystate/keystate_ds.h"
49
50 static const char *module_str = "keystate_ds_x_cmd";
51
52 /** Retrieve KEY from HSM, should only be called for DNSKEYs
53 * @param id, locator of DNSKEY on HSM
54 * @param zone, name of zone key belongs to
55 * @param algorithm, alg of DNSKEY
56 * @param ttl, ttl DS should get. if 0 DNSKEY_TTL is used.
57 * @return RR on succes, NULL on error */
58 static ldns_rr *
get_dnskey(const char * id,const char * zone,int alg,uint32_t ttl)59 get_dnskey(const char *id, const char *zone, int alg, uint32_t ttl)
60 {
61 libhsm_key_t *key;
62 hsm_sign_params_t *sign_params;
63 ldns_rr *dnskey_rr;
64 /* Code to output the DNSKEY record (stolen from hsmutil) */
65 hsm_ctx_t *hsm_ctx = hsm_create_context();
66 if (!hsm_ctx) {
67 ods_log_error("[%s] Could not connect to HSM", module_str);
68 return NULL;
69 }
70 if (!(key = hsm_find_key_by_id(hsm_ctx, id))) {
71 hsm_destroy_context(hsm_ctx);
72 return NULL;
73 }
74
75 /* Sign params only need to be kept around
76 * for the hsm_get_dnskey() call. */
77 sign_params = hsm_sign_params_new();
78 sign_params->owner = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, zone);
79 sign_params->algorithm = (ldns_algorithm) alg;
80 sign_params->flags = LDNS_KEY_ZONE_KEY | LDNS_KEY_SEP_KEY;
81
82 /* Get the DNSKEY record */
83 dnskey_rr = hsm_get_dnskey(hsm_ctx, key, sign_params);
84
85 libhsm_key_free(key);
86 hsm_sign_params_free(sign_params);
87 hsm_destroy_context(hsm_ctx);
88
89 /* Override the TTL in the dnskey rr */
90 if (ttl) ldns_rr_set_ttl(dnskey_rr, ttl);
91
92 return dnskey_rr;
93 }
94
95 /** returns non 0 on error */
96 static int
exec_dnskey_by_id(int sockfd,key_data_t * key,const char * ds_command,const char * action)97 exec_dnskey_by_id(int sockfd, key_data_t *key, const char* ds_command,
98 const char* action)
99 {
100 ldns_rr *dnskey_rr;
101 int ttl = 0, status, i;
102 const char *locator;
103 char *rrstr, *chrptr;
104 zone_db_t* zone;
105 struct stat stat_ret;
106 int cka = 0;
107 char *pos = NULL;
108
109 assert(key);
110
111 zone = key_data_get_zone(key);
112 if(key_data_cache_hsm_key(key) != DB_OK) {
113 ods_log_error_and_printf(sockfd, module_str,
114 "Error fetching from database");
115 zone_db_free(zone);
116 return 1;
117 }
118 locator = hsm_key_locator(key_data_hsm_key(key));
119 if (!locator) {
120 zone_db_free(zone);
121 return 1;
122 }
123 /* This fetches the states from the DB, I'm only assuming they get
124 * cleaned up when 'key' is cleaned(?) */
125 if (key_data_cache_key_states(key) != DB_OK) {
126 zone_db_free(zone);
127 return 1;
128 }
129
130 ttl = key_state_ttl(key_data_cached_dnskey(key));
131
132 dnskey_rr = get_dnskey(locator, zone_db_name(zone), key_data_algorithm(key), ttl);
133 zone_db_free(zone);
134 if (!dnskey_rr) return 2;
135
136 rrstr = ldns_rr2str(dnskey_rr);
137
138 /* Replace tab with white-space */
139 for (i = 0; rrstr[i]; ++i) {
140 if (rrstr[i] == '\t') rrstr[i] = ' ';
141 }
142
143 /* We need to strip off trailing comments before we send
144 to any clients that might be listening */
145 if ((chrptr = strchr(rrstr, ';'))) {
146 chrptr[0] = '\n';
147 chrptr[1] = '\0';
148 }
149
150 if (!ds_command || ds_command[0] == '\0') {
151 ods_log_error_and_printf(sockfd, module_str,
152 "No \"DelegationSigner%sCommand\" "
153 "configured.", action);
154 status = 1;
155 } else {
156 pos = strstr(ds_command, " --cka_id");
157 if (pos){
158 cka = 1;
159 *pos = '\0';
160 rrstr[strlen(rrstr)-1] = '\0';
161 pos = NULL;
162 }
163
164 if (stat(ds_command, &stat_ret) != 0) {
165 ods_log_error_and_printf(sockfd, module_str,
166 "Cannot stat file %s: %s", ds_command,
167 strerror(errno));
168 status = 2;
169 } else if (S_ISREG(stat_ret.st_mode) &&
170 !(stat_ret.st_mode & S_IXUSR ||
171 stat_ret.st_mode & S_IXGRP ||
172 stat_ret.st_mode & S_IXOTH)) {
173 /* Then see if it is a regular file, then if usr, grp or
174 * all have execute set */
175 status = 3;
176 ods_log_error_and_printf(sockfd, module_str,
177 "File %s is not executable", ds_command);
178 } else {
179 /* send records to the configured command */
180 FILE *fp = popen(ds_command, "w");
181 if (fp == NULL) {
182 status = 4;
183 ods_log_error_and_printf(sockfd, module_str,
184 "failed to run command: %s: %s",ds_command,
185 strerror(errno));
186 } else {
187 int bytes_written;
188 if (cka)
189 bytes_written = fprintf(fp, "%s; {cka_id = %s}\n", rrstr, locator);
190 else
191 bytes_written = fprintf(fp, "%s", rrstr);
192 if (bytes_written < 0) {
193 status = 5;
194 ods_log_error_and_printf(sockfd, module_str,
195 "Failed to write to %s: %s", ds_command,
196 strerror(errno));
197 } else if (pclose(fp) == -1) {
198 status = 6;
199 ods_log_error_and_printf(sockfd, module_str,
200 "failed to close %s: %s", ds_command,
201 strerror(errno));
202 } else {
203 client_printf(sockfd, "key %sed to %s\n",
204 action, ds_command);
205 status = 0;
206 }
207 }
208 }
209 }
210 LDNS_FREE(rrstr);
211 ldns_rr_free(dnskey_rr);
212 return status;
213 }
214
215 static int
submit_dnskey_by_id(int sockfd,key_data_t * key,engine_type * engine)216 submit_dnskey_by_id(int sockfd, key_data_t *key, engine_type* engine)
217 {
218 const char* ds_submit_command;
219 ds_submit_command = engine->config->delegation_signer_submit_command;
220 return exec_dnskey_by_id(sockfd, key, ds_submit_command, "submit");
221 }
222
223 static int
retract_dnskey_by_id(int sockfd,key_data_t * key,engine_type * engine)224 retract_dnskey_by_id(int sockfd, key_data_t *key, engine_type* engine)
225 {
226 const char* ds_retract_command;
227 ds_retract_command = engine->config->delegation_signer_retract_command;
228 return exec_dnskey_by_id(sockfd, key, ds_retract_command, "retract");
229 }
230
231 static int
ds_list_keys(db_connection_t * dbconn,int sockfd,key_data_ds_at_parent_t state)232 ds_list_keys(db_connection_t *dbconn, int sockfd,
233 key_data_ds_at_parent_t state)
234 {
235 const char *fmth = "%-31s %-13s %-13s %-40s\n";
236 const char *fmtl = "%-31s %-13s %-13u %-40s\n";
237
238 key_data_list_t *key_list;
239 const key_data_t *key;
240 zone_db_t *zone = NULL;
241 hsm_key_t* hsmkey = NULL;
242 db_clause_list_t* clause_list;
243 db_clause_t* clause;
244
245 if (!(key_list = key_data_list_new(dbconn))
246 || !(clause_list = db_clause_list_new()))
247 {
248 key_data_list_free(key_list);
249 return 10;
250 }
251 if (!(clause = key_data_role_clause(clause_list, KEY_DATA_ROLE_ZSK))
252 || db_clause_set_type(clause, DB_CLAUSE_NOT_EQUAL)
253 || !(clause = key_data_ds_at_parent_clause(clause_list, state))
254 || db_clause_set_type(clause, DB_CLAUSE_EQUAL))
255 {
256 key_data_list_free(key_list);
257 db_clause_list_free(clause_list);
258 return 11;
259 }
260 if (key_data_list_get_by_clauses(key_list, clause_list)) {
261 key_data_list_free(key_list);
262 db_clause_list_free(clause_list);
263 return 12;
264 }
265 db_clause_list_free(clause_list);
266
267 client_printf(sockfd, fmth, "Zone:", "Key role:", "Keytag:", "Id:");
268
269 for (key = key_data_list_next(key_list); key;
270 key = key_data_list_next(key_list))
271 {
272 zone = key_data_get_zone(key);
273 hsmkey = key_data_get_hsm_key(key);
274 client_printf(sockfd, fmtl,
275 (zone ? zone_db_name(zone) : "NOT_FOUND"),
276 key_data_role_text(key), key_data_keytag(key),
277 (hsmkey ? hsm_key_locator(hsmkey) : "NOT_FOUND")
278 );
279 zone_db_free(zone);
280 hsm_key_free(hsmkey);
281 }
282 key_data_list_free(key_list);
283 return 0;
284 }
285
286 static int
push_clauses(db_clause_list_t * clause_list,zone_db_t * zone,key_data_ds_at_parent_t state_from,const hsm_key_t * hsmkey,int keytag)287 push_clauses(db_clause_list_t *clause_list, zone_db_t *zone,
288 key_data_ds_at_parent_t state_from, const hsm_key_t* hsmkey, int keytag)
289 {
290 db_clause_t* clause;
291
292 if (!key_data_zone_id_clause(clause_list, zone_db_id(zone)))
293 return 1;
294 if (!(clause = key_data_role_clause(clause_list, KEY_DATA_ROLE_ZSK)) ||
295 db_clause_set_type(clause, DB_CLAUSE_NOT_EQUAL))
296 return 1;
297 if (!key_data_ds_at_parent_clause(clause_list, state_from))
298 return 1;
299
300 /* filter in id and or keytag conditionally. */
301 if (hsmkey) {
302 if (hsmkey && !key_data_hsm_key_id_clause(clause_list, hsm_key_id(hsmkey)))
303 return 1;
304 }
305 if (keytag > 0) {
306 if (!key_data_keytag_clause(clause_list, keytag))
307 return 1;
308 }
309 return 0;
310 }
311
312 /** Update timestamp on DS of key to now */
313 static int
ds_changed(key_data_t * key)314 ds_changed(key_data_t *key)
315 {
316 key_state_list_t* keystatelist;
317 key_state_t* keystate;
318
319 if(key_data_retrieve_key_state_list(key)) return 1;
320 keystatelist = key_data_key_state_list(key);
321 keystate = key_state_list_get_begin(keystatelist);
322 if (!keystate) return 1;
323
324 while (keystate) {
325 key_state_t* keystate_next;
326 if (keystate->type == KEY_STATE_TYPE_DS) {
327 keystate->last_change = time_now();
328 if(key_state_update(keystate)) {
329 key_state_free(keystate);
330 return 1;
331 }
332 key_state_free(keystate);
333 return 0;
334 }
335 keystate_next = key_state_list_get_next(keystatelist);
336 key_state_free(keystate);
337 keystate = keystate_next;
338 }
339 return 1;
340 }
341
342 /* Change DS state, when zonename not given do it for all zones!
343 */
344 int
change_keys_from_to(db_connection_t * dbconn,int sockfd,const char * zonename,const hsm_key_t * hsmkey,int keytag,key_data_ds_at_parent_t state_from,key_data_ds_at_parent_t state_to,engine_type * engine)345 change_keys_from_to(db_connection_t *dbconn, int sockfd,
346 const char *zonename, const hsm_key_t* hsmkey, int keytag,
347 key_data_ds_at_parent_t state_from, key_data_ds_at_parent_t state_to,
348 engine_type *engine)
349 {
350 key_data_list_t *key_list = NULL;
351 key_data_t *key;
352 zone_db_t *zone = NULL;
353 int status = 0, key_match = 0, key_mod = 0;
354 db_clause_list_t* clause_list = NULL;
355 db_clause_t* clause = NULL;
356 char *tmp_zone_name;
357
358 if (zonename) {
359 if (!(key_list = key_data_list_new(dbconn)) ||
360 !(clause_list = db_clause_list_new()) ||
361 !(zone = zone_db_new_get_by_name(dbconn, zonename)) ||
362 push_clauses(clause_list, zone, state_from, hsmkey, keytag) ||
363 key_data_list_get_by_clauses(key_list, clause_list))
364 {
365 key_data_list_free(key_list);
366 db_clause_list_free(clause_list);
367 zone_db_free(zone);
368 client_printf_err(sockfd, "Could not find ksk for zone %s, "
369 "does zone exist?\n", zonename);
370 ods_log_error("[%s] Error fetching from database", module_str);
371 return 10;
372 }
373 db_clause_list_free(clause_list);
374 } else {
375 /* Select all KSKs */
376 if (!(clause_list = db_clause_list_new()) ||
377 !key_data_ds_at_parent_clause(clause_list, state_from) ||
378 !(clause = key_data_role_clause(clause_list, KEY_DATA_ROLE_ZSK)) ||
379 db_clause_set_type(clause, DB_CLAUSE_NOT_EQUAL) != DB_OK ||
380 !(key_list = key_data_list_new_get_by_clauses(dbconn, clause_list)))
381 {
382 key_data_list_free(key_list);
383 db_clause_list_free(clause_list);
384 ods_log_error("[%s] Error fetching from database", module_str);
385 return 14;
386 }
387 db_clause_list_free(clause_list);
388 }
389 while ((key = key_data_list_get_next(key_list))) {
390 key_match++;
391 /* if from is submit also exec dsSubmit command? */
392 if (state_from == KEY_DATA_DS_AT_PARENT_SUBMIT &&
393 state_to == KEY_DATA_DS_AT_PARENT_SUBMITTED)
394 {
395 (void)submit_dnskey_by_id(sockfd, key, engine);
396 } else if (state_from == KEY_DATA_DS_AT_PARENT_RETRACT &&
397 state_to == KEY_DATA_DS_AT_PARENT_RETRACTED)
398 {
399 (void)retract_dnskey_by_id(sockfd, key, engine);
400 }
401
402 if (key_data_set_ds_at_parent(key, state_to) ||
403 key_data_update(key) || ds_changed(key) )
404 {
405 key_data_free(key);
406 ods_log_error("[%s] Error writing to database", module_str);
407 client_printf(sockfd, "[%s] Error writing to database", module_str);
408 status = 12;
409 break;
410 }
411 key_mod++;
412 /* We need to schedule enforce for owner of key. */
413 tmp_zone_name = zone_db_ext_zonename_from_id(dbconn, &key->zone_id);
414 if (tmp_zone_name)
415 enforce_task_flush_zone(engine, tmp_zone_name);
416 free(tmp_zone_name);
417 key_data_free(key);
418 }
419 key_data_list_free(key_list);
420
421 client_printf(sockfd, "%d KSK matches found.\n", key_match);
422 if (!key_match) status = 11;
423 client_printf(sockfd, "%d KSKs changed.\n", key_mod);
424 if (zone && key_mod > 0) {
425 zone->next_change = 0; /* asap */
426 (void)zone_db_update(zone);
427 }
428 zone_db_free(zone);
429 return status;
430 }
431
432 int
run_ds_cmd(int sockfd,const char * cmd,db_connection_t * dbconn,key_data_ds_at_parent_t state_from,key_data_ds_at_parent_t state_to,engine_type * engine)433 run_ds_cmd(int sockfd, const char *cmd,
434 db_connection_t *dbconn, key_data_ds_at_parent_t state_from,
435 key_data_ds_at_parent_t state_to, engine_type *engine)
436 {
437 #define NARGV 6
438 const char *zonename = NULL, *cka_id = NULL, *keytag_s = NULL;
439 int keytag = -1;
440 hsm_key_t* hsmkey = NULL;
441 int ret;
442 char buf[ODS_SE_MAXLINE];
443 zone_db_t* zone = NULL;
444 int all = 0;
445 int argc = 0, long_index = 0, opt = 0;
446 const char* argv[NARGV];
447
448 static struct option long_options[] = {
449 {"zone", required_argument, 0, 'z'},
450 {"cka_id", required_argument, 0, 'k'},
451 {"keytag", required_argument, 0, 'x'},
452 {"all", no_argument, 0, 'a'},
453 {0, 0, 0, 0}
454 };
455
456 strncpy(buf, cmd, ODS_SE_MAXLINE);
457 buf[sizeof(buf)-1] = '\0';
458 argc = ods_str_explode(buf, NARGV, argv);
459 if (argc == -1) {
460 client_printf_err(sockfd, "too many arguments\n");
461 ods_log_error("[%s] too many arguments for %s command",
462 module_str, cmd);
463 return -1;
464 }
465
466 optind = 0;
467 while ((opt = getopt_long(argc, (char* const*)argv, "z:k:x:a", long_options, &long_index)) != -1) {
468 switch (opt) {
469 case 'z':
470 zonename = optarg;
471 break;
472 case 'k':
473 cka_id = optarg;
474 break;
475 case 'x':
476 keytag_s = optarg;
477 break;
478 case 'a':
479 all = 1;
480 break;
481 default:
482 client_printf_err(sockfd, "unknown arguments\n");
483 ods_log_error("[%s] unknown arguments for %s command",
484 module_str, cmd);
485 return -1;
486 }
487 }
488
489 if (!all && !zonename && !cka_id && !keytag_s) {
490 return ds_list_keys(dbconn, sockfd, state_from);
491 }
492
493 if (keytag_s) {
494 keytag = atoi(keytag_s);
495 if (keytag < 0 || keytag >= 65536) {
496 ods_log_warning("[%s] value \"%d\" for --keytag is invalid",
497 module_str, keytag);
498 client_printf_err(sockfd, "value \"%d\" for --keytag is invalid\n",
499 keytag);
500
501 return 1;
502 }
503 }
504
505 if (all && zonename) {
506 ods_log_warning ("[%s] Error: Unable to use --zone and --all together", module_str);
507 client_printf_err(sockfd, "Error: Unable to use --zone and --all together\n");
508 return -1;
509 }
510
511 if (zonename && (!(zone = zone_db_new(dbconn)) || zone_db_get_by_name(zone, zonename))) {
512 ods_log_warning ("[%s] Error: Unable to find a zone named \"%s\" in database\n", module_str, zonename);
513 client_printf_err(sockfd, "Error: Unable to find a zone named \"%s\" in database\n", zonename);
514 zone_db_free(zone);
515 zone = NULL;
516 return -1;
517 }
518 zone_db_free(zone);
519 zone = NULL;
520 if (!zonename && (keytag != -1 || cka_id)) {
521 ods_log_warning ("[%s] Error: expected --zone <zone>", module_str);
522 client_printf_err(sockfd, "Error: expected --zone <zone>\n");
523 return -1;
524 }
525
526 if (!(zonename && ((cka_id && keytag == -1) || (!cka_id && keytag != -1))) && !all)
527 {
528 ods_log_warning("[%s] expected --zone and either --cka_id or "
529 "--keytag option or expected --all", module_str);
530 client_printf_err(sockfd, "expected --zone and either --cka_id or "
531 "--keytag option or expected --all.\n");
532 return -1;
533 }
534
535 if (cka_id && !(hsmkey = hsm_key_new_get_by_locator(dbconn, cka_id))) {
536 client_printf_err(sockfd, "CKA_ID %s can not be found!\n", cka_id);
537 return -1;
538 }
539 ret = change_keys_from_to(dbconn, sockfd, zonename, hsmkey, keytag,
540 state_from, state_to, engine);
541 hsm_key_free(hsmkey);
542 return ret;
543 }
544