Home | History | Annotate | Line # | Download | only in netinet
ip_dns_pxy.c revision 1.1.1.2
      1 /*	$NetBSD: ip_dns_pxy.c,v 1.1.1.2 2012/07/22 13:45:10 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:45:10 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 *
     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
     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
    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
    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
    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
    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
    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
    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
    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
    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