xref: /netbsd-src/external/bsd/ipf/dist/ip_dns_pxy.c (revision 13885a665959c62f13a82b3caedf986eaa17aa31)
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