272 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * ldnssec-utils
 | |
|  *
 | |
|  * Written in 2021 by Lucas
 | |
|  *
 | |
|  * To the extent possible under law, the author(s) have dedicated all
 | |
|  * copyright and related and neighboring rights to this software to the
 | |
|  * public domain worldwide. This software is distributed without any
 | |
|  * warranty.
 | |
|  *
 | |
|  * You should have received a copy of the CC0 Public Domain Dedication
 | |
|  * along with this software. If not, see
 | |
|  * <http://creativecommons.org/publicdomain/zero/1.0/>.
 | |
|  */
 | |
| 
 | |
| #include <err.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #include <ldns/ldns.h>
 | |
| 
 | |
| #include "util.h"
 | |
| 
 | |
| #define DEFAULT_DURATION	(30 * 86400) /* 30 days */
 | |
| 
 | |
| static int
 | |
| extend_key_from_zone(ldns_key *k, const ldns_zone *z)
 | |
| {
 | |
| 	ldns_rr_list *rrs;
 | |
| 	ldns_rr *soa, *key_rr;
 | |
| 	size_t i;
 | |
| 	int match = 0;
 | |
| 
 | |
| 	soa = ldns_zone_soa(z);
 | |
| 	rrs = ldns_zone_rrs(z);
 | |
| 	key_rr = ldns_key2rr(k);
 | |
| 	if (key_rr == NULL)
 | |
| 		errx(1, "ldns_key2rr");
 | |
| 	for (i = 0; i < ldns_rr_list_rr_count(rrs); i++) {
 | |
| 		ldns_rr *rr;
 | |
| 		ldns_rdf *owner_rdf;
 | |
| 
 | |
| 		rr = ldns_rr_list_rr(rrs, i);
 | |
| 		if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_DNSKEY ||
 | |
| 		    ldns_rdf_compare(ldns_rr_dnskey_protocol(rr),
 | |
| 		    ldns_rr_dnskey_protocol(key_rr)) != 0 ||
 | |
| 		    ldns_rdf_compare(ldns_rr_dnskey_algorithm(rr),
 | |
| 		    ldns_rr_dnskey_algorithm(key_rr)) != 0 ||
 | |
| 		    ldns_rdf_compare(ldns_rr_dnskey_key(rr),
 | |
| 		    ldns_rr_dnskey_key(key_rr)) != 0)
 | |
| 			continue;
 | |
| 
 | |
| 		/* key and DNSKEY have the same pubkey and algorithm */
 | |
| 		owner_rdf = ldns_rdf_clone(ldns_rr_owner(rr));
 | |
| 		if (owner_rdf == NULL)
 | |
| 			errx(1, "ldns_rdf_clone");
 | |
| 		ldns_key_set_pubkey_owner(k, owner_rdf);
 | |
| 		ldns_key_set_origttl(k, ldns_rr_ttl(rr));
 | |
| 		ldns_key_set_keytag(k, ldns_calc_keytag(rr));
 | |
| 		ldns_key_set_flags(k,
 | |
| 		    ldns_rdf2native_int16(ldns_rr_dnskey_flags(rr)));
 | |
| 		match = 1;
 | |
| 		break;
 | |
| 	}
 | |
| 	ldns_rr_free(key_rr);
 | |
| 
 | |
| 	return match;
 | |
| }
 | |
| 
 | |
| static int
 | |
| parse_datetime(const char *buf, struct tm *tm)
 | |
