1/*	$NetBSD: ip_dns_pxy.c,v 1.1.1.2 2012/07/22 13:44:11 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 */
16typedef	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
24typedef 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
32int ipf_p_dns_allow_query __P((ipf_dns_softc_t *, dnsinfo_t *));
33int ipf_p_dns_ctl __P((ipf_main_softc_t *, void *, ap_ctl_t *));
34int ipf_p_dns_del __P((ipf_main_softc_t *, ap_session_t *));
35int ipf_p_dns_get_name __P((ipf_dns_softc_t *, char *, int, char *, int));
36int ipf_p_dns_inout __P((void *, fr_info_t *, ap_session_t *, nat_t *));
37int ipf_p_dns_match __P((fr_info_t *, ap_session_t *, nat_t *));
38int ipf_p_dns_match_names __P((ipf_dns_filter_t *, char *, int));
39int ipf_p_dns_new __P((void *, fr_info_t *, ap_session_t *, nat_t *));
40void *ipf_p_dns_soft_create __P((ipf_main_softc_t *));
41void ipf_p_dns_soft_destroy __P((ipf_main_softc_t *, void *));
42
43typedef 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
62void *
63ipf_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
79void
80ipf_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
100int
101ipf_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 */
178int
179ipf_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 */
219int
220ipf_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 */
239int
240ipf_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
275int
276ipf_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
319int
320ipf_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 */
337int
338ipf_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 */
388int
389ipf_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