/** * * /brief RR iterator over wireformat DNS packet */ /* * Copyright (c) 2013, NLnet Labs, Verisign, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the names of the copyright holders nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "rr-iter.h" #include "config.h" #include #include "gldns/rrdef.h" int _getdns_dname_equal(const uint8_t *s1, const uint8_t *s2) { uint8_t i; for (;;) { if (*s1 != *s2) return 0; else if (!*s1) return 1; for (i = *s1++, s2++; i > 0; i--, s1++, s2++) if (*s1 != *s2 && tolower((unsigned char)*s1) != tolower((unsigned char)*s2)) return 0; } } static void rr_iter_find_nxt(_getdns_rr_iter *i) { assert(i); assert(i->rr_type); i->nxt = i->pkt && i->n < GLDNS_QDCOUNT(i->pkt) ? i->rr_type + 4 : i->rr_type + 10 > i->pkt_end ? i->pkt_end : i->rr_type + 10 + gldns_read_uint16(i->rr_type + 8) > i->pkt_end ? i->pkt_end : i->rr_type + 10 + gldns_read_uint16(i->rr_type + 8); } static _getdns_rr_iter * find_rrtype(_getdns_rr_iter *i) { const uint8_t *pos; assert(i); assert(i->pos); /* Past the last RR in the pkt */ if (i->pkt && (size_t)GLDNS_QDCOUNT(i->pkt) + GLDNS_ANCOUNT(i->pkt) + GLDNS_NSCOUNT(i->pkt) + GLDNS_ARCOUNT(i->pkt) <= i->n) goto done; for (pos = i->pos; pos + 4 < i->pkt_end; pos += *pos + 1) if (!*pos) { i->rr_type = pos + 1; rr_iter_find_nxt(i); return i; } else if ((*pos & 0xC0) == 0xC0) { if ( pos + 6 > i->pkt_end) break; /* No space for class */ i->rr_type = pos + 2; rr_iter_find_nxt(i); return i; } else if (*pos & 0xC0) break; /* Unknown label type */ done: i->pos = NULL; return NULL; } _getdns_rr_iter * _getdns_rr_iter_init(_getdns_rr_iter *i, const uint8_t *pkt, const size_t pkt_len) { assert(i); if (!pkt || pkt_len < GLDNS_HEADER_SIZE + 5) { i->pos = NULL; return NULL; } i->pkt = pkt; i->pkt_end = pkt + pkt_len; i->n = 0; i->pos = pkt + GLDNS_HEADER_SIZE; return find_rrtype(i); } _getdns_rr_iter * _getdns_single_rr_iter_init( _getdns_rr_iter *i, const uint8_t *wire, const size_t wire_len) { assert(i); if (!wire || wire_len < 5 /* name + type + class */) { i->pos = NULL; return NULL; } i->pkt = NULL; i->pos = wire; i->pkt_end = wire + wire_len; i->n = 0; return find_rrtype(i); } _getdns_rr_iter * _getdns_rr_iter_next(_getdns_rr_iter *i) { assert(i); /* Already done */ if (!i->pos) return NULL; i->n += 1; i->pos = i->nxt; return find_rrtype(i); } static const uint8_t * dname_if_or_as_decompressed(const uint8_t *pkt, const uint8_t *pkt_end, const uint8_t *pos, uint8_t *buf, size_t *len, size_t refs) { uint16_t offset; const uint8_t *start; uint8_t *dst; assert(pkt_end); assert(pos); assert(buf); assert(len); if (refs > GLDNS_MAX_POINTERS) goto error; if ((*pos & 0xC0) == 0xC0) { if (!pkt || pos + 1 >= pkt_end) goto error; offset = gldns_read_uint16(pos) & 0x3FFF; if (pkt + offset >= pkt_end) goto error; return dname_if_or_as_decompressed(pkt, pkt_end, pkt + offset, buf, len, refs + 1); } if (*pos & 0xC0) goto error; start = pos; *len = 0; while (*pos) { if ((*pos & 0xC0) == 0xC0) break; else if (*pos & 0xC0) goto error; *len += *pos + 1; pos += *pos + 1; } if (!*pos) { *len += 1; return start; } dst = buf; for (;;) { if (pos > start) { if (dst + (pos - start) > buf + 255) goto error; (void) memcpy(dst, start, pos - start); dst += (pos - start); start = pos; } if ((*pos & 0xC0) == 0xC0) { if (!pkt || pos + 1 >= pkt_end) goto error; offset = gldns_read_uint16(pos) & 0x3FFF; if (pkt + offset >= pkt_end) goto error; start = pos = pkt + offset; if (++refs > 256) goto error; } if ((*pos & 0xC0) == 0xC0) continue; else if (*pos & 0xC0) goto error; else if (!*pos) { *len += 1; *dst = 0; return buf; } *len += *pos + 1; pos += *pos + 1; } error: *len = 0; return NULL; } const uint8_t * _getdns_owner_if_or_as_decompressed(_getdns_rr_iter *i, uint8_t *ff_bytes, size_t *len) { return dname_if_or_as_decompressed(i->pkt, i->pkt_end, i->pos, ff_bytes, len, 0); } _getdns_rrset * _getdns_initialized_rrset_answer(_getdns_rrset_spc *query_rrset) { _getdns_rrset *rrset = &query_rrset->rrset; uint16_t qtype = rrset->rr_type; size_t cname_loop_protection; assert(query_rrset); /* Follow CNAMEs */ rrset->rr_type = GETDNS_RRTYPE_CNAME; for ( cname_loop_protection = MAX_CNAME_REFERRALS ; cname_loop_protection > 0 ; cname_loop_protection-- ) { _getdns_rrtype_iter rr_spc, *rr; _getdns_rdf_iter rdf_spc, *rdf; if (!(rr = _getdns_rrtype_iter_init(&rr_spc, rrset))) break; if (!(rdf = _getdns_rdf_iter_init(&rdf_spc, &rr->rr_i))) break; query_rrset->name_len = sizeof(query_rrset->name_spc); rrset->name = _getdns_rdf_if_or_as_decompressed( rdf, query_rrset->name_spc, &query_rrset->name_len); } rrset->rr_type = qtype; if (qtype == GETDNS_RRTYPE_CNAME && cname_loop_protection < MAX_CNAME_REFERRALS) return rrset; /* The CNAME was the answer */ return _getdns_rrset_has_rrs(rrset) ? rrset : NULL; } _getdns_rrset * _getdns_rrset_answer(_getdns_rrset_spc *spc, const uint8_t *pkt, size_t len) { _getdns_rr_iter rr_spc, *rr; assert(spc); spc->rrset.name = NULL; spc->name_len = sizeof(spc->name_spc); if ( !(rr = _getdns_rr_iter_init(&rr_spc, pkt, len)) || _getdns_rr_iter_section(rr) != SECTION_QUESTION || !(spc->rrset.name = _getdns_owner_if_or_as_decompressed( rr, spc->name_spc, &spc->name_len)) || rr->nxt < rr->rr_type + 4) return NULL; spc->rrset.rr_class = rr_iter_class(rr); spc->rrset.rr_type = rr_iter_type(rr); spc->rrset.pkt = pkt; spc->rrset.pkt_len = len; spc->rrset.sections = SECTION_ANSWER; return _getdns_initialized_rrset_answer(spc); } /* Utility function to compare owner name of rr with name */ static int rr_owner_equal(_getdns_rr_iter *rr, const uint8_t *name) { uint8_t owner_spc[256]; const uint8_t *owner; size_t owner_len = sizeof(owner_spc); return (owner = _getdns_owner_if_or_as_decompressed(rr, owner_spc ,&owner_len)) && name && _getdns_dname_equal(owner, name); } /* First a few filter functions that filter a RR iterator to point only * to RRs with certain constraints (and moves on otherwise). */ /* Filter that only iterates over the sections */ static inline _getdns_rr_iter *rr_iter_section( _getdns_rr_iter *rr, _getdns_section sections) { while (rr && rr->pos && !(sections & _getdns_rr_iter_section(rr))) rr = _getdns_rr_iter_next(rr); return rr && rr->pos ? rr : NULL; } /* Filter that only iterates over RRs with a certain name/class/type */ static _getdns_rr_iter *rr_iter_name_class_type(_getdns_rr_iter *rr, const uint8_t *name, uint16_t rr_class, uint16_t rr_type, _getdns_section sections) { while (rr_iter_section(rr, sections) && !( rr_iter_type(rr) == rr_type && rr_iter_class(rr) == rr_class && rr_owner_equal(rr, name))) rr = _getdns_rr_iter_next(rr); return rr && rr->pos ? rr : NULL; } /* Filter that only iterates over RRs that do not have a name/class/type */ static _getdns_rr_iter *rr_iter_not_name_class_type(_getdns_rr_iter *rr, const uint8_t *name, uint16_t rr_class, uint16_t rr_type, _getdns_section sections) { while (rr_iter_section(rr, sections) && ( rr_iter_type(rr) == GETDNS_RRTYPE_RRSIG || ( rr_iter_type(rr) == rr_type && rr_iter_class(rr) == rr_class && rr_owner_equal(rr, name)))) rr = _getdns_rr_iter_next(rr); return rr && rr->pos ? rr : NULL; } /* Filter that only iterates over RRs that are of type RRSIG, that cover * a RRset with a certain name/class/type */ static _getdns_rr_iter *rr_iter_rrsig_covering(_getdns_rr_iter *rr, const uint8_t *name, uint16_t rr_class, uint16_t rr_type, _getdns_section sections) { while (rr_iter_section(rr, sections) && !( rr_iter_type(rr) == GETDNS_RRTYPE_RRSIG && rr_iter_class(rr) == rr_class && rr->rr_type + 12 <= rr->nxt && gldns_read_uint16(rr->rr_type + 10) == rr_type && rr_owner_equal(rr, name))) rr = _getdns_rr_iter_next(rr); return rr && rr->pos ? rr : NULL; } _getdns_rrtype_iter * _getdns_rrtype_iter_next(_getdns_rrtype_iter *i) { return (_getdns_rrtype_iter *) rr_iter_name_class_type( _getdns_rr_iter_next(&i->rr_i), i->rrset->name, i->rrset->rr_class, i->rrset->rr_type, i->rrset->sections); } _getdns_rrtype_iter * _getdns_rrtype_iter_init(_getdns_rrtype_iter *i, _getdns_rrset *rrset) { i->rrset = rrset; return (_getdns_rrtype_iter *) rr_iter_name_class_type( _getdns_rr_iter_init(&i->rr_i, rrset->pkt, rrset->pkt_len ), i->rrset->name, i->rrset->rr_class, i->rrset->rr_type, i->rrset->sections); } _getdns_rrsig_iter * _getdns_rrsig_iter_next(_getdns_rrsig_iter *i) { return (_getdns_rrsig_iter *) rr_iter_rrsig_covering( _getdns_rr_iter_next(&i->rr_i), i->rrset->name, i->rrset->rr_class, i->rrset->rr_type, i->rrset->sections); } _getdns_rrsig_iter * _getdns_rrsig_iter_init(_getdns_rrsig_iter *i, _getdns_rrset *rrset) { i->rrset = rrset; return (_getdns_rrsig_iter *) rr_iter_rrsig_covering( _getdns_rr_iter_init(&i->rr_i, rrset->pkt, rrset->pkt_len), i->rrset->name, i->rrset->rr_class, i->rrset->rr_type, i->rrset->sections); } _getdns_rrset_iter * _getdns_rrset_iter_init(_getdns_rrset_iter *i, const uint8_t *pkt, size_t pkt_len, _getdns_section sections) { _getdns_rr_iter *rr; i->rrset.name = i->name_spc; i->rrset.pkt = pkt; i->rrset.pkt_len = pkt_len; i->rrset.sections = sections; i->name_len = 0; for ( rr = _getdns_rr_iter_init(&i->rr_i, pkt, pkt_len) ;(rr = rr_iter_section(rr, sections)) ; rr = _getdns_rr_iter_next(rr)) { if ((i->rrset.rr_type = rr_iter_type(rr)) == GETDNS_RRTYPE_RRSIG) continue; i->rrset.rr_class = rr_iter_class(rr); if (!(i->rrset.name = _getdns_owner_if_or_as_decompressed( rr, i->name_spc, &i->name_len))) continue; return i; } return NULL; } _getdns_rrset_iter *_getdns_rrset_iter_next(_getdns_rrset_iter *i) { _getdns_rr_iter *rr; if (!(rr = i && i->rr_i.pos ? &i->rr_i : NULL)) return NULL; if (!(rr = rr_iter_not_name_class_type(rr, i->rrset.name, i->rrset.rr_class, i->rrset.rr_type, i->rrset.sections))) return NULL; i->rrset.rr_type = rr_iter_type(rr); i->rrset.rr_class = rr_iter_class(rr); if (!(i->rrset.name = _getdns_owner_if_or_as_decompressed( rr, i->name_spc, &i->name_len))) /* This is safe, because rr_iter_not_name_class_type will shift * the iterator forward because at least name does not match. * Goal is to skip broken compression pointer issues but keep * processing the packet. */ return _getdns_rrset_iter_next(i); return i; } static _getdns_rdf_iter * rdf_iter_find_nxt(_getdns_rdf_iter *i) { const uint8_t *pos; assert(i); assert(i->pos); assert(i->rdd_pos); if (!i->rdd_repeat && (i->rdd_pos->type & GETDNS_RDF_REPEAT)) { i->rdd_repeat = i->rdd_pos; if (i->rdd_pos->type == GETDNS_RDF_REPEAT && ++i->rdd_pos == i->rdd_end) goto done; } if (i->rdd_pos->type & GETDNS_RDF_FIXEDSZ) i->nxt = i->pos + (i->rdd_pos->type & GETDNS_RDF_FIXEDSZ); else if ((i->rdd_pos->type & GETDNS_RDF_LEN_VAL) == 0x100) i->nxt = i->pos < i->end ? i->pos + *i->pos + 1 : i->end; else if ((i->rdd_pos->type & GETDNS_RDF_LEN_VAL) == 0x200) i->nxt = i->pos + 1 < i->end ? i->pos + gldns_read_uint16(i->pos) + 2 : i->end; else if (i->rdd_pos->type & GETDNS_RDF_DNAME) for (pos = i->pos; pos < i->end; pos += *pos + 1) { if (!*pos) { i->nxt = pos + 1; break; } else if ((*pos & 0xC0) == 0xC0) { i->nxt = pos + 2; break; } else if (*pos & 0xC0) /* Uknown label type */ goto done; } else if ((i->rdd_pos->type & GETDNS_RDF_SPECIAL) && i->rdd_pos->special) { if (!(i->nxt = i->rdd_pos->special->rdf_end( i->pkt, i->pkt_end, i->pos))) i->nxt = i->end; } else /* RDF is for remaining data */ i->nxt = i->end; if ( i->nxt <= i->end && /* Empty rdata fields are only allowed in case of non-repeating * remaining data. So only the GETDNS_RDF_BINDATA bit is set. */ (i->nxt > i->pos || (i->rdd_pos->type == GETDNS_RDF_BINDATA))) return i; done: i->pos = NULL; return NULL; } _getdns_rdf_iter * _getdns_rdf_iter_init(_getdns_rdf_iter *i, _getdns_rr_iter *rr) { const _getdns_rr_def *rr_def; assert(i); assert(rr); i->end = NULL; /* rr_iter already done or in question section */ if (!rr->pos || _getdns_rr_iter_section(rr) == SECTION_QUESTION) goto done; i->pkt = rr->pkt; i->pkt_end = rr->pkt_end; rr_def = _getdns_rr_def_lookup(gldns_read_uint16(rr->rr_type)); i->rdd_pos = rr_def->rdata; i->rdd_end = rr_def->rdata + rr_def->n_rdata_fields; /* No space to read rdata len */ if (rr->rr_type + 10 >= rr->nxt) goto done; i->rdd_repeat = NULL; i->pos = rr->rr_type + 10; i->end = rr->nxt; /* rdata */ if (i->rdd_pos != i->rdd_end) return rdf_iter_find_nxt(i); done: i->pos = NULL; return NULL; } _getdns_rdf_iter * _getdns_rdf_iter_next(_getdns_rdf_iter *i) { if (!i->pos) return NULL; i->rdd_pos += 1; if ((i->pos = i->nxt) > i->end) goto done; /* Overflow */ if (i->rdd_pos >= i->rdd_end && !(i->rdd_pos = i->rdd_repeat)) goto done; /* Remaining rdata, but out of definitions! */ if (i->rdd_pos->type == GETDNS_RDF_REPEAT) i->rdd_pos += 1; return rdf_iter_find_nxt(i); done: i->pos = NULL; return NULL; } _getdns_rdf_iter * _getdns_rdf_iter_init_at( _getdns_rdf_iter *i, _getdns_rr_iter *rr, size_t pos) { for ( i = _getdns_rdf_iter_init(i, rr) ; i && pos ; i = _getdns_rdf_iter_next(i), pos--); return i; } const uint8_t * _getdns_rdf_if_or_as_decompressed( _getdns_rdf_iter *i, uint8_t *ff_bytes, size_t *len) { return dname_if_or_as_decompressed(i->pkt, i->pkt_end, i->pos, ff_bytes, len, 0); }