| {
 | |
| 	char *s;
 | |
| 	time_t t;
 | |
| 
 | |
| 	t = time(NULL);
 | |
| 	if (t == (time_t)-1)
 | |
| 		err(1, "time");
 | |
| 	if (gmtime_r(&t, tm) == NULL)
 | |
| 		errx(1, "gmtime_r");
 | |
| 
 | |
| 	/* attempt to parse as YYYYMMDDhhmmss */
 | |
| 	s = strptime(buf, "%Y %m %d %H %M %S", tm);
 | |
| 	if (s != NULL && *s == '\0')
 | |
| 		return 1;
 | |
| 
 | |
| 	/* failed; attemp to parse as YYYYMMDD */
 | |
| 	s = strptime(buf, "%Y %m %d", tm);
 | |
| 	if (s != NULL && *s == '\0')
 | |
| 		return 1;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void
 | |
| usage(void)
 | |
| {
 | |
| 	const char *p = getprogname();
 | |
| 
 | |
| 	fprintf(stderr, "Usage:\n"
 | |
| 	    "\t%s [-d duration] [-i inception] key [key ...]\n"
 | |
| 	    "\t%s [-e expiration] [-i inception] key [key ...]\n",
 | |
| 	    p, p);
 | |
| 	exit(1);
 | |
| }
 | |
| 
 | |
| int
 | |
| main(int argc, char *argv[])
 | |
| {
 | |
| 	ldns_zone *zone = NULL;
 | |
| 	ldns_dnssec_zone *dnssec_zone;
 | |
| 	ldns_key_list *keys;
 | |
| 	ldns_rr_list *added_rrs, *origin_rrs;
 | |
| 	ldns_rr *origin_soa;
 | |
| 	ldns_status s;
 | |
| 	const char *errstr;
 | |
| 	struct tm tm;
 | |
| 	time_t t;
 | |
| 	size_t i;
 | |
| 	long long n;
 | |
| 	uint32_t expiration, inception, duration;
 | |
| 	int ch, line_nr;
 | |
| 	int dflag, eflag, iflag;
 | |
| 
 | |
| 	dflag = eflag = iflag = 0;
 | |
| 	duration = DEFAULT_DURATION;
 | |
| 	while ((ch = getopt(argc, argv, "d:e:i:")) != -1) {
 | |
| 		switch (ch) {
 | |
| 		case 'd':
 | |
| 			dflag = 1;
 | |
| 			n = strtonum(optarg, 0, UINT32_MAX, &errstr);
 | |
| 			if (errstr != NULL)
 | |
| 				errx(1, "-d: duration is %s: %s", errstr,
 | |
| 				    optarg);
 | |
| 			duration = n;
 | |
| 			break;
 | |
| 		case 'e':
 | |
| 			eflag = 1;
 | |
| 			if (!parse_datetime(optarg, &tm))
 | |
| 				errx(1, "-e: not in YYYYMMDDhhmmss or "
 | |
| 				    "YYYYMMDD format");
 | |
| 			t = mktime(&tm);
 | |
| 			if (t == (time_t)-1)
 | |
| 				errx(1, "mktime");
 | |
| 			expiration = t & 0xffffffff;
 | |
| 			break;
 | |
| 		case 'i':
 | |
| 			iflag = 1;
 | |
| 			if (!parse_datetime(optarg, &tm))
 | |
| 				errx(1, "-i: not in YYYYMMDDhhmmss or "
 | |
| 				    "YYYYMMDD format");
 | |
| 			t = mktime(&tm);
 | |
| 			if (t == (time_t)-1)
 | |
| 				errx(1, "mktime");
 | |
| 			inception = t & 0xffffffff;
 | |
| 			break;
 | |
| 		default:
 | |
| 			usage();
 | |
| 		}
 | |
| 	}
 | |
| 	argc -= optind;
 | |
| 	argv += optind;
 | |
| 
 | |
| 	if (dflag && eflag)
 | |
| 		errx(1, "-d and -e are mutually exclusive");
 | |
| 	if (argc == 0)
 | |
| 		usage();
 | |
| 
 | |
| 	if (!iflag) {
 | |
| 		t = time(NULL);
 | |
| 		if (t == (time_t)-1)
 | |
| 			err(1, "time");
 | |
| 		inception = t & 0xffffffff;
 | |
| 	}
 | |
| 	if (!eflag)
 | |
| 		expiration = inception + duration;
 | |
| 
 | |
| 	fatal_check_minimum_ldns_revision();
 | |
| 
 | |
| 	s = ldns_zone_new_frm_fp_l(&zone, stdin, NULL, LDNS_DEFAULT_TTL,
 | |
| 	    LDNS_RR_CLASS_IN, &line_nr);
 | |
| 	if (s != LDNS_STATUS_OK)
 | |
| 		errx(1, "ldns_zone_new_frm_fp_l: (stdin) line %d: %s",
 | |
| 		    line_nr, ldns_get_errorstr_by_id(s));
 | |
| 
 | |
| 	origin_soa = ldns_zone_soa(zone);
 | |
| 	if (origin_soa == NULL)
 | |
| 		errx(1, "no SOA in zone");
 | |
| 	origin_rrs = ldns_zone_rrs(zone);
 | |
| 
 | |
| 	dnssec_zone = ldns_dnssec_zone_new();
 | |
| 	if (dnssec_zone == NULL)
 | |
| 		err(1, "ldns_dnssec_zone_new");
 | |
| 	s = ldns_dnssec_zone_add_rr(dnssec_zone, origin_soa);
 | |
| 	if (s != LDNS_STATUS_OK)
 | |
| 		errx(1, "ldns_dnssec_zone_add_rr: %s",
 | |
| 		    ldns_get_errorstr_by_id(s));
 | |
| 	for (i = 0; i < ldns_rr_list_rr_count(origin_rrs); i++) {
 | |
| 		ldns_rr *rr;
 | |
| 
 | |
| 		rr = ldns_rr_list_rr(origin_rrs, i);
 | |
| 		if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_DNSKEY ||
 | |
| 		    ldns_rdf_compare(ldns_rr_owner(rr),
 | |
| 		    ldns_rr_owner(origin_soa)) != 0)
 | |
| 			continue;
 | |
| 
 | |
| 		s = ldns_dnssec_zone_add_rr(dnssec_zone, rr);
 | |
| 		if (s != LDNS_STATUS_OK)
 | |
| 			errx(1, "ldns_dnssec_zone_add_rr: %s",
 | |
| 			    ldns_get_errorstr_by_id(s));
 | |
| 	}
 | |
| 
 | |
| 	keys = ldns_key_list_new();
 | |
| 	if (keys == NULL)
 | |
| 		err(1, "ldns_key_list_new");
 | |
| 	for (; *argv != NULL; argv++) {
 | |
| 		ldns_key *k = NULL;
 | |
| 		FILE *fp;
 | |
| 
 | |
| 		fp = fopen(*argv, "r");
 | |
| 		if (fp == NULL)
 | |
| 			err(1, "fopen: %s", *argv);
 | |
| 		s = ldns_key_new_frm_fp_l(&k, fp, &line_nr);
 | |
| 		(void)fclose(fp);
 | |
| 		if (s != LDNS_STATUS_OK)
 | |
| 			errx(1, "ldns_key_new_frm_fp_l: %s line %d: %s",
 | |
| 			    *argv, line_nr, ldns_get_errorstr_by_id(s));
 | |
| 
 | |
| 		if (!extend_key_from_zone(k, zone))
 | |
| 			errx(1, "no DNSKEY in zone for private key \"%s\"",
 | |
| 			    *argv);
 | |
| 		ldns_key_set_expiration(k, expiration);
 | |
| 		ldns_key_set_inception(k, inception);
 | |
| 
 | |
| 		if (!ldns_key_list_push_key(keys, k))
 | |
| 			err(1, "ldns_key_list_push_key");
 | |
| 	}
 | |
| 
 | |
| 	added_rrs = ldns_rr_list_new();
 | |
| 	if (added_rrs == NULL)
 | |
| 		err(1, "ldns_rr_list_new");
 | |
| 	s = ldns_dnssec_zone_create_rrsigs_flg(dnssec_zone, added_rrs, keys,
 | |
| 	    ldns_dnssec_default_replace_signatures, NULL, 0);
 | |
| 	if (s != LDNS_STATUS_OK)
 | |
| 		errx(1, "ldns_dnssec_zone_create_rrsigs_flg: %s",
 | |
| 		    ldns_get_errorstr_by_id(s));
 | |
| 
 | |
| 	for (i = 0; i < ldns_rr_list_rr_count(added_rrs); i++) {
 | |
| 		ldns_rr *rr;
 | |
| 		char *str;
 | |
| 
 | |
| 		rr = ldns_rr_list_rr(added_rrs, i);
 | |
| 		if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_RRSIG ||
 | |
| 		    ldns_rdf2native_int16(ldns_rr_rrsig_typecovered(rr)) !=
 | |
| 		    LDNS_RR_TYPE_DNSKEY)
 | |
| 			continue;
 | |
| 
 | |
| 		str = ldns_rr2str(rr);
 | |
| 		if (str == NULL)
 | |
| 			errx(1, "ldns_rr2str");
 | |
| 		fputs(str, stdout);
 | |
| 		free(str);
 | |
| 	}
 | |
| 
 | |
| 	ldns_key_list_free(keys);
 | |
| 	ldns_dnssec_zone_free(dnssec_zone);
 | |
| 	ldns_zone_deep_free(zone);
 | |
| 	ldns_rr_list_deep_free(added_rrs);
 | |
| 
 | |
| 	return 0;
 | |
| }
 |