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