/**************************************************************************** * Copyright (C) 1998 WIDE Project. All rights reserved. * Copyright (C) 1999,2000,2001,2002 University of Tromso. All rights reserved. * Copyright (C) 2002 Invenia Innovation AS. All rights reserved. * * Author: Feike W. Dillema, feico@pasta.cs.uit.no. * based on newbie code by Yusuke DOI, Keio Univ. Murai Lab. ****************************************************************************/ /* * <$Id: ne_mesg.c,v 3.49 2005/07/04 09:09:22 dillema Exp $> */ #include "totd.h" static int write_dname (u_char *, u_char *, uint16_t *, int, u_char *, u_char *); static int dname_copy (u_char *, u_char *, int); static u_char *dname_redirect (u_char *, u_char *); static u_char *mesg_read_sec (G_List *, u_char *, int, u_char *, int); uint16_t mesg_id (void) { static uint16_t id = 0; if (!id) { srandom (time (NULL)); id = random (); } id++; if (T.debug > 4) syslog (LOG_DEBUG, "mesg_id() = %d", id); return id; } int mesg_make_query (u_char *qname, uint16_t qtype, uint16_t qclass, uint32_t id, int rd, u_char *buf, int buflen) { char *fn = "mesg_make_query()"; u_char *ucp; int i, written_len; Mesg_Hdr *hdr; if (T.debug > 4) syslog (LOG_DEBUG, "%s: (qtype: %s, id: %d): start", fn, string_rtype (qtype), id); hdr = (Mesg_Hdr *) buf; /* write header */ hdr->id = id; hdr->opcode = OP_QUERY; hdr->rcode = RC_OK; hdr->rd = rd; hdr->qr = hdr->aa = hdr->tc = hdr->ra = hdr->zero = 0; hdr->qdcnt = ntohs (1); hdr->ancnt = hdr->nscnt = hdr->arcnt = ntohs (0); written_len = sizeof (Mesg_Hdr); ucp = (u_char *) (hdr + 1); /* write qname */ if (T.debug > 4) syslog (LOG_DEBUG, "%s: qname offset = %td", fn, ucp - buf); i = dname_copy (qname, ucp, buflen - written_len); if (i < 0) return -1; written_len += i; ucp += i; /* write qtype / qclass */ if (T.debug > 4) syslog (LOG_DEBUG, "%s: qtype/qclass offset = %td", fn, ucp - buf); written_len += sizeof (uint16_t) * 2; if (written_len > buflen) return -1; PUTSHORT (qtype, ucp); PUTSHORT (qclass, ucp); return written_len; } int labellen (const u_char *cp) { uint i; i = *cp; if ((i & DNCMP_MASK) == 0) return(i); else if ((i & DNCMP_MASK) == EDNS0_MASK) { uint bitlen; if (i != EDNS0_ELT_BITLABEL) return -1; bitlen = *(cp + 1); if (bitlen == 0) bitlen = 256; return (((bitlen + 7) / 8) + 1); } else return -1; } u_char *mesg_skip_dname (u_char *dname, u_char *end) { int l; if (dname >= end) return NULL; while(*dname) { if ((*dname & DNCMP_MASK) == DNCMP_MASK) { dname += 2; /* redirection */ return dname; } if (dname + 2 > end) /* we need at least 2 bytes */ return NULL; l = labellen(dname); if (l < 0) return NULL; dname += l + 1; if (dname >= end) return NULL; } dname++; /* push away null terminator */ return dname; } int mesg_dname_cmp (u_char *msg, u_char *dname_mesg, u_char *dname) { /* * compare dname_mesg and dname label by label, while doing * decompression of the dname_mesg of the message msg. */ dname_mesg = dname_redirect (dname_mesg, msg); while (*dname_mesg != '\0' && (*dname == *dname_mesg)) { int len; len = labellen(dname_mesg); if (len != labellen(dname)) return -1; if (*dname == EDNS0_ELT_BITLABEL) { if (memcmp (dname_mesg + 1, dname + 1, len)) return -1; } else if (strncasecmp (dname_mesg + 1, dname + 1, len)) return -1; dname += len + 1; dname_mesg += len + 1; dname_mesg = dname_redirect (dname_mesg, msg); } if (*dname != *dname_mesg) return -1; else return 0; } int mesg_write_rrset_list (G_List *rrls, u_char *msg, u_char *msg_tail, uint16_t *dnames, int dnames_len, u_char **wp, uint16_t *cnt) { char *fn = "mesg_write_rrset_list()"; u_char *wp_start, *wp_period; Mesg_Hdr *hdr; uint16_t us; uint32_t ul; RRset *rrsp; RR *rrp; int i, ret; if (T.debug > 4) syslog (LOG_DEBUG, "%s: start.", fn); if (!rrls) return 0; wp_start = *wp; hdr = (Mesg_Hdr *) msg; for (rrls = rrls->next; rrls->list_data; rrls = rrls->next) { if (T.debug > 4) syslog (LOG_DEBUG, "%s: write a record", fn); rrsp = (RRset *) rrls->list_data; for (i = 0; i < rrsp->data.d->data_cnt; i++) { wp_period = *wp; /* write the owner name */ ret = write_dname (msg, msg_tail, dnames, dnames_len, rrset_owner (rrsp), *wp); if (ret < 0) { syslog (LOG_DEBUG, "write ownername failed"); *wp = wp_period; return wp_period - wp_start; } *wp += ret; /* write RR field and data */ rrp = (RR *) (rrsp->data.p + data_offset (i, rrsp->data.p)); if (*wp + sizeof (uint16_t) * 3 + sizeof (uint32_t) + rrp->rd_len > msg_tail) { syslog (LOG_DEBUG, "write rdata failed"); *wp = wp_period; return wp_period - wp_start; } PUTSHORT (rrsp->key.info->r_type, *wp); PUTSHORT (rrsp->key.info->r_class, *wp); ul = rrp->ttl; PUTLONG (ul, *wp); /* XXX RDATA COMPRESSION NOT IMPLEMENTED */ PUTSHORT (rrp->rd_len, *wp); memcpy (*wp, rr_rdata (rrp), rrp->rd_len); *wp += rrp->rd_len; /* update header */ us = ntohs (*cnt) + 1; /* and caller's counter */ *cnt = htons (us); if (T.debug > 4) syslog (LOG_DEBUG, "%s: now counter = %u", fn, us); } } if (T.debug > 4) syslog (LOG_DEBUG, "%s: return %td", fn, *wp - wp_start); return (*wp - wp_start); } #define MESG_ASSEMBLE_OFFSET_LEN 64 int mesg_assemble (G_List *an_list, G_List *ns_list, G_List *ar_list, u_char *buf, uint16_t buflen, u_char *mesg, int mesg_len) { uint16_t dnames[MESG_ASSEMBLE_OFFSET_LEN]; u_char *ucp, *ucp_tmp; int written_len, ret; Mesg_Hdr *hdr; /* check if header is already present */ if (mesg) memcpy (buf, mesg, mesg_len); else memset (buf, 0, buflen); hdr = (Mesg_Hdr *) buf; written_len = 0; /* check and reset header */ hdr->qr = 1; hdr->ancnt = 0; hdr->nscnt = 0; hdr->arcnt = 0; if (hdr->qdcnt) { int qdcnt = ntohs(hdr->qdcnt); /* * Register question name to compression name list * Note that for simplicity we only register * the first, and ignore multiple question */ dnames[0] = (uint16_t) (sizeof (Mesg_Hdr)); dnames[1] = 0; /* terminator */ /* * Skip the question section. */ ucp = buf + sizeof(Mesg_Hdr); while (qdcnt--) { /* skip QNAME */ if (!(ucp = mesg_skip_dname(ucp, buf + mesg_len)) || ucp + 2 * sizeof(uint16_t) > buf + mesg_len) { syslog (LOG_NOTICE, "query message overrun"); return -1; } /* skip QTYPE and QCLASS */ ucp += (2 * sizeof(uint16_t)); } written_len = ucp - buf; } else { /* there mustnot be any question */ written_len = sizeof (Mesg_Hdr); ucp = buf + written_len; dnames[0] = 0; } /* write answers */ ucp_tmp = ucp; ret = mesg_write_rrset_list (an_list, buf, buf + buflen, dnames, MESG_ASSEMBLE_OFFSET_LEN, &ucp, &(hdr->ancnt)); if (ret < 0) { /* truncated message */ hdr->tc = 1; return ucp_tmp - buf; } written_len += ret; /* write ns */ ucp_tmp = ucp; ret = mesg_write_rrset_list (ns_list, buf, buf + buflen, dnames, MESG_ASSEMBLE_OFFSET_LEN, &ucp, &(hdr->nscnt)); if (ret < 0) { /* truncated message */ hdr->tc = 1; return ucp_tmp - buf; } written_len += ret; /* write additonal records */ ucp_tmp = ucp; ret = mesg_write_rrset_list (ar_list, buf, buf + buflen, dnames, MESG_ASSEMBLE_OFFSET_LEN, &ucp, &(hdr->arcnt)); /* * ignore if error -- we may leave out additional * records if we do not have room for them */ if (ret < 0) return ucp_tmp - buf; else return written_len + ret; } int mesg_extract_rr (u_char *mesg, u_char *msg_end, uint16_t r_type, uint16_t r_class, u_char *rrp, u_char *buf, int buflen) { int i, written_len; u_char *rp, *wp; written_len = 0; switch (r_type) { case RT_NS: case RT_CNAME: case RT_PTR: /* just extract domain name */ if (!dname_decompress (buf, buflen, rrp, mesg, msg_end, &written_len)) { syslog (LOG_INFO, "record invalid -- %s", string_rtype (r_type)); return -1; } break; case RT_SOA: rp = rrp; wp = buf; rp = dname_decompress (wp, buflen, rp, mesg, msg_end, &i); if (!rp) { syslog (LOG_INFO, "record invalid -- SOA MNAME"); return -1; } wp += i; rp = dname_decompress (wp, buflen - (wp - buf), rp, mesg, msg_end, &i); if (!rp) { syslog (LOG_INFO, "record invalid -- SOA RNAME"); return -1; } wp += i; memcpy (wp, rp, (i = sizeof (uint32_t) * 5)); wp += i; written_len = wp - buf; break; case RT_MX: rp = rrp; wp = buf; memcpy (wp, rp, (i = sizeof (uint16_t) * 1)); /* PREFERENCE */ wp += i; rp += i; if (!dname_decompress (wp, buflen - (wp - buf), rp, mesg, msg_end, &i)) { syslog (LOG_INFO, "record invalid -- MX EXCHANGE"); return -1; } wp += i; written_len = wp - buf; break; case RT_RP: /* two domain names */ rp = rrp; wp = buf; rp = dname_decompress (wp, buflen, rp, mesg, msg_end, &i); if (!rp) { syslog (LOG_INFO, "record invalid -- RP MBOX-DNAME"); return -1; } wp += i; rp = dname_decompress (wp, buflen - (wp - buf), rp, mesg, msg_end, &i); if (!rp) { syslog (LOG_INFO, "record invalid -- RP TXT-DNAME"); return -1; } wp += i; written_len = wp - buf; break; case RT_A: case RT_HINFO: case RT_AAAA: case RT_A6: case RT_SRV: case RT_TXT: /* no modification */ return 0; default: syslog (LOG_INFO, "unknown resource type %d", r_type); return 0; } return written_len; } int mesg_parse (u_char *msg, int msg_len, G_List *an_list, G_List *ns_list, G_List *ar_list) { char *fn = "mesg_parse()"; Mesg_Hdr *hdr; u_char *rp; if (T.debug > 4) syslog (LOG_DEBUG, "%s: start", fn); if (msg_len < sizeof (*hdr)) return -1; hdr = (Mesg_Hdr *) msg; rp = (u_char *) (hdr + 1); /* read next of the header */ if (hdr->qdcnt) { /* skip question section */ rp = mesg_skip_dname (rp, msg + msg_len); rp += 4; /* sizeof qtype + qclass */ if (rp > msg + msg_len) return -1; } rp = mesg_read_sec (an_list, rp, ntohs(hdr->ancnt), msg, msg_len); if (!rp) return -1; rp = mesg_read_sec (ns_list, rp, ntohs(hdr->nscnt), msg, msg_len); if (!rp) return -1; rp = mesg_read_sec (ar_list, rp, ntohs(hdr->arcnt), msg, msg_len); if (!rp) return -1; return 0; } u_char *dname_decompress (u_char *buf, int buflen, u_char *dname, u_char *m_head, u_char *m_tail, int *written) { int token_len, written_len, iter; u_char *cp, *next; int pktsiz = m_tail - m_head; next = NULL; written_len = token_len = 0; for (cp = dname; *cp; cp += token_len) { iter = 0; top: if ((*cp & DNCMP_MASK) == DNCMP_MASK) { uint16_t ui; if (iter++ >= pktsiz) /* we're probably in a loop. */ return NULL; if (!m_head || !m_tail) /* irregular redirect */ return NULL; /* redirect */ next = cp + 2; GETSHORT (ui, cp); ui = ui & ~DNCMP_MASK_INT16T; cp = m_head + ui; if (cp < m_head || m_tail < cp) return NULL; goto top; } token_len = labellen(cp); if (token_len < 0) return NULL; else token_len++; if (T.debug > 4) syslog (LOG_DEBUG, "token_len: %d", token_len); if (written_len + token_len >= buflen) return NULL; /* buffer overrun */ if (cp + token_len > m_tail) return NULL; /* out of bounds */ if (written) { /* non-printable dname string */ memcpy (buf, cp, token_len); written_len += token_len; buf += token_len; } else { /* write printable string */ if ((*cp & DNCMP_MASK) != EDNS0_MASK) { memcpy (buf, cp + 1, token_len - 1); *(buf + (token_len - 1)) = DNAME_DELIM; written_len += token_len; buf += token_len; } else if (*cp == EDNS0_ELT_BITLABEL) { int bitlength, i; u_char *wp; /* a bit conservative test, but simple */ if (written_len + token_len*2 + 7 >= buflen) return NULL; /* buffer overrun */ wp = buf; wp += sprintf(wp, "\\[x"); for (i = 1; i < token_len-1; i++) { u_char d1, d2; uint b; b = (int) *(cp + 1 + i); d1 = hex[(b >> 4) & 0x0f]; d2 = hex[b & 0x0f]; wp += sprintf(wp, "%c%c", d1, d2); } bitlength = *(cp + 1) ? *(cp + 1) : 256; wp += sprintf(wp, "/%u].", bitlength); written_len += (wp - buf); buf += written_len; } } } *buf = '\0'; if (written) *written = written_len + 1; if (!next) next = cp+1; return next; } char *string_rclass (uint16_t rclass) { switch (rclass) { case C_IN: return "IN"; case C_NONE: return "NONE"; case C_ANY: return "ANY"; default: syslog (LOG_NOTICE, "Unknown resource class(%d)", rclass); return "UNKNOWN"; } } char *string_rtype (uint16_t rtype) { switch (rtype) { case RT_VOID: return "(void)"; case RT_A: return "A"; case RT_NS: return "NS"; case RT_MD: return "MD"; case RT_MF: return "MF"; case RT_CNAME: return "CNAME"; case RT_SOA: return "SOA"; case RT_MB: return "MB"; case RT_MG: return "MG"; case RT_MR: return "MR"; case RT_NULL: return "NULL"; case RT_WKS: return "WKS"; case RT_PTR: return "PTR"; case RT_HINFO: return "HINFO"; case RT_MINFO: return "MINFO"; case RT_MX: return "MX"; case RT_TXT: return "TXT"; case RT_RP: return "RP"; case RT_AAAA: return "AAAA"; case RT_SRV: return "SRV"; case RT_A6: return "A6"; case RT_UINFO: return "UINFO"; case RT_TSIG: return "TSIG"; case RT_IXFR: return "IXFR"; case RT_AXFR: return "AXFR"; case RT_ALL: return "ANY"; default: syslog (LOG_NOTICE, "Unknown resource type(%d)", rtype); return "UNKNOWN"; } } static int dname_copy (u_char *from, u_char *to, int tolen) { int skip, written_len; written_len = 0; while (*from) { skip = labellen(from) + 1; written_len += skip; if (written_len >= tolen) return -1; memcpy (to, from, skip); from += skip; to += skip; } *to = '\0'; written_len++; return written_len; } static u_char *dname_redirect (u_char *label, u_char *msg) { uint16_t us; if (msg && (*label & DNCMP_MASK) == DNCMP_MASK) { GETSHORT (us, label); us = us & (~DNCMP_MASK_INT16T); label = msg + us; } return label; } static int write_dname (u_char *msg, u_char *msg_tail, uint16_t *dnames, int dnames_len, u_char *dname, u_char *wp) { char *fn = "write_dname()"; u_char *bestmatch_rpd = NULL; u_char *bestmatch_rpm = NULL; int bestmatch_len; u_char *rpd, *rpm; int written_len; if (T.debug > 4) syslog (LOG_DEBUG, "%s: start", fn); /* * Check if some (part of) dname has already appeared in message * * Start with full name, and chop first name label off for each * iteration. */ bestmatch_len = 0; for (rpd = dname; *rpd && !bestmatch_len; rpd += labellen(rpd) + 1) { int i; /* * The dnames array contains a list of pointers to domainnames * in the message that we will try to match our name against. */ for (i = 0; dnames[i] != 0 && i < dnames_len; i++) { /* * Again, start with full name, and chop first name label * off for each iteration. Meanwhile, we follow * redirections. */ for (rpm = dname_redirect (msg + dnames[i], msg); *rpm; rpm = dname_redirect (labellen(rpm)+rpm+1, msg)) { u_char *cpd, *cpm; int match_len; if (rpm < msg || msg_tail < rpm) return -1; /* out of bounds */ /* comparison pointers */ cpd = rpd; cpm = rpm; match_len = 0; while (*cpm && *cpm == *cpd) { int mlen; mlen = labellen(cpm); if (mlen != labellen(cpd)) break; /* binary comparison */ if (*cpm == EDNS0_ELT_BITLABEL && memcmp (cpm+1, cpd+1, mlen)) break; /* case-insensitive comparison */ if (*cpm != EDNS0_ELT_BITLABEL && strncasecmp (cpm+1, cpd+1, *cpm)) break; /* a label matched, move to next one */ cpm += mlen + 1; cpd += mlen + 1; /* check redirection */ cpm = dname_redirect (cpm, msg); if (cpm < msg || msg_tail < cpm) return -1; /* out of bounds */ match_len++; } /* * matched parts have to be postfixes, i.e. * both need to be NULL terminated. We record * the match if it is better than the best one * we found so far. */ if (*cpm == '\0' && *cpd == '\0' && match_len > bestmatch_len) { bestmatch_rpd = rpd; bestmatch_rpm = rpm; bestmatch_len = match_len; } } } } /* register this name if not complete match */ if (bestmatch_rpd != dname) { int i; /* we didn't find ourselves */ for (i = 0; dnames[i] != 0; i++); if (i + 1 < dnames_len) { /* we still have room for one more in the table */ if (((uint16_t) (bestmatch_rpm - msg) < DNCMP_REDIRECT_LIMIT)) { /* It is within range */ dnames[i] = (uint16_t) (wp - msg); dnames[i + 1] = 0; } } } /* write dname */ written_len = 0; rpd = dname; /* write first unique part */ while (*rpd && rpd != bestmatch_rpd) { int i; i = labellen(rpd) + 1; if (wp + i > msg_tail) return -1; /* overflow! */ memcpy (wp, rpd, i); written_len += i; rpd += i; wp += i; } /* write second redirected part, if any found, or terminate */ if (rpd == bestmatch_rpd) { uint16_t us; /* write redirection pointer */ if (wp + sizeof (uint16_t) > msg_tail) return -1; /* overflow */ us = (uint16_t) (bestmatch_rpm - msg) | DNCMP_MASK_INT16T; PUTSHORT (us, wp); written_len += sizeof (uint16_t); } else { *wp = '\0'; written_len++; } if (T.debug > 4) syslog (LOG_DEBUG, "%s: return (written_len = %d)", fn, written_len); return written_len; } static u_char *mesg_read_sec (G_List *target_list, u_char *section, int count, u_char *mesg, int mesg_len) { char *fn = "mesg_read_sec()"; G_List *rc_list, *gl; u_char buf[MAX_PACKET]; u_char *msg_end, *rp; int i; if (T.debug > 4) syslog (LOG_DEBUG, "%s: start", fn); /* initialize */ rc_list = list_init (); if (!rc_list) return NULL; rp = section; msg_end = mesg + mesg_len; for (i = 0; i < count; i++) { u_char *rname, *rp_ex, *rdp; uint16_t r_type, r_class; uint16_t rdlen, rdlen_ex; uint32_t r_ttl; RR_List *rrl; /* parse header */ rname = rp; rp = mesg_skip_dname (rp, msg_end); if (!rp) goto error; if (rp + sizeof(uint16_t)*3 + sizeof(uint32_t) > msg_end) goto error; GETSHORT (r_type, rp); GETSHORT (r_class, rp); GETLONG (r_ttl, rp); GETSHORT (rdlen, rp); rdp = rp; rp += rdlen; if (rp > msg_end) goto error; /* seek for matching RRset_Couple */ for (gl = rc_list->next; gl->list_data; gl = gl->next) { RRset_Couple *rc; rc = (RRset_Couple *) (gl->list_data); if ((rc->rrs->key.info->r_type == r_type) && (rc->rrs->key.info->r_class == r_class) && !mesg_dname_cmp (mesg, rname, rrset_owner (rc->rrs))) { if (T.debug > 4) syslog (LOG_DEBUG, "%s: matching record \ found rrs->dname = %s / rname = %s", fn, rrset_owner (rc->rrs), rname); break; } } /* if no match, create a new RRset_Couple */ if (!gl->list_data) { RRset_Couple *rc; int dname_len; if (!dname_decompress (buf, sizeof (buf), rname, mesg, msg_end, &dname_len)) goto error; rc = malloc (sizeof (RRset_Couple)); if (!rc) goto error; rc->rrl = rr_list_alloc (); rc->rrs = rrset_create (r_type, r_class, dname_len, buf, NULL); /* if ok, add it to RRset_Couple list */ if (!rc->rrl || !rc->rrs || list_add (rc_list, rc)) { rrset_couple_free(rc); goto error; } /* point it to the one we just added */ gl = rc_list->next; } /* extract resource record */ if (rdlen) { int ret; ret = mesg_extract_rr (mesg, msg_end, r_type, r_class, rdp, buf, sizeof (buf)); if (ret < 0) goto error; if (!ret) { rp_ex = rdp; rdlen_ex = rdlen; } else { rp_ex = buf; rdlen_ex = ret; } } else { rp_ex = NULL; rdlen_ex = 0; } /* add the extracted RR to matching RR_List */ rrl = rr_list_add (((RRset_Couple *) (gl->list_data))->rrl, r_ttl, rdlen_ex, rp_ex); if (!rrl) goto error; ((RRset_Couple *) (gl->list_data))->rrl = rrl; } if (T.debug > 4) syslog (LOG_DEBUG, "%s: make each RRset from list.", fn); rc_list->list_data = NULL; for (gl = rc_list->next; gl->list_data; gl = gl->next) { RRset_Couple *rc; RRset *rrs; /* create complete RRset */ rc = (RRset_Couple *) (gl->list_data); rrs = rrset_create (rc->rrs->key.info->r_type, rc->rrs->key.info->r_class, rc->rrs->key.info->owner_len, rrset_owner (rc->rrs), rc->rrl); if (!rrs) goto error; if (target_list) { if (list_add (target_list, rrset_copy (rrs)) < 0) { rrset_free (rrs); goto error; } } rrset_free (rrs); } /* free unused resources */ list_destroy (rc_list, rrset_couple_freev); if (T.debug > 4) syslog (LOG_DEBUG, "%s: end", fn); return rp; /* byte after of the section we just processed */ error: syslog (LOG_INFO, "%s: message extraction failed", fn); list_destroy (rc_list, rrset_couple_freev); return NULL; }