| 1 | /* update.c |
| 2 | * |
| 3 | * Functions for RFC 2136 Dynamic Update |
| 4 | * |
| 5 | * Copyright (c) 2005-2008, NLnet Labs. All rights reserved. |
| 6 | * |
| 7 | * See LICENSE for the license. |
| 8 | */ |
| 9 | |
| 10 | #include <ldns/config.h> |
| 11 | |
| 12 | #include <ldns/ldns.h> |
| 13 | |
| 14 | #include <strings.h> |
| 15 | #include <stdlib.h> |
| 16 | #include <limits.h> |
| 17 | |
| 18 | /* |
| 19 | * RFC 2136 sections mapped to RFC 1035: |
| 20 | * zone/ZO -- QD/question |
| 21 | * prerequisites/PR -- AN/answers |
| 22 | * updates/UP -- NS/authority records |
| 23 | * additional data/AD -- AR/additional records |
| 24 | */ |
| 25 | |
| 26 | ldns_pkt * |
| 27 | ldns_update_pkt_new(ldns_rdf *zone_rdf, ldns_rr_class c, |
| 28 | ldns_rr_list *pr_rrlist, ldns_rr_list *up_rrlist, ldns_rr_list *ad_rrlist) |
| 29 | { |
| 30 | ldns_pkt *p; |
| 31 | |
| 32 | if (!zone_rdf || !up_rrlist) { |
| 33 | return NULL; |
| 34 | } |
| 35 | |
| 36 | if (c == 0) { |
| 37 | c = LDNS_RR_CLASS_IN; |
| 38 | } |
| 39 | |
| 40 | /* Create packet, fill in Zone Section. */ |
| 41 | p = ldns_pkt_query_new(zone_rdf, LDNS_RR_TYPE_SOA, c, LDNS_RD); |
| 42 | if (!p) { |
| 43 | return NULL; |
| 44 | } |
| 45 | zone_rdf = NULL; /* No longer safe to use. */ |
| 46 | |
| 47 | ldns_pkt_set_opcode(p, LDNS_PACKET_UPDATE); |
| 48 | |
| 49 | ldns_rr_list_deep_free(p->_authority); |
| 50 | |
| 51 | ldns_pkt_set_authority(p, ldns_rr_list_clone(up_rrlist)); |
| 52 | |
| 53 | ldns_update_set_upcount(p, ldns_rr_list_rr_count(up_rrlist)); |
| 54 | |
| 55 | if (pr_rrlist) { |
| 56 | ldns_rr_list_deep_free(p->_answer); /*XXX access function */ |
| 57 | ldns_pkt_set_answer(p, ldns_rr_list_clone(pr_rrlist)); |
| 58 | ldns_update_set_prcount(p, ldns_rr_list_rr_count(pr_rrlist)); |
| 59 | } |
| 60 | |
| 61 | if (ad_rrlist) { |
| 62 | ldns_rr_list_deep_free(p->_additional); |
| 63 | ldns_pkt_set_additional(p, ldns_rr_list_clone(ad_rrlist)); |
| 64 | ldns_update_set_adcount(p, ldns_rr_list_rr_count(ad_rrlist)); |
| 65 | } |
| 66 | return p; |
| 67 | } |
| 68 | |
| 69 | ldns_status |
| 70 | ldns_update_pkt_tsig_add(ldns_pkt *p, ldns_resolver *r) |
| 71 | { |
| 72 | #ifdef HAVE_SSL |
| 73 | uint16_t fudge = 300; /* Recommended fudge. [RFC2845 6.4] */ |
| 74 | if (ldns_resolver_tsig_keyname(r) && ldns_resolver_tsig_keydata(r)) |
| 75 | return ldns_pkt_tsig_sign(p, ldns_resolver_tsig_keyname(r), |
| 76 | ldns_resolver_tsig_keydata(r), fudge, |
| 77 | ldns_resolver_tsig_algorithm(r), NULL); |
| 78 | #else |
| 79 | /* do nothing */ |
| 80 | (void)p; |
| 81 | (void)r; |
| 82 | #endif /* HAVE_SSL */ |
| 83 | /* No TSIG to do. */ |
| 84 | return LDNS_STATUS_OK; |
| 85 | } |
| 86 | |
| 87 | /* Move to higher.c or similar? */ |
| 88 | /* XXX doc */ |
| 89 | ldns_status |
| 90 | ldns_update_soa_mname(ldns_rdf *zone, ldns_resolver *r, |
| 91 | ldns_rr_class c, ldns_rdf **mname) |
| 92 | { |
| 93 | ldns_rr *soa_rr; |
| 94 | ldns_pkt *query, *resp; |
| 95 | |
| 96 | /* Nondestructive, so clone 'zone' here */ |
| 97 | query = ldns_pkt_query_new(ldns_rdf_clone(zone), LDNS_RR_TYPE_SOA, |
| 98 | c, LDNS_RD); |
| 99 | if (!query) { |
| 100 | return LDNS_STATUS_ERR; |
| 101 | } |
| 102 | |
| 103 | ldns_pkt_set_random_id(query); |
| 104 | if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) { |
| 105 | ldns_pkt_free(query); |
| 106 | return LDNS_STATUS_ERR; |
| 107 | } |
| 108 | ldns_pkt_free(query); |
| 109 | if (!resp) { |
| 110 | return LDNS_STATUS_ERR; |
| 111 | } |
| 112 | |
| 113 | /* Expect a SOA answer. */ |
| 114 | *mname = NULL; |
| 115 | while ((soa_rr = ldns_rr_list_pop_rr(ldns_pkt_answer(resp)))) { |
| 116 | if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA |
| 117 | || ldns_rr_rdf(soa_rr, 0) == NULL) |
| 118 | continue; |
| 119 | /* [RFC1035 3.3.13] */ |
| 120 | *mname = ldns_rdf_clone(ldns_rr_rdf(soa_rr, 0)); |
| 121 | break; |
| 122 | } |
| 123 | ldns_pkt_free(resp); |
| 124 | |
| 125 | return *mname ? LDNS_STATUS_OK : LDNS_STATUS_ERR; |
| 126 | } |
| 127 | |
| 128 | /* Try to get zone and MNAME from SOA queries. */ |
| 129 | ldns_status |
| 130 | ldns_update_soa_zone_mname(const char *fqdn, ldns_resolver *r, |
| 131 | ldns_rr_class c, ldns_rdf **zone_rdf, ldns_rdf **mname_rdf) |
| 132 | { |
| 133 | ldns_rr *soa_rr, *rr; |
| 134 | ldns_rdf *soa_zone = NULL, *soa_mname = NULL; |
| 135 | ldns_rdf *ipaddr, *fqdn_rdf, *tmp; |
| 136 | ldns_rdf **nslist; |
| 137 | ldns_pkt *query, *resp; |
| 138 | size_t i; |
| 139 | |
| 140 | /* |
| 141 | * XXX Ok, this cannot be the best way to find this...? |
| 142 | * XXX (I run into weird cache-related stuff here) |
| 143 | */ |
| 144 | |
| 145 | /* Step 1 - first find a nameserver that should know *something* */ |
| 146 | fqdn_rdf = ldns_dname_new_frm_str(fqdn); |
| 147 | query = ldns_pkt_query_new(fqdn_rdf, LDNS_RR_TYPE_SOA, c, LDNS_RD); |
| 148 | if (!query) { |
| 149 | return LDNS_STATUS_ERR; |
| 150 | } |
| 151 | fqdn_rdf = NULL; |
| 152 | |
| 153 | ldns_pkt_set_random_id(query); |
| 154 | if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) { |
| 155 | ldns_pkt_free(query); |
| 156 | return LDNS_STATUS_ERR; |
| 157 | } |
| 158 | ldns_pkt_free(query); |
| 159 | if (!resp) { |
| 160 | return LDNS_STATUS_ERR; |
| 161 | } |
| 162 | |
| 163 | /* XXX Is it safe to only look in authority section here? */ |
| 164 | while ((soa_rr = ldns_rr_list_pop_rr(ldns_pkt_authority(resp)))) { |
| 165 | if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA |
| 166 | || ldns_rr_rdf(soa_rr, 0) == NULL) |
| 167 | continue; |
| 168 | /* [RFC1035 3.3.13] */ |
| 169 | soa_mname = ldns_rdf_clone(ldns_rr_rdf(soa_rr, 0)); |
| 170 | break; |
| 171 | } |
| 172 | ldns_pkt_free(resp); |
| 173 | if (!soa_rr) { |
| 174 | return LDNS_STATUS_ERR; |
| 175 | } |
| 176 | |
| 177 | /* Step 2 - find SOA MNAME IP address, add to resolver */ |
| 178 | query = ldns_pkt_query_new(soa_mname, LDNS_RR_TYPE_A, c, LDNS_RD); |
| 179 | if (!query) { |
| 180 | return LDNS_STATUS_ERR; |
| 181 | } |
| 182 | soa_mname = NULL; |
| 183 | |
| 184 | ldns_pkt_set_random_id(query); |
| 185 | if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) { |
| 186 | ldns_pkt_free(query); |
| 187 | return LDNS_STATUS_ERR; |
| 188 | } |
| 189 | ldns_pkt_free(query); |
| 190 | if (!resp) { |
| 191 | return LDNS_STATUS_ERR; |
| 192 | } |
| 193 | |
| 194 | if (ldns_pkt_ancount(resp) == 0) { |
| 195 | ldns_pkt_free(resp); |
| 196 | return LDNS_STATUS_ERR; |
| 197 | } |
| 198 | |
| 199 | /* XXX There may be more than one answer RR here. */ |
| 200 | rr = ldns_rr_list_pop_rr(ldns_pkt_answer(resp)); |
| 201 | ipaddr = ldns_rr_rdf(rr, 0); |
| 202 | |
| 203 | /* Put the SOA mname IP first in the nameserver list. */ |
| 204 | nslist = ldns_resolver_nameservers(r); |
| 205 | for (i = 0; i < ldns_resolver_nameserver_count(r); i++) { |
| 206 | if (ldns_rdf_compare(ipaddr, nslist[i]) == 0) { |
| 207 | if (i) { |
| 208 | tmp = nslist[0]; |
| 209 | nslist[0] = nslist[i]; |
| 210 | nslist[i] = tmp; |
| 211 | } |
| 212 | break; |
| 213 | } |
| 214 | } |
| 215 | if (i >= ldns_resolver_nameserver_count(r)) { |
| 216 | /* SOA mname was not part of the resolver so add it first. */ |
| 217 | (void) ldns_resolver_push_nameserver(r, ipaddr); |
| 218 | nslist = ldns_resolver_nameservers(r); |
| 219 | i = ldns_resolver_nameserver_count(r) - 1; |
| 220 | tmp = nslist[0]; |
| 221 | nslist[0] = nslist[i]; |
| 222 | nslist[i] = tmp; |
| 223 | } |
| 224 | ldns_pkt_free(resp); |
| 225 | |
| 226 | /* Make sure to ask the first in the list, i.e SOA mname */ |
| 227 | ldns_resolver_set_random(r, false); |
| 228 | |
| 229 | /* Step 3 - Redo SOA query, sending to SOA MNAME directly. */ |
| 230 | fqdn_rdf = ldns_dname_new_frm_str(fqdn); |
| 231 | query = ldns_pkt_query_new(fqdn_rdf, LDNS_RR_TYPE_SOA, c, LDNS_RD); |
| 232 | if (!query) { |
| 233 | return LDNS_STATUS_ERR; |
| 234 | } |
| 235 | fqdn_rdf = NULL; |
| 236 | |
| 237 | ldns_pkt_set_random_id(query); |
| 238 | if (ldns_resolver_send_pkt(&resp, r, query) != LDNS_STATUS_OK) { |
| 239 | ldns_pkt_free(query); |
| 240 | return LDNS_STATUS_ERR; |
| 241 | } |
| 242 | ldns_pkt_free(query); |
| 243 | if (!resp) { |
| 244 | return LDNS_STATUS_ERR; |
| 245 | } |
| 246 | |
| 247 | /* XXX Is it safe to only look in authority section here, too? */ |
| 248 | while ((soa_rr = ldns_rr_list_pop_rr(ldns_pkt_authority(resp)))) { |
| 249 | if (ldns_rr_get_type(soa_rr) != LDNS_RR_TYPE_SOA |
| 250 | || ldns_rr_rdf(soa_rr, 0) == NULL) |
| 251 | continue; |
| 252 | /* [RFC1035 3.3.13] */ |
| 253 | soa_mname = ldns_rdf_clone(ldns_rr_rdf(soa_rr, 0)); |
| 254 | soa_zone = ldns_rdf_clone(ldns_rr_owner(soa_rr)); |
| 255 | break; |
| 256 | } |
| 257 | ldns_pkt_free(resp); |
| 258 | if (!soa_rr) { |
| 259 | return LDNS_STATUS_ERR; |
| 260 | } |
| 261 | |
| 262 | /* That seems to have worked, pass results to caller. */ |
| 263 | *zone_rdf = soa_zone; |
| 264 | *mname_rdf = soa_mname; |
| 265 | return LDNS_STATUS_OK; |
| 266 | } |
| 267 | |
| 268 | /* |
| 269 | * ldns_update_{get,set}_{zo,pr,up,ad}count |
| 270 | */ |
| 271 | |
| 272 | uint16_t |
| 273 | ldns_update_zocount(const ldns_pkt *p) |
| 274 | { |
| 275 | return ldns_pkt_qdcount(p); |
| 276 | } |
| 277 | |
| 278 | uint16_t |
| 279 | ldns_update_prcount(const ldns_pkt *p) |
| 280 | { |
| 281 | return ldns_pkt_ancount(p); |
| 282 | } |
| 283 | |
| 284 | uint16_t |
| 285 | ldns_update_upcount(const ldns_pkt *p) |
| 286 | { |
| 287 | return ldns_pkt_nscount(p); |
| 288 | } |
| 289 | |
| 290 | uint16_t |
| 291 | ldns_update_ad(const ldns_pkt *p) |
| 292 | { |
| 293 | return ldns_pkt_arcount(p); |
| 294 | } |
| 295 | |
| 296 | void |
| 297 | ldns_update_set_zo(ldns_pkt *p, uint16_t v) |
| 298 | { |
| 299 | ldns_pkt_set_qdcount(p, v); |
| 300 | } |
| 301 | |
| 302 | void |
| 303 | ldns_update_set_prcount(ldns_pkt *p, uint16_t v) |
| 304 | { |
| 305 | ldns_pkt_set_ancount(p, v); |
| 306 | } |
| 307 | |
| 308 | void |
| 309 | ldns_update_set_upcount(ldns_pkt *p, uint16_t v) |
| 310 | { |
| 311 | ldns_pkt_set_nscount(p, v); |
| 312 | } |
| 313 | |
| 314 | void |
| 315 | ldns_update_set_adcount(ldns_pkt *p, uint16_t v) |
| 316 | { |
| 317 | ldns_pkt_set_arcount(p, v); |
| 318 | } |