/** * Sign notify. * */ static void notify_tsig_sign(notify_type* notify, buffer_type* buffer) { tsig_algo_type* algo = NULL; if (!notify || !notify->tsig_rr || !notify->secondary || !notify->secondary->tsig || !notify->secondary->tsig->key || !buffer) { return; /* no tsig configured */ } algo = tsig_lookup_algo(notify->secondary->tsig->algorithm); if (!algo) { ods_log_error("[%s] unable to sign notify: tsig unknown algorithm " "%s", notify_str, notify->secondary->tsig->algorithm); return; } ods_log_assert(algo); tsig_rr_reset(notify->tsig_rr, algo, notify->secondary->tsig->key); notify->tsig_rr->original_query_id = buffer_pkt_id(buffer); notify->tsig_rr->algo_name = ldns_rdf_clone(notify->tsig_rr->algo->wf_name); notify->tsig_rr->key_name = ldns_rdf_clone(notify->tsig_rr->key->dname); log_dname(notify->tsig_rr->key_name, "tsig sign notify with key %s", LOG_DEBUG); log_dname(notify->tsig_rr->algo_name, "tsig sign notify with algorithm %s", LOG_DEBUG); tsig_rr_prepare(notify->tsig_rr); tsig_rr_update(notify->tsig_rr, buffer, buffer_position(buffer)); tsig_rr_sign(notify->tsig_rr); ods_log_debug("[%s] tsig append rr to notify id=%u", notify_str, buffer_pkt_id(buffer)); tsig_rr_append(notify->tsig_rr, buffer); buffer_pkt_set_arcount(buffer, buffer_pkt_arcount(buffer)+1); tsig_rr_prepare(notify->tsig_rr); return; }
/** * Publish the NSEC3 parameters as indicated by the signer configuration. * */ ods_status zone_publish_nsec3param(zone_type* zone) { rrset_type* rrset = NULL; rr_type* n3prr = NULL; ldns_rr* rr = NULL; ods_status status = ODS_STATUS_OK; if (!zone || !zone->name || !zone->db || !zone->signconf) { return ODS_STATUS_ASSERT_ERR; } if (!zone->signconf->nsec3params) { /* NSEC */ ods_log_assert(zone->signconf->nsec_type == LDNS_RR_TYPE_NSEC); return ODS_STATUS_OK; } if (!zone->signconf->nsec3params->rr) { rr = ldns_rr_new_frm_type(LDNS_RR_TYPE_NSEC3PARAMS); if (!rr) { ods_log_error("[%s] unable to publish nsec3params for zone %s: " "error creating rr (%s)", zone_str, zone->name, ods_status2str(status)); return ODS_STATUS_MALLOC_ERR; } ldns_rr_set_class(rr, zone->klass); ldns_rr_set_ttl(rr, 0); ldns_rr_set_owner(rr, ldns_rdf_clone(zone->apex)); ldns_nsec3_add_param_rdfs(rr, zone->signconf->nsec3params->algorithm, 0, zone->signconf->nsec3params->iterations, zone->signconf->nsec3params->salt_len, zone->signconf->nsec3params->salt_data); /** * Always set bit 7 of the flags to zero, * according to rfc5155 section 11 */ ldns_set_bit(ldns_rdf_data(ldns_rr_rdf(rr, 1)), 7, 0); zone->signconf->nsec3params->rr = rr; } ods_log_assert(zone->signconf->nsec3params->rr); status = zone_add_rr(zone, zone->signconf->nsec3params->rr, 0); if (status == ODS_STATUS_UNCHANGED) { /* rr already exists, adjust pointer */ rrset = zone_lookup_rrset(zone, zone->apex, LDNS_RR_TYPE_NSEC3PARAMS); ods_log_assert(rrset); n3prr = rrset_lookup_rr(rrset, zone->signconf->nsec3params->rr); ods_log_assert(n3prr); if (n3prr->rr != zone->signconf->nsec3params->rr) { ldns_rr_free(zone->signconf->nsec3params->rr); } zone->signconf->nsec3params->rr = n3prr->rr; status = ODS_STATUS_OK; } else if (status != ODS_STATUS_OK) { ods_log_error("[%s] unable to publish nsec3params for zone %s: " "error adding nsec3params (%s)", zone_str, zone->name, ods_status2str(status)); } return status; }
ldns_status ldns_resolver_push_nameserver(ldns_resolver *r, ldns_rdf *n) { ldns_rdf **nameservers; size_t ns_count; size_t *rtt; if (ldns_rdf_get_type(n) != LDNS_RDF_TYPE_A && ldns_rdf_get_type(n) != LDNS_RDF_TYPE_AAAA) { return LDNS_STATUS_ERR; } ns_count = ldns_resolver_nameserver_count(r); nameservers = ldns_resolver_nameservers(r); rtt = ldns_resolver_rtt(r); /* make room for the next one */ nameservers = LDNS_XREALLOC(nameservers, ldns_rdf *, (ns_count + 1)); /* don't forget the rtt */ rtt = LDNS_XREALLOC(rtt, size_t, (ns_count + 1)); /* set the new value in the resolver */ ldns_resolver_set_nameservers(r, nameservers); /* slide n in its slot. */ /* we clone it here, because then we can free the original * rr's where it stood */ nameservers[ns_count] = ldns_rdf_clone(n); rtt[ns_count] = LDNS_RESOLV_RTT_MIN; ldns_resolver_incr_nameserver_count(r); ldns_resolver_set_rtt(r, rtt); return LDNS_STATUS_OK; }
/* this will probably be moved to a better place in the library itself */ ldns_rr_list * get_rrset(const ldns_zone *zone, const ldns_rdf *owner_name, const ldns_rr_type qtype, const ldns_rr_class qclass) { const char* result; switch(qtype) { case LDNS_RR_TYPE_A: result = rp_get_a_record(rp_handle, owner_name->_data); break; default: result = 0; } if(!result) { return 0; } uint16_t i; ldns_rr_list *rrlist = ldns_rr_list_new(); if (!zone || !owner_name) { fprintf(stderr, "Warning: get_rrset called with NULL zone or owner name\n"); return rrlist; } ldns_rr* rr = ldns_rr_new_frm_type(LDNS_RR_TYPE_A); ldns_rr_set_owner(rr, ldns_rdf_clone(owner_name)); ldns_rdf* rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_A, result); ldns_rr_push_rdf(rr, rdf); ldns_rr_list_push_rr(rrlist, ldns_rr_clone(rr)); return rrlist; }
static ldns_status read_soa(struct zonefile *z) { ldns_rr *rr; ldns_status status; for (;;) { status = ldns_rr_new_frm_fp_l(&rr, z->fp, &z->ttl, &z->origin, &z->prev, NULL); switch (status) { case LDNS_STATUS_OK: goto out; case LDNS_STATUS_SYNTAX_EMPTY: case LDNS_STATUS_SYNTAX_TTL: case LDNS_STATUS_SYNTAX_ORIGIN: status = LDNS_STATUS_OK; break; default: goto out; } } out: if (status != LDNS_STATUS_OK) { z->valid = false; return (LDNS_STATUS_ERR); } if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_SOA) { ldns_rr_free(rr); z->valid = false; return (LDNS_STATUS_ERR); } z->count = 1; z->domain = ldns_rdf_clone(ldns_rr_owner(rr)); z->origin = ldns_rdf_clone(ldns_rr_owner(rr)); z->rr_soa = rr; return (LDNS_STATUS_OK); }
void ldns_resolver_push_searchlist(ldns_resolver *r, ldns_rdf *d) { ldns_rdf **searchlist; size_t list_count; if (ldns_rdf_get_type(d) != LDNS_RDF_TYPE_DNAME) { return; } list_count = ldns_resolver_searchlist_count(r); searchlist = ldns_resolver_searchlist(r); searchlist = LDNS_XREALLOC(searchlist, ldns_rdf *, (list_count + 1)); if (searchlist) { r->_searchlist = searchlist; searchlist[list_count] = ldns_rdf_clone(d); ldns_resolver_set_searchlist_count(r, list_count + 1); } }
ldns_status ldns_resolver_prepare_query_pkt(ldns_pkt **query_pkt, ldns_resolver *r, const ldns_rdf *name, ldns_rr_type type, ldns_rr_class c, uint16_t flags) { /* prepare a question pkt from the parameters * and then send this */ *query_pkt = ldns_pkt_query_new(ldns_rdf_clone(name), type, c, flags); if (!*query_pkt) { return LDNS_STATUS_ERR; } /* set DO bit if necessary */ if (ldns_resolver_dnssec(r)) { if (ldns_resolver_edns_udp_size(r) == 0) { ldns_resolver_set_edns_udp_size(r, 4096); } ldns_pkt_set_edns_do(*query_pkt, true); if (ldns_resolver_dnssec_cd(r) || (flags & LDNS_CD)) { ldns_pkt_set_cd(*query_pkt, true); } } /* transfer the udp_edns_size from the resolver to the packet */ if (ldns_resolver_edns_udp_size(r) != 0) { ldns_pkt_set_edns_udp_size(*query_pkt, ldns_resolver_edns_udp_size(r)); } if (ldns_resolver_debug(r)) { ldns_pkt_print(stdout, *query_pkt); } /* only set the id if it is not set yet */ if (ldns_pkt_id(*query_pkt) == 0) { ldns_pkt_set_random_id(*query_pkt); } return LDNS_STATUS_OK; }
ldns_status ldns_dnssec_zone_mark_glue(ldns_dnssec_zone *zone) { ldns_rbnode_t *cur_node; ldns_dnssec_name *cur_name; ldns_rdf *cur_owner, *cur_parent; cur_node = ldns_rbtree_first(zone->names); while (cur_node != LDNS_RBTREE_NULL) { cur_name = (ldns_dnssec_name *) cur_node->data; cur_node = ldns_rbtree_next(cur_node); if (ldns_dnssec_name_has_only_a(cur_name)) { /* assume glue XXX check for zone cur */ cur_owner = ldns_rdf_clone(ldns_rr_owner( cur_name->rrsets->rrs->rr)); while (ldns_dname_label_count(cur_owner) > ldns_dname_label_count(zone->soa->name)) { if (ldns_dnssec_zone_find_rrset(zone, cur_owner, LDNS_RR_TYPE_NS)) { /* fprintf(stderr, "[XX] Marking as glue: "); ldns_rdf_print(stderr, cur_name->name); fprintf(stderr, "\n"); */ cur_name->is_glue = true; } cur_parent = ldns_dname_left_chop(cur_owner); ldns_rdf_deep_free(cur_owner); cur_owner = cur_parent; } ldns_rdf_deep_free(cur_owner); } } return LDNS_STATUS_OK; }
ldns_rr * ldns_create_empty_rrsig(ldns_rr_list *rrset, ldns_key *current_key) { uint32_t orig_ttl; time_t now; ldns_rr *current_sig; uint8_t label_count; label_count = ldns_dname_label_count(ldns_rr_owner(ldns_rr_list_rr(rrset, 0))); current_sig = ldns_rr_new_frm_type(LDNS_RR_TYPE_RRSIG); /* set the type on the new signature */ orig_ttl = ldns_rr_ttl(ldns_rr_list_rr(rrset, 0)); ldns_rr_set_ttl(current_sig, orig_ttl); ldns_rr_set_owner(current_sig, ldns_rdf_clone( ldns_rr_owner( ldns_rr_list_rr(rrset, 0)))); /* fill in what we know of the signature */ /* set the orig_ttl */ (void)ldns_rr_rrsig_set_origttl( current_sig, ldns_native2rdf_int32(LDNS_RDF_TYPE_INT32, orig_ttl)); /* the signers name */ (void)ldns_rr_rrsig_set_signame( current_sig, ldns_rdf_clone(ldns_key_pubkey_owner(current_key))); /* label count - get it from the first rr in the rr_list */ (void)ldns_rr_rrsig_set_labels( current_sig, ldns_native2rdf_int8(LDNS_RDF_TYPE_INT8, label_count)); /* inception, expiration */ now = time(NULL); if (ldns_key_inception(current_key) != 0) { (void)ldns_rr_rrsig_set_inception( current_sig, ldns_native2rdf_int32( LDNS_RDF_TYPE_TIME, ldns_key_inception(current_key))); } else { (void)ldns_rr_rrsig_set_inception( current_sig, ldns_native2rdf_int32(LDNS_RDF_TYPE_TIME, now)); } if (ldns_key_expiration(current_key) != 0) { (void)ldns_rr_rrsig_set_expiration( current_sig, ldns_native2rdf_int32( LDNS_RDF_TYPE_TIME, ldns_key_expiration(current_key))); } else { (void)ldns_rr_rrsig_set_expiration( current_sig, ldns_native2rdf_int32( LDNS_RDF_TYPE_TIME, now + LDNS_DEFAULT_EXP_TIME)); } (void)ldns_rr_rrsig_set_keytag( current_sig, ldns_native2rdf_int16(LDNS_RDF_TYPE_INT16, ldns_key_keytag(current_key))); (void)ldns_rr_rrsig_set_algorithm( current_sig, ldns_native2rdf_int8( LDNS_RDF_TYPE_ALG, ldns_key_algorithm(current_key))); (void)ldns_rr_rrsig_set_typecovered( current_sig, ldns_native2rdf_int16( LDNS_RDF_TYPE_TYPE, ldns_rr_get_type(ldns_rr_list_rr(rrset, 0)))); return current_sig; }
/** * Read zone file. * */ static ods_status adfile_read_file(FILE* fd, zone_type* zone) { ods_status result = ODS_STATUS_OK; ldns_rr* rr = NULL; ldns_rdf* prev = NULL; ldns_rdf* orig = NULL; ldns_rdf* dname = NULL; uint32_t ttl = 0; uint32_t new_serial = 0; ldns_status status = LDNS_STATUS_OK; char line[SE_ADFILE_MAXLINE]; unsigned int line_update_interval = 100000; unsigned int line_update = line_update_interval; unsigned int l = 0; ods_log_assert(fd); ods_log_assert(zone); /* $ORIGIN <zone name> */ dname = adapi_get_origin(zone); if (!dname) { ods_log_error("[%s] error getting default value for $ORIGIN", adapter_str); return ODS_STATUS_ERR; } orig = ldns_rdf_clone(dname); if (!orig) { ods_log_error("[%s] error setting default value for $ORIGIN", adapter_str); return ODS_STATUS_ERR; } /* $TTL <default ttl> */ ttl = adapi_get_ttl(zone); /* read RRs */ while ((rr = adfile_read_rr(fd, zone, line, &orig, &prev, &ttl, &status, &l)) != NULL) { /* check status */ if (status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RR at line %i (%s): %s", adapter_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; break; } /* debug update */ if (l > line_update) { ods_log_debug("[%s] ...at line %i: %s", adapter_str, l, line); line_update += line_update_interval; } /* SOA? */ if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_SOA) { new_serial = ldns_rdf2native_int32(ldns_rr_rdf(rr, SE_SOA_RDATA_SERIAL)); } /* add to the database */ result = adapi_add_rr(zone, rr, 0); if (result == ODS_STATUS_UNCHANGED) { ods_log_debug("[%s] skipping RR at line %i (duplicate): %s", adapter_str, l, line); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_OK; continue; } else if (result != ODS_STATUS_OK) { ods_log_error("[%s] error adding RR at line %i: %s", adapter_str, l, line); ldns_rr_free(rr); rr = NULL; break; } } /* and done */ if (orig) { ldns_rdf_deep_free(orig); orig = NULL; } if (prev) { ldns_rdf_deep_free(prev); prev = NULL; } if (result == ODS_STATUS_OK && status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RR at line %i (%s): %s", adapter_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; } /* input zone ok, set inbound serial and apply differences */ if (result == ODS_STATUS_OK) { result = namedb_examine(zone->db); if (result != ODS_STATUS_OK) { ods_log_error("[%s] unable to read file: zonefile contains errors", adapter_str); return result; } adapi_set_serial(zone, new_serial); } return result; }
/** * Read namedb from backup file. * */ ods_status backup_read_namedb(FILE* in, void* zone) { zone_type* z = (zone_type*) zone; denial_type* denial = NULL; rrset_type* rrset = NULL; ods_status result = ODS_STATUS_OK; ldns_rr_type type_covered; ldns_rr* rr = NULL; ldns_rdf* prev = NULL; ldns_rdf* orig = NULL; ldns_rdf* dname = NULL; ldns_status status = LDNS_STATUS_OK; char line[SE_ADFILE_MAXLINE]; char* str = NULL; char* locator = NULL; uint32_t flags = 0; unsigned int l = 0; ods_log_assert(in); ods_log_assert(z); /* $ORIGIN <zone name> */ dname = adapi_get_origin(z); if (!dname) { ods_log_error("[%s] error getting default value for $ORIGIN", backup_str); return ODS_STATUS_ERR; } orig = ldns_rdf_clone(dname); if (!orig) { ods_log_error("[%s] error setting default value for $ORIGIN", backup_str); return ODS_STATUS_ERR; } /* read RRs */ ods_log_debug("[%s] read RRs %s", backup_str, z->name); while ((rr = backup_read_rr(in, z, line, &orig, &prev, &status, &l)) != NULL) { /* check status */ if (status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RR #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; goto backup_namedb_done; } /* add to the database */ result = adapi_add_rr(z, rr, 1); if (result == ODS_STATUS_UNCHANGED) { ods_log_debug("[%s] skipping RR #%i (duplicate): %s", backup_str, l, line); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_OK; continue; } else if (result != ODS_STATUS_OK) { ods_log_error("[%s] error adding RR #%i: %s", backup_str, l, line); ldns_rr_free(rr); rr = NULL; goto backup_namedb_done; } } if (result == ODS_STATUS_OK && status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RR #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; goto backup_namedb_done; } namedb_diff(z->db, 0, 0); /* read NSEC(3)s */ ods_log_debug("[%s] read NSEC(3)s %s", backup_str, z->name); l = 0; while ((rr = backup_read_rr(in, z, line, &orig, &prev, &status, &l)) != NULL) { /* check status */ if (status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading NSEC(3) #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; goto backup_namedb_done; } if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_NSEC && ldns_rr_get_type(rr) != LDNS_RR_TYPE_NSEC3) { ods_log_error("[%s] error NSEC(3) #%i is not NSEC(3): %s", backup_str, l, line); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; goto backup_namedb_done; } /* add to the denial chain */ denial = namedb_lookup_denial(z->db, ldns_rr_owner(rr)); if (!denial) { ods_log_error("[%s] error adding NSEC(3) #%i: %s", backup_str, l, line); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; goto backup_namedb_done; } denial_add_rr(denial, rr); } if (result == ODS_STATUS_OK && status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading NSEC(3) #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; goto backup_namedb_done; } /* read RRSIGs */ ods_log_debug("[%s] read RRSIGs %s", backup_str, z->name); l = 0; while ((rr = backup_read_rr(in, z, line, &orig, &prev, &status, &l)) != NULL) { /* check status */ if (status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RRSIG #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; goto backup_namedb_done; } if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_RRSIG) { ods_log_error("[%s] error RRSIG #%i is not RRSIG: %s", backup_str, l, line); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; goto backup_namedb_done; } /* read locator and flags */ str = strstr(line, "flags"); if (str) { flags = (uint32_t) atoi(str+6); } str = strstr(line, "locator"); if (str) { locator = replace_space_with_nul(str+8); } /* add signatures */ type_covered = ldns_rdf2rr_type(ldns_rr_rrsig_typecovered(rr)); if (type_covered == LDNS_RR_TYPE_NSEC || type_covered == LDNS_RR_TYPE_NSEC3) { denial = namedb_lookup_denial(z->db, ldns_rr_owner(rr)); if (!denial) { ods_log_error("[%s] error restoring RRSIG #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; goto backup_namedb_done; } rrset = denial->rrset; } else { rrset = zone_lookup_rrset(z, ldns_rr_owner(rr), type_covered); } if (!rrset || !rrset_add_rrsig(rrset, rr, locator, flags)) { ods_log_error("[%s] error restoring RRSIG #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; goto backup_namedb_done; } else { rrset->needs_signing = 0; } } if (result == ODS_STATUS_OK && status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RRSIG #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; } backup_namedb_done: if (orig) { ldns_rdf_deep_free(orig); orig = NULL; } if (prev) { ldns_rdf_deep_free(prev); prev = NULL; } return result; }
/** * Read ixfr journal from file. * * */ ods_status backup_read_ixfr(FILE* in, void* zone) { zone_type* z = (zone_type*) zone; ods_status result = ODS_STATUS_OK; ldns_rr* rr = NULL; ldns_rdf* prev = NULL; ldns_rdf* orig = NULL; ldns_rdf* dname = NULL; ldns_status status = LDNS_STATUS_OK; char line[SE_ADFILE_MAXLINE]; uint32_t serial = 0; unsigned l = 0; unsigned first_soa = 1; /* expect soa first */ unsigned del_mode = 0; ods_log_assert(in); ods_log_assert(z); /* $ORIGIN <zone name> */ dname = adapi_get_origin(z); if (!dname) { ods_log_error("[%s] error getting default value for $ORIGIN", backup_str); return ODS_STATUS_ERR; } orig = ldns_rdf_clone(dname); if (!orig) { ods_log_error("[%s] error setting default value for $ORIGIN", backup_str); return ODS_STATUS_ERR; } /* read RRs */ while ((rr = backup_read_rr(in, z, line, &orig, &prev, &status, &l)) != NULL) { /* check status */ if (status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RR #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; goto backup_ixfr_done; } if (first_soa == 2) { ods_log_error("[%s] bad ixfr journal: trailing RRs after final " "SOA", backup_str); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; goto backup_ixfr_done; } if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_SOA) { serial = ldns_rdf2native_int32( ldns_rr_rdf(rr, SE_SOA_RDATA_SERIAL)); if (first_soa) { ods_log_debug("[%s] ixfr first SOA: %s", backup_str, ldns_rr2str(rr)); /* first SOA */ ldns_rr_free(rr); rr = NULL; if (z->db->outserial != serial) { ods_log_error("[%s] bad ixfr journal: first SOA wrong " "serial (was %u, expected %u)", backup_str, serial, z->db->outserial); result = ODS_STATUS_ERR; goto backup_ixfr_done; } first_soa = 0; continue; } ods_log_assert(!first_soa); if (!del_mode) { if (z->db->outserial == serial) { /* final SOA */ ods_log_debug("[%s] ixfr final SOA: %s", backup_str, ldns_rr2str(rr)); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_OK; first_soa = 2; continue; } else { ods_log_debug("[%s] new part SOA: %s", backup_str, ldns_rr2str(rr)); lock_basic_lock(&z->ixfr->ixfr_lock); ixfr_purge(z->ixfr); lock_basic_unlock(&z->ixfr->ixfr_lock); } } else { ods_log_debug("[%s] second part SOA: %s", backup_str, ldns_rr2str(rr)); } del_mode = !del_mode; } /* ixfr add or del rr */ if (first_soa) { ods_log_error("[%s] bad ixfr journal: first RR not SOA", backup_str); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; goto backup_ixfr_done; } ods_log_assert(!first_soa); lock_basic_lock(&z->ixfr->ixfr_lock); if (del_mode) { ods_log_debug("[%s] -IXFR: %s", backup_str, ldns_rr2str(rr)); ixfr_del_rr(z->ixfr, rr); } else { ods_log_debug("[%s] +IXFR: %s", backup_str, ldns_rr2str(rr)); ixfr_add_rr(z->ixfr, rr); } lock_basic_unlock(&z->ixfr->ixfr_lock); } if (result == ODS_STATUS_OK && status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RR #%i (%s): %s", backup_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; } backup_ixfr_done: if (orig) { ldns_rdf_deep_free(orig); orig = NULL; } if (prev) { ldns_rdf_deep_free(prev); prev = NULL; } return result; }
ldns_rr_list* getaddr_rdf(ldns_resolver *res, ldns_rdf *hostrdf) { ldns_rdf *rdf; ldns_resolver *r = NULL; ldns_rr_list *rrl; ldns_rr_list *ret = NULL; ldns_pkt *pkt; ldns_status status; if (ldns_rdf_get_type(hostrdf) == LDNS_RDF_TYPE_A #ifdef USE_IPV6 || ldns_rdf_get_type(hostrdf) == LDNS_RDF_TYPE_AAAA #endif ) { rdf = ldns_rdf_address_reverse(hostrdf); r = res; if (res == NULL) { status = ldns_resolver_new_frm_file(&r, NULL); if (status != LDNS_STATUS_OK) return NULL; } // Fetch PTR pkt = ldns_resolver_query(r, rdf, LDNS_RR_TYPE_PTR, LDNS_RR_CLASS_IN, LDNS_RD); if (pkt == NULL || ldns_pkt_get_rcode(pkt) != LDNS_RCODE_NOERROR) return NULL; rrl = ldns_pkt_rr_list_by_name_and_type(pkt, rdf, LDNS_RR_TYPE_PTR, LDNS_SECTION_ANSWER); if (ldns_rr_list_rr_count(rrl) != 1) return NULL; ldns_rdf_deep_free(rdf); rdf = ldns_rdf_clone(ldns_rr_rdf(ldns_rr_list_rr(rrl,0),0)); ldns_pkt_free(pkt); ldns_rr_list_deep_free(rrl); } else if (ldns_rdf_get_type(hostrdf) == LDNS_RDF_TYPE_DNAME) { rdf = hostrdf; } else { return NULL; } if (r == NULL) { r = res; if (res == NULL) { status = ldns_resolver_new_frm_file(&r, NULL); if (status != LDNS_STATUS_OK) return NULL; } } #ifdef USE_IPV6 if (ldns_rdf_get_type(hostrdf) != LDNS_RDF_TYPE_A) { // Fetch AAAA pkt = ldns_resolver_query(r, rdf, LDNS_RR_TYPE_AAAA, LDNS_RR_CLASS_IN, LDNS_RD); if (pkt != NULL && ldns_pkt_get_rcode(pkt) == LDNS_RCODE_NOERROR) { rrl = ldns_pkt_rr_list_by_name_and_type(pkt, rdf, LDNS_RR_TYPE_AAAA, LDNS_SECTION_ANSWER); ldns_pkt_free(pkt); if (ldns_rr_list_rr_count(rrl) > 0) { ret = rrl; rrl = NULL; } else { ldns_rr_list_free(rrl); } } } if (ldns_rdf_get_type(hostrdf) != LDNS_RDF_TYPE_AAAA) { #else if (1) { #endif /* USE_IPV6 */ // Fetch AA pkt = ldns_resolver_query(r, rdf, LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN, LDNS_RD); if (pkt != NULL && ldns_pkt_get_rcode(pkt) == LDNS_RCODE_NOERROR) { rrl = ldns_pkt_rr_list_by_name_and_type(pkt, rdf, LDNS_RR_TYPE_A, LDNS_SECTION_ANSWER); ldns_pkt_free(pkt); if (ldns_rr_list_rr_count(rrl) > 0) { if (ret == NULL) { ret = rrl; } else { ldns_rr_list_cat(ret, rrl); ldns_rr_list_free(rrl); } } else { ldns_rr_list_free(rrl); } } // if (pkt != NULL && ldns_pkt_ge... } if (res == NULL) ldns_resolver_deep_free(r); return ret; } ldns_rr_list* getaddr(ldns_resolver *res, const char *hostname) { ldns_rdf *rdf; /* Check if hostname is a ip */ rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_A, hostname); #ifdef USE_IPV6 if (!rdf) { rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_AAAA, hostname); } #endif if (!rdf) { rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, hostname); } if (!rdf) return NULL; return getaddr_rdf(res, rdf); }
/** * Read IXFR from file. * */ static ods_status addns_read_file(FILE* fd, zone_type* zone) { ldns_rr* rr = NULL; uint32_t new_serial = 0; uint32_t old_serial = 0; uint32_t tmp_serial = 0; ldns_rdf* prev = NULL; ldns_rdf* orig = NULL; ldns_rdf* dname = NULL; uint32_t ttl = 0; size_t rr_count = 0; ods_status result = ODS_STATUS_OK; ldns_status status = LDNS_STATUS_OK; char line[SE_ADFILE_MAXLINE]; unsigned is_axfr = 0; unsigned del_mode = 0; unsigned soa_seen = 0; unsigned line_update_interval = 100000; unsigned line_update = line_update_interval; unsigned l = 0; ods_log_assert(fd); ods_log_assert(zone); /* $ORIGIN <zone name> */ dname = adapi_get_origin(zone); if (!dname) { ods_log_error("[%s] error getting default value for $ORIGIN", adapter_str); return ODS_STATUS_ERR; } orig = ldns_rdf_clone(dname); if (!orig) { ods_log_error("[%s] error setting default value for $ORIGIN", adapter_str); return ODS_STATUS_ERR; } /* $TTL <default ttl> */ ttl = adapi_get_ttl(zone); /* read RRs */ while ((rr = addns_read_rr(fd, line, &orig, &prev, &ttl, &status, &l)) != NULL) { /* check status */ if (status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RR at line %i (%s): %s", adapter_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; break; } /* debug update */ if (l > line_update) { ods_log_debug("[%s] ...at line %i: %s", adapter_str, l, line); line_update += line_update_interval; } /* first RR: check if SOA and correct zone & serialno */ if (rr_count == 0) { rr_count++; if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_SOA) { ods_log_error("[%s] bad xfr, first rr is not soa", adapter_str); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; break; } soa_seen++; if (ldns_dname_compare(ldns_rr_owner(rr), zone->apex)) { ods_log_error("[%s] bad xfr, soa dname not equal to zone " "dname %s", adapter_str, zone->name); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; break; } tmp_serial = ldns_rdf2native_int32(ldns_rr_rdf(rr, SE_SOA_RDATA_SERIAL)); old_serial = adapi_get_serial(zone); if (!util_serial_gt(tmp_serial, old_serial)) { ods_log_info("[%s] zone %s is already up to date, have " "serial %u, got serial %u", adapter_str, zone->name, old_serial, tmp_serial); new_serial = tmp_serial; ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_UNCHANGED; break; } ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_OK; continue; } /* second RR: if not soa, this is an AXFR */ if (rr_count == 1) { if (ldns_rr_get_type(rr) != LDNS_RR_TYPE_SOA) { ods_log_verbose("[%s] detected axfr serial=%u for zone %s", adapter_str, tmp_serial, zone->name); new_serial = tmp_serial; is_axfr = 1; del_mode = 0; } else { ods_log_verbose("[%s] detected ixfr serial=%u for zone %s", adapter_str, tmp_serial, zone->name); new_serial = tmp_serial; tmp_serial = ldns_rdf2native_int32(ldns_rr_rdf(rr, SE_SOA_RDATA_SERIAL)); ldns_rr_free(rr); rr = NULL; rr_count++; if (tmp_serial < new_serial) { del_mode = 1; result = ODS_STATUS_OK; continue; } else { ods_log_error("[%s] bad xfr for zone %s, bad soa serial", adapter_str, zone->name); result = ODS_STATUS_ERR; break; } } } /* soa means swap */ rr_count++; if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_SOA) { if (!is_axfr) { tmp_serial = ldns_rdf2native_int32(ldns_rr_rdf(rr, SE_SOA_RDATA_SERIAL)); if (tmp_serial <= new_serial) { if (tmp_serial == new_serial) { soa_seen++; } del_mode = !del_mode; ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_OK; continue; } else { ods_log_assert(tmp_serial > new_serial); ods_log_error("[%s] bad xfr for zone %s, bad soa serial", adapter_str, zone->name); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_ERR; break; } } else { /* for axfr */ soa_seen++; } } /* [add to/remove from] the zone */ if (!is_axfr && del_mode) { ods_log_debug("[%s] delete RR #%i at line %i: %s", adapter_str, rr_count, l, line); result = adapi_del_rr(zone, rr, 0); ldns_rr_free(rr); rr = NULL; } else { ods_log_debug("[%s] add RR #%i at line %i: %s", adapter_str, rr_count, l, line); result = adapi_add_rr(zone, rr, 0); } if (result == ODS_STATUS_UNCHANGED) { ods_log_debug("[%s] skipping RR at line %i (%s): %s", adapter_str, l, del_mode?"not found":"duplicate", line); ldns_rr_free(rr); rr = NULL; result = ODS_STATUS_OK; continue; } else if (result != ODS_STATUS_OK) { ods_log_error("[%s] error %s RR at line %i: %s", adapter_str, del_mode?"deleting":"adding", l, line); ldns_rr_free(rr); rr = NULL; break; } } /* and done */ if (orig) { ldns_rdf_deep_free(orig); orig = NULL; } if (prev) { ldns_rdf_deep_free(prev); prev = NULL; } if (result == ODS_STATUS_OK && status != LDNS_STATUS_OK) { ods_log_error("[%s] error reading RR at line %i (%s): %s", adapter_str, l, ldns_get_errorstr_by_id(status), line); result = ODS_STATUS_ERR; } /* check the number of SOAs seen */ if (result == ODS_STATUS_OK) { if ((is_axfr && soa_seen != 2) || (!is_axfr && soa_seen != 3)) { ods_log_error("[%s] bad %s, wrong number of SOAs (%u)", adapter_str, is_axfr?"axfr":"ixfr", soa_seen); result = ODS_STATUS_ERR; } } /* input zone ok, set inbound serial and apply differences */ if (result == ODS_STATUS_OK || result == ODS_STATUS_UNCHANGED) { adapi_set_serial(zone, new_serial); if (is_axfr) { adapi_trans_full(zone); } else { adapi_trans_diff(zone); } if (result == ODS_STATUS_UNCHANGED) { result = ODS_STATUS_OK; } } return result; }
/** * Get RRSIG from one of the HSMs, given a RRset and a key. * */ ldns_rr* lhsm_sign(hsm_ctx_t* ctx, ldns_rr_list* rrset, key_type* key_id, ldns_rdf* owner, time_t inception, time_t expiration) { ods_status status = ODS_STATUS_OK; char* error = NULL; ldns_rr* result = NULL; hsm_sign_params_t* params = NULL; int retries = 0; if (!owner || !key_id || !rrset || !inception || !expiration) { ods_log_error("[%s] unable to sign: missing required elements", hsm_str); return NULL; } lhsm_sign_start: /* get dnskey */ if (!key_id->dnskey) { status = lhsm_get_key(ctx, owner, key_id); if (status != ODS_STATUS_OK) { error = hsm_get_error(ctx); if (error) { ods_log_error("[%s] %s", hsm_str, error); free((void*)error); } else if (!retries) { lhsm_clear_key_cache(key_id); retries++; goto lhsm_sign_start; } ods_log_error("[%s] unable to sign: get key failed", hsm_str); return NULL; } } ods_log_assert(key_id->dnskey); ods_log_assert(key_id->hsmkey); ods_log_assert(key_id->params); /* adjust parameters */ params = hsm_sign_params_new(); params->owner = ldns_rdf_clone(key_id->params->owner); params->algorithm = key_id->algorithm; params->flags = key_id->flags; params->inception = inception; params->expiration = expiration; params->keytag = ldns_calc_keytag(key_id->dnskey); ods_log_deeebug("[%s] sign RRset[%i] with key %s tag %u", hsm_str, ldns_rr_get_type(ldns_rr_list_rr(rrset, 0)), key_id->locator?key_id->locator:"(null)", params->keytag); result = hsm_sign_rrset(ctx, rrset, key_id->hsmkey, params); hsm_sign_params_free(params); if (!result) { error = hsm_get_error(ctx); if (error) { ods_log_error("[%s] %s", hsm_str, error); free((void*)error); } else if (!retries) { lhsm_clear_key_cache(key_id); retries++; goto lhsm_sign_start; } ods_log_crit("[%s] error signing rrset with libhsm", hsm_str); } return result; }
/** * Get key from one of the HSMs. * */ ods_status lhsm_get_key(hsm_ctx_t* ctx, ldns_rdf* owner, key_type* key_id) { char *error = NULL; int retries = 0; if (!owner || !key_id) { ods_log_error("[%s] unable to get key: missing required elements", hsm_str); return ODS_STATUS_ASSERT_ERR; } lhsm_key_start: /* set parameters */ if (!key_id->params) { key_id->params = hsm_sign_params_new(); if (key_id->params) { key_id->params->owner = ldns_rdf_clone(owner); key_id->params->algorithm = key_id->algorithm; key_id->params->flags = key_id->flags; } else { /* could not create params */ error = hsm_get_error(ctx); if (error) { ods_log_error("[%s] %s", hsm_str, error); free((void*)error); } else if (!retries) { lhsm_clear_key_cache(key_id); retries++; goto lhsm_key_start; } ods_log_error("[%s] unable to get key: create params for key %s " "failed", hsm_str, key_id->locator?key_id->locator:"(null)"); return ODS_STATUS_ERR; } } /* lookup key */ if (!key_id->hsmkey) { key_id->hsmkey = hsm_find_key_by_id(ctx, key_id->locator); } if (!key_id->hsmkey) { error = hsm_get_error(ctx); if (error) { ods_log_error("[%s] %s", hsm_str, error); free((void*)error); } else if (!retries) { lhsm_clear_key_cache(key_id); retries++; goto lhsm_key_start; } /* could not find key */ ods_log_error("[%s] unable to get key: key %s not found", hsm_str, key_id->locator?key_id->locator:"(null)"); return ODS_STATUS_ERR; } /* get dnskey */ if (!key_id->dnskey) { key_id->dnskey = hsm_get_dnskey(ctx, key_id->hsmkey, key_id->params); } if (!key_id->dnskey) { error = hsm_get_error(ctx); if (error) { ods_log_error("[%s] %s", hsm_str, error); free((void*)error); } else if (!retries) { lhsm_clear_key_cache(key_id); retries++; goto lhsm_key_start; } ods_log_error("[%s] unable to get key: hsm failed to create dnskey", hsm_str); return ODS_STATUS_ERR; } key_id->params->keytag = ldns_calc_keytag(key_id->dnskey); return ODS_STATUS_OK; }
ldns_status ldns_verify_denial(ldns_pkt *pkt, ldns_rdf *name, ldns_rr_type type, ldns_rr_list **nsec_rrs, ldns_rr_list **nsec_rr_sigs) { uint16_t nsec_i; ldns_rr_list *nsecs; ldns_status result; if (verbosity >= 5) { printf("VERIFY DENIAL FROM:\n"); ldns_pkt_print(stdout, pkt); } result = LDNS_STATUS_CRYPTO_NO_RRSIG; /* Try to see if there are NSECS in the packet */ nsecs = ldns_pkt_rr_list_by_type(pkt, LDNS_RR_TYPE_NSEC, LDNS_SECTION_ANY_NOQUESTION); if (nsecs) { for (nsec_i = 0; nsec_i < ldns_rr_list_rr_count(nsecs); nsec_i++) { /* there are four options: * - name equals ownername and is covered by the type bitmap * - name equals ownername but is not covered by the type bitmap * - name falls within nsec coverage but is not equal to the owner name * - name falls outside of nsec coverage */ if (ldns_dname_compare(ldns_rr_owner(ldns_rr_list_rr(nsecs, nsec_i)), name) == 0) { /* printf("CHECKING NSEC:\n"); ldns_rr_print(stdout, ldns_rr_list_rr(nsecs, nsec_i)); printf("DAWASEM\n"); */ if (ldns_nsec_bitmap_covers_type( ldns_nsec_get_bitmap(ldns_rr_list_rr(nsecs, nsec_i)), type)) { /* Error, according to the nsec this rrset is signed */ result = LDNS_STATUS_CRYPTO_NO_RRSIG; } else { /* ok nsec denies existence */ if (verbosity >= 3) { printf(";; Existence of data set with this type denied by NSEC\n"); } /*printf(";; Verifiably insecure.\n");*/ if (nsec_rrs && nsec_rr_sigs) { (void) get_dnssec_rr(pkt, ldns_rr_owner(ldns_rr_list_rr(nsecs, nsec_i)), LDNS_RR_TYPE_NSEC, nsec_rrs, nsec_rr_sigs); } ldns_rr_list_deep_free(nsecs); return LDNS_STATUS_OK; } } else if (ldns_nsec_covers_name(ldns_rr_list_rr(nsecs, nsec_i), name)) { if (verbosity >= 3) { printf(";; Existence of data set with this name denied by NSEC\n"); } if (nsec_rrs && nsec_rr_sigs) { (void) get_dnssec_rr(pkt, ldns_rr_owner(ldns_rr_list_rr(nsecs, nsec_i)), LDNS_RR_TYPE_NSEC, nsec_rrs, nsec_rr_sigs); } ldns_rr_list_deep_free(nsecs); return LDNS_STATUS_OK; } else { /* nsec has nothing to do with this data */ } } ldns_rr_list_deep_free(nsecs); } else if( (nsecs = ldns_pkt_rr_list_by_type(pkt, LDNS_RR_TYPE_NSEC3, LDNS_SECTION_ANY_NOQUESTION)) ) { ldns_rr_list* sigs = ldns_pkt_rr_list_by_type(pkt, LDNS_RR_TYPE_RRSIG, LDNS_SECTION_ANY_NOQUESTION); ldns_rr* q = ldns_rr_new(); if(!sigs) return LDNS_STATUS_MEM_ERR; if(!q) return LDNS_STATUS_MEM_ERR; ldns_rr_set_question(q, 1); ldns_rr_set_ttl(q, 0); ldns_rr_set_owner(q, ldns_rdf_clone(name)); if(!ldns_rr_owner(q)) return LDNS_STATUS_MEM_ERR; ldns_rr_set_type(q, type); result = ldns_dnssec_verify_denial_nsec3(q, nsecs, sigs, ldns_pkt_get_rcode(pkt), type, ldns_pkt_ancount(pkt) == 0); ldns_rr_free(q); ldns_rr_list_deep_free(nsecs); ldns_rr_list_deep_free(sigs); } return result; }
/*return hash name match*/ ldns_rr * ldns_nsec3_exact_match(ldns_rdf *qname, ldns_rr_type qtype, ldns_rr_list *nsec3s) { uint8_t algorithm; uint32_t iterations; uint8_t salt_length; uint8_t *salt; ldns_rdf *sname, *hashed_sname; size_t nsec_i; ldns_rr *nsec; ldns_rr *result = NULL; ldns_status status; const ldns_rr_descriptor *descriptor; ldns_rdf *zone_name; if (verbosity >= 4) { printf(";; finding exact match for "); descriptor = ldns_rr_descript(qtype); if (descriptor && descriptor->_name) { printf("%s ", descriptor->_name); } else { printf("TYPE%d ", qtype); } ldns_rdf_print(stdout, qname); printf("\n"); } if (!qname || !nsec3s || ldns_rr_list_rr_count(nsec3s) < 1) { if (verbosity >= 4) { printf("no qname, nsec3s or list empty\n"); } return NULL; } nsec = ldns_rr_list_rr(nsec3s, 0); algorithm = ldns_nsec3_algorithm(nsec); salt_length = ldns_nsec3_salt_length(nsec); salt = ldns_nsec3_salt_data(nsec); iterations = ldns_nsec3_iterations(nsec); sname = ldns_rdf_clone(qname); if (verbosity >= 4) { printf(";; owner name hashes to: "); } hashed_sname = ldns_nsec3_hash_name(sname, algorithm, iterations, salt_length, salt); zone_name = ldns_dname_left_chop(ldns_rr_owner(nsec)); status = ldns_dname_cat(hashed_sname, zone_name); if (verbosity >= 4) { ldns_rdf_print(stdout, hashed_sname); printf("\n"); } for (nsec_i = 0; nsec_i < ldns_rr_list_rr_count(nsec3s); nsec_i++) { nsec = ldns_rr_list_rr(nsec3s, nsec_i); /* check values of iterations etc! */ /* exact match? */ if (ldns_dname_compare(ldns_rr_owner(nsec), hashed_sname) == 0) { result = nsec; goto done; } } done: ldns_rdf_deep_free(zone_name); ldns_rdf_deep_free(sname); ldns_rdf_deep_free(hashed_sname); LDNS_FREE(salt); if (verbosity >= 4) { if (result) { printf(";; Found.\n"); } else { printf(";; Not foud.\n"); } } return result; }
ldns_status ldns_dnssec_zone_sign_nsec3(ldns_dnssec_zone *zone, ldns_rr_list *new_rrs, ldns_key_list *key_list, int (*func)(ldns_rr *, void *), void *arg, uint8_t algorithm, uint8_t flags, uint16_t iterations, uint8_t salt_length, uint8_t *salt) { ldns_rr *nsec3, *nsec3params; ldns_status result = LDNS_STATUS_OK; /* zone is already sorted */ ldns_dnssec_zone_mark_glue(zone); /* TODO if there are already nsec3s presents and their * parameters are the same as these, we don't have to recreate */ if (zone->names) { /* add empty nonterminals */ ldns_dnssec_zone_add_empty_nonterminals(zone); nsec3 = ((ldns_dnssec_name *)zone->names->root->data)->nsec; if (nsec3 && ldns_rr_get_type(nsec3) == LDNS_RR_TYPE_NSEC3) { /* no need to recreate */ } else { if (!ldns_dnssec_zone_find_rrset(zone, zone->soa->name, LDNS_RR_TYPE_NSEC3PARAMS)) { /* create and add the nsec3params rr */ nsec3params = ldns_rr_new_frm_type(LDNS_RR_TYPE_NSEC3PARAMS); ldns_rr_set_owner(nsec3params, ldns_rdf_clone(zone->soa->name)); ldns_nsec3_add_param_rdfs(nsec3params, algorithm, flags, iterations, salt_length, salt); /* always set bit 7 of the flags to zero, according to * rfc5155 section 11 */ ldns_set_bit(ldns_rdf_data(ldns_rr_rdf(nsec3params, 1)), 7, 0); ldns_dnssec_zone_add_rr(zone, nsec3params); ldns_rr_list_push_rr(new_rrs, nsec3params); } result = ldns_dnssec_zone_create_nsec3s(zone, new_rrs, algorithm, flags, iterations, salt_length, salt); if (result != LDNS_STATUS_OK) { return result; } } result = ldns_dnssec_zone_create_rrsigs(zone, new_rrs, key_list, func, arg); } return result; }
ldns_status ldns_zone_new_frm_fp_l(ldns_zone **z, FILE *fp, ldns_rdf *origin, uint32_t ttl, ldns_rr_class c, int *line_nr) { ldns_zone *newzone; ldns_rr *rr; uint32_t my_ttl = ttl; ldns_rr_class my_class = c; ldns_rr *last_rr = NULL; ldns_rdf *my_origin; ldns_rdf *my_prev; bool soa_seen = false; /* 2 soa are an error */ ldns_status s; newzone = ldns_zone_new(); my_origin = origin; my_ttl = ttl; my_class = c; if (origin) { my_origin = ldns_rdf_clone(origin); /* also set the prev */ my_prev = ldns_rdf_clone(origin); } else { my_origin = NULL; my_prev = NULL; } while(!feof(fp)) { s = ldns_rr_new_frm_fp_l(&rr, fp, &my_ttl, &my_origin, &my_prev, line_nr); switch (s) { case LDNS_STATUS_OK: if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_SOA) { if (soa_seen) { /* second SOA * just skip, maybe we want to say * something??? */ ldns_rr_free(rr); continue; } soa_seen = true; ldns_zone_set_soa(newzone, rr); /* set origin to soa if not specified */ if (!my_origin) { my_origin = ldns_rdf_clone(ldns_rr_owner(rr)); } continue; } /* a normal RR - as sofar the DNS is normal */ last_rr = rr; if (!ldns_zone_push_rr(newzone, rr)) { if (my_origin) { ldns_rdf_deep_free(my_origin); } ldns_zone_free(newzone); return LDNS_STATUS_MEM_ERR; } /*my_origin = ldns_rr_owner(rr);*/ my_ttl = ldns_rr_ttl(rr); my_class = ldns_rr_get_class(rr); case LDNS_STATUS_SYNTAX_EMPTY: /* empty line was seen */ case LDNS_STATUS_SYNTAX_TTL: /* the function set the ttl */ break; case LDNS_STATUS_SYNTAX_ORIGIN: /* the function set the origin */ break; default: ldns_zone_free(newzone); return s; } } if (my_origin) { ldns_rdf_deep_free(my_origin); } if (my_prev) { ldns_rdf_deep_free(my_prev); } if (z) { *z = newzone; } return LDNS_STATUS_OK; }
/* this is NOT the hash, but the original name! */ ldns_rdf * ldns_nsec3_closest_encloser(ldns_rdf *qname, ldns_rr_type qtype, ldns_rr_list *nsec3s) { /* remember parameters, they must match */ uint8_t algorithm; uint32_t iterations; uint8_t salt_length; uint8_t *salt; ldns_rdf *sname, *hashed_sname, *tmp; ldns_rr *ce; bool flag; bool exact_match_found; bool in_range_found; ldns_status status; ldns_rdf *zone_name; size_t nsec_i; ldns_rr *nsec; ldns_rdf *result = NULL; if (!qname || !nsec3s || ldns_rr_list_rr_count(nsec3s) < 1) { return NULL; } if (verbosity >= 4) { printf(";; finding closest encloser for type %d ", qtype); ldns_rdf_print(stdout, qname); printf("\n"); } nsec = ldns_rr_list_rr(nsec3s, 0); algorithm = ldns_nsec3_algorithm(nsec); salt_length = ldns_nsec3_salt_length(nsec); salt = ldns_nsec3_salt_data(nsec); iterations = ldns_nsec3_iterations(nsec); sname = ldns_rdf_clone(qname); ce = NULL; flag = false; zone_name = ldns_dname_left_chop(ldns_rr_owner(nsec)); /* algorithm from nsec3-07 8.3 */ while (ldns_dname_label_count(sname) > 0) { exact_match_found = false; in_range_found = false; if (verbosity >= 3) { printf(";; "); ldns_rdf_print(stdout, sname); printf(" hashes to: "); } hashed_sname = ldns_nsec3_hash_name(sname, algorithm, iterations, salt_length, salt); status = ldns_dname_cat(hashed_sname, zone_name); if (verbosity >= 3) { ldns_rdf_print(stdout, hashed_sname); printf("\n"); } for (nsec_i = 0; nsec_i < ldns_rr_list_rr_count(nsec3s); nsec_i++) { nsec = ldns_rr_list_rr(nsec3s, nsec_i); /* check values of iterations etc! */ /* exact match? */ if (ldns_dname_compare(ldns_rr_owner(nsec), hashed_sname) == 0) { if (verbosity >= 4) { printf(";; exact match found\n"); } exact_match_found = true; } else if (ldns_nsec_covers_name(nsec, hashed_sname)) { if (verbosity >= 4) { printf(";; in range of an nsec\n"); } in_range_found = true; } } if (!exact_match_found && in_range_found) { flag = true; } else if (exact_match_found && flag) { result = ldns_rdf_clone(sname); } else if (exact_match_found && !flag) { // error! if (verbosity >= 4) { printf(";; the closest encloser is the same name (ie. this is an exact match, ie there is no closest encloser)\n"); } ldns_rdf_deep_free(hashed_sname); goto done; } else { flag = false; } ldns_rdf_deep_free(hashed_sname); tmp = sname; sname = ldns_dname_left_chop(sname); ldns_rdf_deep_free(tmp); } done: LDNS_FREE(salt); ldns_rdf_deep_free(zone_name); ldns_rdf_deep_free(sname); if (!result) { if (verbosity >= 4) { printf(";; no closest encloser found\n"); } } /* todo checks from end of 6.2. here or in caller? */ return result; }
static ldns_status ldns_tsig_mac_new(ldns_rdf **tsig_mac, uint8_t *pkt_wire, size_t pkt_wire_size, const char *key_data, ldns_rdf *key_name_rdf, ldns_rdf *fudge_rdf, ldns_rdf *algorithm_rdf, ldns_rdf *time_signed_rdf, ldns_rdf *error_rdf, ldns_rdf *other_data_rdf, ldns_rdf *orig_mac_rdf, int tsig_timers_only) { ldns_status status; char *wireformat; int wiresize; unsigned char *mac_bytes = NULL; unsigned char *key_bytes = NULL; int key_size; const EVP_MD *digester; char *algorithm_name = NULL; unsigned int md_len = EVP_MAX_MD_SIZE; ldns_rdf *result = NULL; ldns_buffer *data_buffer = NULL; ldns_rdf *canonical_key_name_rdf = NULL; ldns_rdf *canonical_algorithm_rdf = NULL; if (key_name_rdf == NULL || algorithm_rdf == NULL) { return LDNS_STATUS_NULL; } canonical_key_name_rdf = ldns_rdf_clone(key_name_rdf); if (canonical_key_name_rdf == NULL) { return LDNS_STATUS_MEM_ERR; } canonical_algorithm_rdf = ldns_rdf_clone(algorithm_rdf); if (canonical_algorithm_rdf == NULL) { ldns_rdf_deep_free(canonical_key_name_rdf); return LDNS_STATUS_MEM_ERR; } /* * prepare the digestable information */ data_buffer = ldns_buffer_new(LDNS_MAX_PACKETLEN); if (!data_buffer) { status = LDNS_STATUS_MEM_ERR; goto clean; } /* if orig_mac is not NULL, add it too */ if (orig_mac_rdf) { (void) ldns_rdf2buffer_wire(data_buffer, orig_mac_rdf); } ldns_buffer_write(data_buffer, pkt_wire, pkt_wire_size); if (!tsig_timers_only) { ldns_dname2canonical(canonical_key_name_rdf); (void)ldns_rdf2buffer_wire(data_buffer, canonical_key_name_rdf); ldns_buffer_write_u16(data_buffer, LDNS_RR_CLASS_ANY); ldns_buffer_write_u32(data_buffer, 0); ldns_dname2canonical(canonical_algorithm_rdf); (void)ldns_rdf2buffer_wire(data_buffer, canonical_algorithm_rdf); } (void)ldns_rdf2buffer_wire(data_buffer, time_signed_rdf); (void)ldns_rdf2buffer_wire(data_buffer, fudge_rdf); if (!tsig_timers_only) { (void)ldns_rdf2buffer_wire(data_buffer, error_rdf); (void)ldns_rdf2buffer_wire(data_buffer, other_data_rdf); } wireformat = (char *) data_buffer->_data; wiresize = (int) ldns_buffer_position(data_buffer); algorithm_name = ldns_rdf2str(algorithm_rdf); if(!algorithm_name) { status = LDNS_STATUS_MEM_ERR; goto clean; } /* prepare the key */ key_bytes = LDNS_XMALLOC(unsigned char, ldns_b64_pton_calculate_size(strlen(key_data))); if(!key_bytes) { status = LDNS_STATUS_MEM_ERR; goto clean; } key_size = ldns_b64_pton(key_data, key_bytes, ldns_b64_pton_calculate_size(strlen(key_data))); if (key_size < 0) { status = LDNS_STATUS_INVALID_B64; goto clean; } /* hmac it */ /* 2 spare bytes for the length */ mac_bytes = LDNS_XMALLOC(unsigned char, md_len+2); if(!mac_bytes) { status = LDNS_STATUS_MEM_ERR; goto clean; } memset(mac_bytes, 0, md_len+2); digester = ldns_digest_function(algorithm_name); if (digester) { (void) HMAC(digester, key_bytes, key_size, (void *)wireformat, (size_t) wiresize, mac_bytes + 2, &md_len); ldns_write_uint16(mac_bytes, md_len); result = ldns_rdf_new_frm_data(LDNS_RDF_TYPE_INT16_DATA, md_len + 2, mac_bytes); } else { status = LDNS_STATUS_CRYPTO_UNKNOWN_ALGO; goto clean; } *tsig_mac = result; status = LDNS_STATUS_OK; clean: LDNS_FREE(mac_bytes); LDNS_FREE(key_bytes); LDNS_FREE(algorithm_name); ldns_buffer_free(data_buffer); ldns_rdf_deep_free(canonical_algorithm_rdf); ldns_rdf_deep_free(canonical_key_name_rdf); return status; }
ldns_status ldns_dnssec_zone_new_frm_fp_l(ldns_dnssec_zone** z, FILE* fp, ldns_rdf* origin, uint32_t ttl, ldns_rr_class ATTR_UNUSED(c), int* line_nr) { ldns_rr* cur_rr; size_t i; ldns_rdf *my_origin = NULL; ldns_rdf *my_prev = NULL; ldns_dnssec_zone *newzone = ldns_dnssec_zone_new(); /* when reading NSEC3s, there is a chance that we encounter nsecs for empty nonterminals, whose nonterminals we cannot derive yet because the needed information is to be read later. in that case we keep a list of those nsec3's and retry to add them later */ ldns_rr_list* todo_nsec3s = ldns_rr_list_new(); ldns_rr_list* todo_nsec3_rrsigs = ldns_rr_list_new(); ldns_status status = LDNS_STATUS_MEM_ERR; #ifdef FASTER_DNSSEC_ZONE_NEW_FRM_FP ldns_zone* zone = NULL; if (ldns_zone_new_frm_fp_l(&zone, fp, origin,ttl, c, line_nr) != LDNS_STATUS_OK) goto error; #else uint32_t my_ttl = ttl; #endif if (!newzone || !todo_nsec3s || !todo_nsec3_rrsigs ) goto error; if (origin) { if (!(my_origin = ldns_rdf_clone(origin))) goto error; if (!(my_prev = ldns_rdf_clone(origin))) goto error; } #ifdef FASTER_DNSSEC_ZONE_NEW_FRM_FP if (ldns_dnssec_zone_add_rr(newzone, ldns_zone_soa(zone)) != LDNS_STATUS_OK) goto error; for (i = 0; i < ldns_rr_list_rr_count(ldns_zone_rrs(zone)); i++) { cur_rr = ldns_rr_list_rr(ldns_zone_rrs(zone), i); status = LDNS_STATUS_OK; #else while (!feof(fp)) { status = ldns_rr_new_frm_fp_l(&cur_rr, fp, &my_ttl, &my_origin, &my_prev, line_nr); #endif switch (status) { case LDNS_STATUS_OK: status = ldns_dnssec_zone_add_rr(newzone, cur_rr); if (status == LDNS_STATUS_DNSSEC_NSEC3_ORIGINAL_NOT_FOUND) { if (rr_is_rrsig_covering(cur_rr, LDNS_RR_TYPE_NSEC3)){ ldns_rr_list_push_rr(todo_nsec3_rrsigs, cur_rr); } else { ldns_rr_list_push_rr(todo_nsec3s, cur_rr); } status = LDNS_STATUS_OK; } else if (status != LDNS_STATUS_OK) goto error; break; case LDNS_STATUS_SYNTAX_EMPTY: /* empty line was seen */ case LDNS_STATUS_SYNTAX_TTL: /* the ttl was set*/ case LDNS_STATUS_SYNTAX_ORIGIN: /* the origin was set*/ status = LDNS_STATUS_OK; break; case LDNS_STATUS_SYNTAX_INCLUDE:/* $include not implemented */ status = LDNS_STATUS_SYNTAX_INCLUDE_ERR_NOTIMPL; break; default: goto error; } } if (ldns_rr_list_rr_count(todo_nsec3s) > 0) { (void) ldns_dnssec_zone_add_empty_nonterminals(newzone); for (i = 0; status == LDNS_STATUS_OK && i < ldns_rr_list_rr_count(todo_nsec3s); i++) { cur_rr = ldns_rr_list_rr(todo_nsec3s, i); status = ldns_dnssec_zone_add_rr(newzone, cur_rr); } } if (ldns_rr_list_rr_count(todo_nsec3_rrsigs) > 0) { for (i = 0; status == LDNS_STATUS_OK && i < ldns_rr_list_rr_count(todo_nsec3_rrsigs); i++){ cur_rr = ldns_rr_list_rr(todo_nsec3_rrsigs, i); status = ldns_dnssec_zone_add_rr(newzone, cur_rr); } } if (z) { *z = newzone; newzone = NULL; } else { ldns_dnssec_zone_free(newzone); } error: #ifdef FASTER_DNSSEC_ZONE_NEW_FRM_FP if (zone) { ldns_zone_free(zone); } #endif ldns_rr_list_free(todo_nsec3_rrsigs); ldns_rr_list_free(todo_nsec3s); if (my_origin) { ldns_rdf_deep_free(my_origin); } if (my_prev) { ldns_rdf_deep_free(my_prev); } if (newzone) { ldns_dnssec_zone_free(newzone); } return status; } ldns_status ldns_dnssec_zone_new_frm_fp(ldns_dnssec_zone** z, FILE* fp, ldns_rdf* origin, uint32_t ttl, ldns_rr_class ATTR_UNUSED(c)) { return ldns_dnssec_zone_new_frm_fp_l(z, fp, origin, ttl, c, NULL); } static void ldns_dnssec_name_node_free(ldns_rbnode_t *node, void *arg) { (void) arg; ldns_dnssec_name_free((ldns_dnssec_name *)node->data); LDNS_FREE(node); }
ldns_status do_chase(ldns_resolver *res, ldns_rdf *name, ldns_rr_type type, ldns_rr_class c, ldns_rr_list *trusted_keys, ldns_pkt *pkt_o, uint16_t qflags, ldns_rr_list *prev_key_list, int verbosity) { ldns_rr_list *rrset = NULL; ldns_status result; ldns_rr *orig_rr = NULL; /* ldns_rr_list *sigs; ldns_rr *cur_sig; uint16_t sig_i; ldns_rr_list *keys; */ ldns_pkt *pkt; ldns_status tree_result; ldns_dnssec_data_chain *chain; ldns_dnssec_trust_tree *tree; const ldns_rr_descriptor *descriptor; descriptor = ldns_rr_descript(type); ldns_dname2canonical(name); pkt = ldns_pkt_clone(pkt_o); if (!name) { ldns_pkt_free(pkt); return LDNS_STATUS_EMPTY_LABEL; } if (verbosity != -1) { printf(";; Chasing: "); ldns_rdf_print(stdout, name); if (descriptor && descriptor->_name) { printf(" %s\n", descriptor->_name); } else { printf(" type %d\n", type); } } if (!trusted_keys || ldns_rr_list_rr_count(trusted_keys) < 1) { } if (pkt) { rrset = ldns_pkt_rr_list_by_name_and_type(pkt, name, type, LDNS_SECTION_ANSWER ); if (!rrset) { /* nothing in answer, try authority */ rrset = ldns_pkt_rr_list_by_name_and_type(pkt, name, type, LDNS_SECTION_AUTHORITY ); } /* answer might be a cname, chase that first, then chase cname target? (TODO) */ if (!rrset) { rrset = ldns_pkt_rr_list_by_name_and_type(pkt, name, LDNS_RR_TYPE_CNAME, LDNS_SECTION_ANSWER ); if (!rrset) { /* nothing in answer, try authority */ rrset = ldns_pkt_rr_list_by_name_and_type(pkt, name, LDNS_RR_TYPE_CNAME, LDNS_SECTION_AUTHORITY ); } } } else { /* no packet? */ if (verbosity >= 0) { fprintf(stderr, "%s", ldns_get_errorstr_by_id(LDNS_STATUS_MEM_ERR)); fprintf(stderr, "\n"); } return LDNS_STATUS_MEM_ERR; } if (!rrset) { /* not found in original packet, try again */ ldns_pkt_free(pkt); pkt = NULL; pkt = ldns_resolver_query(res, name, type, c, qflags); if (!pkt) { if (verbosity >= 0) { fprintf(stderr, "%s", ldns_get_errorstr_by_id(LDNS_STATUS_NETWORK_ERR)); fprintf(stderr, "\n"); } return LDNS_STATUS_NETWORK_ERR; } if (verbosity >= 5) { ldns_pkt_print(stdout, pkt); } rrset = ldns_pkt_rr_list_by_name_and_type(pkt, name, type, LDNS_SECTION_ANSWER ); } orig_rr = ldns_rr_new(); /* if the answer had no answer section, we need to construct our own rr (for instance if * the rr qe asked for doesn't exist. This rr will be destroyed when the chain is freed */ if (ldns_pkt_ancount(pkt) < 1) { ldns_rr_set_type(orig_rr, type); ldns_rr_set_owner(orig_rr, ldns_rdf_clone(name)); chain = ldns_dnssec_build_data_chain(res, qflags, rrset, pkt, ldns_rr_clone(orig_rr)); } else { /* chase the first answer */ chain = ldns_dnssec_build_data_chain(res, qflags, rrset, pkt, NULL); } if (verbosity >= 4) { printf("\n\nDNSSEC Data Chain:\n"); ldns_dnssec_data_chain_print(stdout, chain); } result = LDNS_STATUS_OK; tree = ldns_dnssec_derive_trust_tree(chain, NULL); if (verbosity >= 2) { printf("\n\nDNSSEC Trust tree:\n"); ldns_dnssec_trust_tree_print(stdout, tree, 0, true); } if (ldns_rr_list_rr_count(trusted_keys) > 0) { tree_result = ldns_dnssec_trust_tree_contains_keys(tree, trusted_keys); if (tree_result == LDNS_STATUS_DNSSEC_EXISTENCE_DENIED) { if (verbosity >= 1) { printf("Existence denied or verifiably insecure\n"); } result = LDNS_STATUS_OK; } else if (tree_result != LDNS_STATUS_OK) { if (verbosity >= 1) { printf("No trusted keys found in tree: first error was: %s\n", ldns_get_errorstr_by_id(tree_result)); } result = tree_result; } } else { result = -1; if (verbosity >= 0) { printf("You have not provided any trusted keys.\n"); } } ldns_rr_free(orig_rr); ldns_dnssec_trust_tree_free(tree); ldns_dnssec_data_chain_deep_free(chain); ldns_rr_list_deep_free(rrset); ldns_pkt_free(pkt); /* ldns_rr_free(orig_rr);*/ return result; }
ldns_status ldns_send_buffer(ldns_pkt **result, ldns_resolver *r, ldns_buffer *qb, ldns_rdf *tsig_mac) { uint8_t i; struct sockaddr_storage *src = NULL; size_t src_len; struct sockaddr_storage *ns; size_t ns_len; struct timeval tv_s; struct timeval tv_e; ldns_rdf **ns_array; size_t *rtt; ldns_pkt *reply; bool all_servers_rtt_inf; uint8_t retries; uint8_t *reply_bytes = NULL; size_t reply_size = 0; ldns_status status, send_status; assert(r != NULL); status = LDNS_STATUS_OK; rtt = ldns_resolver_rtt(r); ns_array = ldns_resolver_nameservers(r); reply = NULL; ns_len = 0; all_servers_rtt_inf = true; if (ldns_resolver_random(r)) { ldns_resolver_nameservers_randomize(r); } if(ldns_resolver_source(r)) { src = ldns_rdf2native_sockaddr_storage_port( ldns_resolver_source(r), 0, &src_len); } /* loop through all defined nameservers */ for (i = 0; i < ldns_resolver_nameserver_count(r); i++) { if (rtt[i] == LDNS_RESOLV_RTT_INF) { /* not reachable nameserver! */ continue; } /* maybe verbosity setting? printf("Sending to "); ldns_rdf_print(stdout, ns_array[i]); printf("\n"); */ ns = ldns_rdf2native_sockaddr_storage(ns_array[i], ldns_resolver_port(r), &ns_len); #ifndef S_SPLINT_S if ((ns->ss_family == AF_INET) && (ldns_resolver_ip6(r) == LDNS_RESOLV_INET6)) { /* not reachable */ LDNS_FREE(ns); continue; } if ((ns->ss_family == AF_INET6) && (ldns_resolver_ip6(r) == LDNS_RESOLV_INET)) { /* not reachable */ LDNS_FREE(ns); continue; } #endif all_servers_rtt_inf = false; gettimeofday(&tv_s, NULL); send_status = LDNS_STATUS_ERR; /* reply_bytes implicitly handles our error */ if (ldns_resolver_usevc(r)) { for (retries = ldns_resolver_retry(r); retries > 0; retries--) { send_status = ldns_tcp_send_from(&reply_bytes, qb, ns, (socklen_t)ns_len, src, (socklen_t)src_len, ldns_resolver_timeout(r), &reply_size); if (send_status == LDNS_STATUS_OK) { break; } } } else { for (retries = ldns_resolver_retry(r); retries > 0; retries--) { /* ldns_rdf_print(stdout, ns_array[i]); */ send_status = ldns_udp_send_from(&reply_bytes, qb, ns, (socklen_t)ns_len, src, (socklen_t)src_len, ldns_resolver_timeout(r), &reply_size); if (send_status == LDNS_STATUS_OK) { break; } } } if (send_status != LDNS_STATUS_OK) { ldns_resolver_set_nameserver_rtt(r, i, LDNS_RESOLV_RTT_INF); status = send_status; } /* obey the fail directive */ if (!reply_bytes) { /* the current nameserver seems to have a problem, blacklist it */ if (ldns_resolver_fail(r)) { LDNS_FREE(ns); return LDNS_STATUS_ERR; } else { LDNS_FREE(ns); continue; } } status = ldns_wire2pkt(&reply, reply_bytes, reply_size); if (status != LDNS_STATUS_OK) { LDNS_FREE(reply_bytes); LDNS_FREE(ns); return status; } LDNS_FREE(ns); gettimeofday(&tv_e, NULL); if (reply) { ldns_pkt_set_querytime(reply, (uint32_t) ((tv_e.tv_sec - tv_s.tv_sec) * 1000) + (tv_e.tv_usec - tv_s.tv_usec) / 1000); ldns_pkt_set_answerfrom(reply, ldns_rdf_clone(ns_array[i])); ldns_pkt_set_timestamp(reply, tv_s); ldns_pkt_set_size(reply, reply_size); break; } else { if (ldns_resolver_fail(r)) { /* if fail is set bail out, after the first * one */ break; } } /* wait retrans seconds... */ sleep((unsigned int) ldns_resolver_retrans(r)); } if(src) { LDNS_FREE(src); } if (all_servers_rtt_inf) { LDNS_FREE(reply_bytes); return LDNS_STATUS_RES_NO_NS; } #ifdef HAVE_SSL if (tsig_mac && reply && reply_bytes) { if (!ldns_pkt_tsig_verify(reply, reply_bytes, reply_size, ldns_resolver_tsig_keyname(r), ldns_resolver_tsig_keydata(r), tsig_mac)) { status = LDNS_STATUS_CRYPTO_TSIG_BOGUS; } } #else (void)tsig_mac; #endif /* HAVE_SSL */ LDNS_FREE(reply_bytes); if (result) { *result = reply; } return status; }