ip_dns_pxy.c revision 1.1 1 /* $NetBSD: ip_dns_pxy.c,v 1.1 2012/03/23 20:36:53 christos Exp $ */
2
3 /*
4 * Copyright (C) 2010 by Darren Reed.
5 *
6 * See the IPFILTER.LICENCE file for details on licencing.
7 *
8 * Id: ip_dns_pxy.c,v 1.1.2.8 2012/01/29 05:30:35 darren_r 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 *
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
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
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
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 dlen = fin->fin_dlen - sizeof(udphdr_t);
189 if (dlen < sizeof(ipf_dns_hdr_t)) {
190 /*
191 * No real DNS packet is smaller than that.
192 */
193 return -1;
194 }
195
196 aps->aps_psiz = sizeof(dnsinfo_t);
197 KMALLOCS(di, dnsinfo_t *, sizeof(dnsinfo_t));
198 if (di == NULL) {
199 printf("ipf_dns_new:KMALLOCS(%d) failed\n", sizeof(*di));
200 return -1;
201 }
202
203 MUTEX_INIT(&di->dnsi_lock, "dns lock");
204
205 aps->aps_data = di;
206
207 dlen = fin->fin_dlen - sizeof(udphdr_t);
208 COPYDATA(fin->fin_m, fin->fin_hlen + sizeof(udphdr_t),
209 MIN(dlen, sizeof(di->dnsi_buffer)), di->dnsi_buffer);
210 di->dnsi_id = (di->dnsi_buffer[0] << 8) | di->dnsi_buffer[1];
211 return 0;
212 }
213
214
215 /* ARGSUSED */
216 int
217 ipf_p_dns_del(softc, aps)
218 ipf_main_softc_t *softc;
219 ap_session_t *aps;
220 {
221 #ifdef USE_MUTEXES
222 dnsinfo_t *di = aps->aps_data;
223
224 MUTEX_DESTROY(&di->dnsi_lock);
225 #endif
226 KFREES(aps->aps_data, aps->aps_psiz);
227 aps->aps_data = NULL;
228 aps->aps_psiz = 0;
229 return 0;
230 }
231
232
233 /*
234 * Tries to match the base string (in our ACL) with the query from a packet.
235 */
236 int
237 ipf_p_dns_match_names(idns, query, qlen)
238 ipf_dns_filter_t *idns;
239 char *query;
240 int qlen;
241 {
242 int blen;
243 char *base;
244
245 blen = idns->idns_namelen;
246 base = idns->idns_name;
247
248 if (blen > qlen)
249 return 1;
250
251 if (blen == qlen)
252 return strncasecmp(base, query, qlen);
253
254 /*
255 * If the base string string is shorter than the query, allow the
256 * tail of the base to match the same length tail of the query *if*:
257 * - the base string starts with a '*' (*cnn.com)
258 * - the base string represents a domain (.cnn.com)
259 * as otherwise it would not be possible to block just "cnn.com"
260 * without also impacting "foocnn.com", etc.
261 */
262 if (*base == '*') {
263 base++;
264 blen--;
265 } else if (*base != '.')
266 return 1;
267
268 return strncasecmp(base, query + qlen - blen, blen);
269 }
270
271
272 int
273 ipf_p_dns_get_name(softd, start, len, buffer, buflen)
274 ipf_dns_softc_t *softd;
275 char *start;
276 int len;
277 char *buffer;
278 int buflen;
279 {
280 char *s, *t, clen;
281 int slen, blen;
282
283 s = start;
284 t = buffer;
285 slen = len;
286 blen = buflen - 1; /* Always make room for trailing \0 */
287
288 while (*s != '\0') {
289 clen = *s;
290 if ((clen & 0xc0) == 0xc0) { /* Doesn't do compression */
291 softd->ipf_p_dns_compress++;
292 return 0;
293 }
294 if (clen > slen) {
295 softd->ipf_p_dns_toolong++;
296 return 0; /* Does the name run off the end? */
297 }
298 if ((clen + 1) > blen) {
299 softd->ipf_p_dns_nospace++;
300 return 0; /* Enough room for name+.? */
301 }
302 s++;
303 bcopy(s, t, clen);
304 t += clen;
305 s += clen;
306 *t++ = '.';
307 slen -= clen;
308 blen -= (clen + 1);
309 }
310
311 *(t - 1) = '\0';
312 return s - start;
313 }
314
315
316 int
317 ipf_p_dns_allow_query(softd, dnsi)
318 ipf_dns_softc_t *softd;
319 dnsinfo_t *dnsi;
320 {
321 ipf_dns_filter_t *idns;
322 int len;
323
324 len = strlen(dnsi->dnsi_buffer);
325
326 for (idns = softd->ipf_p_dns_list; idns != NULL; idns = idns->idns_next)
327 if (ipf_p_dns_match_names(idns, dnsi->dnsi_buffer, len) == 0)
328 return idns->idns_pass;
329 return 0;
330 }
331
332
333 /* ARGSUSED */
334 int
335 ipf_p_dns_inout(arg, fin, aps, nat)
336 void *arg;
337 fr_info_t *fin;
338 ap_session_t *aps;
339 nat_t *nat;
340 {
341 ipf_dns_softc_t *softd = arg;
342 ipf_dns_hdr_t *dns;
343 dnsinfo_t *di;
344 char *data;
345 int dlen, q, rc = 0;
346
347 if (fin->fin_dlen < sizeof(*dns))
348 return APR_ERR(1);
349
350 dns = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
351
352 q = dns->dns_qdcount;
353
354 data = (char *)(dns + 1);
355 dlen = fin->fin_dlen - sizeof(*dns) - sizeof(udphdr_t);
356
357 di = aps->aps_data;
358
359 READ_ENTER(&softd->ipf_p_dns_rwlock);
360 MUTEX_ENTER(&di->dnsi_lock);
361
362 for (; (dlen > 0) && (q > 0); q--) {
363 int len;
364
365 len = ipf_p_dns_get_name(softd, data, dlen, di->dnsi_buffer,
366 sizeof(di->dnsi_buffer));
367 if (len == 0) {
368 rc = 1;
369 break;
370 }
371 rc = ipf_p_dns_allow_query(softd, di);
372 if (rc != 0)
373 break;
374 data += len;
375 dlen -= len;
376 }
377 MUTEX_EXIT(&di->dnsi_lock);
378 RWLOCK_EXIT(&softd->ipf_p_dns_rwlock);
379
380 return APR_ERR(rc);
381 }
382
383
384 /* ARGSUSED */
385 int
386 ipf_p_dns_match(fin, aps, nat)
387 fr_info_t *fin;
388 ap_session_t *aps;
389 nat_t *nat;
390 {
391 dnsinfo_t *di = aps->aps_data;
392 ipf_dns_hdr_t *dnh;
393
394 if ((fin->fin_dlen < sizeof(u_short)) || (fin->fin_flx & FI_FRAG))
395 return -1;
396
397 dnh = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
398 if (((dnh->dns_id[0] << 8) | dnh->dns_id[1]) != di->dnsi_id)
399 return -1;
400 return 0;
401 }
402