/************************************** * AUTHOR: Federico Tomassini * * Copyright (C) Federico Tomassini * * Contact effetom@gmail.com * *********************************************** ******* BEGIN 3/2006 ******** ************************************************************************* * * * 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 of the License, 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. * * * ************************************************************************/ #define _GNU_SOURCE #include #include "dnslib.h" #include "err_errno.h" #include "log.h" #include "xmalloc.h" /* * Takes a label: is there a ptr? * Returns: * -1 is a malformed label is found * 0 if there's no pointer * if a pointer is found */ int getlblptr(char *buf) { uint16_t dlbl; char c[2]; memcpy(c, buf, 2); if (!LBL_PTR(*c)) /* No ptr */ return 0; if (LBL_PTR(*c) != LBL_PTR_MASK) { debug(DBG_INSANE, "In getlblptr: invalid octet %02x", (unsigned char) c[0]); err_ret(ERR_DNSMLO, -1); } (*c) &= LBL_PTR_OFF_MASK; memcpy(&dlbl, c, 2); dlbl = ntohs(dlbl); return dlbl; /* offset */ } /* * Reads a contiguous octet-sequence-label. * Writes on dst. * There are two limits: * the name has to be less than MAX_SEQ_LBL_LEN * we must stay in pkt_len * -limit- is the less limit * * Returns: * -1 On error * Bytes readed if OK */ int read_label_octet(const char *src, char *dst, int limit) { int how; how = *src++; if (how > limit || how > DNS_MAX_LABELS) { error("In read_label_octet: got %d with limti %d\n", how, limit); err_ret(ERR_DNSMSL, -1); } memcpy(dst, src, how); return how; } /* * Converts a dns compliant sequence label name to string. * we start to read at -buf- * we need start_pkt for pointers * we need limit to remain under pktlen * Returns: * Bytes readed if OK * -1 on error */ int lbltoname(char *buf, char *start_pkt, char *dst, int limit) { char *crow; int how, recursion = 0; int ptr; int writed = 0, readed = 0; int new_limit = limit; crow = buf; while (*crow) { ptr = getlblptr(crow); if (ptr) { /* Got a pointer.... or got an error */ if (ptr == -1) { debug(DBG_INSANE, err_str); err_ret(ERR_DNSMSL, -1); } if (++recursion > MAX_RECURSION_PTR) err_ret(ERR_DNSTRP, -1); if (recursion == 1) readed += 2; /* we read the pointer */ crow = start_pkt + ptr; new_limit = limit - (int) (crow - buf); if (new_limit <= 0 || new_limit > (int) (buf - start_pkt) + limit) err_ret(ERR_DNSPLB, -1); if (getlblptr(crow)) err_ret(ERR_DNSPTP, -1); } how = read_label_octet(crow, dst, min(new_limit, DNS_MAX_HNAME_LEN - writed)); if (how == -1) { debug(DBG_INSANE, err_str); err_ret(ERR_DNSMSL, -1); } if (!recursion) readed += how + 1; writed += how + 1; dst += how; crow += how + 1; *dst++ = (*crow) ? '.' : 0; } if (!recursion) readed++; return readed; } /* * DNS PTR query ask for 4.3.2.1.in-addr.arpa to know * who is 1.2.3.4. * This function reads this type of query transalting it * in the second form. * Writes result on *dst. * -1 on error. */ int swap_straddr(char *src, char *dst) { char a[3]; int i, slen; char *crow, *tmp, *atom; int count = 0, offset = 0; slen = strlen(src); if (slen > DNS_MAX_HNAME_LEN) goto mlf_addr; tmp = src; for (i = 0; i < 4; i++) { count = 0; atom = a; while (*tmp && *tmp != '.') { if (count > 2) goto mlf_addr; *atom++ = *tmp++; count++; } if (!count) goto mlf_addr; crow = dst + slen - count - offset; strncpy(crow, a, count); offset += count; if (!(*tmp)) break; else { if (i == 3) goto mlf_addr; *(crow - 1) = '.'; offset++; tmp++; } } *(dst + slen) = 0; return 0; mlf_addr: debug(DBG_INSANE, "in swap_straddr: invalid address `%s`.\n", src); err_ret(ERR_DNSMDD, -1); } int swap_straddr6(char *src, char *dst) { int slen; char *tmp; slen = strlen(src); tmp = src + slen - 1; while (tmp != src) *dst++ = *tmp--; *dst++ = *tmp; *dst = 0; return 0; } int rm_inv_prefix(char *src, char *dst) { char *temp; int ret; if (!src) { debug(DBG_INSANE, "In rm_inv_prefix: NULL argument!"); err_ret(ERR_DNSMDD, -1); } if (! ((temp = (char *) strcasestr(src, DNS_INV_PREFIX)) || (temp = (char *) strcasestr(src, DNS_INV_PREFIX6)) || (temp = (char *) strcasestr(src, OLD_DNS_INV_PREFIX6)))) { debug(DBG_INSANE, "In rm_inv_prefix(): no suffix for PTR query."); err_ret(ERR_DNSMDD, -1); } if (temp - src >= DNS_MAX_HNAME_LEN) { error("In rm_inv_prefix(): name too long."); err_ret(ERR_DNSMDD, -1); } ret = strstr(temp, "6") ? AF_INET6 : AF_INET; strncpy(dst, src, temp - src); dst[temp - src] = 0; return ret; } int add_inv_prefix(char *s, int family) { int len; len = strlen(s); if (family == AF_INET) strcat(s, DNS_INV_PREFIX); else strcat(s, DNS_INV_PREFIX6); return 0; } int swapped_straddr(char *src, char *dst) { char temp[DNS_MAX_HNAME_LEN]; int res; res = rm_inv_prefix(src, temp); if (res == -1) { error(err_str); err_ret(ERR_DNSMDD, -1); } if (res == AF_INET) res = swap_straddr(temp, dst); else res = swap_straddr6(temp, dst); if (res == -1) { error(err_str); err_ret(ERR_DNSMDD, -1); } return 0; } int swapped_straddr_pref(char *src, char *dst, int family) { int res; if (family == AF_INET) res = swap_straddr(src, dst); else res = swap_straddr6(src, dst); if (res == -1) { error(err_str); err_ret(ERR_DNSMDD, -1); } add_inv_prefix(dst, family); return 0; } /* * Converts a domain_name_string into a sequence label format, * dns compliant. Writes on dst. * -1 on error, number of bytes writed on success */ int nametolbl(char *name, char *dst) { char *crow; int offset = 0, res; if (strlen(name) > DNS_MAX_HNAME_LEN) { debug(DBG_INSANE, "Malformed name: %s.", name); err_ret(ERR_DNSMDA, -1); } while ((crow = strstr(name + 1, "."))) { res = crow - name; if (res > DNS_MAX_LABELS) { debug(DBG_INSANE, "Malformed name: %s.", name); err_ret(ERR_DNSMDA, -1); } *dst = (char) res; /* write the octet length */ dst++; offset++; memcpy(dst, name, (size_t) res); /* write label */ name += res + 1; dst += res; offset += res; /* shift ptrs */ } if (!name) return offset; if ((res = (char) strlen(name)) > DNS_MAX_LABELS) { debug(DBG_INSANE, "Malformed name: %s", name); err_ret(ERR_DNSMDA, -1); } *dst++ = (char) res; strcpy(dst, name); offset += res + 2; return offset; } /* * Disassembles DNS packet headers, writing a yet allocated * dns_pkt_hdr struct. * No controls on len, bcz <<--the min_pkt_len is controlled * by recv.-->> * Returns the number of bytes readed (always DNS_HDR_SZ). */ int d_hdr_u(char *buf, dns_pkt_hdr * dph) { uint8_t c; uint16_t s; // ROW 1 memcpy(&s, buf, sizeof(uint16_t)); dph->id = ntohs(s); // ROW 2 buf += 2; memcpy(&c, buf, sizeof(uint8_t)); dph->qr = (c >> 7) & 0x01; dph->opcode = (c >> 3) & 0x0f; dph->aa = (c >> 2) & 0x01; dph->tc = (c >> 1) & 0x01; dph->rd = c & 0x01; buf++; memcpy(&c, buf, sizeof(uint8_t)); dph->ra = (c >> 7) & 0x01; dph->z = (c >> 4) & 0x07; dph->rcode = c & 0x0f; // ROW 3 buf++; memcpy(&s, buf, sizeof(uint16_t)); dph->qdcount = ntohs(s); // ROW 4 buf += 2; memcpy(&s, buf, sizeof(uint16_t)); dph->ancount = ntohs(s); // ROW 5 buf += 2; memcpy(&s, buf, sizeof(uint16_t)); dph->nscount = ntohs(s); // ROW 6 buf += 2; memcpy(&s, buf, sizeof(uint16_t)); dph->arcount = ntohs(s); buf += 2; return DNS_HDR_SZ; // i.e. 12 :) } /* * This function alloc a new dns_pkt_qst to store a dns_question_section. * The new dns_pkt_qst is also added to the principal dp-struct * Returns bytes readed if OK. -1 otherwise. */ int d_qst_u(char *start_buf, char *buf, dns_pkt * dp, int limit_len) { int count; uint16_t s; dns_pkt_qst *dpq; dpq = dns_add_qst(dp); /* get name */ if ((count = lbltoname(buf, start_buf, dpq->qname, limit_len)) == -1) { error(err_str); err_ret(ERR_DNSMDD, 1); } buf += count; /* Now we have to write 2+2 bytes */ if (count + 4 > limit_len) err_ret(ERR_DNSPLB, 1); /* shift to type and class */ memcpy(&s, buf, 2); dpq->qtype = ntohs(s); count += 2; buf += 2; memcpy(&s, buf, 2); dpq->qclass = ntohs(s); count += 2; return count; } /* * Disassembles a DNS qst_section_set. * Use the above function for each question section. * -1 on error. Number of bytes readed on success. * If -1 is returned, rcode ha sto be set to E_INTRPRT */ int d_qsts_u(char *start_buf, char *buf, dns_pkt * dp, int limit_len) { int offset = 0, res; int i, count; if (!(count = DP_QDCOUNT(dp))) return 0; /* No questions. */ for (i = 0; i < count; i++) { if ((res = d_qst_u(start_buf, buf + offset, dp, limit_len - offset)) == -1) { error(err_str); err_ret(ERR_DNSMDD, -1); } offset += res; } return offset; } /* * The behavior of this function is in all similar to dpkttoqst. * Returns -1 on error. Bytes readed otherwise. */ int d_a_u(char *start_buf, char *buf, dns_pkt_a ** dpa_orig, int limit_len) { int count, rdlen; dns_pkt_a *dpa; uint16_t s; uint32_t ui; dpa = dns_add_a(dpa_orig); /* get name */ if ((count = lbltoname(buf, start_buf, dpa->name, limit_len)) == -1) { error(err_str); err_ret(ERR_DNSMDD, -1); } buf += count; /* Now we have to write 2+2+4+2 bytes */ if (count + 10 > limit_len) err_ret(ERR_DNSPLB, -1); memcpy(&s, buf, 2); dpa->type = ntohs(s); count += 2; buf += 2; memcpy(&s, buf, 2); dpa->cl = ntohs(s); count += 2; buf += 2; memcpy(&ui, buf, 4); dpa->ttl = ntohl(ui); count += 4; buf += 4; memcpy(&s, buf, 2); dpa->rdlength = ntohs(s); count += 2; buf += 2; rdlen = dpa->rdlength; if (rdlen > DNS_MAX_HNAME_LEN) err_ret(ERR_DNSMDD, -1); /* Now we have to write dpa->rdlength bytes */ if (count + rdlen > limit_len) err_ret(ERR_DNSPLB, -1); if (dpa->type == T_A) { memcpy(dpa->rdata, buf, rdlen); /* 32bit address */ count += rdlen; } else if (dpa->type == T_MX) { memcpy(dpa->rdata, buf, 2); if ((ui = lbltoname(buf + 2, start_buf, dpa->rdata + 2, rdlen - 2)) == -1) { error(err_str); err_ret(ERR_DNSMDD, -1); } if (rdlen != ui + 2) { debug(DBG_NORMAL, "In d_a_u(): rdlen (%d) differs from readed bytes (%d).", rdlen, ui + 2); err_ret(ERR_DNSMDD, -1); } count += 2 + ui; } else { if ((ui = lbltoname(buf, start_buf, dpa->rdata, rdlen)) == -1) { error(err_str); err_intret(ERR_DNSMDD); } if (rdlen != ui) { debug(DBG_NORMAL, "In d_a_u(): rdlen (%d) differs from readed bytes (%d).", rdlen, ui); err_ret(ERR_DNSMDD, -1); } count += ui; } return count; } /* * like d_qs_u. count is the number of section to read. * -1 on error. Bytes readed otherwise. */ int d_as_u(char *start_buf, char *buf, dns_pkt_a ** dpa, int limit_len, int count) { int offset = 0, res; int i; if (!count) return 0; for (i = 0; i < count; i++) { if ((res = d_a_u(start_buf, buf + offset, dpa, limit_len - offset)) == -1) { error(err_str); err_intret(ERR_DNSMDD); } offset += res; } return offset; } /* * This is a main function: takes the pkt-buf and translate * it in structured data. * It cares about dns_pkt allocations. * * Returns: * -1 on E_INTRPRT * 0 if pkt must be discarded. * Number of bytes readed otherwise */ int d_u(char *buf, int pktlen, dns_pkt ** dpp) { dns_pkt *dp; int offset = 0, res; char *crow; crow = buf; /* Controls pkt consistency: we must at least read pkt headers */ if (pktlen < DNS_HDR_SZ) err_ret(ERR_DNSMDP, 0); *dpp = dp = create_dns_pkt(); /* Writes headers */ offset += d_hdr_u(buf, &(dp->pkt_hdr)); if (pktlen > DNS_MAX_SZ) /* If pkt is too long: the headers are written, * so we can reply with E_INTRPRT */ err_intret(ERR_DNSPLB); crow += offset; /* Writes qsts */ if (dp->pkt_hdr.qdcount) { if ((res = d_qsts_u(buf, crow, dp, pktlen - offset)) == -1) { error(err_str); err_intret(ERR_DNSMDP); } offset += res; crow += res; } if (dp->pkt_hdr.ancount) { if ((res = d_as_u(buf, crow, &(dp->pkt_answ), pktlen - offset, DP_ANCOUNT(dp))) == -1) { error(err_str); err_intret(ERR_DNSMDP); } offset += res; } /*crow+=res; if ((res=dpkttoas(buf,crow,&(dp->pkt_auth),pktlen-offset,DP_NSCOUNT(dp)))==-1) return -1; offset+=res; crow+=res; if ((res=dpkttoas(buf,crow,&(dp->pkt_add),pktlen-offset,DP_ARCOUNT(dp)))==-1) return -1; */ return offset; } /* * This function is the d_hdr_u inverse. * Takes a dns_pkt struct and builds the * header pkt-buffer * Returns the number of bytes writed. */ int d_hdr_p(dns_pkt * dp, char *buf) { char *crow = buf; uint16_t u; dns_pkt_hdr *dph; dph = &(dp->pkt_hdr); u = htons(dph->id); memcpy(buf, &u, 2); buf += 2; if (dph->qr) *buf |= 0x80; *buf |= dph->opcode << 3; *buf |= dph->aa << 2; *buf |= dph->tc << 1; *buf |= dph->rd; buf++; *buf |= dph->ra << 7; *buf |= dph->z << 4; *buf |= dph->rcode; buf++; u = htons(dph->qdcount); memcpy(buf, &u, 2); buf += 2; u = htons(dph->ancount); memcpy(buf, &u, 2); buf += 2; u = htons(dph->nscount); memcpy(buf, &u, 2); buf += 2; u = htons(dph->arcount); memcpy(buf, &u, 2); buf += 2; return (int) (buf - crow); } /* * Translate a struct dns_pkt_qst in the dns-buffer buf. * Returns: * -1 On error * Bytes writed otherwise. */ int d_qst_p(dns_pkt_qst * dpq, char *buf, int limitlen) { int offset; uint16_t u; if ((offset = nametolbl(dpq->qname, buf)) == -1) { error(err_str); err_ret(ERR_DNSMDA, -1); } if (offset + 4 > limitlen) err_ret(ERR_DNSPLB, -1); buf += offset; u = htons(dpq->qtype); memcpy(buf, &u, 2); buf += 2; offset += 2; u = htons(dpq->qclass); memcpy(buf, &u, 2); buf += 2; offset += 2; return offset; } /* * Translates the question sections of a struct dns_pkt * into buf. * Returns: * -1 on error. * Number of bytes writed otherwise, */ int d_qsts_p(dns_pkt * dp, char *buf, int limitlen) { int offset = 0, res; int i; dns_pkt_qst *dpq; dpq = dp->pkt_qst; for (i = 0; dpq && i < DP_QDCOUNT(dp); i++) { if ((res = d_qst_p(dpq, buf + offset, limitlen - offset)) == -1) { error(err_str); err_ret(ERR_DNSMDA, -1); } offset += res; dpq = dpq->next; } return offset; } int d_a_p(dns_pkt_a * dpa, char *buf, int limitlen) { int offset, rdlen; uint16_t u; int i; if ((rdlen = nametolbl(dpa->name, buf)) == -1) return -1; offset = rdlen; if (offset + 10 > limitlen) err_intret(ERR_DNSPLB); buf += offset; u = htons(dpa->type); memcpy(buf, &u, 2); buf += 2; offset += 2; u = htons(dpa->cl); memcpy(buf, &u, 2); buf += 2; offset += 2; i = htonl(dpa->ttl); memcpy(buf, &i, 4); buf += 4; offset += 4; if (dpa->type == T_A) { if (offset + dpa->rdlength > limitlen) err_intret(ERR_DNSPLB); memcpy(buf + 2, dpa->rdata, dpa->rdlength); offset += dpa->rdlength; } else if (dpa->type == T_MX) { memcpy(buf + 2, dpa->rdata, 2); if ((rdlen = nametolbl(dpa->rdata + 2, buf + 4)) == -1) { error(err_str); err_ret(ERR_DNSMDA, -1); } offset += rdlen + 2; if (offset > limitlen) err_ret(ERR_DNSPLB, -1); dpa->rdlength = rdlen + 2; } else { if ((rdlen = nametolbl(dpa->rdata, buf + 2)) == -1) { error(err_str); err_ret(ERR_DNSMDA, -1); } offset += rdlen; if (offset > limitlen) err_ret(ERR_DNSPLB, -1); dpa->rdlength = rdlen; } u = htons(dpa->rdlength); memcpy(buf, &u, 2); offset += 2; return offset; } int d_as_p(dns_pkt_a * dpa, char *buf, int limitlen, int count) { int offset = 0, res; int i; for (i = 0; dpa && i < count; i++) { if ((res = d_a_p(dpa, buf + offset, limitlen - offset)) == -1) { error(err_str); err_ret(ERR_DNSMDA, -1); } offset += res; dpa = dpa->next; } return offset; } /* * Transform a dns_pkt structure in char stream. * * Returns: * -1 on error * len(stream) if OK * * The stream has at least the header section writed. * `buf' must be at least of DNS_MAX_SZ bytes. * * DANGER: This function realeses *ALWAYS* the dns_pkt *dp!!!! */ int d_p(dns_pkt * dp, char *buf) { int offset, res; memset(buf, 0, DNS_MAX_SZ); offset = d_hdr_p(dp, buf); buf += offset; if ((res = d_qsts_p(dp, buf, DNS_MAX_SZ - offset)) == -1) goto server_fail; offset += res; buf += res; if ((res = d_as_p(dp->pkt_answ, buf, DNS_MAX_SZ - offset, DP_ANCOUNT(dp))) == -1) goto server_fail; offset += res; /*buf+=res; if ( (res=astodpkt(dp->pkt_auth,buf,DNS_MAX_SZ-offset,DP_NSCOUNT(dp)))==-1) goto server_fail; offset+=res; buf+=res; */ /*if ( (res=astodpkt(dp->pkt_add,buf,DNS_MAX_SZ-offset,DP_ARCOUNT(dp)))==-1) goto server_fail; offset+=res; */ destroy_dns_pkt(dp); return offset; server_fail: error(err_str); destroy_dns_pkt(dp); err_ret(ERR_DNSPDS, -1); } /* Memory Functions */ dns_pkt * create_dns_pkt(void) { dns_pkt *dp; dp = xmalloc(DNS_PKT_SZ); memset(dp, 0, DNS_PKT_SZ); dp->pkt_qst = NULL; dp->pkt_answ = NULL; dp->pkt_add = NULL; dp->pkt_auth = NULL; return dp; } dns_pkt_qst * create_dns_pkt_qst(void) { dns_pkt_qst *dpq; dpq = xmalloc(DNS_PKT_QST_SZ); dpq->next = NULL; memset(dpq->qname, 0, DNS_MAX_HNAME_LEN); return dpq; } dns_pkt_a * create_dns_pkt_a(void) { dns_pkt_a *dpa; dpa = xmalloc(DNS_PKT_A_SZ); memset(dpa->name, 0, DNS_MAX_HNAME_LEN); memset(dpa->rdata, 0, DNS_MAX_HNAME_LEN); dpa->next = NULL; return dpa; } dns_pkt_qst * dns_add_qst(dns_pkt * dp) { dns_pkt_qst *dpq, *temp; dpq = create_dns_pkt_qst(); temp = dp->pkt_qst; if (!temp) { dp->pkt_qst = dpq; return dpq; } while (temp->next) temp = temp->next; temp->next = dpq; return dpq; } void dns_del_last_qst(dns_pkt * dp) { dns_pkt_qst *dpq = dp->pkt_qst; if (!dpq) return; if (!(dpq->next)) { xfree(dpq); dp->pkt_qst = NULL; return; } while ((dpq->next)->next); xfree(dpq->next); dpq->next = NULL; return; } dns_pkt_a * dns_add_a(dns_pkt_a ** dpa) { dns_pkt_a *dpa_add, *a; int count = 0; a = *dpa; dpa_add = create_dns_pkt_a(); if (!a) { (*dpa) = dpa_add; } else { while (a->next) { a = a->next; count++; } a->next = dpa_add; } return dpa_add; } void dns_a_default_fill(dns_pkt * dp, dns_pkt_a * dpa) { strcpy(dpa->name, dp->pkt_qst->qname); dpa->cl = C_IN; dpa->ttl = DNS_TTL; dpa->type = dp->pkt_qst->qtype; } void destroy_dns_pkt(dns_pkt * dp) { dns_pkt_a *dpa, *dpa_t; dns_pkt_qst *dpq, *dpq_t; if (dp->pkt_qst) { dpq = dp->pkt_qst; while (dpq) { dpq_t = dpq->next; xfree(dpq); dpq = dpq_t; } } if (dp->pkt_answ) { dpa = dp->pkt_answ; while (dpa) { dpa_t = dpa->next; xfree(dpa); dpa = dpa_t; } } if (dp->pkt_add) { dpa = dp->pkt_add; while (dpa) { dpa_t = dpa->next; xfree(dpa); dpa = dpa_t; } } if (dp->pkt_auth) { dpa = dp->pkt_auth; while (dpa) { dpa_t = dpa->next; xfree(dpa); dpa = dpa_t; } } xfree(dp); return; }