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