/* * wire2host.c * * conversion routines from the wire to the host * format. * This will usually just a re-ordering of the * data (as we store it in network format) * * a Net::DNS like library for C * * (c) NLnet Labs, 2004-2006 * * See the file LICENSE for the license */ #include #include /*#include */ #include #include /* * Set of macro's to deal with the dns message header as specified * in RFC1035 in portable way. * */ /* * * 1 1 1 1 1 1 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | ID | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * |QR| Opcode |AA|TC|RD|RA| Z|AD|CD| RCODE | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | QDCOUNT | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | ANCOUNT | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | NSCOUNT | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * | ARCOUNT | * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ * */ /* allocates memory to *dname! */ ldns_status ldns_wire2dname(ldns_rdf **dname, const uint8_t *wire, size_t max, size_t *pos) { uint8_t label_size; uint16_t pointer_target; uint8_t pointer_target_buf[2]; size_t dname_pos = 0; size_t uncompressed_length = 0; size_t compression_pos = 0; uint8_t tmp_dname[LDNS_MAX_DOMAINLEN]; unsigned int pointer_count = 0; if (*pos >= max) { return LDNS_STATUS_PACKET_OVERFLOW; } label_size = wire[*pos]; while (label_size > 0) { /* compression */ while (label_size >= 192) { if (compression_pos == 0) { compression_pos = *pos + 2; } pointer_count++; /* remove first two bits */ if (*pos + 2 > max) { return LDNS_STATUS_PACKET_OVERFLOW; } pointer_target_buf[0] = wire[*pos] & 63; pointer_target_buf[1] = wire[*pos + 1]; pointer_target = ldns_read_uint16(pointer_target_buf); if (pointer_target == 0) { return LDNS_STATUS_INVALID_POINTER; } else if (pointer_target > max) { return LDNS_STATUS_INVALID_POINTER; } else if (pointer_count > LDNS_MAX_POINTERS) { return LDNS_STATUS_INVALID_POINTER; } *pos = pointer_target; label_size = wire[*pos]; } if(label_size == 0) break; /* break from pointer to 0 byte */ if (label_size > LDNS_MAX_LABELLEN) { return LDNS_STATUS_LABEL_OVERFLOW; } if (*pos + label_size > max) { return LDNS_STATUS_LABEL_OVERFLOW; } /* check space for labelcount itself */ if (dname_pos + 1 > LDNS_MAX_DOMAINLEN) { return LDNS_STATUS_DOMAINNAME_OVERFLOW; } tmp_dname[dname_pos] = label_size; if (label_size > 0) { dname_pos++; } *pos = *pos + 1; if (dname_pos + label_size > LDNS_MAX_DOMAINLEN) { return LDNS_STATUS_DOMAINNAME_OVERFLOW; } memcpy(&tmp_dname[dname_pos], &wire[*pos], label_size); uncompressed_length += label_size + 1; dname_pos += label_size; *pos = *pos + label_size; if (*pos < max) { label_size = wire[*pos]; } } if (compression_pos > 0) { *pos = compression_pos; } else { *pos = *pos + 1; } if (dname_pos >= LDNS_MAX_DOMAINLEN) { return LDNS_STATUS_DOMAINNAME_OVERFLOW; } tmp_dname[dname_pos] = 0; dname_pos++; *dname = ldns_rdf_new_frm_data(LDNS_RDF_TYPE_DNAME, (uint16_t) dname_pos, tmp_dname); if (!*dname) { return LDNS_STATUS_MEM_ERR; } return LDNS_STATUS_OK; } /* maybe make this a goto error so data can be freed or something/ */ #define LDNS_STATUS_CHECK_RETURN(st) {if (st != LDNS_STATUS_OK) { return st; }} #define LDNS_STATUS_CHECK_GOTO(st, label) {if (st != LDNS_STATUS_OK) { /*printf("STG %s:%d: status code %d\n", __FILE__, __LINE__, st);*/ goto label; }} ldns_status ldns_wire2rdf(ldns_rr *rr, const uint8_t *wire, size_t max, size_t *pos) { size_t end; size_t cur_rdf_length; uint8_t rdf_index; uint8_t *data; uint16_t rd_length; ldns_rdf *cur_rdf = NULL; ldns_rdf_type cur_rdf_type; const ldns_rr_descriptor *descriptor = ldns_rr_descript(ldns_rr_get_type(rr)); ldns_status status; if (*pos + 2 > max) { return LDNS_STATUS_PACKET_OVERFLOW; } rd_length = ldns_read_uint16(&wire[*pos]); *pos = *pos + 2; if (*pos + rd_length > max) { return LDNS_STATUS_PACKET_OVERFLOW; } end = *pos + (size_t) rd_length; for (rdf_index = 0; rdf_index < ldns_rr_descriptor_maximum(descriptor); rdf_index++) { if (*pos >= end) { break; } cur_rdf_length = 0; cur_rdf_type = ldns_rr_descriptor_field_type(descriptor, rdf_index); /* handle special cases immediately, set length for fixed length rdata and do them below */ switch (cur_rdf_type) { case LDNS_RDF_TYPE_DNAME: status = ldns_wire2dname(&cur_rdf, wire, max, pos); LDNS_STATUS_CHECK_RETURN(status); break; case LDNS_RDF_TYPE_CLASS: case LDNS_RDF_TYPE_ALG: case LDNS_RDF_TYPE_INT8: cur_rdf_length = LDNS_RDF_SIZE_BYTE; break; case LDNS_RDF_TYPE_TYPE: case LDNS_RDF_TYPE_INT16: case LDNS_RDF_TYPE_CERT_ALG: cur_rdf_length = LDNS_RDF_SIZE_WORD; break; case LDNS_RDF_TYPE_TIME: case LDNS_RDF_TYPE_INT32: case LDNS_RDF_TYPE_A: case LDNS_RDF_TYPE_PERIOD: cur_rdf_length = LDNS_RDF_SIZE_DOUBLEWORD; break; case LDNS_RDF_TYPE_TSIGTIME: cur_rdf_length = LDNS_RDF_SIZE_6BYTES; break; case LDNS_RDF_TYPE_AAAA: cur_rdf_length = LDNS_RDF_SIZE_16BYTES; break; case LDNS_RDF_TYPE_STR: case LDNS_RDF_TYPE_NSEC3_SALT: /* len is stored in first byte * it should be in the rdf too, so just * copy len+1 from this position */ cur_rdf_length = ((size_t) wire[*pos]) + 1; break; case LDNS_RDF_TYPE_INT16_DATA: cur_rdf_length = (size_t) ldns_read_uint16(&wire[*pos]) + 2; break; case LDNS_RDF_TYPE_B32_EXT: case LDNS_RDF_TYPE_NSEC3_NEXT_OWNER: /* length is stored in first byte */ cur_rdf_length = ((size_t) wire[*pos]) + 1; break; case LDNS_RDF_TYPE_APL: case LDNS_RDF_TYPE_B64: case LDNS_RDF_TYPE_HEX: case LDNS_RDF_TYPE_NSEC: case LDNS_RDF_TYPE_UNKNOWN: case LDNS_RDF_TYPE_SERVICE: case LDNS_RDF_TYPE_LOC: case LDNS_RDF_TYPE_WKS: case LDNS_RDF_TYPE_NSAP: case LDNS_RDF_TYPE_ATMA: case LDNS_RDF_TYPE_IPSECKEY: case LDNS_RDF_TYPE_TSIG: case LDNS_RDF_TYPE_NONE: /* * Read to end of rr rdata */ cur_rdf_length = end - *pos; break; } /* fixed length rdata */ if (cur_rdf_length > 0) { if (cur_rdf_length + *pos > end) { return LDNS_STATUS_PACKET_OVERFLOW; } data = LDNS_XMALLOC(uint8_t, rd_length); if (!data) { return LDNS_STATUS_MEM_ERR; } memcpy(data, &wire[*pos], cur_rdf_length); cur_rdf = ldns_rdf_new(cur_rdf_type, cur_rdf_length, data); *pos = *pos + cur_rdf_length; } if (cur_rdf) { ldns_rr_push_rdf(rr, cur_rdf); cur_rdf = NULL; } } return LDNS_STATUS_OK; } /* TODO: can *pos be incremented at READ_INT? or maybe use something like RR_CLASS(wire)? uhhm Jelte?? */ ldns_status ldns_wire2rr(ldns_rr **rr_p, const uint8_t *wire, size_t max, size_t *pos, ldns_pkt_section section) { ldns_rdf *owner = NULL; ldns_rr *rr = ldns_rr_new(); ldns_status status; status = ldns_wire2dname(&owner, wire, max, pos); LDNS_STATUS_CHECK_GOTO(status, status_error); ldns_rr_set_owner(rr, owner); if (*pos + 4 > max) { status = LDNS_STATUS_PACKET_OVERFLOW; goto status_error; } ldns_rr_set_type(rr, ldns_read_uint16(&wire[*pos])); *pos = *pos + 2; ldns_rr_set_class(rr, ldns_read_uint16(&wire[*pos])); *pos = *pos + 2; if (section != LDNS_SECTION_QUESTION) { if (*pos + 4 > max) { status = LDNS_STATUS_PACKET_OVERFLOW; goto status_error; } ldns_rr_set_ttl(rr, ldns_read_uint32(&wire[*pos])); *pos = *pos + 4; status = ldns_wire2rdf(rr, wire, max, pos); LDNS_STATUS_CHECK_GOTO(status, status_error); ldns_rr_set_question(rr, false); } else { ldns_rr_set_question(rr, true); } *rr_p = rr; return LDNS_STATUS_OK; status_error: ldns_rr_free(rr); return status; } static ldns_status ldns_wire2pkt_hdr(ldns_pkt *packet, const uint8_t *wire, size_t max, size_t *pos) { if (*pos + LDNS_HEADER_SIZE > max) { return LDNS_STATUS_WIRE_INCOMPLETE_HEADER; } else { ldns_pkt_set_id(packet, LDNS_ID_WIRE(wire)); ldns_pkt_set_qr(packet, LDNS_QR_WIRE(wire)); ldns_pkt_set_opcode(packet, LDNS_OPCODE_WIRE(wire)); ldns_pkt_set_aa(packet, LDNS_AA_WIRE(wire)); ldns_pkt_set_tc(packet, LDNS_TC_WIRE(wire)); ldns_pkt_set_rd(packet, LDNS_RD_WIRE(wire)); ldns_pkt_set_ra(packet, LDNS_RA_WIRE(wire)); ldns_pkt_set_ad(packet, LDNS_AD_WIRE(wire)); ldns_pkt_set_cd(packet, LDNS_CD_WIRE(wire)); ldns_pkt_set_rcode(packet, LDNS_RCODE_WIRE(wire)); ldns_pkt_set_qdcount(packet, LDNS_QDCOUNT(wire)); ldns_pkt_set_ancount(packet, LDNS_ANCOUNT(wire)); ldns_pkt_set_nscount(packet, LDNS_NSCOUNT(wire)); ldns_pkt_set_arcount(packet, LDNS_ARCOUNT(wire)); *pos += LDNS_HEADER_SIZE; return LDNS_STATUS_OK; } } ldns_status ldns_buffer2pkt_wire(ldns_pkt **packet, ldns_buffer *buffer) { /* lazy */ return ldns_wire2pkt(packet, ldns_buffer_begin(buffer), ldns_buffer_limit(buffer)); } ldns_status ldns_wire2pkt(ldns_pkt **packet_p, const uint8_t *wire, size_t max) { size_t pos = 0; uint16_t i; ldns_rr *rr; ldns_pkt *packet = ldns_pkt_new(); ldns_status status = LDNS_STATUS_OK; int have_edns = 0; uint8_t data[4]; status = ldns_wire2pkt_hdr(packet, wire, max, &pos); LDNS_STATUS_CHECK_GOTO(status, status_error); for (i = 0; i < ldns_pkt_qdcount(packet); i++) { status = ldns_wire2rr(&rr, wire, max, &pos, LDNS_SECTION_QUESTION); if (status == LDNS_STATUS_PACKET_OVERFLOW) { status = LDNS_STATUS_WIRE_INCOMPLETE_QUESTION; } LDNS_STATUS_CHECK_GOTO(status, status_error); if (!ldns_rr_list_push_rr(ldns_pkt_question(packet), rr)) { ldns_pkt_free(packet); return LDNS_STATUS_INTERNAL_ERR; } } for (i = 0; i < ldns_pkt_ancount(packet); i++) { status = ldns_wire2rr(&rr, wire, max, &pos, LDNS_SECTION_ANSWER); if (status == LDNS_STATUS_PACKET_OVERFLOW) { status = LDNS_STATUS_WIRE_INCOMPLETE_ANSWER; } LDNS_STATUS_CHECK_GOTO(status, status_error); if (!ldns_rr_list_push_rr(ldns_pkt_answer(packet), rr)) { ldns_pkt_free(packet); return LDNS_STATUS_INTERNAL_ERR; } } for (i = 0; i < ldns_pkt_nscount(packet); i++) { status = ldns_wire2rr(&rr, wire, max, &pos, LDNS_SECTION_AUTHORITY); if (status == LDNS_STATUS_PACKET_OVERFLOW) { status = LDNS_STATUS_WIRE_INCOMPLETE_AUTHORITY; } LDNS_STATUS_CHECK_GOTO(status, status_error); if (!ldns_rr_list_push_rr(ldns_pkt_authority(packet), rr)) { ldns_pkt_free(packet); return LDNS_STATUS_INTERNAL_ERR; } } for (i = 0; i < ldns_pkt_arcount(packet); i++) { status = ldns_wire2rr(&rr, wire, max, &pos, LDNS_SECTION_ADDITIONAL); if (status == LDNS_STATUS_PACKET_OVERFLOW) { status = LDNS_STATUS_WIRE_INCOMPLETE_ADDITIONAL; } LDNS_STATUS_CHECK_GOTO(status, status_error); if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_OPT) { ldns_pkt_set_edns_udp_size(packet, ldns_rr_get_class(rr)); ldns_write_uint32(data, ldns_rr_ttl(rr)); ldns_pkt_set_edns_extended_rcode(packet, data[0]); ldns_pkt_set_edns_version(packet, data[1]); ldns_pkt_set_edns_z(packet, ldns_read_uint16(&data[2])); /* edns might not have rdfs */ if (ldns_rr_rdf(rr, 0)) { ldns_pkt_set_edns_data(packet, ldns_rdf_clone(ldns_rr_rdf(rr, 0))); } ldns_rr_free(rr); have_edns = 1; } else if (ldns_rr_get_type(rr) == LDNS_RR_TYPE_TSIG) { ldns_pkt_set_tsig(packet, rr); ldns_pkt_set_arcount(packet, ldns_pkt_arcount(packet) - 1); } else if (!ldns_rr_list_push_rr(ldns_pkt_additional(packet), rr)) { ldns_pkt_free(packet); return LDNS_STATUS_INTERNAL_ERR; } } ldns_pkt_set_size(packet, max); if(have_edns) ldns_pkt_set_arcount(packet, ldns_pkt_arcount(packet) - 1); *packet_p = packet; return status; status_error: ldns_pkt_free(packet); return status; }