1 /*
2 * Copyright (C) 2012 by Darren Reed.
3 *
4 * See the IPFILTER.LICENCE file for details on licencing.
5 *
6 * $Id: ip_dns_pxy.c,v 1.1.2.10 2012/07/22 08:04:23 darren_r Exp $
7 */
8
9 #define IPF_DNS_PROXY
10
11 /*
12 * map ... proxy port dns/udp 53 { block .cnn.com; }
13 */
14 typedef struct ipf_dns_filter {
15 struct ipf_dns_filter *idns_next;
16 char *idns_name;
17 int idns_namelen;
18 int idns_pass;
19 } ipf_dns_filter_t;
20
21
22 typedef struct ipf_dns_softc_s {
23 ipf_dns_filter_t *ipf_p_dns_list;
24 ipfrwlock_t ipf_p_dns_rwlock;
25 u_long ipf_p_dns_compress;
26 u_long ipf_p_dns_toolong;
27 u_long ipf_p_dns_nospace;
28 } ipf_dns_softc_t;
29
30 int ipf_p_dns_allow_query(ipf_dns_softc_t *, dnsinfo_t *);
31 int ipf_p_dns_ctl(ipf_main_softc_t *, void *, ap_ctl_t *);
32 void ipf_p_dns_del(ipf_main_softc_t *, ap_session_t *);
33 int ipf_p_dns_get_name(ipf_dns_softc_t *, char *, int, char *, int);
34 int ipf_p_dns_inout(void *, fr_info_t *, ap_session_t *, nat_t *);
35 int ipf_p_dns_match(fr_info_t *, ap_session_t *, nat_t *);
36 int ipf_p_dns_match_names(ipf_dns_filter_t *, char *, int);
37 int ipf_p_dns_new(void *, fr_info_t *, ap_session_t *, nat_t *);
38 void *ipf_p_dns_soft_create(ipf_main_softc_t *);
39 void ipf_p_dns_soft_destroy(ipf_main_softc_t *, void *);
40
41 typedef struct {
42 u_char dns_id[2];
43 u_short dns_ctlword;
44 u_short dns_qdcount;
45 u_short dns_ancount;
46 u_short dns_nscount;
47 u_short dns_arcount;
48 } ipf_dns_hdr_t;
49
50 #define DNS_QR(x) ((ntohs(x) & 0x8000) >> 15)
51 #define DNS_OPCODE(x) ((ntohs(x) & 0x7800) >> 11)
52 #define DNS_AA(x) ((ntohs(x) & 0x0400) >> 10)
53 #define DNS_TC(x) ((ntohs(x) & 0x0200) >> 9)
54 #define DNS_RD(x) ((ntohs(x) & 0x0100) >> 8)
55 #define DNS_RA(x) ((ntohs(x) & 0x0080) >> 7)
56 #define DNS_Z(x) ((ntohs(x) & 0x0070) >> 4)
57 #define DNS_RCODE(x) ((ntohs(x) & 0x000f) >> 0)
58
59
60 void *
ipf_p_dns_soft_create(ipf_main_softc_t * softc)61 ipf_p_dns_soft_create(ipf_main_softc_t *softc)
62 {
63 ipf_dns_softc_t *softd;
64
65 KMALLOC(softd, ipf_dns_softc_t *);
66 if (softd == NULL)
67 return (NULL);
68
69 bzero((char *)softd, sizeof(*softd));
70 RWLOCK_INIT(&softd->ipf_p_dns_rwlock, "ipf dns rwlock");
71
72 return (softd);
73 }
74
75
76 void
ipf_p_dns_soft_destroy(ipf_main_softc_t * softc,void * arg)77 ipf_p_dns_soft_destroy(ipf_main_softc_t *softc, void *arg)
78 {
79 ipf_dns_softc_t *softd = arg;
80 ipf_dns_filter_t *idns;
81
82 while ((idns = softd->ipf_p_dns_list) != NULL) {
83 KFREES(idns->idns_name, idns->idns_namelen);
84 idns->idns_name = NULL;
85 idns->idns_namelen = 0;
86 softd->ipf_p_dns_list = idns->idns_next;
87 KFREE(idns);
88 }
89 RW_DESTROY(&softd->ipf_p_dns_rwlock);
90
91 KFREE(softd);
92 }
93
94
95 int
ipf_p_dns_ctl(ipf_main_softc_t * softc,void * arg,ap_ctl_t * ctl)96 ipf_p_dns_ctl(ipf_main_softc_t *softc, void *arg, ap_ctl_t *ctl)
97 {
98 ipf_dns_softc_t *softd = arg;
99 ipf_dns_filter_t *tmp, *idns, **idnsp;
100 int error = 0;
101
102 /*
103 * To make locking easier.
104 */
105 KMALLOC(tmp, ipf_dns_filter_t *);
106
107 WRITE_ENTER(&softd->ipf_p_dns_rwlock);
108 for (idnsp = &softd->ipf_p_dns_list; (idns = *idnsp) != NULL;
109 idnsp = &idns->idns_next) {
110 if (idns->idns_namelen != ctl->apc_dsize)
111 continue;
112 if (!strncmp(ctl->apc_data, idns->idns_name,
113 idns->idns_namelen))
114 break;
115 }
116
117 switch (ctl->apc_cmd)
118 {
119 case APC_CMD_DEL :
120 if (idns == NULL) {
121 IPFERROR(80006);
122 error = ESRCH;
123 break;
124 }
125 *idnsp = idns->idns_next;
126 idns->idns_next = NULL;
127 KFREES(idns->idns_name, idns->idns_namelen);
128 idns->idns_name = NULL;
129 idns->idns_namelen = 0;
130 KFREE(idns);
131 break;
132 case APC_CMD_ADD :
133 if (idns != NULL) {
134 IPFERROR(80007);
135 error = EEXIST;
136 break;
137 }
138 if (tmp == NULL) {
139 IPFERROR(80008);
140 error = ENOMEM;
141 break;
142 }
143 idns = tmp;
144 tmp = NULL;
145 idns->idns_namelen = ctl->apc_dsize;
146 idns->idns_name = ctl->apc_data;
147 idns->idns_pass = ctl->apc_arg;
148 idns->idns_next = NULL;
149 *idnsp = idns;
150 ctl->apc_data = NULL;
151 ctl->apc_dsize = 0;
152 break;
153 default :
154 IPFERROR(80009);
155 error = EINVAL;
156 break;
157 }
158 RWLOCK_EXIT(&softd->ipf_p_dns_rwlock);
159
160 if (tmp != NULL) {
161 KFREE(tmp);
162 tmp = NULL;
163 }
164
165 return (error);
166 }
167
168
169 /* ARGSUSED */
170 int
ipf_p_dns_new(void * arg,fr_info_t * fin,ap_session_t * aps,nat_t * nat)171 ipf_p_dns_new(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat)
172 {
173 dnsinfo_t *di;
174 int dlen;
175
176 if (fin->fin_v != 4)
177 return (-1);
178
179 dlen = fin->fin_dlen - sizeof(udphdr_t);
180 if (dlen < sizeof(ipf_dns_hdr_t)) {
181 /*
182 * No real DNS packet is smaller than that.
183 */
184 return (-1);
185 }
186
187 aps->aps_psiz = sizeof(dnsinfo_t);
188 KMALLOCS(di, dnsinfo_t *, sizeof(dnsinfo_t));
189 if (di == NULL) {
190 printf("ipf_dns_new:KMALLOCS(%d) failed\n", sizeof(*di));
191 return (-1);
192 }
193
194 MUTEX_INIT(&di->dnsi_lock, "dns lock");
195
196 aps->aps_data = di;
197
198 dlen = fin->fin_dlen - sizeof(udphdr_t);
199 COPYDATA(fin->fin_m, fin->fin_hlen + sizeof(udphdr_t),
200 MIN(dlen, sizeof(di->dnsi_buffer)), di->dnsi_buffer);
201 di->dnsi_id = (di->dnsi_buffer[0] << 8) | di->dnsi_buffer[1];
202 return (0);
203 }
204
205
206 /* ARGSUSED */
207 void
ipf_p_dns_del(ipf_main_softc_t * softc,ap_session_t * aps)208 ipf_p_dns_del(ipf_main_softc_t *softc, ap_session_t *aps)
209 {
210 #ifdef USE_MUTEXES
211 dnsinfo_t *di = aps->aps_data;
212
213 MUTEX_DESTROY(&di->dnsi_lock);
214 #endif
215 KFREES(aps->aps_data, aps->aps_psiz);
216 aps->aps_data = NULL;
217 aps->aps_psiz = 0;
218 }
219
220
221 /*
222 * Tries to match the base string (in our ACL) with the query from a packet.
223 */
224 int
ipf_p_dns_match_names(ipf_dns_filter_t * idns,char * query,int qlen)225 ipf_p_dns_match_names(ipf_dns_filter_t *idns, char *query, int qlen)
226 {
227 int blen;
228 char *base;
229
230 blen = idns->idns_namelen;
231 base = idns->idns_name;
232
233 if (blen > qlen)
234 return (1);
235
236 if (blen == qlen)
237 return (strncasecmp(base, query, qlen));
238
239 /*
240 * If the base string string is shorter than the query, allow the
241 * tail of the base to match the same length tail of the query *if*:
242 * - the base string starts with a '*' (*cnn.com)
243 * - the base string represents a domain (.cnn.com)
244 * as otherwise it would not be possible to block just "cnn.com"
245 * without also impacting "foocnn.com", etc.
246 */
247 if (*base == '*') {
248 base++;
249 blen--;
250 } else if (*base != '.')
251 return (1);
252
253 return (strncasecmp(base, query + qlen - blen, blen));
254 }
255
256
257 int
ipf_p_dns_get_name(ipf_dns_softc_t * softd,char * start,int len,char * buffer,int buflen)258 ipf_p_dns_get_name(ipf_dns_softc_t *softd, char *start, int len,
259 char *buffer, int buflen)
260 {
261 char *s, *t, clen;
262 int slen, blen;
263
264 s = start;
265 t = buffer;
266 slen = len;
267 blen = buflen - 1; /* Always make room for trailing \0 */
268
269 while (*s != '\0') {
270 clen = *s;
271 if ((clen & 0xc0) == 0xc0) { /* Doesn't do compression */
272 softd->ipf_p_dns_compress++;
273 return (0);
274 }
275 if (clen > slen) {
276 softd->ipf_p_dns_toolong++;
277 return (0); /* Does the name run off the end? */
278 }
279 if ((clen + 1) > blen) {
280 softd->ipf_p_dns_nospace++;
281 return (0); /* Enough room for name+.? */
282 }
283 s++;
284 bcopy(s, t, clen);
285 t += clen;
286 s += clen;
287 *t++ = '.';
288 slen -= clen;
289 blen -= (clen + 1);
290 }
291
292 *(t - 1) = '\0';
293 return (s - start);
294 }
295
296
297 int
ipf_p_dns_allow_query(ipf_dns_softc_t * softd,dnsinfo_t * dnsi)298 ipf_p_dns_allow_query(ipf_dns_softc_t *softd, dnsinfo_t *dnsi)
299 {
300 ipf_dns_filter_t *idns;
301 int len;
302
303 len = strlen(dnsi->dnsi_buffer);
304
305 for (idns = softd->ipf_p_dns_list; idns != NULL; idns = idns->idns_next)
306 if (ipf_p_dns_match_names(idns, dnsi->dnsi_buffer, len) == 0)
307 return (idns->idns_pass);
308 return (0);
309 }
310
311
312 /* ARGSUSED */
313 int
ipf_p_dns_inout(void * arg,fr_info_t * fin,ap_session_t * aps,nat_t * nat)314 ipf_p_dns_inout(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat)
315 {
316 ipf_dns_softc_t *softd = arg;
317 ipf_dns_hdr_t *dns;
318 dnsinfo_t *di;
319 char *data;
320 int dlen, q, rc = 0;
321
322 if (fin->fin_dlen < sizeof(*dns))
323 return (APR_ERR(1));
324
325 dns = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
326
327 q = dns->dns_qdcount;
328
329 data = (char *)(dns + 1);
330 dlen = fin->fin_dlen - sizeof(*dns) - sizeof(udphdr_t);
331
332 di = aps->aps_data;
333
334 READ_ENTER(&softd->ipf_p_dns_rwlock);
335 MUTEX_ENTER(&di->dnsi_lock);
336
337 for (; (dlen > 0) && (q > 0); q--) {
338 int len;
339
340 len = ipf_p_dns_get_name(softd, data, dlen, di->dnsi_buffer,
341 sizeof(di->dnsi_buffer));
342 if (len == 0) {
343 rc = 1;
344 break;
345 }
346 rc = ipf_p_dns_allow_query(softd, di);
347 if (rc != 0)
348 break;
349 data += len;
350 dlen -= len;
351 }
352 MUTEX_EXIT(&di->dnsi_lock);
353 RWLOCK_EXIT(&softd->ipf_p_dns_rwlock);
354
355 return (APR_ERR(rc));
356 }
357
358
359 /* ARGSUSED */
360 int
ipf_p_dns_match(fr_info_t * fin,ap_session_t * aps,nat_t * nat)361 ipf_p_dns_match(fr_info_t *fin, ap_session_t *aps, nat_t *nat)
362 {
363 dnsinfo_t *di = aps->aps_data;
364 ipf_dns_hdr_t *dnh;
365
366 if ((fin->fin_dlen < sizeof(u_short)) || (fin->fin_flx & FI_FRAG))
367 ( return (-1);
368
369 dnh = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
370 if (((dnh->dns_id[0] << 8) | dnh->dns_id[1]) != di->dnsi_id)
371 return (-1);
372 return (0);
373 }
374