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