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 *));
32272996Scyvoid 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 */
217272996Scyvoid
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}
231254401Scy
232254401Scy
233254401Scy/*
234254401Scy * Tries to match the base string (in our ACL) with the query from a packet.
235254401Scy */
236254401Scyint
237254401Scyipf_p_dns_match_names(idns, query, qlen)
238254401Scy	ipf_dns_filter_t *idns;
239254401Scy	char *query;
240254401Scy	int qlen;
241254401Scy{
242254401Scy	int blen;
243254401Scy	char *base;
244254401Scy
245254401Scy	blen = idns->idns_namelen;
246254401Scy	base = idns->idns_name;
247254401Scy
248254401Scy	if (blen > qlen)
249254401Scy		return 1;
250254401Scy
251254401Scy	if (blen == qlen)
252254401Scy		return strncasecmp(base, query, qlen);
253254401Scy
254254401Scy	/*
255254401Scy	 * If the base string string is shorter than the query, allow the
256254401Scy	 * tail of the base to match the same length tail of the query *if*:
257254401Scy	 * - the base string starts with a '*' (*cnn.com)
258254401Scy	 * - the base string represents a domain (.cnn.com)
259254401Scy	 * as otherwise it would not be possible to block just "cnn.com"
260254401Scy	 * without also impacting "foocnn.com", etc.
261254401Scy	 */
262254401Scy	if (*base == '*') {
263254401Scy		base++;
264254401Scy		blen--;
265254401Scy	} else if (*base != '.')
266254401Scy		return 1;
267254401Scy
268254401Scy	return strncasecmp(base, query + qlen - blen, blen);
269254401Scy}
270254401Scy
271254401Scy
272254401Scyint
273254401Scyipf_p_dns_get_name(softd, start, len, buffer, buflen)
274254401Scy	ipf_dns_softc_t *softd;
275254401Scy	char *start;
276254401Scy	int len;
277254401Scy	char *buffer;
278254401Scy	int buflen;
279254401Scy{
280254401Scy	char *s, *t, clen;
281254401Scy	int slen, blen;
282254401Scy
283254401Scy	s = start;
284254401Scy	t = buffer;
285254401Scy	slen = len;
286254401Scy	blen = buflen - 1;	/* Always make room for trailing \0 */
287254401Scy
288254401Scy	while (*s != '\0') {
289254401Scy		clen = *s;
290254401Scy		if ((clen & 0xc0) == 0xc0) {	/* Doesn't do compression */
291254401Scy			softd->ipf_p_dns_compress++;
292254401Scy			return 0;
293254401Scy		}
294254401Scy		if (clen > slen) {
295254401Scy			softd->ipf_p_dns_toolong++;
296254401Scy			return 0;	/* Does the name run off the end? */
297254401Scy		}
298254401Scy		if ((clen + 1) > blen) {
299254401Scy			softd->ipf_p_dns_nospace++;
300254401Scy			return 0;	/* Enough room for name+.? */
301254401Scy		}
302254401Scy		s++;
303254401Scy		bcopy(s, t, clen);
304254401Scy		t += clen;
305254401Scy		s += clen;
306254401Scy		*t++ = '.';
307254401Scy		slen -= clen;
308254401Scy		blen -= (clen + 1);
309254401Scy	}
310254401Scy
311254401Scy	*(t - 1) = '\0';
312254401Scy	return s - start;
313254401Scy}
314254401Scy
315254401Scy
316254401Scyint
317254401Scyipf_p_dns_allow_query(softd, dnsi)
318254401Scy	ipf_dns_softc_t *softd;
319254401Scy	dnsinfo_t *dnsi;
320254401Scy{
321254401Scy	ipf_dns_filter_t *idns;
322254401Scy	int len;
323254401Scy
324254401Scy	len = strlen(dnsi->dnsi_buffer);
325254401Scy
326254401Scy	for (idns = softd->ipf_p_dns_list; idns != NULL; idns = idns->idns_next)
327254401Scy		if (ipf_p_dns_match_names(idns, dnsi->dnsi_buffer, len) == 0)
328254401Scy			return idns->idns_pass;
329254401Scy	return 0;
330254401Scy}
331254401Scy
332254401Scy
333254401Scy/* ARGSUSED */
334254401Scyint
335254401Scyipf_p_dns_inout(arg, fin, aps, nat)
336254401Scy	void *arg;
337254401Scy	fr_info_t *fin;
338254401Scy	ap_session_t *aps;
339254401Scy	nat_t *nat;
340254401Scy{
341254401Scy	ipf_dns_softc_t *softd = arg;
342254401Scy	ipf_dns_hdr_t *dns;
343254401Scy	dnsinfo_t *di;
344254401Scy	char *data;
345254401Scy	int dlen, q, rc = 0;
346254401Scy
347254401Scy	if (fin->fin_dlen < sizeof(*dns))
348254401Scy		return APR_ERR(1);
349254401Scy
350254401Scy	dns = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
351254401Scy
352254401Scy	q = dns->dns_qdcount;
353254401Scy
354254401Scy	data = (char *)(dns + 1);
355254401Scy	dlen = fin->fin_dlen - sizeof(*dns) - sizeof(udphdr_t);
356254401Scy
357254401Scy	di = aps->aps_data;
358254401Scy
359254401Scy	READ_ENTER(&softd->ipf_p_dns_rwlock);
360254401Scy	MUTEX_ENTER(&di->dnsi_lock);
361254401Scy
362254401Scy	for (; (dlen > 0) && (q > 0); q--) {
363254401Scy		int len;
364254401Scy
365254401Scy		len = ipf_p_dns_get_name(softd, data, dlen, di->dnsi_buffer,
366254401Scy					 sizeof(di->dnsi_buffer));
367254401Scy		if (len == 0) {
368254401Scy			rc = 1;
369254401Scy			break;
370254401Scy		}
371254401Scy		rc = ipf_p_dns_allow_query(softd, di);
372254401Scy		if (rc != 0)
373254401Scy			break;
374254401Scy		data += len;
375254401Scy		dlen -= len;
376254401Scy	}
377254401Scy	MUTEX_EXIT(&di->dnsi_lock);
378254401Scy	RWLOCK_EXIT(&softd->ipf_p_dns_rwlock);
379254401Scy
380254401Scy	return APR_ERR(rc);
381254401Scy}
382254401Scy
383254401Scy
384254401Scy/* ARGSUSED */
385254401Scyint
386254401Scyipf_p_dns_match(fin, aps, nat)
387254401Scy	fr_info_t *fin;
388254401Scy	ap_session_t *aps;
389254401Scy	nat_t *nat;
390254401Scy{
391254401Scy	dnsinfo_t *di = aps->aps_data;
392254401Scy	ipf_dns_hdr_t *dnh;
393254401Scy
394254401Scy	if ((fin->fin_dlen < sizeof(u_short)) || (fin->fin_flx & FI_FRAG))
395254401Scy                return -1;
396254401Scy
397254401Scy	dnh = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
398254401Scy	if (((dnh->dns_id[0] << 8) | dnh->dns_id[1]) != di->dnsi_id)
399254401Scy		return -1;
400254401Scy	return 0;
401254401Scy}
402