summaryrefslogtreecommitdiff
path: root/examples/ldns-verify-zone.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/ldns-verify-zone.c')
-rw-r--r--examples/ldns-verify-zone.c914
1 files changed, 914 insertions, 0 deletions
diff --git a/examples/ldns-verify-zone.c b/examples/ldns-verify-zone.c
new file mode 100644
index 0000000..8a438ce
--- /dev/null
+++ b/examples/ldns-verify-zone.c
@@ -0,0 +1,914 @@
+/*
+ * read a zone file from disk and prints it, one RR per line
+ *
+ * (c) NLnetLabs 2008
+ *
+ * See the file LICENSE for the license
+ *
+ * Missing from the checks: empty non-terminals
+ */
+
+#include "config.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include <ldns/ldns.h>
+
+#include <errno.h>
+
+#ifdef HAVE_SSL
+#include <openssl/err.h>
+
+static int verbosity = 3;
+static time_t check_time = 0;
+static int32_t inception_offset = 0;
+static int32_t expiration_offset = 0;
+static bool do_sigchase = false;
+static bool no_nomatch_msg = false;
+
+static FILE* myout;
+static FILE* myerr;
+
+static void
+update_error(ldns_status* result, ldns_status status)
+{
+ if (status != LDNS_STATUS_OK) {
+ if (*result == LDNS_STATUS_OK || *result == LDNS_STATUS_ERR ||
+ ( *result == LDNS_STATUS_CRYPTO_NO_MATCHING_KEYTAG_DNSKEY
+ && status != LDNS_STATUS_ERR
+ )) {
+ *result = status;
+ }
+ }
+}
+
+static void
+print_type(FILE* stream, ldns_rr_type type)
+{
+ const ldns_rr_descriptor *descriptor = ldns_rr_descript(type);
+
+ if (descriptor && descriptor->_name) {
+ fprintf(stream, "%s", descriptor->_name);
+ } else {
+ fprintf(stream, "TYPE%u", type);
+ }
+}
+
+static ldns_status
+read_key_file(const char *filename, ldns_rr_list *keys)
+{
+ ldns_status status = LDNS_STATUS_ERR;
+ ldns_rr *rr;
+ FILE *fp;
+ uint32_t my_ttl = 0;
+ ldns_rdf *my_origin = NULL;
+ ldns_rdf *my_prev = NULL;
+ int line_nr;
+
+ if (!(fp = fopen(filename, "r"))) {
+ return LDNS_STATUS_FILE_ERR;
+ }
+ while (!feof(fp)) {
+ status = ldns_rr_new_frm_fp_l(&rr, fp, &my_ttl, &my_origin,
+ &my_prev, &line_nr);
+
+ if (status == LDNS_STATUS_OK) {
+
+ if ( ldns_rr_get_type(rr) == LDNS_RR_TYPE_DS
+ || ldns_rr_get_type(rr) == LDNS_RR_TYPE_DNSKEY)
+
+ ldns_rr_list_push_rr(keys, rr);
+
+ } else if ( status == LDNS_STATUS_SYNTAX_EMPTY
+ || status == LDNS_STATUS_SYNTAX_TTL
+ || status == LDNS_STATUS_SYNTAX_ORIGIN
+ || status == LDNS_STATUS_SYNTAX_INCLUDE)
+
+ status = LDNS_STATUS_OK;
+ else
+ break;
+ }
+ fclose(fp);
+ return status;
+}
+
+
+
+static void
+print_rr_error(FILE* stream, ldns_rr* rr, const char* msg)
+{
+ if (verbosity > 0) {
+ fprintf(stream, "Error: %s for ", msg);
+ ldns_rdf_print(stream, ldns_rr_owner(rr));
+ fprintf(stream, "\t");
+ print_type(stream, ldns_rr_get_type(rr));
+ fprintf(stream, "\n");
+ }
+}
+
+static void
+print_rr_status_error(FILE* stream, ldns_rr* rr, ldns_status status)
+{
+ if (status != LDNS_STATUS_OK) {
+ print_rr_error(stream, rr, ldns_get_errorstr_by_id(status));
+ if (verbosity > 0 && status == LDNS_STATUS_SSL_ERR) {
+ ERR_load_crypto_strings();
+ ERR_print_errors_fp(stream);
+ }
+ }
+}
+
+static void
+print_rrs_status_error(FILE* stream, ldns_rr_list* rrs, ldns_status status,
+ ldns_dnssec_rrs* cur_sig)
+{
+ if (status != LDNS_STATUS_OK) {
+ if (ldns_rr_list_rr_count(rrs) > 0) {
+ print_rr_status_error(stream, ldns_rr_list_rr(rrs, 0),
+ status);
+ } else if (verbosity > 0) {
+ fprintf(stream, "Error: %s for <unknown>\n",
+ ldns_get_errorstr_by_id(status));
+ }
+ if (verbosity >= 4) {
+ fprintf(stream, "RRSet:\n");
+ ldns_rr_list_print(stream, rrs);
+ fprintf(stream, "Signature:\n");
+ ldns_rr_print(stream, cur_sig->rr);
+ fprintf(stream, "\n");
+ }
+ }
+}
+
+static ldns_status
+rrsig_check_time_margins(ldns_rr* rrsig
+#if 0 /* Passing those as arguments becomes sensible when
+ * rrsig_check_time_margins will be added to the library.
+ */
+ ,time_t check_time, int32_t inception_offset, int32_t expiration_offset
+#endif
+ )
+{
+ int32_t inception, expiration;
+
+ inception = ldns_rdf2native_int32(ldns_rr_rrsig_inception (rrsig));
+ expiration = ldns_rdf2native_int32(ldns_rr_rrsig_expiration(rrsig));
+
+ if (((int32_t) (check_time - inception_offset)) - inception < 0) {
+ return LDNS_STATUS_CRYPTO_SIG_NOT_INCEPTED_WITHIN_MARGIN;
+ }
+ if (expiration - ((int32_t) (check_time + expiration_offset)) < 0) {
+ return LDNS_STATUS_CRYPTO_SIG_EXPIRED_WITHIN_MARGIN;
+ }
+ return LDNS_STATUS_OK;
+}
+
+static ldns_status
+verify_rrs(ldns_rr_list* rrset_rrs, ldns_dnssec_rrs* cur_sig,
+ ldns_rr_list* keys)
+{
+ ldns_rr_list* good_keys;
+ ldns_status status, result = LDNS_STATUS_OK;
+
+ while (cur_sig) {
+ good_keys = ldns_rr_list_new();
+ status = ldns_verify_rrsig_keylist_time(rrset_rrs, cur_sig->rr,
+ keys, check_time, good_keys);
+ status = status ? status
+ : rrsig_check_time_margins(cur_sig->rr);
+ if (status != LDNS_STATUS_CRYPTO_NO_MATCHING_KEYTAG_DNSKEY ||
+ !no_nomatch_msg) {
+
+ print_rrs_status_error(myerr, rrset_rrs, status,
+ cur_sig);
+ }
+ update_error(&result, status);
+ ldns_rr_list_free(good_keys);
+ cur_sig = cur_sig->next;
+ }
+ return result;
+}
+
+static ldns_status
+verify_dnssec_rrset(ldns_rdf *zone_name, ldns_rdf *name,
+ ldns_dnssec_rrsets *rrset, ldns_rr_list *keys)
+{
+ ldns_rr_list *rrset_rrs;
+ ldns_dnssec_rrs *cur_rr, *cur_sig;
+ ldns_status status;
+
+ if (!rrset->rrs) return LDNS_STATUS_OK;
+
+ rrset_rrs = ldns_rr_list_new();
+ cur_rr = rrset->rrs;
+ while(cur_rr && cur_rr->rr) {
+ ldns_rr_list_push_rr(rrset_rrs, cur_rr->rr);
+ cur_rr = cur_rr->next;
+ }
+ cur_sig = rrset->signatures;
+ if (cur_sig) {
+ status = verify_rrs(rrset_rrs, cur_sig, keys);
+
+ } else /* delegations may be unsigned (on opt out...) */
+ if (rrset->type != LDNS_RR_TYPE_NS ||
+ ldns_dname_compare(name, zone_name) == 0) {
+
+ print_rr_error(myerr, rrset->rrs->rr, "no signatures");
+ status = LDNS_STATUS_CRYPTO_NO_RRSIG;
+ } else {
+ status = LDNS_STATUS_OK;
+ }
+ ldns_rr_list_free(rrset_rrs);
+
+ return status;
+}
+
+static ldns_status
+verify_single_rr(ldns_rr *rr, ldns_dnssec_rrs *signature_rrs,
+ ldns_rr_list *keys)
+{
+ ldns_rr_list *rrset_rrs;
+ ldns_status status;
+
+ rrset_rrs = ldns_rr_list_new();
+ ldns_rr_list_push_rr(rrset_rrs, rr);
+
+ status = verify_rrs(rrset_rrs, signature_rrs, keys);
+
+ ldns_rr_list_free(rrset_rrs);
+
+ return status;
+}
+
+static ldns_status
+verify_next_hashed_name(ldns_dnssec_zone* zone, ldns_dnssec_name *name)
+{
+ ldns_rbnode_t *next_node;
+ ldns_dnssec_name *next_name;
+ int cmp;
+ char *next_owner_str;
+ ldns_rdf *next_owner_dname;
+
+ assert(name->hashed_name != NULL);
+
+ next_node = ldns_rbtree_search(zone->hashed_names, name->hashed_name);
+ assert(next_node != NULL);
+ do {
+ next_node = ldns_rbtree_next(next_node);
+ if (next_node == LDNS_RBTREE_NULL) {
+ next_node = ldns_rbtree_first(zone->hashed_names);
+ }
+ next_name = (ldns_dnssec_name *) next_node->data;
+ } while (! next_name->nsec);
+
+ next_owner_str = ldns_rdf2str(ldns_nsec3_next_owner(name->nsec));
+ next_owner_dname = ldns_dname_new_frm_str(next_owner_str);
+ cmp = ldns_dname_compare(next_owner_dname, next_name->hashed_name);
+ ldns_rdf_deep_free(next_owner_dname);
+ LDNS_FREE(next_owner_str);
+ if (cmp != 0) {
+ if (verbosity > 0) {
+ fprintf(myerr, "Error: The NSEC3 record for ");
+ ldns_rdf_print(stdout, name->name);
+ fprintf(myerr, " points to the wrong next hashed owner"
+ " name\n\tshould point to ");
+ ldns_rdf_print(myerr, next_name->name);
+ fprintf(myerr, ", whose hashed name is ");
+ ldns_rdf_print(myerr, next_name->hashed_name);
+ fprintf(myerr, "\n");
+ }
+ return LDNS_STATUS_ERR;
+ } else {
+ return LDNS_STATUS_OK;
+ }
+}
+
+static bool zone_is_nsec3_optout(ldns_dnssec_zone* zone)
+{
+ static int remember = -1;
+
+ if (remember == -1) {
+ remember = ldns_dnssec_zone_is_nsec3_optout(zone) ? 1 : 0;
+ }
+ return remember == 1;
+}
+
+static ldns_status
+verify_nsec(ldns_dnssec_zone* zone, ldns_rbnode_t *cur_node,
+ ldns_rr_list *keys)
+{
+ ldns_rbnode_t *next_node;
+ ldns_dnssec_name *name, *next_name;
+ ldns_status status, result;
+ result = LDNS_STATUS_OK;
+
+ name = (ldns_dnssec_name *) cur_node->data;
+ if (name->nsec) {
+ if (name->nsec_signatures) {
+ status = verify_single_rr(name->nsec,
+ name->nsec_signatures, keys);
+
+ update_error(&result, status);
+ } else {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "Error: the NSEC(3) record of ");
+ ldns_rdf_print(myerr, name->name);
+ fprintf(myerr, " has no signatures\n");
+ }
+ update_error(&result, LDNS_STATUS_ERR);
+ }
+ /* check whether the NSEC record points to the right name */
+ switch (ldns_rr_get_type(name->nsec)) {
+ case LDNS_RR_TYPE_NSEC:
+ /* simply try next name */
+ next_node = ldns_rbtree_next(cur_node);
+ if (next_node == LDNS_RBTREE_NULL) {
+ next_node = ldns_rbtree_first(
+ zone->names);
+ }
+ next_node = ldns_dnssec_name_node_next_nonglue(
+ next_node);
+ if (!next_node) {
+ next_node =
+ ldns_dnssec_name_node_next_nonglue(
+ ldns_rbtree_first(zone->names));
+ }
+ next_name = (ldns_dnssec_name*)next_node->data;
+ if (ldns_dname_compare(next_name->name,
+ ldns_rr_rdf(name->nsec,
+ 0)) != 0) {
+ if (verbosity > 0) {
+ fprintf(myerr, "Error: the "
+ "NSEC record for ");
+ ldns_rdf_print(myerr,
+ name->name);
+ fprintf(myerr, " points to "
+ "the wrong "
+ "next owner name\n");
+ }
+ if (verbosity >= 4) {
+ fprintf(myerr, "\t: ");
+ ldns_rdf_print(myerr,
+ ldns_rr_rdf(
+ name->nsec,
+ 0));
+ fprintf(myerr, " i.s.o. ");
+ ldns_rdf_print(myerr,
+ next_name->name);
+ fprintf(myerr, ".\n");
+ }
+ update_error(&result,
+ LDNS_STATUS_ERR);
+ }
+ break;
+ case LDNS_RR_TYPE_NSEC3:
+ /* find the hashed next name in the tree */
+ /* this is expensive, do we need to add
+ * support for this in the structs?
+ * (ie. pointer to next hashed name?)
+ */
+ status = verify_next_hashed_name(zone, name);
+ update_error(&result, status);
+ break;
+ default:
+ break;
+ }
+ } else {
+ if (zone_is_nsec3_optout(zone) &&
+ (ldns_dnssec_name_is_glue(name) ||
+ ( ldns_dnssec_rrsets_contains_type(name->rrsets,
+ LDNS_RR_TYPE_NS)
+ && !ldns_dnssec_rrsets_contains_type(name->rrsets,
+ LDNS_RR_TYPE_DS)))) {
+ /* ok, no problem, but we need to remember to check
+ * whether the chain does not actually point to this
+ * name later */
+ } else {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "Error: there is no NSEC(3) for ");
+ ldns_rdf_print(myerr, name->name);
+ fprintf(myerr, "\n");
+ }
+ update_error(&result, LDNS_STATUS_ERR);
+ }
+ }
+ return result;
+}
+
+static ldns_status
+verify_dnssec_name(ldns_rdf *zone_name, ldns_dnssec_zone* zone,
+ ldns_rbnode_t *cur_node, ldns_rr_list *keys)
+{
+ ldns_status result = LDNS_STATUS_OK;
+ ldns_status status;
+ ldns_dnssec_rrsets *cur_rrset;
+ ldns_dnssec_name *name;
+ int on_delegation_point;
+ /* for NSEC chain checks */
+
+ name = (ldns_dnssec_name *) cur_node->data;
+ if (verbosity >= 5) {
+ fprintf(myout, "Checking: ");
+ ldns_rdf_print(myout, name->name);
+ fprintf(myout, "\n");
+ }
+
+ if (ldns_dnssec_name_is_glue(name)) {
+ /* glue */
+ cur_rrset = name->rrsets;
+ while (cur_rrset) {
+ if (cur_rrset->signatures) {
+ if (verbosity > 0) {
+ fprintf(myerr, "Error: ");
+ ldns_rdf_print(myerr, name->name);
+ fprintf(myerr, "\t");
+ print_type(myerr, cur_rrset->type);
+ fprintf(myerr, " has signature(s),"
+ " but is glue\n");
+ }
+ result = LDNS_STATUS_ERR;
+ }
+ cur_rrset = cur_rrset->next;
+ }
+ if (name->nsec) {
+ if (verbosity > 0) {
+ fprintf(myerr, "Error: ");
+ ldns_rdf_print(myerr, name->name);
+ fprintf(myerr, " has an NSEC(3),"
+ " but is glue\n");
+ }
+ result = LDNS_STATUS_ERR;
+ }
+ } else {
+ /* not glue, do real verify */
+
+ on_delegation_point =
+ ldns_dnssec_rrsets_contains_type(name->rrsets,
+ LDNS_RR_TYPE_NS)
+ && !ldns_dnssec_rrsets_contains_type(name->rrsets,
+ LDNS_RR_TYPE_SOA);
+ cur_rrset = name->rrsets;
+ while(cur_rrset) {
+
+ /* Do not check occluded rrsets
+ * on the delegation point
+ */
+ if ((on_delegation_point &&
+ (cur_rrset->type == LDNS_RR_TYPE_NS ||
+ cur_rrset->type == LDNS_RR_TYPE_DS)) ||
+ (!on_delegation_point &&
+ cur_rrset->type != LDNS_RR_TYPE_RRSIG &&
+ cur_rrset->type != LDNS_RR_TYPE_NSEC)) {
+
+ status = verify_dnssec_rrset(zone_name,
+ name->name, cur_rrset, keys);
+ update_error(&result, status);
+ }
+ cur_rrset = cur_rrset->next;
+ }
+ status = verify_nsec(zone, cur_node, keys);
+ update_error(&result, status);
+ }
+ return result;
+}
+
+static void
+add_keys_with_matching_ds(ldns_dnssec_rrsets* from_keys, ldns_rr_list *dss,
+ ldns_rr_list *to_keys)
+{
+ size_t i;
+ ldns_rr* ds_rr;
+ ldns_dnssec_rrs *cur_key;
+
+ for (i = 0; i < ldns_rr_list_rr_count(dss); i++) {
+
+ if (ldns_rr_get_type(ds_rr = ldns_rr_list_rr(dss, i))
+ == LDNS_RR_TYPE_DS) {
+
+ for (cur_key = from_keys->rrs; cur_key;
+ cur_key = cur_key->next ) {
+
+ if (ldns_rr_compare_ds(cur_key->rr, ds_rr)) {
+ ldns_rr_list_push_rr(to_keys,
+ cur_key->rr);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static ldns_status
+sigchase(ldns_resolver* res, ldns_rdf *zone_name, ldns_dnssec_rrsets *zonekeys,
+ ldns_rr_list *keys)
+{
+ ldns_dnssec_rrs* cur_key;
+ ldns_status status;
+ bool free_resolver = false;
+ ldns_rdf* parent_name;
+ ldns_rr_list* parent_keys;
+ ldns_rr_list* ds_keys;
+
+ add_keys_with_matching_ds(zonekeys, keys, keys);
+
+ /* First try to authenticate the keys offline.
+ * When do_sigchase is given validation may continue lookup up
+ * keys online. Reporting the failure of the offline validation
+ * should then be suppressed.
+ */
+ no_nomatch_msg = do_sigchase;
+ status = verify_dnssec_rrset(zone_name, zone_name, zonekeys, keys);
+ no_nomatch_msg = false;
+
+ /* Continue online on validation failure when the -S option was given.
+ */
+ if (do_sigchase &&
+ status == LDNS_STATUS_CRYPTO_NO_MATCHING_KEYTAG_DNSKEY &&
+ ldns_dname_label_count(zone_name) > 0 ) {
+
+ if (!res) {
+ if ((status = ldns_resolver_new_frm_file(&res, NULL))){
+ ldns_resolver_free(res);
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "Could not create resolver: "
+ "%s\n",
+ ldns_get_errorstr_by_id(status)
+ );
+ }
+ return status;
+ }
+ free_resolver = true;
+ ldns_resolver_set_dnssec(res,1);
+ ldns_resolver_set_dnssec_cd(res, 1);
+ }
+ if ((parent_name = ldns_dname_left_chop(zone_name))) {
+ /*
+ * Use the (authenticated) keys of the parent zone ...
+ */
+ parent_keys = ldns_fetch_valid_domain_keys(res,
+ parent_name, keys, &status);
+ ldns_rdf_deep_free(parent_name);
+
+ /*
+ * ... to validate the DS for the zone ...
+ */
+ ds_keys = ldns_validate_domain_ds(res, zone_name,
+ parent_keys);
+ ldns_rr_list_free(parent_keys);
+
+ /*
+ * ... to use it to add the KSK to the trusted keys ...
+ */
+ add_keys_with_matching_ds(zonekeys, ds_keys, keys);
+ ldns_rr_list_free(ds_keys);
+
+ /*
+ * ... to validate all zonekeys ...
+ */
+ status = verify_dnssec_rrset(zone_name, zone_name,
+ zonekeys, keys);
+ } else {
+ status = LDNS_STATUS_MEM_ERR;
+ }
+ if (free_resolver) {
+ ldns_resolver_deep_free(res);
+ }
+
+ }
+ /*
+ * ... so they can all be added to our list of trusted keys.
+ */
+ if (status == LDNS_STATUS_OK)
+ for (cur_key = zonekeys->rrs; cur_key; cur_key = cur_key->next)
+ ldns_rr_list_push_rr(keys, cur_key->rr);
+ return status;
+}
+
+static ldns_status
+verify_dnssec_zone(ldns_dnssec_zone *dnssec_zone, ldns_rdf *zone_name,
+ ldns_rr_list *keys, bool apexonly, int percentage)
+{
+ ldns_rbnode_t *cur_node;
+ ldns_dnssec_rrsets *cur_key_rrset;
+ ldns_dnssec_rrs *cur_key;
+ ldns_status status;
+ ldns_status result = LDNS_STATUS_OK;
+
+ cur_key_rrset = ldns_dnssec_zone_find_rrset(dnssec_zone, zone_name,
+ LDNS_RR_TYPE_DNSKEY);
+ if (!cur_key_rrset || !cur_key_rrset->rrs) {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "Error: No DNSKEY records at zone apex\n");
+ }
+ result = LDNS_STATUS_ERR;
+ } else {
+ /* are keys given with -k to use for validation? */
+ if (ldns_rr_list_rr_count(keys) > 0) {
+ if ((result = sigchase(NULL, zone_name, cur_key_rrset,
+ keys)))
+ goto error;
+ } else
+ for (cur_key = cur_key_rrset->rrs; cur_key;
+ cur_key = cur_key->next)
+ ldns_rr_list_push_rr(keys, cur_key->rr);
+
+ cur_node = ldns_rbtree_first(dnssec_zone->names);
+ if (cur_node == LDNS_RBTREE_NULL) {
+ if (verbosity > 0) {
+ fprintf(myerr, "Error: Empty zone?\n");
+ }
+ result = LDNS_STATUS_ERR;
+ }
+ if (apexonly) {
+ /*
+ * In this case, only the first node in the treewalk
+ * below should be checked.
+ */
+ assert( cur_node->data == dnssec_zone->soa );
+ /*
+ * Allthough the percentage option doesn't make sense
+ * here, we set it to 100 to force the first node to
+ * be checked.
+ */
+ percentage = 100;
+ }
+ while (cur_node != LDNS_RBTREE_NULL) {
+ /* should we check this one? saves calls to random. */
+ if (percentage == 100
+ || ((random() % 100) >= 100 - percentage)) {
+ status = verify_dnssec_name(zone_name,
+ dnssec_zone, cur_node, keys);
+ update_error(&result, status);
+ if (apexonly)
+ break;
+ }
+ cur_node = ldns_rbtree_next(cur_node);
+ }
+ }
+error:
+ ldns_rr_list_free(keys);
+ return result;
+}
+
+static void print_usage(FILE *out, const char *progname)
+{
+ fprintf(out, "Usage: %s [OPTIONS] <zonefile>\n", progname);
+ fprintf(out, "\tReads the zonefile and checks for DNSSEC errors.\n");
+ fprintf(out, "\nIt checks whether NSEC(3)s are present, "
+ "and verifies all signatures\n");
+ fprintf(out, "It also checks the NSEC(3) chain, but it "
+ "will error on opted-out delegations\n");
+ fprintf(out, "\nOPTIONS:\n");
+ fprintf(out, "\t-h\t\tshow this text\n");
+ fprintf(out, "\t-a\t\tapex only, check only the zone apex\n");
+ fprintf(out, "\t-e <period>\tsignatures may not expire "
+ "within this period.\n\t\t\t"
+ "(default no period is used)\n");
+ fprintf(out, "\t-i <period>\tsignatures must have been "
+ "valid at least this long.\n\t\t\t"
+ "(default signatures should just be valid now)\n");
+ fprintf(out, "\t-k <file>\tspecify a file that contains a "
+ "trusted DNSKEY or DS rr.\n\t\t\t"
+ "This option may be given more than once.\n"
+ "\t\t\tDefault is %s\n", LDNS_TRUST_ANCHOR_FILE);
+ fprintf(out, "\t-p [0-100]\tonly checks this percentage of "
+ "the zone.\n\t\t\tDefaults to 100\n");
+ fprintf(out, "\t-S\t\tchase signature(s) to a known key. "
+ "The network may be\n\t\t\taccessed to "
+ "validate the zone's DNSKEYs. (implies -k)\n");
+ fprintf(out, "\t-t YYYYMMDDhhmmss | [+|-]offset\n\t\t\t"
+ "set the validation time either by an "
+ "absolute time\n\t\t\tvalue or as an "
+ "offset in seconds from <now>.\n\t\t\t"
+ "For data that came from the network (while "
+ "chasing),\n\t\t\tsystem time will be used "
+ "for validating it regardless.\n");
+ fprintf(out, "\t-v\t\tshows the version and exits\n");
+ fprintf(out, "\t-V [0-5]\tset verbosity level (default 3)\n");
+ fprintf(out, "\n<period>s are given in ISO 8601 duration format: "
+ "P[n]Y[n]M[n]DT[n]H[n]M[n]S\n");
+ fprintf(out, "\nif no file is given standard input is read\n");
+}
+
+int
+main(int argc, char **argv)
+{
+ char *filename;
+ FILE *fp;
+ int line_nr = 0;
+ int c;
+ ldns_status s;
+ ldns_dnssec_zone *dnssec_zone = NULL;
+ ldns_status result = LDNS_STATUS_ERR;
+ bool apexonly = false;
+ int percentage = 100;
+ struct tm tm;
+ ldns_duration_type *duration;
+ ldns_rr_list *keys = ldns_rr_list_new();
+ size_t nkeys = 0;
+ const char *progname = argv[0];
+
+ check_time = ldns_time(NULL);
+ myout = stdout;
+ myerr = stderr;
+
+ while ((c = getopt(argc, argv, "ae:hi:k:vV:p:St:")) != -1) {
+ switch(c) {
+ case 'a':
+ apexonly = true;
+ break;
+ case 'h':
+ print_usage(stdout, progname);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'e':
+ case 'i':
+ duration = ldns_duration_create_from_string(optarg);
+ if (!duration) {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "<period> should be in ISO "
+ "8601 duration format: "
+ "P[n]Y[n]M[n]DT[n]H[n]M[n]S\n"
+ );
+ }
+ exit(EXIT_FAILURE);
+ }
+ if (c == 'e')
+ expiration_offset =
+ ldns_duration2time(duration);
+ else
+ inception_offset =
+ ldns_duration2time(duration);
+ break;
+ case 'k':
+ s = read_key_file(optarg, keys);
+ if (s == LDNS_STATUS_FILE_ERR) {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "Error opening %s: %s\n",
+ optarg, strerror(errno));
+ }
+ }
+ if (s != LDNS_STATUS_OK) {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "Could not parse key file "
+ "%s: %s\n",optarg,
+ ldns_get_errorstr_by_id(s));
+ }
+ exit(EXIT_FAILURE);
+ }
+ if (ldns_rr_list_rr_count(keys) == nkeys) {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "No keys found in file %s\n",
+ optarg);
+ }
+ exit(EXIT_FAILURE);
+ }
+ nkeys = ldns_rr_list_rr_count(keys);
+ break;
+ case 'p':
+ percentage = atoi(optarg);
+ if (percentage < 0 || percentage > 100) {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "percentage needs to fall "
+ "between 0..100\n");
+ }
+ exit(EXIT_FAILURE);
+ }
+ srandom(time(NULL) ^ getpid());
+ break;
+ case 'S':
+ do_sigchase = true;
+ /* may chase */
+ break;
+ case 't':
+ if (strlen(optarg) == 14 &&
+ sscanf(optarg, "%4d%2d%2d%2d%2d%2d",
+ &tm.tm_year, &tm.tm_mon,
+ &tm.tm_mday, &tm.tm_hour,
+ &tm.tm_min , &tm.tm_sec ) == 6) {
+
+ tm.tm_year -= 1900;
+ tm.tm_mon--;
+ check_time = ldns_mktime_from_utc(&tm);
+ }
+ else {
+ check_time += atoi(optarg);
+ }
+ break;
+ case 'v':
+ printf("verify-zone version %s (ldns version %s)\n",
+ LDNS_VERSION, ldns_version());
+ exit(EXIT_SUCCESS);
+ break;
+ case 'V':
+ verbosity = atoi(optarg);
+ break;
+ }
+ }
+ if (do_sigchase && nkeys == 0) {
+ (void) read_key_file(LDNS_TRUST_ANCHOR_FILE, keys);
+ nkeys = ldns_rr_list_rr_count(keys);
+
+ if (nkeys == 0) {
+ if (verbosity > 0) {
+ fprintf(myerr, "Unable to chase "
+ "signature without keys.\n");
+ }
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ fp = stdin;
+ } else if (argc == 1) {
+ filename = argv[0];
+
+ fp = fopen(filename, "r");
+ if (!fp) {
+ if (verbosity > 0) {
+ fprintf(myerr, "Unable to open %s: %s\n",
+ filename, strerror(errno));
+ }
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ print_usage(stderr, progname);
+ exit(EXIT_FAILURE);
+ }
+
+ s = ldns_dnssec_zone_new_frm_fp_l(&dnssec_zone, fp, NULL, 0,
+ LDNS_RR_CLASS_IN, &line_nr);
+ if (s == LDNS_STATUS_OK) {
+ if (!dnssec_zone->soa) {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "; Error: no SOA in the zone\n");
+ }
+ exit(EXIT_FAILURE);
+ }
+
+ result = ldns_dnssec_zone_mark_glue(dnssec_zone);
+ if (result != LDNS_STATUS_OK) {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "There were errors identifying the "
+ "glue in the zone\n");
+ }
+ }
+ if (verbosity >= 5) {
+ ldns_dnssec_zone_print(myout, dnssec_zone);
+ }
+
+ result = verify_dnssec_zone(dnssec_zone,
+ dnssec_zone->soa->name, keys, apexonly,
+ percentage);
+
+ if (result == LDNS_STATUS_OK) {
+ if (verbosity >= 3) {
+ fprintf(myout,
+ "Zone is verified and complete\n");
+ }
+ } else {
+ if (verbosity > 0) {
+ fprintf(myerr,
+ "There were errors in the zone\n");
+ }
+ }
+
+ ldns_dnssec_zone_deep_free(dnssec_zone);
+ } else {
+ if (verbosity > 0) {
+ fprintf(myerr, "%s at %d\n",
+ ldns_get_errorstr_by_id(s), line_nr);
+ }
+ exit(EXIT_FAILURE);
+ }
+ fclose(fp);
+
+ exit(result);
+}
+
+#else
+
+int
+main(int argc, char **argv)
+{
+ fprintf(stderr, "ldns-verify-zone needs OpenSSL support, "
+ "which has not been compiled in\n");
+ return 1;
+}
+#endif /* HAVE_SSL */
+