Imported Upstream version 2.88
[platform/upstream/dnsmasq.git] / src / rrfilter.c
1 /* dnsmasq is Copyright (c) 2000-2022 Simon Kelley
2
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; version 2 dated June, 1991, or
6    (at your option) version 3 dated 29 June, 2007.
7  
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12      
13    You should have received a copy of the GNU General Public License
14    along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17 /* Code to safely remove RRs from a DNS answer */ 
18
19 #include "dnsmasq.h"
20
21 /* Go through a domain name, find "pointers" and fix them up based on how many bytes
22    we've chopped out of the packet, or check they don't point into an elided part.  */
23 static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
24 {
25   unsigned char *ansp = *namep;
26
27   while(1)
28     {
29       unsigned int label_type;
30       
31       if (!CHECK_LEN(header, ansp, plen, 1))
32         return 0;
33       
34       label_type = (*ansp) & 0xc0;
35
36       if (label_type == 0xc0)
37         {
38           /* pointer for compression. */
39           unsigned int offset;
40           int i;
41           unsigned char *p;
42           
43           if (!CHECK_LEN(header, ansp, plen, 2))
44             return 0;
45
46           offset = ((*ansp++) & 0x3f) << 8;
47           offset |= *ansp++;
48
49           p = offset + (unsigned char *)header;
50           
51           for (i = 0; i < rr_count; i++)
52             if (p < rrs[i])
53               break;
54             else
55               if (i & 1)
56                 offset -= rrs[i] - rrs[i-1];
57
58           /* does the pointer end up in an elided RR? */
59           if (i & 1)
60             return 0;
61
62           /* No, scale the pointer */
63           if (fixup)
64             {
65               ansp -= 2;
66               *ansp++ = (offset >> 8) | 0xc0;
67               *ansp++ = offset & 0xff;
68             }
69           break;
70         }
71       else if (label_type == 0x80)
72         return 0; /* reserved */
73       else if (label_type == 0x40)
74         {
75           /* Extended label type */
76           unsigned int count;
77           
78           if (!CHECK_LEN(header, ansp, plen, 2))
79             return 0;
80           
81           if (((*ansp++) & 0x3f) != 1)
82             return 0; /* we only understand bitstrings */
83           
84           count = *(ansp++); /* Bits in bitstring */
85           
86           if (count == 0) /* count == 0 means 256 bits */
87             ansp += 32;
88           else
89             ansp += ((count-1)>>3)+1;
90         }
91       else
92         { /* label type == 0 Bottom six bits is length */
93           unsigned int len = (*ansp++) & 0x3f;
94           
95           if (!ADD_RDLEN(header, ansp, plen, len))
96             return 0;
97
98           if (len == 0)
99             break; /* zero length label marks the end. */
100         }
101     }
102
103   *namep = ansp;
104
105   return 1;
106 }
107
108 /* Go through RRs and check or fixup the domain names contained within */
109 static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
110 {
111   int i, j, type, class, rdlen;
112   unsigned char *pp;
113   
114   for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++)
115     {
116       pp = p;
117
118       if (!(p = skip_name(p, header, plen, 10)))
119         return 0;
120       
121       GETSHORT(type, p); 
122       GETSHORT(class, p);
123       p += 4; /* TTL */
124       GETSHORT(rdlen, p);
125
126       /* If this RR is to be elided, don't fix up its contents */
127       for (j = 0; j < rr_count; j += 2)
128         if (rrs[j] == pp)
129           break;
130
131       if (j >= rr_count)
132         {
133           /* fixup name of RR */
134           if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
135             return 0;
136           
137           if (class == C_IN)
138             {
139               u16 *d;
140  
141               for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++)
142                 {
143                   if (*d != 0)
144                     pp += *d;
145                   else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
146                     return 0;
147                 }
148             }
149         }
150       
151       if (!ADD_RDLEN(header, p, plen, rdlen))
152         return 0;
153     }
154   
155   return 1;
156 }
157         
158
159 /* mode may be remove EDNS0 or DNSSEC RRs or remove A or AAAA from answer section. */
160 size_t rrfilter(struct dns_header *header, size_t plen, int mode)
161 {
162   static unsigned char **rrs = NULL;
163   static int rr_sz = 0;
164
165   unsigned char *p = (unsigned char *)(header+1);
166   int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar;
167
168   if (ntohs(header->qdcount) != 1 ||
169       !(p = skip_name(p, header, plen, 4)))
170     return plen;
171   
172   GETSHORT(qtype, p);
173   GETSHORT(qclass, p);
174
175   /* First pass, find pointers to start and end of all the records we wish to elide:
176      records added for DNSSEC, unless explicitly queried for */
177   for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; 
178        i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount);
179        i++)
180     {
181       unsigned char *pstart = p;
182       int type, class;
183
184       if (!(p = skip_name(p, header, plen, 10)))
185         return plen;
186       
187       GETSHORT(type, p); 
188       GETSHORT(class, p);
189       p += 4; /* TTL */
190       GETSHORT(rdlen, p);
191         
192       if (!ADD_RDLEN(header, p, plen, rdlen))
193         return plen;
194
195       if (mode == RRFILTER_EDNS0) /* EDNS */
196         {
197           /* EDNS mode, remove T_OPT from additional section only */
198           if (i < (ntohs(header->nscount) + ntohs(header->ancount)) || type != T_OPT)
199             continue;
200         }
201       else if (mode == RRFILTER_DNSSEC)
202         {
203           if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
204             /* DNSSEC mode, remove SIGs and NSECs from all three sections. */
205             continue;
206
207           /* Don't remove the answer. */
208           if (i < ntohs(header->ancount) && type == qtype && class == qclass)
209             continue;
210         }
211       else
212         {
213           /* Only looking at answer section now. */
214           if (i >= ntohs(header->ancount))
215             break;
216
217           if (class != C_IN)
218             continue;
219           
220           if (mode == RRFILTER_A && type != T_A)
221             continue;
222
223           if (mode == RRFILTER_AAAA && type != T_AAAA)
224             continue;
225         }
226       
227       if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
228         return plen; 
229       
230       rrs[rr_found++] = pstart;
231       rrs[rr_found++] = p;
232       
233       if (i < ntohs(header->ancount))
234         chop_an++;
235       else if (i < (ntohs(header->nscount) + ntohs(header->ancount)))
236         chop_ns++;
237       else
238         chop_ar++;
239     }
240   
241   /* Nothing to do. */
242   if (rr_found == 0)
243     return plen;
244
245   /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
246      point to records we're going to elide. This is theoretically possible, but unlikely. If
247      it happens, we give up and leave the answer unchanged. */
248   p = (unsigned char *)(header+1);
249   
250   /* question first */
251   if (!check_name(&p, header, plen, 0, rrs, rr_found))
252     return plen;
253   p += 4; /* qclass, qtype */
254   
255   /* Now answers and NS */
256   if (!check_rrs(p, header, plen, 0, rrs, rr_found))
257     return plen;
258   
259   /* Third pass, actually fix up pointers in the records */
260   p = (unsigned char *)(header+1);
261   
262   check_name(&p, header, plen, 1, rrs, rr_found);
263   p += 4; /* qclass, qtype */
264   
265   check_rrs(p, header, plen, 1, rrs, rr_found);
266
267   /* Fourth pass, elide records */
268   for (p = rrs[0], i = 1; i < rr_found; i += 2)
269     {
270       unsigned char *start = rrs[i];
271       unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)header) + plen;
272       
273       memmove(p, start, end-start);
274       p += end-start;
275     }
276      
277   plen = p - (unsigned char *)header;
278   header->ancount = htons(ntohs(header->ancount) - chop_an);
279   header->nscount = htons(ntohs(header->nscount) - chop_ns);
280   header->arcount = htons(ntohs(header->arcount) - chop_ar);
281
282   return plen;
283 }
284
285 /* This is used in the DNSSEC code too, hence it's exported */
286 u16 *rrfilter_desc(int type)
287 {
288   /* List of RRtypes which include domains in the data.
289      0 -> domain
290      integer -> no. of plain bytes
291      -1 -> end
292
293      zero is not a valid RRtype, so the final entry is returned for
294      anything which needs no mangling.
295   */
296   
297   static u16 rr_desc[] = 
298     { 
299       T_NS, 0, -1, 
300       T_MD, 0, -1,
301       T_MF, 0, -1,
302       T_CNAME, 0, -1,
303       T_SOA, 0, 0, -1,
304       T_MB, 0, -1,
305       T_MG, 0, -1,
306       T_MR, 0, -1,
307       T_PTR, 0, -1,
308       T_MINFO, 0, 0, -1,
309       T_MX, 2, 0, -1,
310       T_RP, 0, 0, -1,
311       T_AFSDB, 2, 0, -1,
312       T_RT, 2, 0, -1,
313       T_SIG, 18, 0, -1,
314       T_PX, 2, 0, 0, -1,
315       T_NXT, 0, -1,
316       T_KX, 2, 0, -1,
317       T_SRV, 6, 0, -1,
318       T_DNAME, 0, -1,
319       0, -1 /* wildcard/catchall */
320     }; 
321   
322   u16 *p = rr_desc;
323   
324   while (*p != type && *p != 0)
325     while (*p++ != (u16)-1);
326
327   return p+1;
328 }
329
330 int expand_workspace(unsigned char ***wkspc, int *szp, int new)
331 {
332   unsigned char **p;
333   int old = *szp;
334
335   if (old >= new+1)
336     return 1;
337
338   if (new >= 100)
339     return 0;
340
341   new += 5;
342
343   if (!(p = whine_realloc(*wkspc, new * sizeof(unsigned char *))))
344     return 0;
345
346   memset(p+old, 0, new-old);
347   
348   *wkspc = p;
349   *szp = new;
350
351   return 1;
352 }