1 /*
2  * DNS library functions
3  * Copyright (c) 2006-2008 David Bird <david@coova.com>
4  *
5  * The contents of this file may be used under the terms of the GNU
6  * General Public License Version 2, provided that the above copyright
7  * notice and this permission notice is included in all copies or
8  * substantial portions of the software.
9  *
10  */
11 
12 #include "dns.h"
13 #include "garden.h"
14 #include "syserr.h"
15 #include "dhcp.h"
16 #include "options.h"
17 
18 #define antidnstunnel options.dnsparanoia
19 
20 extern struct dhcp_t *dhcp;
21 
22 char *
dns_fullname(char * data,size_t dlen,uint8_t * res,uint8_t * opkt,size_t olen,int lvl)23 dns_fullname(char *data, size_t dlen, uint8_t *res, uint8_t *opkt, size_t olen, int lvl) {
24   char *d = data;
25   unsigned short l;
26 
27   if (lvl >= 15)
28     return 0;
29 
30   while ((l = *res++) != 0) {
31     if ((l & 0xC0) == 0xC0) {
32       unsigned short offset = ((l & ~0xC0) << 8) + *res;
33       if (offset > olen) {
34 	log_dbg("bad value");
35 	return 0;
36       }
37       /*log_dbg("skip[%d]\n",offset);*/
38       dns_fullname(d, dlen, opkt + (size_t)offset, opkt, olen, lvl+1);
39       break;
40     }
41 
42     if (l >= dlen) {
43       log_dbg("bad value");
44       return 0;
45     }
46 
47     /*log_dbg("part[%.*s]\n",l,res);*/
48 
49     memcpy(d, res, l);
50     d += l; dlen -= l;
51     res += l;
52 
53     *d = '.';
54     d += 1; dlen -= 1;
55   }
56 
57   if (!lvl && data[strlen((char*)data)-1]=='.')
58     data[strlen((char*)data)-1]=0;
59 
60   return data;
61 }
62 
dns_getname(uint8_t ** pktp,size_t * left,char * name,size_t namesz,size_t * nameln)63 int dns_getname(uint8_t **pktp, size_t *left, char *name, size_t namesz, size_t *nameln) {
64   size_t namelen = *nameln;
65   uint8_t *p_pkt = *pktp;
66   size_t len = *left;
67   uint8_t l;
68 
69   namelen = 0;
70   while (len-- && (l = name[namelen++] = *p_pkt++) != 0) {
71     if ((l & 0xC0) == 0xC0) {
72       if (namelen >= namesz) {
73 	log_err(0, "name too long in DNS packet");
74 	break;
75       }
76       name[namelen++] = *p_pkt++;
77       len--;
78       break;
79     }
80   }
81 
82   *pktp = p_pkt;
83   *nameln = namelen;
84   *left = len;
85 
86   if (!len) {
87     log_err(0, "failed to parse DNS packet");
88     return -1;
89   }
90 
91   return 0;
92 }
93 
94 static void
add_A_to_garden(uint8_t * p)95 add_A_to_garden(uint8_t *p) {
96   struct in_addr reqaddr;
97   pass_through pt;
98   memcpy(&reqaddr.s_addr, p, 4);
99   memset(&pt, 0, sizeof(pass_through));
100   pt.mask.s_addr = 0xffffffff;
101   pt.host = reqaddr;
102   if (pass_through_add(dhcp->pass_throughs,
103 		       MAX_PASS_THROUGHS,
104 		       &dhcp->num_pass_throughs,
105 		       &pt))
106     ;
107 }
108 
109 int
dns_copy_res(int q,uint8_t ** pktp,size_t * left,uint8_t * opkt,size_t olen,char * question,size_t qsize)110 dns_copy_res(int q,
111 	     uint8_t **pktp, size_t *left,
112 	     uint8_t *opkt,  size_t olen,
113 	     char *question, size_t qsize) {
114 
115 #define return_error \
116 { log_dbg("%s:%d: failed parsing DNS packet",__FILE__,__LINE__); return -1; }
117 
118   uint8_t *p_pkt = *pktp;
119   size_t len = *left;
120 
121   uint8_t name[PKT_IP_PLEN];
122   size_t namelen = 0;
123 
124   uint16_t type;
125   uint16_t class;
126   uint32_t ttl;
127   uint16_t rdlen;
128 
129   uint32_t ul;
130   uint16_t us;
131 
132   if (dns_getname(&p_pkt, &len, (char *)name, sizeof(name), &namelen))
133     return_error;
134 
135   if (antidnstunnel && namelen > 128) {
136     log_warn(0,"dropping dns for anti-dnstunnel (namelen: %d)",namelen);
137     return -1;
138   }
139 
140   if (len < 4)
141     return_error;
142 
143   memcpy(&us, p_pkt, sizeof(us));
144   type = ntohs(us);
145   p_pkt += 2;
146   len -= 2;
147 
148   memcpy(&us, p_pkt, sizeof(us));
149   class = ntohs(us);
150   p_pkt += 2;
151   len -= 2;
152 
153   log_dbg("It was a dns record type: %d class: %d", type, class);
154 
155 
156   /* if dnsparanoia, checks here */
157 
158   if (antidnstunnel) {
159     switch (type) {
160     case 1:/* A */
161       log_dbg("A record");
162       break;
163     case 5:/* CNAME */
164       log_dbg("CNAME record");
165       break;
166     default:
167       if (options.debug) switch(type) {
168       case 6:  log_dbg("SOA record"); break;
169       case 12: log_dbg("PTR record"); break;
170       case 15: log_dbg("MX record");  break;
171       case 16: log_dbg("TXT record"); break;
172       default: log_dbg("Record type %d", type); break;
173       }
174       log_warn(0, "dropping dns for anti-dnstunnel (type %d: length %d)", type, rdlen);
175       return -1;
176     }
177   }
178 
179   if (q) {
180     dns_fullname(question, qsize, *pktp, opkt, olen, 0);
181 
182     log_dbg("Q: %s", question);
183 
184     *pktp = p_pkt;
185     *left = len;
186 
187     return 0;
188   }
189 
190   if (len < 6)
191     return_error;
192 
193   memcpy(&ul, p_pkt, sizeof(ul));
194   ttl = ntohl(ul);
195   p_pkt += 4;
196   len -= 4;
197 
198   memcpy(&us, p_pkt, sizeof(us));
199   rdlen = ntohs(us);
200   p_pkt += 2;
201   len -= 2;
202 
203   /*log_dbg("-> w ttl: %d rdlength: %d/%d", ttl, rdlen, len);*/
204 
205   if (len < rdlen)
206     return_error;
207 
208   /*
209    *  dns records
210    */
211 
212   switch (type) {
213 
214   case 1:/* A */
215     log_dbg("A record");
216     if (options.uamdomains) {
217       int id;
218       for (id=0; options.uamdomains[id]; id++) {
219 
220 	log_dbg("checking %s [%s]", options.uamdomains[id], question);
221 
222 	if (strlen(question) >= strlen(options.uamdomains[id]) &&
223 	    !strcmp(options.uamdomains[id],
224 		    question + (strlen(question) - strlen(options.uamdomains[id])))) {
225 	  size_t offset;
226 	  for (offset=0; offset < rdlen; offset += 4) {
227 	    add_A_to_garden(p_pkt+offset);
228 	  }
229 
230 	  break;
231 	}
232       }
233     }
234     break;
235 
236   case 5:/* CNAME */
237     {
238       char cname[256];
239       memset(cname,0,sizeof(cname));
240       dns_fullname(cname, sizeof(cname)-1, p_pkt, opkt, olen, 0);
241       log_dbg("CNAME record %s", cname);
242     }
243     break;
244 
245   default:
246 
247     if (options.debug) switch(type) {
248     case 6:  log_dbg("SOA record"); break;
249     case 12: log_dbg("PTR record"); break;
250     case 15: log_dbg("MX record");  break;
251     case 16: log_dbg("TXT record"); break;
252     default: log_dbg("Record type %d", type); break;
253     }
254 
255     if (antidnstunnel) {
256       log_warn(0, "dropping dns for anti-dnstunnel (type %d: length %d)", type, rdlen);
257       return -1;
258     }
259 
260     break;
261   }
262 
263   p_pkt += rdlen;
264   len -= rdlen;
265 
266   *pktp = p_pkt;
267   *left = len;
268 
269   return 0;
270 }
271