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