/* $Id: dnsparse.C,v 1.7 2001/01/13 19:46:10 dm Exp $ */ /* * * Copyright (C) 1998 David Mazieres (dm@uun.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * */ /* DNS packet parsing. This code is easy to understand if and only if * you also have a copy of RFC 1035. You can get this by anonymous * ftp from, among other places, ftp.internic.net:/rfc/rfc1035.txt. */ #include "dnsparse.h" #include "arena.h" #include "vec.h" extern "C" { #ifdef NEED_DN_SKIPNAME_DECL int dn_skipname (const u_char *, const u_char *); #endif /* NEED_DN_SKIPNAME_DECL */ #ifdef NEED_DN_EXPAND_DECL int dn_expand (const u_char *, const u_char *, const u_char *, char *, int); #endif /* NEED_DN_EXPAND_DECL */ } #if 0 int dn_skipname (const u_char *icp, const u_char *eom) { const u_char *cp = icp; int n; while (cp < eom && (n = *cp++)) switch (n & 0xc0) { case 0: cp += n; break; case 0xc0: cp++; break; default: return -1; } return cp > eom ? -1 : cp - icp; } #endif dnsparse::dnsparse (const u_char *buf, size_t len) : buf (buf), eom (buf + len), anp (NULL), error (0), hdr (len > sizeof (HEADER) ? (HEADER *) buf : NULL), ancount (hdr ? ntohs (hdr->ancount) : 0) { if (!hdr) error = ARERR_BADRESP; else if (hdr->rcode) error = hdr->rcode; else if (hdr->tc || !hdr->qr || !ntohs (hdr->qdcount)) error = ARERR_BADRESP; else { const u_char *cp = getqp (); for (int i = 0, l = ntohs (hdr->qdcount); i < l; i++) { int n = dn_skipname (cp, eom); cp += n + 4; if (n < 0 || cp > eom) { error = ARERR_BADRESP; return; } } anp = cp; } } bool dnsparse::qparse (const u_char **cpp, question *qp) { const u_char *cp = *cpp; int n = dn_expand (buf, eom, cp, qp->q_name, sizeof (qp->q_name)); if (n < 0 || cp + 4 > eom) return false; cp += n; GETSHORT (qp->q_type, cp); GETSHORT (qp->q_class, cp); *cpp = cp; return true; } bool dnsparse::qparse (question *qp) { const u_char *cp = getqp (); return qparse (&cp, qp); } bool dnsparse::rrparse (const u_char **cpp, resrec *rrp) { const u_char *cp = *cpp; const u_char *e; u_int16_t rdlen; int n; if ((n = dn_expand (buf, eom, cp, rrp->rr_name, sizeof (rrp->rr_name))) < 0 || cp + n + 10 > eom) return false; cp += n; GETSHORT (rrp->rr_type, cp); GETSHORT (rrp->rr_class, cp); GETLONG (rrp->rr_ttl, cp); GETSHORT (rdlen, cp); rrp->rr_rdlen = rdlen; if ((e = cp + rdlen) > eom) return false; switch (rrp->rr_type) { case T_A: memcpy (&rrp->rr_a, cp, sizeof (rrp->rr_a)); cp += sizeof (rrp->rr_a); break; case T_CNAME: case T_PTR: if ((n = dn_expand (buf, eom, cp, rrp->rr_cname, sizeof (rrp->rr_cname))) < 0) return false; cp += n; break; case T_MX: if (rdlen < 2) return false; GETSHORT (rrp->rr_mx.mx_pref, cp); if ((n = dn_expand (buf, eom, cp, rrp->rr_mx.mx_exch, sizeof (rrp->rr_mx.mx_exch))) < 0) return false; cp += n; break; case T_TXT: if (rdlen >= sizeof (rrp->rr_txt)) return false; memcpy (rrp->rr_txt, cp, rdlen); rrp->rr_txt[rdlen] = '\0'; cp += rdlen; break; case T_SOA: if ((n = dn_expand (buf, eom, cp, rrp->rr_soa.soa_mname, sizeof (rrp->rr_soa.soa_mname))) < 0) return false; cp += n; if ((n = dn_expand (buf, eom, cp, rrp->rr_soa.soa_rname, sizeof (rrp->rr_soa.soa_rname))) < 0) return false; cp += n; if (cp + 20 > eom) return false; GETLONG (rrp->rr_soa.soa_serial, cp); GETLONG (rrp->rr_soa.soa_refresh, cp); GETLONG (rrp->rr_soa.soa_retry, cp); GETLONG (rrp->rr_soa.soa_expire, cp); GETLONG (rrp->rr_soa.soa_minimum, cp); break; default: cp = e; break; } assert (cp == e); *cpp = cp; return true; } /* Takes the response from an DNS query, and turns it into a * dynamically allocated hostent structure (defined in netdb.h). All * data for the hostent structure is allocated contiguously, so that * it can all be freed by simply freeing the address of the hostent * structure returned. If no addresses are returned (i.e. no A * records returned by the query), then a valid hostent will still be * returned, but with h->h_addr_list[0] will be NULL. */ ptr dnsparse::tohostent () { const u_char *cp = getanp (); arena a; vec av; char *name = NULL; char *cname = NULL; if (!cp) return NULL; for (size_t i = 0; i < ancount; i++) { resrec rr; if (!rrparse (&cp, &rr)) { error = ARERR_BADRESP; return NULL; } if (rr.rr_class == C_IN) switch (rr.rr_type) { case T_A: if (!name) name = a.strdup (rr.rr_name); av.push_back (rr.rr_a); break; case T_CNAME: if (!cname) cname = a.strdup (rr.rr_name); break; } } if (!name) { error = ARERR_NXREC; return NULL; } ref h = refcounted::alloc (sizeof (*h) + (!!cname + 2 + av.size ()) * sizeof (char *) + av.size () * sizeof (in_addr) + strlen (name) + 1 + (cname ? strlen (cname) + 1 : 0)); h->h_addrtype = AF_INET; h->h_length = sizeof (in_addr); h->h_aliases = (char **) &h[1]; h->h_addr_list = &h->h_aliases[1+!!cname]; size_t i; for (i = 0; i < av.size (); i++) { h->h_addr_list[i] = ((char *) &h->h_addr_list[1 + av.size ()] + i * sizeof (in_addr)); *(in_addr *) h->h_addr_list[i] = av[i]; } h->h_addr_list[i] = NULL; h->h_name = ((char *) &h->h_addr_list[1 + av.size ()] + i * sizeof (in_addr)); strcpy (h->h_name, name); if (cname) { h->h_aliases[0] = h->h_name + strlen (h->h_name) + 1; h->h_aliases[1] = NULL; strcpy (h->h_aliases[0], cname); } else h->h_aliases[0] = NULL; return h; } int dnsparse::mxrec_cmp (const void *_a, const void *_b) { const mxrec *a = (mxrec *) _a, *b = (mxrec *) _b; int d = (int) a->pref - (int) b->pref; if (d) return d; return strcasecmp (a->name, b->name); } ptr dnsparse::tomxlist () { const u_char *cp = getanp (); arena a; char *name = NULL; int n_chars = 0; if (!cp) return NULL; u_int nmx = 0; mxrec *mxes = (mxrec *) a.alloc ((1 + ancount) * sizeof (*mxes)); for (u_int i = 0; i < ancount; i++) { resrec rr; if (rrparse (&cp, &rr)) { error = ARERR_BADRESP; return NULL; } if (rr.rr_class == C_IN && rr.rr_type == T_MX) { u_short pr = rr.rr_mx.mx_pref; u_int mxi; if (!name) name = a.strdup (rr.rr_name); for (mxi = 0; mxi < nmx && strcasecmp (rr.rr_mx.mx_exch, mxes[mxi].name); mxi++) ; if (mxi < nmx) { if (pr < mxes[mxi].pref) mxes[mxi].pref = pr; } else { n_chars += strlen (rr.rr_mx.mx_exch) + 1; mxes[nmx].pref = pr; mxes[nmx].name = a.strdup (rr.rr_mx.mx_exch); nmx++; } } } if (nmx == 0) { error = ARERR_NXREC; return NULL; } else if (nmx > 1) qsort (mxes, nmx, sizeof (*mxes), mxrec_cmp); n_chars += strlen (name) + 1; ref mxl = refcounted::alloc (offsetof (mxlist, m_mxes[nmx]) + n_chars); mxrec *mxrecs = mxl->m_mxes; char *np = (char *) &mxrecs[nmx]; mxl->m_nmx = nmx; mxl->m_name = np; strcpy (np, name); np += strlen (np) + 1; for (u_int i = 0; i < nmx; i++) { mxrecs[i].pref = mxes[i].pref; mxrecs[i].name = np; strcpy (np, mxes[i].name); np += strlen (np) + 1; } return mxl; } #if 1 void printaddrs (const char *msg, ptr h, int dns_errno) { char **aliases; struct in_addr **addrs; if (msg) printf ("%s (hostent):\n", msg); if (! h) { printf (" Error: %s\n", dns_strerror (dns_errno)); return; } aliases = h->h_aliases; addrs = (struct in_addr **) h->h_addr_list; printf (" Name: %s\n", h->h_name); while (*aliases) printf (" Alias: %s\n", *aliases++); while (*addrs) printf (" Address: %s\n", inet_ntoa (**addrs++)); } void printmxlist (const char *msg, ptr m, int dns_errno) { int i; if (msg) printf ("%s (mxlist):\n", msg); if (!m) { printf (" Error: %s\n", dns_strerror (dns_errno)); return; } printf (" Name: %s\n", m->m_name); for (i = 0; i < m->m_nmx; i++) printf (" MX: %3d %s\n", m->m_mxes[i].pref, m->m_mxes[i].name); } #endif