1254401Scy/*
2254401Scy * Copyright (C) 2012 by Darren Reed.
3254401Scy *
4254401Scy * See the IPFILTER.LICENCE file for details on licencing.
5254401Scy *
6254401Scy * $Id: ip_dns_pxy.c,v 1.1.2.10 2012/07/22 08:04:23 darren_r Exp $
7254401Scy */
8254401Scy
9254401Scy#define	IPF_DNS_PROXY
10254401Scy
11254401Scy/*
12254401Scy * map ... proxy port dns/udp 53 { block .cnn.com; }
13254401Scy */
14254401Scytypedef	struct	ipf_dns_filter	{
15254401Scy	struct	ipf_dns_filter	*idns_next;
16254401Scy	char			*idns_name;
17254401Scy	int			idns_namelen;
18254401Scy	int			idns_pass;
19254401Scy} ipf_dns_filter_t;
20254401Scy
21254401Scy
22254401Scytypedef struct ipf_dns_softc_s {
23254401Scy	ipf_dns_filter_t	*ipf_p_dns_list;
24254401Scy	ipfrwlock_t		ipf_p_dns_rwlock;
25254401Scy	u_long			ipf_p_dns_compress;
26254401Scy	u_long			ipf_p_dns_toolong;
27254401Scy	u_long			ipf_p_dns_nospace;
28254401Scy} ipf_dns_softc_t;
29254401Scy
30254401Scyint ipf_p_dns_allow_query __P((ipf_dns_softc_t *, dnsinfo_t *));
31254401Scyint ipf_p_dns_ctl __P((ipf_main_softc_t *, void *, ap_ctl_t *));
32254401Scyint ipf_p_dns_del __P((ipf_main_softc_t *, ap_session_t *));
33254401Scyint ipf_p_dns_get_name __P((ipf_dns_softc_t *, char *, int, char *, int));
34254401Scyint ipf_p_dns_inout __P((void *, fr_info_t *, ap_session_t *, nat_t *));
35254401Scyint ipf_p_dns_match __P((fr_info_t *, ap_session_t *, nat_t *));
36254401Scyint ipf_p_dns_match_names __P((ipf_dns_filter_t *, char *, int));
37254401Scyint ipf_p_dns_new __P((void *, fr_info_t *, ap_session_t *, nat_t *));
38254401Scyvoid *ipf_p_dns_soft_create __P((ipf_main_softc_t *));
39254401Scyvoid ipf_p_dns_soft_destroy __P((ipf_main_softc_t *, void *));
40254401Scy
41254401Scytypedef struct {
42254401Scy	u_char		dns_id[2];
43254401Scy	u_short		dns_ctlword;
44254401Scy	u_short		dns_qdcount;
45254401Scy	u_short		dns_ancount;
46254401Scy	u_short		dns_nscount;
47254401Scy	u_short		dns_arcount;
48254401Scy} ipf_dns_hdr_t;
49254401Scy
50254401Scy#define	DNS_QR(x)	((ntohs(x) & 0x8000) >> 15)
51254401Scy#define	DNS_OPCODE(x)	((ntohs(x) & 0x7800) >> 11)
52254401Scy#define	DNS_AA(x)	((ntohs(x) & 0x0400) >> 10)
53254401Scy#define	DNS_TC(x)	((ntohs(x) & 0x0200) >> 9)
54254401Scy#define	DNS_RD(x)	((ntohs(x) & 0x0100) >> 8)
55254401Scy#define	DNS_RA(x)	((ntohs(x) & 0x0080) >> 7)
56254401Scy#define	DNS_Z(x)	((ntohs(x) & 0x0070) >> 4)
57254401Scy#define	DNS_RCODE(x)	((ntohs(x) & 0x000f) >> 0)
58254401Scy
59254401Scy
60254401Scyvoid *
61254401Scyipf_p_dns_soft_create(softc)
62254401Scy	ipf_main_softc_t *softc;
63254401Scy{
64254401Scy	ipf_dns_softc_t *softd;
65254401Scy
66254401Scy	KMALLOC(softd, ipf_dns_softc_t *);
67254401Scy	if (softd == NULL)
68254401Scy		return NULL;
69254401Scy
70254401Scy	bzero((char *)softd, sizeof(*softd));
71254401Scy	RWLOCK_INIT(&softd->ipf_p_dns_rwlock, "ipf dns rwlock");
72254401Scy
73254401Scy	return softd;
74254401Scy}
75254401Scy
76254401Scy
77254401Scyvoid
78254401Scyipf_p_dns_soft_destroy(softc, arg)
79254401Scy	ipf_main_softc_t *softc;
80254401Scy	void *arg;
81254401Scy{
82254401Scy	ipf_dns_softc_t *softd = arg;
83254401Scy	ipf_dns_filter_t *idns;
84254401Scy
85254401Scy	while ((idns = softd->ipf_p_dns_list) != NULL) {
86254401Scy		KFREES(idns->idns_name, idns->idns_namelen);
87254401Scy		idns->idns_name = NULL;
88254401Scy		idns->idns_namelen = 0;
89254401Scy		softd->ipf_p_dns_list = idns->idns_next;
90254401Scy		KFREE(idns);
91254401Scy	}
92254401Scy	RW_DESTROY(&softd->ipf_p_dns_rwlock);
93254401Scy
94254401Scy	KFREE(softd);
95254401Scy}
96254401Scy
97254401Scy
98254401Scyint
99254401Scyipf_p_dns_ctl(softc, arg, ctl)
100254401Scy	ipf_main_softc_t *softc;
101254401Scy	void *arg;
102254401Scy	ap_ctl_t *ctl;
103254401Scy{
104254401Scy	ipf_dns_softc_t *softd = arg;
105254401Scy	ipf_dns_filter_t *tmp, *idns, **idnsp;
106254401Scy	int error = 0;
107254401Scy
108254401Scy	/*
109254401Scy	 * To make locking easier.
110254401Scy	 */
111254401Scy	KMALLOC(tmp, ipf_dns_filter_t *);
112254401Scy
113254401Scy	WRITE_ENTER(&softd->ipf_p_dns_rwlock);
114254401Scy	for (idnsp = &softd->ipf_p_dns_list; (idns = *idnsp) != NULL;
115254401Scy	     idnsp = &idns->idns_next) {
116254401Scy		if (idns->idns_namelen != ctl->apc_dsize)
117254401Scy			continue;
118254401Scy		if (!strncmp(ctl->apc_data, idns->idns_name,
119254401Scy		    idns->idns_namelen))
120254401Scy			break;
121254401Scy	}
122254401Scy
123254401Scy	switch (ctl->apc_cmd)
124254401Scy	{
125254401Scy	case APC_CMD_DEL :
126254401Scy		if (idns == NULL) {
127254401Scy			IPFERROR(80006);
128254401Scy			error = ESRCH;
129254401Scy			break;
130254401Scy		}
131254401Scy		*idnsp = idns->idns_next;
132254401Scy		idns->idns_next = NULL;
133254401Scy		KFREES(idns->idns_name, idns->idns_namelen);
134254401Scy		idns->idns_name = NULL;
135254401Scy		idns->idns_namelen = 0;
136254401Scy		KFREE(idns);
137254401Scy		break;
138254401Scy	case APC_CMD_ADD :
139254401Scy		if (idns != NULL) {
140254401Scy			IPFERROR(80007);
141254401Scy			error = EEXIST;
142254401Scy			break;
143254401Scy		}
144254401Scy		if (tmp == NULL) {
145254401Scy			IPFERROR(80008);
146254401Scy			error = ENOMEM;
147254401Scy			break;
148254401Scy		}
149254401Scy		idns = tmp;
150254401Scy		tmp = NULL;
151254401Scy		idns->idns_namelen = ctl->apc_dsize;
152254401Scy		idns->idns_name = ctl->apc_data;
153254401Scy		idns->idns_pass = ctl->apc_arg;
154254401Scy		idns->idns_next = NULL;
155254401Scy		*idnsp = idns;
156254401Scy		ctl->apc_data = NULL;
157254401Scy		ctl->apc_dsize = 0;
158254401Scy		break;
159254401Scy	default :
160254401Scy		IPFERROR(80009);
161254401Scy		error = EINVAL;
162254401Scy		break;
163254401Scy	}
164254401Scy	RWLOCK_EXIT(&softd->ipf_p_dns_rwlock);
165254401Scy
166254401Scy	if (tmp != NULL) {
167254401Scy		KFREE(tmp);
168254401Scy		tmp = NULL;
169254401Scy	}
170254401Scy
171254401Scy	return error;
172254401Scy}
173254401Scy
174254401Scy
175254401Scy/* ARGSUSED */
176254401Scyint
177254401Scyipf_p_dns_new(arg, fin, aps, nat)
178254401Scy	void *arg;
179254401Scy	fr_info_t *fin;
180254401Scy	ap_session_t *aps;
181254401Scy	nat_t *nat;
182254401Scy{
183254401Scy	dnsinfo_t *di;
184254401Scy	int dlen;
185254401Scy
186254401Scy	if (fin->fin_v != 4)
187254401Scy		return -1;
188254401Scy
189254401Scy	dlen = fin->fin_dlen - sizeof(udphdr_t);
190254401Scy	if (dlen < sizeof(ipf_dns_hdr_t)) {
191254401Scy		/*
192254401Scy		 * No real DNS packet is smaller than that.
193254401Scy		 */
194254401Scy		return -1;
195254401Scy	}
196254401Scy
197254401Scy	aps->aps_psiz = sizeof(dnsinfo_t);
198254401Scy	KMALLOCS(di, dnsinfo_t *, sizeof(dnsinfo_t));
199254401Scy	if (di == NULL) {
200254401Scy		printf("ipf_dns_new:KMALLOCS(%d) failed\n", sizeof(*di));
201254401Scy		return -1;
202254401Scy        }
203254401Scy
204254401Scy	MUTEX_INIT(&di->dnsi_lock, "dns lock");
205254401Scy
206254401Scy	aps->aps_data = di;
207254401Scy
208254401Scy	dlen = fin->fin_dlen - sizeof(udphdr_t);
209254401Scy	COPYDATA(fin->fin_m, fin->fin_hlen + sizeof(udphdr_t),
210254401Scy		 MIN(dlen, sizeof(di->dnsi_buffer)), di->dnsi_buffer);
211254401Scy	di->dnsi_id = (di->dnsi_buffer[0] << 8) | di->dnsi_buffer[1];
212254401Scy	return 0;
213254401Scy}
214254401Scy
215254401Scy
216254401Scy/* ARGSUSED */
217254401Scyint
218254401Scyipf_p_dns_del(softc, aps)
219254401Scy	ipf_main_softc_t *softc;
220254401Scy	ap_session_t *aps;
221254401Scy{
222254401Scy#ifdef USE_MUTEXES
223254401Scy	dnsinfo_t *di = aps->aps_data;
224254401Scy
225254401Scy	MUTEX_DESTROY(&di->dnsi_lock);
226254401Scy#endif
227254401Scy	KFREES(aps->aps_data, aps->aps_psiz);
228254401Scy	aps->aps_data = NULL;
229254401Scy	aps->aps_psiz = 0;
230254401Scy	return 0;
231254401Scy}
232254401Scy
233254401Scy
234254401Scy/*
235254401Scy * Tries to match the base string (in our ACL) with the query from a packet.
236254401Scy */
237254401Scyint
238254401Scyipf_p_dns_match_names(idns, query, qlen)
239254401Scy	ipf_dns_filter_t *idns;
240254401Scy	char *query;
241254401Scy	int qlen;
242254401Scy{
243254401Scy	int blen;
244254401Scy	char *base;
245254401Scy
246254401Scy	blen = idns->idns_namelen;
247254401Scy	base = idns->idns_name;
248254401Scy
249254401Scy	if (blen > qlen)
250254401Scy		return 1;
251254401Scy
252254401Scy	if (blen == qlen)
253254401Scy		return strncasecmp(base, query, qlen);
254254401Scy
255254401Scy	/*
256254401Scy	 * If the base string string is shorter than the query, allow the
257254401Scy	 * tail of the base to match the same length tail of the query *if*:
258254401Scy	 * - the base string starts with a '*' (*cnn.com)
259254401Scy	 * - the base string represents a domain (.cnn.com)
260254401Scy	 * as otherwise it would not be possible to block just "cnn.com"
261254401Scy	 * without also impacting "foocnn.com", etc.
262254401Scy	 */
263254401Scy	if (*base == '*') {
264254401Scy		base++;
265254401Scy		blen--;
266254401Scy	} else if (*base != '.')
267254401Scy		return 1;
268254401Scy
269254401Scy	return strncasecmp(base, query + qlen - blen, blen);
270254401Scy}
271254401Scy
272254401Scy
273254401Scyint
274254401Scyipf_p_dns_get_name(softd, start, len, buffer, buflen)
275254401Scy	ipf_dns_softc_t *softd;
276254401Scy	char *start;
277254401Scy	int len;
278254401Scy	char *buffer;
279254401Scy	int buflen;
280254401Scy{
281254401Scy	char *s, *t, clen;
282254401Scy	int slen, blen;
283254401Scy
284254401Scy	s = start;
285254401Scy	t = buffer;
286254401Scy	slen = len;
287254401Scy	blen = buflen - 1;	/* Always make room for trailing \0 */
288254401Scy
289254401Scy	while (*s != '\0') {
290254401Scy		clen = *s;
291254401Scy		if ((clen & 0xc0) == 0xc0) {	/* Doesn't do compression */
292254401Scy			softd->ipf_p_dns_compress++;
293254401Scy			return 0;
294254401Scy		}
295254401Scy		if (clen > slen) {
296254401Scy			softd->ipf_p_dns_toolong++;
297254401Scy			return 0;	/* Does the name run off the end? */
298254401Scy		}
299254401Scy		if ((clen + 1) > blen) {
300254401Scy			softd->ipf_p_dns_nospace++;
301254401Scy			return 0;	/* Enough room for name+.? */
302254401Scy		}
303254401Scy		s++;
304254401Scy		bcopy(s, t, clen);
305254401Scy		t += clen;
306254401Scy		s += clen;
307254401Scy		*t++ = '.';
308254401Scy		slen -= clen;
309254401Scy		blen -= (clen + 1);
310254401Scy	}
311254401Scy
312254401Scy	*(t - 1) = '\0';
313254401Scy	return s - start;
314254401Scy}
315254401Scy
316254401Scy
317254401Scyint
318254401Scyipf_p_dns_allow_query(softd, dnsi)
319254401Scy	ipf_dns_softc_t *softd;
320254401Scy	dnsinfo_t *dnsi;
321254401Scy{
322254401Scy	ipf_dns_filter_t *idns;
323254401Scy	int len;
324254401Scy
325254401Scy	len = strlen(dnsi->dnsi_buffer);
326254401Scy
327254401Scy	for (idns = softd->ipf_p_dns_list; idns != NULL; idns = idns->idns_next)
328254401Scy		if (ipf_p_dns_match_names(idns, dnsi->dnsi_buffer, len) == 0)
329254401Scy			return idns->idns_pass;
330254401Scy	return 0;
331254401Scy}
332254401Scy
333254401Scy
334254401Scy/* ARGSUSED */
335254401Scyint
336254401Scyipf_p_dns_inout(arg, fin, aps, nat)
337254401Scy	void *arg;
338254401Scy	fr_info_t *fin;
339254401Scy	ap_session_t *aps;
340254401Scy	nat_t *nat;
341254401Scy{
342254401Scy	ipf_dns_softc_t *softd = arg;
343254401Scy	ipf_dns_hdr_t *dns;
344254401Scy	dnsinfo_t *di;
345254401Scy	char *data;
346254401Scy	int dlen, q, rc = 0;
347254401Scy
348254401Scy	if (fin->fin_dlen < sizeof(*dns))
349254401Scy		return APR_ERR(1);
350254401Scy
351254401Scy	dns = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
352254401Scy
353254401Scy	q = dns->dns_qdcount;
354254401Scy
355254401Scy	data = (char *)(dns + 1);
356254401Scy	dlen = fin->fin_dlen - sizeof(*dns) - sizeof(udphdr_t);
357254401Scy
358254401Scy	di = aps->aps_data;
359254401Scy
360254401Scy	READ_ENTER(&softd->ipf_p_dns_rwlock);
361254401Scy	MUTEX_ENTER(&di->dnsi_lock);
362254401Scy
363254401Scy	for (; (dlen > 0) && (q > 0); q--) {
364254401Scy		int len;
365254401Scy
366254401Scy		len = ipf_p_dns_get_name(softd, data, dlen, di->dnsi_buffer,
367254401Scy					 sizeof(di->dnsi_buffer));
368254401Scy		if (len == 0) {
369254401Scy			rc = 1;
370254401Scy			break;
371254401Scy		}
372254401Scy		rc = ipf_p_dns_allow_query(softd, di);
373254401Scy		if (rc != 0)
374254401Scy			break;
375254401Scy		data += len;
376254401Scy		dlen -= len;
377254401Scy	}
378254401Scy	MUTEX_EXIT(&di->dnsi_lock);
379254401Scy	RWLOCK_EXIT(&softd->ipf_p_dns_rwlock);
380254401Scy
381254401Scy	return APR_ERR(rc);
382254401Scy}
383254401Scy
384254401Scy
385254401Scy/* ARGSUSED */
386254401Scyint
387254401Scyipf_p_dns_match(fin, aps, nat)
388254401Scy	fr_info_t *fin;
389254401Scy	ap_session_t *aps;
390254401Scy	nat_t *nat;
391254401Scy{
392254401Scy	dnsinfo_t *di = aps->aps_data;
393254401Scy	ipf_dns_hdr_t *dnh;
394254401Scy
395254401Scy	if ((fin->fin_dlen < sizeof(u_short)) || (fin->fin_flx & FI_FRAG))
396254401Scy                return -1;
397254401Scy
398254401Scy	dnh = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
399254401Scy	if (((dnh->dns_id[0] << 8) | dnh->dns_id[1]) != di->dnsi_id)
400254401Scy		return -1;
401254401Scy	return 0;
402254401Scy}
403