chiark / gitweb /
src/types.c: Handle inconsistent CNAME records between addr answers.
[adns.git] / src / types.c
index 29e16ad75f24415fc4e797d1af8fa77539fe690a..c1d235ebb389fb27e2f9881d2e01afb18964d867 100644 (file)
@@ -378,10 +378,32 @@ static unsigned addr_rrtypeflag(adns_rrtype type) {
   return i < addr_nrrtypes ? 1 << i : 0;
 }
 
+/* About CNAME handling in addr queries.
+ *
+ * A user-level addr query is translated into a number of protocol-level
+ * queries, and its job is to reassemble the results.  This gets tricky if
+ * the answers aren't consistent.  In particular, if the answers report
+ * inconsistent indirection via CNAME records (e.g., different CNAMEs, or
+ * some indirect via a CNAME, and some don't) then we have trouble.
+ *
+ * Once we've received an answer, even if it was NODATA, we set
+ * adns__qf_addr_answer on the parent query.  This will let us detect a
+ * conflict between a no-CNAME-with-NODATA reply and a subsequent CNAME.
+ *
+ * If we detect a conflict of any kind, then at least one answer came back
+ * with a CNAME record, so we pick the first such answer (somewhat
+ * arbitrarily) as being the `right' canonical name, and set this in the
+ * parent query's answer->cname slot.  We discard address records from the
+ * wrong name.  And finally we cancel the outstanding child queries, and
+ * resubmit address queries for the address families we don't yet have, with
+ * adns__qf_addr_cname set so that we know that we're in the fixup state.
+ */
+
 static adns_status pap_addr(const parseinfo *pai, int rrty, size_t rrsz,
                            int *cbyte_io, int max, adns_rr_addr *storeto) {
   const byte *dgram= pai->dgram;
   int af, addrlen, salen;
+  struct in6_addr v6map;
   const void *oaddr= dgram + *cbyte_io;
   int avail= max - *cbyte_io;
   int step= -1;
@@ -389,10 +411,26 @@ static adns_status pap_addr(const parseinfo *pai, int rrty, size_t rrsz,
 
   switch (rrty) {
   case adns_r_a:
+    if ((pai->qu->flags & adns_qf_ipv6_mapv4) &&
+       (pai->qu->answer->type & adns__qtf_bigaddr)) {
+      if (avail < 4) return adns_s_invaliddata;
+      memset(v6map.s6_addr +  0, 0x00, 10);
+      memset(v6map.s6_addr + 10, 0xff,  2);
+      memcpy(v6map.s6_addr + 12, oaddr, 4);
+      oaddr= v6map.s6_addr; avail= sizeof(v6map.s6_addr);
+      if (step < 0) step= 4;
+      goto aaaa;
+    }
     af= AF_INET; addrlen= 4;
     addrp= &storeto->addr.inet.sin_addr;
     salen= sizeof(storeto->addr.inet);
     break;
+  case adns_r_aaaa:
+  aaaa:
+    af= AF_INET6; addrlen= 16;
+    addrp= storeto->addr.inet6.sin6_addr.s6_addr;
+    salen= sizeof(storeto->addr.inet6);
+    break;
   }
   assert(addrp);
 
@@ -596,9 +634,55 @@ static void icb_addr(adns_query parent, adns_query child) {
   adns_answer *pans= parent->answer, *cans= child->answer;
   struct timeval now;
   adns_status err;
+  adns_queryflags qf;
+  int id;
 
   propagate_ttl(parent, child);
 
+  if (!(child->flags & adns__qf_addr_cname) &&
+      (parent->flags & adns__qf_addr_answer) &&
+      (!!pans->cname != !!cans->cname ||
+       (pans->cname && strcmp(pans->cname, cans->cname)))) {
+    /* We've detected an inconsistency in CNAME records, and must deploy
+     * countermeasures.
+     */
+
+    if (!pans->cname) {
+      /* The child has a CNAME record, but the parent doesn't.  We must
+       * discard all of the parent's addresses, and substitute the child's.
+       */
+
+      assert(pans->rrsz == cans->rrsz);
+      adns__free_interim(parent, pans->rrs.bytes);
+      adns__transfer_interim(child, parent, cans->rrs.bytes);
+      pans->rrs.bytes= cans->rrs.bytes;
+      pans->nrrs= cans->nrrs;
+      parent->ctx.tinfo.addr.have= 0;
+      done_addr_type(parent, cans->type);
+      err= copy_cname_from_child(parent, child); if (err) goto x_err;
+    }
+
+    /* We've settled on the CNAME (now) associated with the parent, which
+     * already has appropriate address records.  Build a query datagram for
+     * this name so that we can issue child queries for the missing address
+     * families.  The child's vbuf looks handy for this.
+     */
+    err= adns__mkquery(ads, &child->vb, &id, pans->cname,
+                      strlen(pans->cname), &tinfo_addrsub,
+                      adns_r_addr, parent->flags);
+    if (err) goto x_err;
+
+    /* Now cancel the remaining children, and try again with the CNAME we've
+     * settled on.
+     */
+    adns__cancel_children(parent);
+    if (gettimeofday(&now, 0)) goto x_gtod;
+    qf= adns__qf_addr_cname;
+    if (!(parent->flags & adns_qf_cname_loose)) qf |= adns_qf_cname_forbid;
+    addr_subqueries(parent, now, qf, child->vb.buf, child->vb.used);
+    return;
+  }
+
   if (cans->cname && !pans->cname) {
     err= copy_cname_from_child(parent, child);
     if (err) goto x_err;
@@ -631,6 +715,7 @@ static void icb_addr(adns_query parent, adns_query child) {
   if (parent->children.head) LIST_LINK_TAIL(ads->childw, parent);
   else if (!pans->nrrs) adns__query_fail(parent, adns_s_nodata);
   else adns__query_done(parent);
+  parent->flags |= adns__qf_addr_answer;
   return;
 
 x_gtod:
@@ -842,7 +927,7 @@ static adns_status pap_hostaddr(const parseinfo *pai, int *cbyte_io,
   ctx.callback= icb_hostaddr;
   ctx.pinfo.hostaddr= rrp;
   
-  nflags= adns_qf_quoteok_query;
+  nflags= adns_qf_quoteok_query | (pai->qu->flags & adns_qf_ipv6_mapv4);
   if (!(pai->qu->flags & adns_qf_cname_loose)) nflags |= adns_qf_cname_forbid;
   
   st= addr_submit(pai->qu, &nqu, &pai->qu->vb, id, want,