/* * Copyright (C) 2006 Justin Karneges * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "jdns_packet.h" #include "jdns_p.h" // jer's endian functions static unsigned short int net2short(const unsigned char **bufp) { unsigned short int i; i = **bufp; i <<= 8; i |= *(*bufp + 1); *bufp += 2; return i; } static unsigned long int net2long(const unsigned char **bufp) { unsigned long int l; l = **bufp; l <<= 8; l |= *(*bufp + 1); l <<= 8; l |= *(*bufp + 2); l <<= 8; l |= *(*bufp + 3); *bufp += 4; return l; } static void short2net(unsigned short int i, unsigned char **bufp) { *(*bufp + 1) = (unsigned char)i; i >>= 8; **bufp = (unsigned char)i; *bufp += 2; } static void long2net(unsigned long int l, unsigned char **bufp) { *(*bufp + 3) = (unsigned char)l; l >>= 8; *(*bufp + 2) = (unsigned char)l; l >>= 8; *(*bufp + 1) = (unsigned char)l; l >>= 8; **bufp = (unsigned char)l; *bufp += 4; } // label stuff typedef struct jdns_packet_label { JDNS_OBJECT int offset; jdns_string_t *value; } jdns_packet_label_t; static void jdns_packet_label_delete(jdns_packet_label_t *a); static jdns_packet_label_t *jdns_packet_label_copy(const jdns_packet_label_t *a); static jdns_packet_label_t *jdns_packet_label_new() { jdns_packet_label_t *a = JDNS_OBJECT_NEW(jdns_packet_label); a->offset = 0; a->value = 0; return a; } jdns_packet_label_t *jdns_packet_label_copy(const jdns_packet_label_t *a) { jdns_packet_label_t *c = jdns_packet_label_new(); c->offset = a->offset; if(a->value) c->value = jdns_string_copy(a->value); return c; } void jdns_packet_label_delete(jdns_packet_label_t *a) { if(!a) return; jdns_string_delete(a->value); jdns_object_free(a); } // gets an offset for decompression. does range and hop count checking also static int getoffset(const unsigned char *str, int refsize, int *hopsleft) { unsigned short int x; if(*hopsleft <= 0) return -1; --(*hopsleft); x = str[0] & 0x3f; x <<= 8; x |= str[1]; // stay in bounds if(x >= refsize) return -1; return x; } static int readlabel(const unsigned char *in, int insize, const unsigned char *ref, int refsize, int *_at, jdns_string_t **name) { int at; unsigned char out[255]; int out_size; const unsigned char *label, *last; int hopped_yet; int hopsleft; int label_size; at = *_at; // stay in range if(at < 0 || at >= insize) return 0; out_size = 0; label = in + at; hopped_yet = 0; last = in + insize; while(1) { // need a byte if(label + 1 > last) goto error; // we make this a while loop instead of an 'if', in case // there's a pointer to a pointer. as a precaution, // we will hop no more than 8 times hopsleft = 8; while(*label & 0xc0) { int offset; // need the next byte, too if(label + 2 > last) goto error; offset = getoffset(label, refsize, &hopsleft); if(offset == -1) goto error; label = ref + offset; if(!hopped_yet) { at += 2; hopped_yet = 1; last = ref + refsize; } // need a byte if(label + 1 > last) goto error; } label_size = *label & 0x3f; // null label? then we're done if(label_size == 0) { if(!hopped_yet) ++at; break; } // enough source bytes? (length byte + length) if(label + label_size + 1 > last) goto error; // enough dest bytes? (length + dot) if(out_size + label_size + 1 > 255) goto error; memcpy(out + out_size, label + 1, label_size); out_size += label_size; out[out_size] = '.'; ++out_size; if(!hopped_yet) at += label_size + 1; label += label_size + 1; } *_at = at; *name = jdns_string_new(); jdns_string_set(*name, out, out_size); return 1; error: return 0; } // this function compares labels in label format: // [length] [value ...] [length] [value ...] [0] static int matchlabel(const unsigned char *a, int asize, const unsigned char *b, int bsize, const unsigned char *ref, int refsize, int ahopsleft, int bhopsleft) { int n, alen, blen, offset; // same pointer? if(a == b) return 1; if(asize < 1 || bsize < 1) return 0; // always ensure we get called without a pointer if(*a & 0xc0) { if(asize < 2) return 0; offset = getoffset(a, refsize, &ahopsleft); if(offset == -1) return 0; return matchlabel(ref + offset, refsize - offset, b, bsize, ref, refsize, ahopsleft, bhopsleft); } if(*b & 0xc0) { if(bsize < 2) return 0; offset = getoffset(b, refsize, &bhopsleft); if(offset == -1) return 0; return matchlabel(a, asize, ref + offset, refsize - offset, ref, refsize, ahopsleft, bhopsleft); } alen = *a & 0x3f; blen = *b & 0x3f; // must be same length if(alen != blen) return 0; // done? if(alen == 0) return 1; // length byte + length + first byte of next label if(asize < alen + 2) return 0; if(bsize < blen + 2) return 0; // compare the value for(n = 1; n < alen + 1; ++n) { if(a[n] != b[n]) return 0; } // try next labels n = alen + 1; return matchlabel(a + n, asize - n, b + n, bsize - n, ref, refsize, ahopsleft, bhopsleft); } int jdns_packet_name_isvalid(const unsigned char *name, int size) { int n, at, len; // at least one byte, no larger than 254 (one byte is gained when // converting to a label, which has a 255 byte max) if(size < 1 || size > 254) return 0; // last byte must be a dot if(name[size - 1] != '.') return 0; // first byte can't be a dot if there are characters after if(size > 1 && name[0] == '.') return 0; // each sublabel must be between 1 and 63 in length at = 0; while(1) { // search for dot or end for(n = at; n < size; ++n) { if(name[n] == '.') break; } // length of last one is always zero if(n >= size) break; len = n - at; if(len < 1 || len > 63) return 0; at = n + 1; // skip over the dot } return 1; } // this function assumes label is pointing to a 255 byte buffer static int name_to_label(const jdns_string_t *name, unsigned char *label) { int n, i, at, len; if(!jdns_packet_name_isvalid(name->data, name->size)) return -1; if(name->size == 1) { label[0] = 0; return 1; } at = 0; i = 0; while(1) { // search for dot or end for(n = at; n < name->size; ++n) { if(name->data[n] == '.') break; } len = n - at; if(i + (len + 1) > 255) // length byte + length return 0; label[i++] = len; memcpy(label + i, name->data + at, len); i += len; if(n >= name->size) // end? break; at = n + 1; // skip over the dot } return i; } // lookup list is made of jdns_packet_labels static int writelabel(const jdns_string_t *name, int at, int left, unsigned char **bufp, jdns_list_t *lookup) { unsigned char label[255]; int n, i, len; unsigned char *l; unsigned char *ref; int refsize; len = name_to_label(name, label); if(len == -1) return 0; ref = *bufp - at; refsize = at + left; for(n = 0; label[n]; n += label[n] + 1) { for(i = 0; i < lookup->count; ++i) { jdns_packet_label_t *pl = (jdns_packet_label_t *)lookup->item[i]; if(matchlabel(label + n, len - n, pl->value->data, pl->value->size, ref, refsize, 8, 8)) { // set up a pointer right here, overwriting // the length byte and the first content // byte of this section within 'label'. // this is safe, because the length value // will always be greater than zero, // ensuring we have two bytes available to // use. l = label + n; short2net((unsigned short int)pl->offset, &l); label[n] |= 0xc0; len = n + 2; // cut things short break; } } if(label[n] & 0xc0) // double loop, so break again break; } if(left < len) return 0; // copy into buffer, point there now memcpy(*bufp, label, len); l = *bufp; *bufp += len; // for each new label, store its location for future compression for(n = 0; l[n]; n += l[n] + 1) { jdns_string_t *str; jdns_packet_label_t *pl; if(l[n] & 0xc0) break; pl = jdns_packet_label_new(); str = jdns_string_new(); jdns_string_set(str, l + n, len - n); pl->offset = l + n - ref; pl->value = str; jdns_list_insert(lookup, pl, -1); } return 1; } //---------------------------------------------------------------------------- // jdns_packet_write //---------------------------------------------------------------------------- #define JDNS_PACKET_WRITE_RAW 0 #define JDNS_PACKET_WRITE_NAME 1 struct jdns_packet_write { JDNS_OBJECT int type; jdns_string_t *value; }; void jdns_packet_write_delete(jdns_packet_write_t *a); jdns_packet_write_t *jdns_packet_write_copy(const jdns_packet_write_t *a); jdns_packet_write_t *jdns_packet_write_new() { jdns_packet_write_t *a = JDNS_OBJECT_NEW(jdns_packet_write); a->type = 0; a->value = 0; return a; } jdns_packet_write_t *jdns_packet_write_copy(const jdns_packet_write_t *a) { jdns_packet_write_t *c = jdns_packet_write_new(); c->type = a->type; if(a->value) c->value = jdns_string_copy(a->value); return c; } void jdns_packet_write_delete(jdns_packet_write_t *a) { if(!a) return; jdns_string_delete(a->value); jdns_object_free(a); } //---------------------------------------------------------------------------- // jdns_packet_question //---------------------------------------------------------------------------- jdns_packet_question_t *jdns_packet_question_new() { jdns_packet_question_t *a = JDNS_OBJECT_NEW(jdns_packet_question); a->qname = 0; a->qtype = 0; a->qclass = 0; return a; } jdns_packet_question_t *jdns_packet_question_copy(const jdns_packet_question_t *a) { jdns_packet_question_t *c = jdns_packet_question_new(); if(a->qname) c->qname = jdns_string_copy(a->qname); c->qtype = a->qtype; c->qclass = a->qclass; return c; } void jdns_packet_question_delete(jdns_packet_question_t *a) { if(!a) return; jdns_string_delete(a->qname); jdns_object_free(a); } //---------------------------------------------------------------------------- // jdns_packet_resource //---------------------------------------------------------------------------- jdns_packet_resource_t *jdns_packet_resource_new() { jdns_packet_resource_t *a = JDNS_OBJECT_NEW(jdns_packet_resource); a->qname = 0; a->qtype = 0; a->qclass = 0; a->ttl = 0; a->rdlength = 0; a->rdata = 0; a->writelog = jdns_list_new(); a->writelog->valueList = 1; return a; } jdns_packet_resource_t *jdns_packet_resource_copy(const jdns_packet_resource_t *a) { jdns_packet_resource_t *c = jdns_packet_resource_new(); if(a->qname) c->qname = jdns_string_copy(a->qname); c->qtype = a->qtype; c->qclass = a->qclass; c->ttl = a->ttl; c->rdlength = a->rdlength; c->rdata = jdns_copy_array(a->rdata, a->rdlength); jdns_list_delete(c->writelog); c->writelog = jdns_list_copy(a->writelog); return c; } void jdns_packet_resource_delete(jdns_packet_resource_t *a) { if(!a) return; jdns_string_delete(a->qname); if(a->rdata) jdns_free(a->rdata); jdns_list_delete(a->writelog); jdns_object_free(a); } void jdns_packet_resource_add_bytes(jdns_packet_resource_t *a, const unsigned char *data, int size) { jdns_packet_write_t *write = jdns_packet_write_new(); write->type = JDNS_PACKET_WRITE_RAW; write->value = jdns_string_new(); jdns_string_set(write->value, data, size); jdns_list_insert_value(a->writelog, write, -1); jdns_packet_write_delete(write); } void jdns_packet_resource_add_name(jdns_packet_resource_t *a, const jdns_string_t *name) { jdns_packet_write_t *write = jdns_packet_write_new(); write->type = JDNS_PACKET_WRITE_NAME; write->value = jdns_string_copy(name); jdns_list_insert_value(a->writelog, write, -1); jdns_packet_write_delete(write); } int jdns_packet_resource_read_name(const jdns_packet_resource_t *a, const jdns_packet_t *p, int *at, jdns_string_t **name) { return readlabel(a->rdata, a->rdlength, p->raw_data, p->raw_size, at, name); } //---------------------------------------------------------------------------- // jdns_packet //---------------------------------------------------------------------------- // note: both process_qsection and process_rrsection modify the 'dest' list, // even if later items cause an error. this turns out to be convenient // for handling truncated dns packets static int process_qsection(jdns_list_t *dest, int count, const unsigned char *data, int size, const unsigned char **bufp) { int n; int offset, at; jdns_string_t *name = 0; const unsigned char *buf; buf = *bufp; for(n = 0; n < count; ++n) { jdns_packet_question_t *q; offset = buf - data; at = 0; if(!readlabel(data + offset, size - offset, data, size, &at, &name)) goto error; offset += at; // need 4 more bytes if(size - offset < 4) goto error; buf = data + offset; q = jdns_packet_question_new(); q->qname = name; name = 0; q->qtype = net2short(&buf); q->qclass = net2short(&buf); jdns_list_insert_value(dest, q, -1); jdns_packet_question_delete(q); } *bufp = buf; return 1; error: jdns_string_delete(name); return 0; } static int process_rrsection(jdns_list_t *dest, int count, const unsigned char *data, int size, const unsigned char **bufp) { int n; int offset, at; jdns_string_t *name = 0; const unsigned char *buf; buf = *bufp; for(n = 0; n < count; ++n) { jdns_packet_resource_t *r; offset = buf - data; at = 0; if(!readlabel(data + offset, size - offset, data, size, &at, &name)) goto error; offset += at; // need 10 more bytes if(offset + 10 > size) goto error; buf = data + offset; r = jdns_packet_resource_new(); r->qname = name; name = 0; r->qtype = net2short(&buf); r->qclass = net2short(&buf); r->ttl = net2long(&buf); r->rdlength = net2short(&buf); offset = buf - data; // make sure we have enough for the rdata if(size - offset < r->rdlength) { jdns_packet_resource_delete(r); goto error; } r->rdata = jdns_copy_array(buf, r->rdlength); buf += r->rdlength; jdns_list_insert_value(dest, r, -1); jdns_packet_resource_delete(r); } *bufp = buf; return 1; error: jdns_string_delete(name); return 0; } static int append_qsection(const jdns_list_t *src, int at, int left, unsigned char **bufp, jdns_list_t *lookup) { unsigned char *buf, *start, *last; int n; buf = *bufp; start = buf - at; last = buf + left; for(n = 0; n < src->count; ++n) { jdns_packet_question_t *q = (jdns_packet_question_t *)src->item[n]; if(!writelabel(q->qname, buf - start, last - buf, &buf, lookup)) goto error; if(buf + 4 > last) goto error; short2net(q->qtype, &buf); short2net(q->qclass, &buf); } *bufp = buf; return 1; error: return 0; } static int append_rrsection(const jdns_list_t *src, int at, int left, unsigned char **bufp, jdns_list_t *lookup) { unsigned char *buf, *start, *last, *rdlengthp; int n, i, rdlength; buf = *bufp; start = buf - at; last = buf + left; for(n = 0; n < src->count; ++n) { jdns_packet_resource_t *r = (jdns_packet_resource_t *)src->item[n]; if(!writelabel(r->qname, buf - start, last - buf, &buf, lookup)) goto error; if(buf + 10 > last) goto error; short2net(r->qtype, &buf); short2net(r->qclass, &buf); long2net(r->ttl, &buf); // skip over rdlength rdlengthp = buf; buf += 2; // play write log rdlength = 0; for(i = 0; i < r->writelog->count; ++i) { jdns_packet_write_t *write = (jdns_packet_write_t *)r->writelog->item[i]; if(write->type == JDNS_PACKET_WRITE_RAW) { if(buf + write->value->size > last) goto error; memcpy(buf, write->value->data, write->value->size); buf += write->value->size; } else // JDNS_PACKET_WRITE_NAME { if(!writelabel(write->value, buf - start, last - buf, &buf, lookup)) goto error; } } i = buf - rdlengthp; // should be rdata size + 2 short2net((unsigned short int)(i - 2), &rdlengthp); } *bufp = buf; return 1; error: return 0; } jdns_packet_t *jdns_packet_new() { jdns_packet_t *a = JDNS_OBJECT_NEW(jdns_packet); a->id = 0; a->opts.qr = 0; a->opts.opcode = 0; a->opts.aa = 0; a->opts.tc = 0; a->opts.rd = 0; a->opts.ra = 0; a->opts.z = 0; a->opts.rcode = 0; a->questions = jdns_list_new(); a->answerRecords = jdns_list_new(); a->authorityRecords = jdns_list_new(); a->additionalRecords = jdns_list_new(); a->questions->valueList = 1; a->answerRecords->valueList = 1; a->authorityRecords->valueList = 1; a->additionalRecords->valueList = 1; a->fully_parsed = 0; a->raw_size = 0; a->raw_data = 0; return a; } jdns_packet_t *jdns_packet_copy(const jdns_packet_t *a) { jdns_packet_t *c = jdns_packet_new(); c->id = a->id; c->opts.qr = a->opts.qr; c->opts.opcode = a->opts.opcode; c->opts.aa = a->opts.aa; c->opts.tc = a->opts.tc; c->opts.rd = a->opts.rd; c->opts.ra = a->opts.ra; c->opts.z = a->opts.z; c->opts.rcode = a->opts.rcode; jdns_list_delete(c->questions); jdns_list_delete(c->answerRecords); jdns_list_delete(c->authorityRecords); jdns_list_delete(c->additionalRecords); c->questions = jdns_list_copy(a->questions); c->answerRecords = jdns_list_copy(a->answerRecords); c->authorityRecords = jdns_list_copy(a->authorityRecords); c->additionalRecords = jdns_list_copy(a->additionalRecords); c->fully_parsed = a->fully_parsed; c->raw_size = a->raw_size; c->raw_data = jdns_copy_array(a->raw_data, a->raw_size); return c; } void jdns_packet_delete(jdns_packet_t *a) { if(!a) return; jdns_list_delete(a->questions); jdns_list_delete(a->answerRecords); jdns_list_delete(a->authorityRecords); jdns_list_delete(a->additionalRecords); if(a->raw_data) jdns_free(a->raw_data); jdns_object_free(a); } int jdns_packet_import(jdns_packet_t **a, const unsigned char *data, int size) { jdns_packet_t *tmp = 0; const unsigned char *buf; // need at least some data if(!data || size == 0) return 0; // header (id + options + item counts) is 12 bytes if(size < 12) goto error; tmp = jdns_packet_new(); buf = data; // id tmp->id = net2short(&buf); // options if(buf[0] & 0x80) // qr is bit 7 tmp->opts.qr = 1; tmp->opts.opcode = (buf[0] & 0x78) >> 3; // opcode is bits 6,5,4,3 if(buf[0] & 0x04) // aa is bit 2 tmp->opts.aa = 1; if(buf[0] & 0x02) // tc is bit 1 tmp->opts.tc = 1; if(buf[0] & 0x01) // rd is bit 0 tmp->opts.rd = 1; if(buf[1] & 0x80) // ra is bit 7 (second byte) tmp->opts.ra = 1; tmp->opts.z = (buf[1] & 0x70) >> 4; // z is bits 6,5,4 tmp->opts.rcode = buf[1] & 0x0f; // rcode is bits 3,2,1,0 buf += 2; // item counts tmp->qdcount = net2short(&buf); tmp->ancount = net2short(&buf); tmp->nscount = net2short(&buf); tmp->arcount = net2short(&buf); // if these fail, we don't count them as errors, since the packet // might have been truncated if(!process_qsection(tmp->questions, tmp->qdcount, data, size, &buf)) goto skip; if(!process_rrsection(tmp->answerRecords, tmp->ancount, data, size, &buf)) goto skip; if(!process_rrsection(tmp->authorityRecords, tmp->nscount, data, size, &buf)) goto skip; if(!process_rrsection(tmp->additionalRecords, tmp->arcount, data, size, &buf)) goto skip; tmp->fully_parsed = 1; skip: // keep the raw data for reference during rdata parsing tmp->raw_size = size; tmp->raw_data = jdns_copy_array(data, size); *a = tmp; return 1; error: jdns_packet_delete(tmp); return 0; } int jdns_packet_export(jdns_packet_t *a, int maxsize) { unsigned char *block = 0; unsigned char *buf, *last; unsigned char c; int size; jdns_list_t *lookup = 0; // to hold jdns_packet_label_t // clear out any existing raw data before we begin if(a->raw_data) { jdns_free(a->raw_data); a->raw_data = 0; a->raw_size = 0; } // preallocate size = maxsize; block = (unsigned char *)jdns_alloc(size); memset(block, 0, size); buf = block; last = block + size; if(size < 12) goto error; short2net(a->id, &buf); if(a->opts.qr) buf[0] |= 0x80; c = (unsigned char)a->opts.opcode; buf[0] |= c << 3; if(a->opts.aa) buf[0] |= 0x04; if(a->opts.tc) buf[0] |= 0x02; if(a->opts.rd) buf[0] |= 0x01; if(a->opts.ra) buf[1] |= 0x80; c = (unsigned char)a->opts.z; buf[1] |= c << 4; c = (unsigned char)a->opts.rcode; buf[1] |= c; buf += 2; short2net((unsigned short int)a->questions->count, &buf); short2net((unsigned short int)a->answerRecords->count, &buf); short2net((unsigned short int)a->authorityRecords->count, &buf); short2net((unsigned short int)a->additionalRecords->count, &buf); // append sections lookup = jdns_list_new(); lookup->autoDelete = 1; if(!append_qsection(a->questions, buf - block, last - buf, &buf, lookup)) goto error; if(!append_rrsection(a->answerRecords, buf - block, last - buf, &buf, lookup)) goto error; if(!append_rrsection(a->authorityRecords, buf - block, last - buf, &buf, lookup)) goto error; if(!append_rrsection(a->additionalRecords, buf - block, last - buf, &buf, lookup)) goto error; // done with all sections jdns_list_delete(lookup); // condense size = buf - block; block = (unsigned char *)jdns_realloc(block, size); // finalize a->qdcount = a->questions->count; a->ancount = a->answerRecords->count; a->nscount = a->authorityRecords->count; a->arcount = a->additionalRecords->count; a->raw_data = block; a->raw_size = size; return 1; error: jdns_list_delete(lookup); if(block) jdns_free(block); return 0; }