From: Miek Gieben Date: Tue, 13 Sep 2005 12:00:13 +0000 (+0000) Subject: duh add the new files too X-Git-Tag: release-1.0.0~149 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c81d51fa2a886afadb2bea2cc997e0dcfa6ab015;p=thirdparty%2Fldns.git duh add the new files too --- diff --git a/ldns-update.c b/ldns-update.c new file mode 100644 index 00000000..e10e6e12 --- /dev/null +++ b/ldns-update.c @@ -0,0 +1,69 @@ +/* $Id: ldns-update.c,v 1.1 2005/09/13 09:37:05 ho Exp $ */ + +#include +#include + +#include + +int +main(int argc, char **argv) +{ + char *fqdn, *ipaddr, *zone; + u_int16_t defttl = 300; + ldns_status ret; + ldns_tsig_credentials tsig_cr, *tsig_cred; + int c = 2; + + switch (argc) { + case 3: + case 4: + case 6: + case 7: + break; + default: + fprintf(stderr, "usage: %s FQDN [zone] IP " + "[tsig_name tsig_alg tsig_hmac]\n", argv[0]); + fprintf(stderr, "Example: %s my.host.org 1.2.3.4\n", argv[0]); + fprintf(stderr, "Use 'none' instead of IP to remove any " + "previous address.\n"); + fprintf(stderr, "If 'zone' is not specified, " + "try to figure it from SOA.\n"); + exit(1); + } + + fqdn = argv[1]; + c = 2; + if (argc == 4 || argc == 7) + zone = argv[c++]; + else + zone = NULL; + + if (strcmp(argv[c], "none") == 0) + ipaddr = NULL; + else + ipaddr = argv[c]; + c++; + if (argc == 6 || argc == 7) { + tsig_cr.keyname = argv[c++]; + if (strncasecmp(argv[c], "hmac-sha1", 9) == 0) + tsig_cr.algorithm = "hmac-sha1."; + else if (strncasecmp(argv[c], "hmac-md5", 8) == 0) + tsig_cr.algorithm = "hmac-md5.sig-alg.reg.int."; + else { + fprintf(stderr, "Unknown algorithm, try \"hmac-md5\" " + "or \"hmac-sha1\".\n"); + exit(1); + } + tsig_cr.keydata = argv[++c]; + tsig_cred = &tsig_cr; + } else + tsig_cred = NULL; + + printf(";; trying UPDATE with FQDN \"%s\" and IP \"%s\"\n", + fqdn, ipaddr ? ipaddr : ""); + printf(";; tsig: \"%s\" \"%s\" \"%s\"\n", tsig_cr.keyname, + tsig_cr.algorithm, tsig_cr.keydata); + + ret = ldns_update_send_simple_A(fqdn, zone, ipaddr, defttl, tsig_cred); + exit(ret); +} diff --git a/ldns/tsig.h b/ldns/tsig.h new file mode 100644 index 00000000..c1bb0cd7 --- /dev/null +++ b/ldns/tsig.h @@ -0,0 +1,54 @@ +/* + * tsig.h -- defines for TSIG [RFC2845] + * + * Copyright (c) 2001-2005, NLnet Labs. All rights reserved. + * + * See LICENSE for the license. + */ + +#ifndef _LDNS_TSIG_H_ +#define _LDNS_TSIG_H_ + +#include +#include +#include +#include + +typedef struct _ldns_tsig_credentials +{ + char *algorithm; + char *keyname; + char *keydata; + /* XXX More eventually. */ +} ldns_tsig_credentials; + +char *ldns_tsig_algorithm(ldns_tsig_credentials *); +char *ldns_tsig_keyname(ldns_tsig_credentials *); +char *ldns_tsig_keydata(ldns_tsig_credentials *); +char *ldns_tsig_keyname_clone(ldns_tsig_credentials *); +char *ldns_tsig_keydata_clone(ldns_tsig_credentials *); + +/** + * verifies the tsig rr for the given packet and key (string?). + * The wire must be given too because tsig does not sign normalized packets. + * + * \return true if tsig is correct, false if not, or if tsig is not set + */ +bool ldns_pkt_tsig_verify(ldns_pkt *pkt, uint8_t *wire, size_t wire_size, + const char *key_name, const char *key_data, ldns_rdf *mac); + +/** + * creates a tsig rr for the given packet and key (string?). + * \param[in] pkt the packet to sign + * \param[in] key_name the name of the shared key + * \param[in] key_data the key in base 64 format + * \param[in] fudge seconds of error permitted in time signed + * \param[in] algorithm_name the name of the algorithm used (TODO more than only hmac-md5.sig-alg.reg.int.?) + * \param[in] query_mac is added to the digest if not NULL (so NULL is for signing queries, not NULL is for signing answers) + * \return status (OK if success) + */ +ldns_status ldns_pkt_tsig_sign(ldns_pkt *pkt, const char *key_name, + const char *key_data, uint16_t fudge, const char *algorithm_name, + ldns_rdf *query_mac); + +#endif /* _LDNS_TSIG_H_ */ diff --git a/ldns/update.h b/ldns/update.h new file mode 100644 index 00000000..8825a1df --- /dev/null +++ b/ldns/update.h @@ -0,0 +1,30 @@ +/* + * update.h + * + * Functions for RFC 2136 Dynamic Update + * + * Copyright (c) 2005, NLnet Labs. All rights reserved. + * + * See LICENSE for the license. + */ + +#ifndef _LDNS_UPDATE_H +#define _LDNS_UPDATE_H + +ldns_pkt *ldns_update_pkt_new(ldns_rdf *, ldns_rr_class, ldns_rr_list *, + ldns_rr_list *, ldns_rr_list *); +ldns_status ldns_update_pkt_tsig_add(ldns_pkt *, ldns_resolver *); +ldns_resolver *ldns_update_resolver_new(const char *, const char *, + ldns_rr_class, ldns_tsig_credentials *, ldns_rdf **); + +uint16_t ldns_update_get_zo(const ldns_pkt *); +uint16_t ldns_update_get_pr(const ldns_pkt *); +uint16_t ldns_update_get_up(const ldns_pkt *); +uint16_t ldns_update_get_ad(const ldns_pkt *); + +void ldns_update_set_zo(ldns_pkt *, u_int16_t); +void ldns_update_set_pr(ldns_pkt *, u_int16_t); +void ldns_update_set_up(ldns_pkt *, u_int16_t); +void ldns_update_set_ad(ldns_pkt *, u_int16_t); + +#endif /* !_LDNS_UPDATE_H */ diff --git a/tsig.c b/tsig.c new file mode 100644 index 00000000..31a5fe0c --- /dev/null +++ b/tsig.c @@ -0,0 +1,404 @@ +/* + * tsig.c + * + * contains the functions needed for TSIG [RFC2845] + * + * See the file LICENSE for the license + */ + +#include + +#include + +#include + +#include +#include + +char * +ldns_tsig_algorithm(ldns_tsig_credentials *tc) +{ + return tc->algorithm; +} + +char * +ldns_tsig_keyname(ldns_tsig_credentials *tc) +{ + return tc->keyname; +} + +char * +ldns_tsig_keydata(ldns_tsig_credentials *tc) +{ + return tc->keydata; +} + +char * +ldns_tsig_keyname_clone(ldns_tsig_credentials *tc) +{ + return strdup(tc->keyname); +} + +char * +ldns_tsig_keydata_clone(ldns_tsig_credentials *tc) +{ + return strdup(tc->keydata); +} + +/* + * Makes an exact copy of the wire, but with the tsig rr removed + */ +uint8_t * +ldns_tsig_prepare_pkt_wire(uint8_t *wire, size_t wire_len, size_t *result_len) +{ + uint8_t *wire2 = NULL; + uint16_t qd_count; + uint16_t an_count; + uint16_t ns_count; + uint16_t ar_count; + ldns_rr *rr; + + size_t pos; + uint16_t i; + + ldns_status status; + + /* fake parse the wire */ + qd_count = LDNS_QDCOUNT(wire); + an_count = LDNS_ANCOUNT(wire); + ns_count = LDNS_NSCOUNT(wire); + ar_count = LDNS_ARCOUNT(wire); + + if (ar_count > 0) { + ar_count--; + } else { + return NULL; + } + + pos = LDNS_HEADER_SIZE; + + for (i = 0; i < qd_count; i++) { + status = ldns_wire2rr(&rr, wire, wire_len, &pos, + LDNS_SECTION_QUESTION); + if (status != LDNS_STATUS_OK) { + return NULL; + } + ldns_rr_free(rr); + } + + for (i = 0; i < an_count; i++) { + status = ldns_wire2rr(&rr, wire, wire_len, &pos, + LDNS_SECTION_ANSWER); + if (status != LDNS_STATUS_OK) { + return NULL; + } + ldns_rr_free(rr); + } + + for (i = 0; i < ns_count; i++) { + status = ldns_wire2rr(&rr, wire, wire_len, &pos, + LDNS_SECTION_AUTHORITY); + if (status != LDNS_STATUS_OK) { + return NULL; + } + ldns_rr_free(rr); + } + + for (i = 0; i < ar_count; i++) { + status = ldns_wire2rr(&rr, wire, wire_len, &pos, + LDNS_SECTION_ADDITIONAL); + if (status != LDNS_STATUS_OK) { + return NULL; + } + ldns_rr_free(rr); + } + + *result_len = pos; + wire2 = LDNS_XMALLOC(uint8_t, *result_len); + memcpy(wire2, wire, *result_len); + + ldns_write_uint16(wire2 + LDNS_ARCOUNT_OFF, ar_count); + + return wire2; +} + +const EVP_MD * +ldns_get_digest_function(char *name) +{ + /* TODO replace with openssl's EVP_get_digestbyname + (need init somewhere for that) + */ + if (strlen(name) == 10 && strncasecmp(name, "hmac-sha1.", 9) == 0) + return EVP_sha1(); + else if (strlen(name) == 25 && strncasecmp(name, + "hmac-md5.sig-alg.reg.int.", 25) == 0) + return EVP_md5(); + else + return NULL; +} + +ldns_status +ldns_create_tsig_mac( + 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 +) +{ + ldns_buffer *data_buffer = NULL; + char *wireformat; + int wiresize; + unsigned char *mac_bytes; + unsigned int md_len = EVP_MAX_MD_SIZE; + unsigned char *key_bytes; + int key_size; + const EVP_MD *digester; + char *algorithm_name; + ldns_rdf *result = NULL; + + /* + * prepare the digestable information + */ + data_buffer = ldns_buffer_new(LDNS_MAX_PACKETLEN); + /* 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); + (void) ldns_rdf2buffer_wire(data_buffer, key_name_rdf); + ldns_buffer_write_u16(data_buffer, LDNS_RR_CLASS_ANY); + ldns_buffer_write_u32(data_buffer, 0); + (void) ldns_rdf2buffer_wire(data_buffer, algorithm_rdf); + (void) ldns_rdf2buffer_wire(data_buffer, time_signed_rdf); + (void) ldns_rdf2buffer_wire(data_buffer, fudge_rdf); + (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); + + /* prepare the key */ + key_bytes = LDNS_XMALLOC(unsigned char, b64_pton_calculate_size(strlen(key_data))); + key_size = b64_pton(key_data, key_bytes, strlen(key_data) * 2); + if (key_size < 0) { + /* LDNS_STATUS_INVALID_B64 */ + dprintf("%s\n", "Bad base64 string"); + return LDNS_STATUS_INVALID_B64; + } + /* hmac it */ + /* 2 spare bytes for the length */ + mac_bytes = LDNS_XMALLOC(unsigned char, md_len); + memset(mac_bytes, 0, md_len); + + digester = ldns_get_digest_function(algorithm_name); + + if (digester) { + (void) HMAC(digester, key_bytes, key_size, (void *)wireformat, 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 { + /*dprintf("No digest found for %s\n", algorithm_name);*/ + return LDNS_STATUS_CRYPTO_UNKNOWN_ALGO; + } + + LDNS_FREE(algorithm_name); + LDNS_FREE(mac_bytes); + LDNS_FREE(key_bytes); + ldns_buffer_free(data_buffer); + + *tsig_mac = result; + + return LDNS_STATUS_OK; +} + + +/* THIS FUNC WILL REMOVE TSIG ITSELF */ +bool +ldns_pkt_tsig_verify(ldns_pkt *pkt, + uint8_t *wire, + size_t wirelen, + const char *key_name, + const char *key_data, + ldns_rdf *orig_mac_rdf) +{ + ldns_rdf *fudge_rdf; + ldns_rdf *algorithm_rdf; + ldns_rdf *time_signed_rdf; + ldns_rdf *orig_id_rdf; + ldns_rdf *error_rdf; + ldns_rdf *other_data_rdf; + ldns_rdf *pkt_mac_rdf; + ldns_rdf *my_mac_rdf; + ldns_rdf *key_name_rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, key_name); + uint16_t pkt_id, orig_pkt_id; + ldns_status status; + + uint8_t *prepared_wire = NULL; + size_t prepared_wire_size = 0; + + ldns_rr *orig_tsig = ldns_pkt_tsig(pkt); + + if (!orig_tsig) { + ldns_rdf_deep_free(key_name_rdf); + return false; + } + algorithm_rdf = ldns_rr_rdf(orig_tsig, 0); + time_signed_rdf = ldns_rr_rdf(orig_tsig, 1); + fudge_rdf = ldns_rr_rdf(orig_tsig, 2); + pkt_mac_rdf = ldns_rr_rdf(orig_tsig, 3); + orig_id_rdf = ldns_rr_rdf(orig_tsig, 4); + error_rdf = ldns_rr_rdf(orig_tsig, 5); + other_data_rdf = ldns_rr_rdf(orig_tsig, 6); + + /* remove temporarily */ + ldns_pkt_set_tsig(pkt, NULL); + /* temporarily change the id to the original id */ + pkt_id = ldns_pkt_id(pkt); + orig_pkt_id = ldns_rdf2native_int16(orig_id_rdf); + ldns_pkt_set_id(pkt, orig_pkt_id); + + prepared_wire = ldns_tsig_prepare_pkt_wire(wire, wirelen, &prepared_wire_size); + + status = ldns_create_tsig_mac(&my_mac_rdf, + prepared_wire, + prepared_wire_size, + key_data, + key_name_rdf, + fudge_rdf, + algorithm_rdf, + time_signed_rdf, + error_rdf, + other_data_rdf, + orig_mac_rdf + ); + + LDNS_FREE(prepared_wire); + + if (status != LDNS_STATUS_OK) { + ldns_rdf_deep_free(key_name_rdf); + return false; + } + /* Put back the values */ + ldns_pkt_set_tsig(pkt, orig_tsig); + ldns_pkt_set_id(pkt, pkt_id); + + ldns_rdf_deep_free(key_name_rdf); + + if (ldns_rdf_compare(pkt_mac_rdf, my_mac_rdf) == 0) { + ldns_rdf_deep_free(my_mac_rdf); + return true; + } else { + ldns_rdf_deep_free(my_mac_rdf); + return false; + } +} + +/* TODO: memory :p */ +ldns_status +ldns_pkt_tsig_sign(ldns_pkt *pkt, const char *key_name, const char *key_data, uint16_t fudge, const char *algorithm_name, ldns_rdf *query_mac) +{ + ldns_rr *tsig_rr; + ldns_rdf *key_name_rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, key_name); + ldns_rdf *fudge_rdf = NULL; + ldns_rdf *orig_id_rdf = NULL; + ldns_rdf *algorithm_rdf; + ldns_rdf *error_rdf = NULL; + ldns_rdf *mac_rdf = NULL; + ldns_rdf *other_data_rdf = NULL; + + ldns_status status = LDNS_STATUS_OK; + + uint8_t *pkt_wire = NULL; + size_t pkt_wire_len; + + struct timeval tv_time_signed; + uint8_t *time_signed = NULL; + ldns_rdf *time_signed_rdf = NULL; + + algorithm_rdf = ldns_rdf_new_frm_str(LDNS_RDF_TYPE_DNAME, algorithm_name); + + /* eww don't have create tsigtime rdf yet :( */ + /* bleh :p */ + if (gettimeofday(&tv_time_signed, NULL) == 0) { + time_signed = LDNS_XMALLOC(uint8_t, 6); + ldns_write_uint64_as_uint48(time_signed, tv_time_signed.tv_sec); + } else { + status = LDNS_STATUS_INTERNAL_ERR; + goto clean; + } + + time_signed_rdf = ldns_rdf_new(LDNS_RDF_TYPE_TSIGTIME, 6, time_signed); + + fudge_rdf = ldns_native2rdf_int16(LDNS_RDF_TYPE_INT16, fudge); + + orig_id_rdf = ldns_native2rdf_int16(LDNS_RDF_TYPE_INT16, ldns_pkt_id(pkt)); + + error_rdf = ldns_native2rdf_int16(LDNS_RDF_TYPE_INT16, 0); + + other_data_rdf = ldns_native2rdf_int16_data(0, NULL); + + if (ldns_pkt2wire(&pkt_wire, pkt, &pkt_wire_len) != LDNS_STATUS_OK) { + status = LDNS_STATUS_ERR; + goto clean; + } + + status = ldns_create_tsig_mac(&mac_rdf, + pkt_wire, + pkt_wire_len, + key_data, + key_name_rdf, + fudge_rdf, + algorithm_rdf, + time_signed_rdf, + error_rdf, + other_data_rdf, + query_mac + ); + + if (!mac_rdf) { + goto clean; + } + + LDNS_FREE(pkt_wire); + + /* Create the TSIG RR */ + tsig_rr = ldns_rr_new(); + ldns_rr_set_owner(tsig_rr, key_name_rdf); + ldns_rr_set_class(tsig_rr, LDNS_RR_CLASS_ANY); + ldns_rr_set_type(tsig_rr, LDNS_RR_TYPE_TSIG); + ldns_rr_set_ttl(tsig_rr, 0); + + ldns_rr_push_rdf(tsig_rr, algorithm_rdf); + ldns_rr_push_rdf(tsig_rr, time_signed_rdf); + ldns_rr_push_rdf(tsig_rr, fudge_rdf); + ldns_rr_push_rdf(tsig_rr, mac_rdf); + ldns_rr_push_rdf(tsig_rr, orig_id_rdf); + ldns_rr_push_rdf(tsig_rr, error_rdf); + ldns_rr_push_rdf(tsig_rr, other_data_rdf); + + ldns_pkt_set_tsig(pkt, tsig_rr); + + return status; + + clean: + ldns_rdf_free(key_name_rdf); + ldns_rdf_free(algorithm_rdf); + ldns_rdf_free(time_signed_rdf); + ldns_rdf_free(fudge_rdf); + ldns_rdf_free(orig_id_rdf); + ldns_rdf_free(error_rdf); + ldns_rdf_free(other_data_rdf); + return status; +} + + diff --git a/update.c b/update.c new file mode 100644 index 00000000..04d84ce2 --- /dev/null +++ b/update.c @@ -0,0 +1,440 @@ +/* update.c + * + * Functions for RFC 2136 Dynamic Update + * + * Copyright (c) 2005, NLnet Labs. All rights reserved. + * + * See LICENSE for the license. + */ + +#include + +#include + +#include +#include +#include + +/* + * RFC 2136 sections mapped to RFC 1035: + * zone/ZO -- QD/question + * prerequisites/PR -- AN/answers + * updates/UP -- NS/authority records + * additional data/AD -- AR/additional records + */ + +#define _zone _question +#define _prereq _answer +#define _updates _authority + +/** + * create an update packet from zone name, class and the rr lists + * \param[in] zone name of the zone + * \param[in] class zone class + * \param[in] pr_rrlist list of Prerequisite Section RRs + * \param[in] up_rrlist list of Updates Section RRs + * \param[in] ad_rrlist list of Additional Data Section RRs (currently unused) + */ +ldns_pkt * +ldns_update_pkt_new(ldns_rdf *zone_rdf, ldns_rr_class class, + ldns_rr_list *pr_rrlist, ldns_rr_list *up_rrlist, ldns_rr_list *ad_rrlist) +{ + ldns_pkt *p; + + if (!zone_rdf || !up_rrlist) { + dprintf("%s", "bad input to ldns_update_pkt_new()\n"); + return NULL; + } + + if (class == 0) + class = LDNS_RR_CLASS_IN; + + /* Create packet, fill in Zone Section. */ + p = ldns_pkt_query_new(zone_rdf, LDNS_RR_TYPE_SOA, class, LDNS_RD); + if (!p) + return NULL; + zone_rdf = NULL; /* No longer safe to use. */ + + ldns_pkt_set_opcode(p, LDNS_PACKET_UPDATE); + + ldns_rr_list_deep_free(p->_updates); + p->_updates = ldns_rr_list_clone(up_rrlist); + ldns_update_set_up(p, ldns_rr_list_rr_count(up_rrlist)); + + if (pr_rrlist) { + ldns_rr_list_deep_free(p->_prereq); + p->_prereq = ldns_rr_list_clone(pr_rrlist); + ldns_update_set_pr(p, ldns_rr_list_rr_count(pr_rrlist)); + } + + if (ad_rrlist) { + ldns_rr_list_deep_free(p->_additional); + p->_additional = ldns_rr_list_clone(ad_rrlist); + ldns_update_set_ad(p, ldns_rr_list_rr_count(ad_rrlist)); + } + + return p; +} + +ldns_status +ldns_update_pkt_tsig_add(ldns_pkt *p, ldns_resolver *r) +{ + u_int16_t fudge = 300; /* Recommended fudge. [RFC2845 6.4] */ + + if (ldns_resolver_tsig_keyname(r) && ldns_resolver_tsig_keydata(r)) + return ldns_pkt_tsig_sign(p, ldns_resolver_tsig_keyname(r), + ldns_resolver_tsig_keydata(r), fudge, + ldns_resolver_tsig_algorithm(r), NULL); + + /* No TSIG to do. */ + return LDNS_STATUS_OK; +} + +/* Move to higher.c or similar? */ + +ldns_status +ldns_update_get_soa_mname(ldns_rdf *zone, ldns_resolver *r, + ldns_rr_class class, ldns_rdf **mname) +{ + ldns_rr *soa_rr; + ldns_pkt *query, *resp; + + /* Nondestructive, so clone 'zone' here */ + query = ldns_pkt_query_new(ldns_rdf_clone(zone), LDNS_RR_TYPE_SOA, + class, LDNS_RD); + if (!query) + return LDNS_STATUS_ERR; + + ldns_pkt_set_random_id(query); + if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) { + dprintf("%s", "SOA query failed (MNAME)\n"); + ldns_pkt_free(query); + return LDNS_STATUS_ERR; + } + ldns_pkt_free(query); + if (!resp) + return LDNS_STATUS_ERR; + + /* Expect a SOA answer. */ + *mname = NULL; + while ((soa_rr = ldns_rr_list_pop_rr(ldns_pkt_answer(resp)))) { + if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA) + continue; + /* [RFC1035 3.3.13] */ + *mname = ldns_rdf_clone(ldns_rr_rdf(soa_rr, 0)); + break; + } + ldns_pkt_free(resp); + + return *mname ? LDNS_STATUS_OK : LDNS_STATUS_ERR; +} + +/* Try to get zone and MNAME from SOA queries. */ +ldns_status +ldns_update_get_soa_zone_mname(const char *fqdn, ldns_resolver *r, + ldns_rr_class class, ldns_rdf **zone_rdf, ldns_rdf **mname_rdf) +{ + ldns_rr *soa_rr, *rr; + ldns_rdf *soa_zone = NULL, *soa_mname = NULL; + ldns_rdf *ipaddr, *fqdn_rdf, *tmp; + ldns_rdf **nslist; + ldns_pkt *query, *resp; + int i; + + /* + * XXX Ok, this cannot be the best way to find this...? + * XXX (I run into weird cache-related stuff here) + */ + + /* Step 1 - first find a nameserver that should know *something* */ + fqdn_rdf = ldns_dname_new_frm_str(fqdn); + query = ldns_pkt_query_new(fqdn_rdf, LDNS_RR_TYPE_SOA, class, LDNS_RD); + if (!query) + return LDNS_STATUS_ERR; + fqdn_rdf = NULL; + + ldns_pkt_set_random_id(query); + if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) { + dprintf("%s", "SOA query failed\n"); + ldns_pkt_free(query); + return LDNS_STATUS_ERR; + } + ldns_pkt_free(query); + if (!resp) + return LDNS_STATUS_ERR; + + /* XXX Is it safe to only look in authority section here? */ + while ((soa_rr = ldns_rr_list_pop_rr(ldns_pkt_authority(resp)))) { + if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA) + continue; + /* [RFC1035 3.3.13] */ + soa_mname = ldns_rdf_clone(ldns_rr_rdf(soa_rr, 0)); + break; + } + ldns_pkt_free(resp); + if (!soa_rr) + return LDNS_STATUS_ERR; + + /* Step 2 - find SOA MNAME IP address, add to resolver */ + query = ldns_pkt_query_new(soa_mname, LDNS_RR_TYPE_A, class, LDNS_RD); + if (!query) + return LDNS_STATUS_ERR; + soa_mname = NULL; + + ldns_pkt_set_random_id(query); + if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) { + dprintf("%s", "SOA query 2 failed\n"); + ldns_pkt_free(query); + return LDNS_STATUS_ERR; + } + ldns_pkt_free(query); + if (!resp) + return LDNS_STATUS_ERR; + + if (ldns_pkt_ancount(resp) == 0) { + ldns_pkt_free(resp); + return LDNS_STATUS_ERR; + } + + /* XXX There may be more than one answer RR here. */ + rr = ldns_rr_list_pop_rr(ldns_pkt_answer(resp)); + ipaddr = ldns_rr_rdf(rr, 0); + + /* Put the SOA mname IP first in the nameserver list. */ + nslist = ldns_resolver_nameservers(r); + for (i = 0; i < ldns_resolver_nameserver_count(r); i++) { + if (ldns_rdf_compare(ipaddr, nslist[i]) == 0) { + if (i) { + tmp = nslist[0]; + nslist[0] = nslist[i]; + nslist[i] = tmp; + } + break; + } + } + if (i >= ldns_resolver_nameserver_count(r)) { + /* SOA mname was not part of the resolver so add it first. */ + ldns_resolver_push_nameserver(r, ipaddr); + nslist = ldns_resolver_nameservers(r); + i = ldns_resolver_nameserver_count(r) - 1; + tmp = nslist[0]; + nslist[0] = nslist[i]; + nslist[i] = tmp; + } + ldns_pkt_free(resp); + + /* Make sure to ask the first in the list, i.e SOA mname */ + ldns_resolver_set_random(r, false); + + /* Step 3 - Redo SOA query, sending to SOA MNAME directly. */ + fqdn_rdf = ldns_dname_new_frm_str(fqdn); + query = ldns_pkt_query_new(fqdn_rdf, LDNS_RR_TYPE_SOA, class, LDNS_RD); + if (!query) + return LDNS_STATUS_ERR; + fqdn_rdf = NULL; + + ldns_pkt_set_random_id(query); + if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) { + dprintf("%s", "SOA query failed\n"); + ldns_pkt_free(query); + return LDNS_STATUS_ERR; + } + ldns_pkt_free(query); + if (!resp) + return LDNS_STATUS_ERR; + + /* XXX Is it safe to only look in authority section here, too? */ + while ((soa_rr = ldns_rr_list_pop_rr(ldns_pkt_authority(resp)))) { + if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA) + continue; + /* [RFC1035 3.3.13] */ + soa_mname = ldns_rdf_clone(ldns_rr_rdf(soa_rr, 0)); + soa_zone = ldns_rdf_clone(ldns_rr_owner(soa_rr)); + break; + } + ldns_pkt_free(resp); + if (!soa_rr) + return LDNS_STATUS_ERR; + + /* That seems to have worked, pass results to caller. */ + *zone_rdf = soa_zone; + *mname_rdf = soa_mname; + return LDNS_STATUS_OK; +} + +/** + * Create a resolver suitable for use with UPDATE. [RFC2136 4.3] + * SOA MNAME is used as the "primary master". + * \param[in] fqdn FQDN of a host in a zone + * \param[in] zone zone name, if explicitly given, otherwise use SOA + * \param[in] class zone class + * \param[in] tsig_cred TSIG credentials + * \param[out] zone returns zone/owner rdf from the 'fqdn' SOA MNAME query + */ +ldns_resolver * +ldns_update_resolver_new(const char *fqdn, const char *zone, + ldns_rr_class class, ldns_tsig_credentials *tsig_cred, ldns_rdf **zone_rdf) +{ + ldns_resolver *r1, *r2; + ldns_pkt *query = NULL, *resp; + ldns_rr_list *nslist, *iplist; + ldns_rdf *soa_zone, *soa_mname, *ns_name; + int i; + + if (class == 0) + class = LDNS_RR_CLASS_IN; + + /* First, get data from /etc/resolv.conf */ + r1 = ldns_resolver_new_frm_file(NULL); + if (!r1) + return NULL; + + r2 = ldns_resolver_new(); + if (!r2) + goto bad; + + /* TSIG key data available? Copy into the resolver. */ + if (tsig_cred) { + ldns_resolver_set_tsig_algorithm(r2, + ldns_tsig_algorithm(tsig_cred)); + ldns_resolver_set_tsig_keyname(r2, + ldns_tsig_keyname_clone(tsig_cred)); + /* + * XXX Weird that ldns_resolver_deep_free() will free() + * keyname but not hmac key data? + */ + ldns_resolver_set_tsig_keydata(r2, + ldns_tsig_keydata_clone(tsig_cred)); + } + + /* Now get SOA zone, mname, NS, and construct r2. [RFC2136 4.3] */ + + /* Explicit 'zone' or no? */ + if (zone) { + soa_zone = ldns_dname_new_frm_str(zone); + if (ldns_update_get_soa_mname(soa_zone, r1, class, &soa_mname) + != LDNS_STATUS_OK) + goto bad; + } else { + if (ldns_update_get_soa_zone_mname(fqdn, r1, class, &soa_zone, + &soa_mname) != LDNS_STATUS_OK) + goto bad; + } + + /* Pass zone_rdf on upwards. */ + *zone_rdf = ldns_rdf_clone(soa_zone); + + /* NS */ + query = ldns_pkt_query_new(soa_zone, LDNS_RR_TYPE_NS, class, LDNS_RD); + if (!query) + goto bad; + soa_zone = NULL; + + ldns_pkt_set_random_id(query); + if (ldns_resolver_send_pkt(&resp, r1, query) != LDNS_STATUS_OK) { + dprintf("%s", "NS query failed!\n"); + goto bad; + } + ldns_pkt_free(query); + if (!resp) + goto bad; + + /* Match SOA MNAME to NS list, adding it first */ + nslist = ldns_pkt_answer(resp); + for (i = 0; i < ldns_rr_list_rr_count(nslist); i++) { + ns_name = ldns_rr_rdf(ldns_rr_list_rr(nslist, i), 0); + if (!ns_name) + continue; + if (ldns_rdf_compare(soa_mname, ns_name) == 0) { + /* Match */ + iplist = ldns_get_rr_list_addr_by_name(r1, ns_name, + class, 0); + ldns_resolver_push_nameserver_rr_list(r2, iplist); + break; + } + } + + /* Then all the other NSs. XXX Randomize? */ + for (i = 0; i < ldns_rr_list_rr_count(nslist); i++) { + ns_name = ldns_rr_rdf(ldns_rr_list_rr(nslist, i), 0); + if (!ns_name) + continue; + if (ldns_rdf_compare(soa_mname, ns_name) != 0) { + /* No match, add it now. */ + iplist = ldns_get_rr_list_addr_by_name(r1, ns_name, + class, 0); + ldns_resolver_push_nameserver_rr_list(r2, iplist); + } + } + + /* Cleanup and return. */ + ldns_resolver_set_random(r2, false); + ldns_pkt_free(resp); + ldns_resolver_deep_free(r1); + return r2; + + bad: + if (r1) + ldns_resolver_deep_free(r1); + if (r2) + ldns_resolver_deep_free(r2); + if (query) + ldns_pkt_free(query); + if (resp) + ldns_pkt_free(resp); + return NULL; +} + +/* + * ldns_update_{get,set}_{zo,pr,up,ad}. + */ + +uint16_t +ldns_update_get_zo(const ldns_pkt *p) +{ + return ldns_pkt_qdcount(p); +} + +uint16_t +ldns_update_get_pr(const ldns_pkt *p) +{ + return ldns_pkt_ancount(p); +} + +uint16_t +ldns_update_get_up(const ldns_pkt *p) +{ + return ldns_pkt_nscount(p); +} + +uint16_t +ldns_update_get_ad(const ldns_pkt *p) +{ + return ldns_pkt_arcount(p); +} + +void +ldns_update_set_zo(ldns_pkt *p, u_int16_t v) +{ + ldns_pkt_set_qdcount(p, v); +} + +void +ldns_update_set_pr(ldns_pkt *p, u_int16_t v) +{ + ldns_pkt_set_ancount(p, v); +} + +void +ldns_update_set_up(ldns_pkt *p, u_int16_t v) +{ + ldns_pkt_set_nscount(p, v); +} + +void +ldns_update_set_ad(ldns_pkt *p, u_int16_t v) +{ + ldns_pkt_set_arcount(p, v); +} + +