Home | History | Annotate | Line # | Download | only in netinet
ip_dns_pxy.c revision 1.1
      1 /*	$NetBSD: ip_dns_pxy.c,v 1.1 2012/03/23 20:36:53 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (C) 2010 by Darren Reed.
      5  *
      6  * See the IPFILTER.LICENCE file for details on licencing.
      7  *
      8  * Id: ip_dns_pxy.c,v 1.1.2.8 2012/01/29 05:30:35 darren_r 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 	dlen = fin->fin_dlen - sizeof(udphdr_t);
    189 	if (dlen < sizeof(ipf_dns_hdr_t)) {
    190 		/*
    191 		 * No real DNS packet is smaller than that.
    192 		 */
    193 		return -1;
    194 	}
    195 
    196 	aps->aps_psiz = sizeof(dnsinfo_t);
    197 	KMALLOCS(di, dnsinfo_t *, sizeof(dnsinfo_t));
    198 	if (di == NULL) {
    199 		printf("ipf_dns_new:KMALLOCS(%d) failed\n", sizeof(*di));
    200 		return -1;
    201         }
    202 
    203 	MUTEX_INIT(&di->dnsi_lock, "dns lock");
    204 
    205 	aps->aps_data = di;
    206 
    207 	dlen = fin->fin_dlen - sizeof(udphdr_t);
    208 	COPYDATA(fin->fin_m, fin->fin_hlen + sizeof(udphdr_t),
    209 		 MIN(dlen, sizeof(di->dnsi_buffer)), di->dnsi_buffer);
    210 	di->dnsi_id = (di->dnsi_buffer[0] << 8) | di->dnsi_buffer[1];
    211 	return 0;
    212 }
    213 
    214 
    215 /* ARGSUSED */
    216 int
    217 ipf_p_dns_del(softc, aps)
    218 	ipf_main_softc_t *softc;
    219 	ap_session_t *aps;
    220 {
    221 #ifdef USE_MUTEXES
    222 	dnsinfo_t *di = aps->aps_data;
    223 
    224 	MUTEX_DESTROY(&di->dnsi_lock);
    225 #endif
    226 	KFREES(aps->aps_data, aps->aps_psiz);
    227 	aps->aps_data = NULL;
    228 	aps->aps_psiz = 0;
    229 	return 0;
    230 }
    231 
    232 
    233 /*
    234  * Tries to match the base string (in our ACL) with the query from a packet.
    235  */
    236 int
    237 ipf_p_dns_match_names(idns, query, qlen)
    238 	ipf_dns_filter_t *idns;
    239 	char *query;
    240 	int qlen;
    241 {
    242 	int blen;
    243 	char *base;
    244 
    245 	blen = idns->idns_namelen;
    246 	base = idns->idns_name;
    247 
    248 	if (blen > qlen)
    249 		return 1;
    250 
    251 	if (blen == qlen)
    252 		return strncasecmp(base, query, qlen);
    253 
    254 	/*
    255 	 * If the base string string is shorter than the query, allow the
    256 	 * tail of the base to match the same length tail of the query *if*:
    257 	 * - the base string starts with a '*' (*cnn.com)
    258 	 * - the base string represents a domain (.cnn.com)
    259 	 * as otherwise it would not be possible to block just "cnn.com"
    260 	 * without also impacting "foocnn.com", etc.
    261 	 */
    262 	if (*base == '*') {
    263 		base++;
    264 		blen--;
    265 	} else if (*base != '.')
    266 		return 1;
    267 
    268 	return strncasecmp(base, query + qlen - blen, blen);
    269 }
    270 
    271 
    272 int
    273 ipf_p_dns_get_name(softd, start, len, buffer, buflen)
    274 	ipf_dns_softc_t *softd;
    275 	char *start;
    276 	int len;
    277 	char *buffer;
    278 	int buflen;
    279 {
    280 	char *s, *t, clen;
    281 	int slen, blen;
    282 
    283 	s = start;
    284 	t = buffer;
    285 	slen = len;
    286 	blen = buflen - 1;	/* Always make room for trailing \0 */
    287 
    288 	while (*s != '\0') {
    289 		clen = *s;
    290 		if ((clen & 0xc0) == 0xc0) {	/* Doesn't do compression */
    291 			softd->ipf_p_dns_compress++;
    292 			return 0;
    293 		}
    294 		if (clen > slen) {
    295 			softd->ipf_p_dns_toolong++;
    296 			return 0;	/* Does the name run off the end? */
    297 		}
    298 		if ((clen + 1) > blen) {
    299 			softd->ipf_p_dns_nospace++;
    300 			return 0;	/* Enough room for name+.? */
    301 		}
    302 		s++;
    303 		bcopy(s, t, clen);
    304 		t += clen;
    305 		s += clen;
    306 		*t++ = '.';
    307 		slen -= clen;
    308 		blen -= (clen + 1);
    309 	}
    310 
    311 	*(t - 1) = '\0';
    312 	return s - start;
    313 }
    314 
    315 
    316 int
    317 ipf_p_dns_allow_query(softd, dnsi)
    318 	ipf_dns_softc_t *softd;
    319 	dnsinfo_t *dnsi;
    320 {
    321 	ipf_dns_filter_t *idns;
    322 	int len;
    323 
    324 	len = strlen(dnsi->dnsi_buffer);
    325 
    326 	for (idns = softd->ipf_p_dns_list; idns != NULL; idns = idns->idns_next)
    327 		if (ipf_p_dns_match_names(idns, dnsi->dnsi_buffer, len) == 0)
    328 			return idns->idns_pass;
    329 	return 0;
    330 }
    331 
    332 
    333 /* ARGSUSED */
    334 int
    335 ipf_p_dns_inout(arg, fin, aps, nat)
    336 	void *arg;
    337 	fr_info_t *fin;
    338 	ap_session_t *aps;
    339 	nat_t *nat;
    340 {
    341 	ipf_dns_softc_t *softd = arg;
    342 	ipf_dns_hdr_t *dns;
    343 	dnsinfo_t *di;
    344 	char *data;
    345 	int dlen, q, rc = 0;
    346 
    347 	if (fin->fin_dlen < sizeof(*dns))
    348 		return APR_ERR(1);
    349 
    350 	dns = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
    351 
    352 	q = dns->dns_qdcount;
    353 
    354 	data = (char *)(dns + 1);
    355 	dlen = fin->fin_dlen - sizeof(*dns) - sizeof(udphdr_t);
    356 
    357 	di = aps->aps_data;
    358 
    359 	READ_ENTER(&softd->ipf_p_dns_rwlock);
    360 	MUTEX_ENTER(&di->dnsi_lock);
    361 
    362 	for (; (dlen > 0) && (q > 0); q--) {
    363 		int len;
    364 
    365 		len = ipf_p_dns_get_name(softd, data, dlen, di->dnsi_buffer,
    366 					 sizeof(di->dnsi_buffer));
    367 		if (len == 0) {
    368 			rc = 1;
    369 			break;
    370 		}
    371 		rc = ipf_p_dns_allow_query(softd, di);
    372 		if (rc != 0)
    373 			break;
    374 		data += len;
    375 		dlen -= len;
    376 	}
    377 	MUTEX_EXIT(&di->dnsi_lock);
    378 	RWLOCK_EXIT(&softd->ipf_p_dns_rwlock);
    379 
    380 	return APR_ERR(rc);
    381 }
    382 
    383 
    384 /* ARGSUSED */
    385 int
    386 ipf_p_dns_match(fin, aps, nat)
    387 	fr_info_t *fin;
    388 	ap_session_t *aps;
    389 	nat_t *nat;
    390 {
    391 	dnsinfo_t *di = aps->aps_data;
    392 	ipf_dns_hdr_t *dnh;
    393 
    394 	if ((fin->fin_dlen < sizeof(u_short)) || (fin->fin_flx & FI_FRAG))
    395                 return -1;
    396 
    397 	dnh = (ipf_dns_hdr_t *)((char *)fin->fin_dp + sizeof(udphdr_t));
    398 	if (((dnh->dns_id[0] << 8) | dnh->dns_id[1]) != di->dnsi_id)
    399 		return -1;
    400 	return 0;
    401 }
    402