/*
 * Copyright (C) 1999-2001  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* $Id: confip.c,v 1.38.2.2 2001/01/22 20:12:27 bwelling Exp $ */

#include <config.h>


#include <isc/mem.h>
#include <isc/string.h>
#include <isc/util.h>

#include <dns/confip.h>
#include <dns/log.h>

/*
 * Flag for dns_c_ipmatch_element.
 */
#define DNS_C_IPMATCH_NEGATE	0x01	/* match means deny access */


static isc_result_t checkmask(isc_sockaddr_t *address, isc_uint32_t bits);
static isc_result_t bits2v6mask(struct in6_addr *addr, isc_uint32_t bits);

isc_result_t
dns_c_ipmatchelement_new(isc_mem_t *mem, dns_c_ipmatchelement_t **result) {
	dns_c_ipmatchelement_t *ime ;

	REQUIRE(result != NULL);

	*result = NULL;

	ime = isc_mem_get(mem, sizeof(*ime));
	if (ime == NULL) {
		return (ISC_R_NOMEMORY);
	}

	ime->magic = DNS_C_IPMELEM_MAGIC;
	ime->type = dns_c_ipmatch_none;
	ime->flags = 0;
	memset(&ime->u, 0x0, sizeof(ime->u));

	ISC_LINK_INIT(ime, next);

	*result = ime;

	return (ISC_R_SUCCESS);
}

isc_boolean_t
dns_c_ipmatchelement_isneg(dns_c_ipmatchelement_t *elem) {

	REQUIRE(DNS_C_IPMELEM_VALID(elem));

	return (ISC_TF((elem->flags & DNS_C_IPMATCH_NEGATE) ==
			DNS_C_IPMATCH_NEGATE));
}

isc_result_t
dns_c_ipmatchelement_delete(isc_mem_t *mem, dns_c_ipmatchelement_t **ipme) {
	dns_c_ipmatchelement_t *elem;

	REQUIRE(mem != NULL);
	REQUIRE(ipme != NULL);
	REQUIRE(*ipme != NULL);

	elem = *ipme;

	REQUIRE(DNS_C_IPMELEM_VALID(elem));

	switch (elem->type) {
	case dns_c_ipmatch_localhost:
	case dns_c_ipmatch_localnets:
	case dns_c_ipmatch_pattern:
		/* nothing */
		break;

	case dns_c_ipmatch_indirect:
		INSIST(elem->u.indirect.list != NULL);

		if (elem->u.indirect.list != NULL)
			dns_c_ipmatchlist_detach(&elem->u.indirect.list);

		if (elem->u.indirect.refname.base != NULL) {
			isc_mem_put(mem, elem->u.indirect.refname.base,
				    elem->u.indirect.refname.length);
		}
		break;

	case dns_c_ipmatch_key:
		isc_mem_free(mem, elem->u.key);
		break;

	case dns_c_ipmatch_acl:
		isc_mem_free(mem, elem->u.aclname);
		break;

	case dns_c_ipmatch_any:
		/* nothing */
		break;

	case dns_c_ipmatch_none:
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_CONFIG,
			      DNS_LOGMODULE_CONFIG, ISC_LOG_CRITICAL,
			      "dns_ipmath_none element type");
		return (ISC_R_FAILURE);
	}

	elem->magic = 0;
	isc_mem_put(mem, elem, sizeof(*elem));

	*ipme = NULL;

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_c_ipmatchelement_copy(isc_mem_t *mem,
			  dns_c_ipmatchelement_t **dest,
			  dns_c_ipmatchelement_t *src)
{
	isc_result_t result;
	dns_c_ipmatchelement_t *newel;

	REQUIRE(mem != NULL);
	REQUIRE(dest != NULL);
	REQUIRE(DNS_C_IPMELEM_VALID(src));

	result = dns_c_ipmatchelement_new(mem, &newel);
	if (result != ISC_R_SUCCESS) {
		return (result);
	}

	newel->type = src->type;
	newel->flags = src->flags;

	switch(src->type) {
	case dns_c_ipmatch_pattern:
		newel->u.direct.address = src->u.direct.address;
		newel->u.direct.mask = src->u.direct.mask;
		break;

	case dns_c_ipmatch_indirect:
		result = dns_c_ipmatchlist_copy(mem,
						&newel->u.indirect.list,
						src->u.indirect.list);
		break;

	case dns_c_ipmatch_localhost:
		break;

	case dns_c_ipmatch_localnets:
		break;

	case dns_c_ipmatch_key:
		newel->u.key = isc_mem_strdup(mem, src->u.key);
		break;

	case dns_c_ipmatch_acl:
		newel->u.aclname = isc_mem_strdup(mem, src->u.aclname);
		break;

	case dns_c_ipmatch_any:
		break;

	case dns_c_ipmatch_none:
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_CONFIG,
			      DNS_LOGMODULE_CONFIG, ISC_LOG_CRITICAL,
			      "ipmatch 'none' element type");
		return (ISC_R_FAILURE);
	}

	*dest = newel;

	return (ISC_R_SUCCESS);
}

isc_boolean_t
dns_c_ipmatchelement_equal(dns_c_ipmatchelement_t *e1,
			   dns_c_ipmatchelement_t *e2)
{
	REQUIRE(DNS_C_IPMELEM_VALID(e1));
	REQUIRE(DNS_C_IPMELEM_VALID(e2));

	if ((e1->type != e2->type) || (e1->flags != e2->flags))
		return (ISC_FALSE);

	switch (e1->type) {
	case dns_c_ipmatch_pattern:
		if (e1->u.direct.mask != e2->u.direct.mask)
			return (ISC_FALSE);
		return (isc_sockaddr_equal(&e1->u.direct.address,
					   &e2->u.direct.address));

	case dns_c_ipmatch_indirect:
		return (dns_c_ipmatchlist_equal(e1->u.indirect.list,
						e2->u.indirect.list));

	case dns_c_ipmatch_localhost:
		break;

	case dns_c_ipmatch_localnets:
		break;

	case dns_c_ipmatch_key:
		return (ISC_TF(strcmp(e1->u.key, e2->u.key) == 0));

	case dns_c_ipmatch_acl:
		return (ISC_TF(strcmp(e1->u.aclname, e2->u.aclname) == 0));

	case dns_c_ipmatch_any:
		break;

	case dns_c_ipmatch_none:
		break;
	}
	return (ISC_TRUE);
}

isc_result_t
dns_c_ipmatchlocalhost_new(isc_mem_t *mem, dns_c_ipmatchelement_t **result) {
	dns_c_ipmatchelement_t *ime = NULL;
	isc_result_t res;

	REQUIRE(mem != NULL);
	REQUIRE(result != NULL);

	*result = NULL;

	res = dns_c_ipmatchelement_new(mem, &ime);
	if (res == ISC_R_SUCCESS) {
		ime->type = dns_c_ipmatch_localhost;
	}

	*result = ime;

	return (res);
}

isc_result_t
dns_c_ipmatchlocalnets_new(isc_mem_t *mem, dns_c_ipmatchelement_t **result) {
	dns_c_ipmatchelement_t *ime = NULL;
	isc_result_t res;

	REQUIRE(mem != NULL);
	REQUIRE(result != NULL);

	*result = NULL;

	res = dns_c_ipmatchelement_new(mem, &ime);
	if (res == ISC_R_SUCCESS) {
		ime->type = dns_c_ipmatch_localnets;
	}

	*result = ime;

	return (res);
}

isc_result_t
dns_c_ipmatchany_new(isc_mem_t *mem, dns_c_ipmatchelement_t **result) {
	dns_c_ipmatchelement_t *ime = NULL;
	isc_result_t res;

	REQUIRE(mem != NULL);
	REQUIRE(result != NULL);

	*result = NULL;

	res = dns_c_ipmatchelement_new(mem, &ime);
	if (res == ISC_R_SUCCESS) {
		ime->type = dns_c_ipmatch_any;
	}

	*result = ime;

	return (res);
}

isc_result_t
dns_c_ipmatchindirect_new(isc_mem_t *mem,
			  dns_c_ipmatchelement_t **result,
			  dns_c_ipmatchlist_t *iml,
			  const char *name)
{
	dns_c_ipmatchelement_t *ime;
	dns_c_ipmatchlist_t *iml_copy;
	isc_result_t res;

	REQUIRE(mem != NULL);
	REQUIRE(result != NULL);
	REQUIRE(DNS_C_IPMLIST_VALID(iml));

	*result = NULL;

	res = dns_c_ipmatchlist_copy(mem, &iml_copy, iml);
	if (res != ISC_R_SUCCESS) {
		return (res);
	}

	res = dns_c_ipmatchelement_new(mem, &ime);
	if (res == ISC_R_SUCCESS) {
		ime->type = dns_c_ipmatch_indirect;
		ime->u.indirect.list = iml_copy;
		if (name != NULL) {
			ime->u.indirect.refname.length = strlen(name) + 1;
			ime->u.indirect.refname.base =
				isc_mem_get(mem,
					    ime->u.indirect.refname.length);
			RUNTIME_CHECK(ime->u.indirect.refname.base != NULL);
			strcpy(ime->u.indirect.refname.base, name);
		}
	} else {
		dns_c_ipmatchlist_detach(&iml_copy);
	}

	*result = ime;

	return (res);
}

isc_result_t
dns_c_ipmatchpattern_new(isc_mem_t *mem,
			 dns_c_ipmatchelement_t **result,
			 isc_sockaddr_t address,
			 isc_uint32_t maskbits)
{
	dns_c_ipmatchelement_t *ime ;
	isc_result_t res;

	REQUIRE(result != NULL);
	REQUIRE(mem != NULL);

	*result = NULL;

	res = checkmask(&address, maskbits);

	if (res != ISC_R_SUCCESS) {
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_CONFIG,
			      DNS_LOGMODULE_CONFIG, ISC_LOG_ERROR,
			      "host portion of network address is not zero");
		return (res);
	}

	res = dns_c_ipmatchelement_new(mem, &ime);
	if (res != ISC_R_SUCCESS) {
		return (res);
	}

	ime->type = dns_c_ipmatch_pattern;
	ime->u.direct.address = address;
	ime->u.direct.mask = maskbits;

	*result = ime;

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_c_ipmatchkey_new(isc_mem_t *mem,
		     dns_c_ipmatchelement_t **result,
		     const char *key)
{
	dns_c_ipmatchelement_t *ipme;
	isc_result_t res;

	REQUIRE(result != NULL);
	REQUIRE(mem != NULL);
	REQUIRE(key != NULL);

	*result = NULL;

	res = dns_c_ipmatchelement_new(mem, &ipme);
	if (res != ISC_R_SUCCESS) {
		return (res);
	}

	ipme->type = dns_c_ipmatch_key;
	ipme->u.key = isc_mem_strdup(mem, key);

	*result = ipme;

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_c_ipmatch_aclnew(isc_mem_t *mem,
		     dns_c_ipmatchelement_t **result,
		     const char *aclname)
{
	dns_c_ipmatchelement_t *ipme;
	isc_result_t res;

	REQUIRE(result != NULL);
	REQUIRE(mem != NULL);
	REQUIRE(aclname != NULL);

	*result = NULL;

	res = dns_c_ipmatchelement_new(mem, &ipme);
	if (res != ISC_R_SUCCESS) {
		return (res);
	}

	ipme->type = dns_c_ipmatch_acl;
	ipme->u.aclname = isc_mem_strdup(mem, aclname);

	*result = ipme;

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_c_ipmatch_negate(dns_c_ipmatchelement_t *ipe) {
	REQUIRE(DNS_C_IPMELEM_VALID(ipe));

	if ((ipe->flags & DNS_C_IPMATCH_NEGATE) == DNS_C_IPMATCH_NEGATE) {
		ipe->flags &= ~DNS_C_IPMATCH_NEGATE;
	} else {
		ipe->flags |= DNS_C_IPMATCH_NEGATE;
	}

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_c_ipmatchlist_new(isc_mem_t *mem, dns_c_ipmatchlist_t **ptr) {
	dns_c_ipmatchlist_t *newlist;

	REQUIRE(ptr != NULL);
	REQUIRE(mem != NULL);

	newlist = isc_mem_get(mem, sizeof(*newlist));
	if (newlist == NULL) {
		return (ISC_R_NOMEMORY);
	}

	newlist->magic = DNS_C_IPMLIST_MAGIC;
	newlist->mem = mem;
	newlist->refcount = 1;

	ISC_LIST_INIT(newlist->elements);

	*ptr = newlist;

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_c_ipmatchlist_detach(dns_c_ipmatchlist_t **ml) {
	dns_c_ipmatchelement_t *ime;
	dns_c_ipmatchelement_t *iptr;
	dns_c_ipmatchlist_t *iml;
	isc_mem_t *mem;

	REQUIRE(ml != NULL);
	REQUIRE(*ml != NULL);

	iml = *ml;
	*ml = NULL;

	REQUIRE(DNS_C_IPMLIST_VALID(iml));
	INSIST(iml->refcount > 0);

	iml->refcount--;
	if (iml->refcount > 0) {
		return (ISC_R_SUCCESS);
	}

	mem = iml->mem;
	INSIST(mem != NULL);

	ime = ISC_LIST_HEAD(iml->elements);
	while (ime != NULL) {
		iptr = ISC_LIST_NEXT(ime, next);
		dns_c_ipmatchelement_delete(mem, &ime);

		ime = iptr;
	}

	isc_mem_put(mem, iml, sizeof(*iml));

	return (ISC_R_SUCCESS);
}

void
dns_c_ipmatchlist_attach(dns_c_ipmatchlist_t *source,
			 dns_c_ipmatchlist_t **target)
{

	REQUIRE(DNS_C_IPMLIST_VALID(source));

	INSIST(source->refcount > 0);

	source->refcount++;
	*target = source;
}

isc_result_t
dns_c_ipmatchlist_empty(dns_c_ipmatchlist_t *ipml) {
	dns_c_ipmatchelement_t *ime ;
	dns_c_ipmatchelement_t *imptmp;
	isc_result_t res = ISC_R_SUCCESS;

	REQUIRE(DNS_C_IPMLIST_VALID(ipml));

	ime = ISC_LIST_HEAD(ipml->elements);
	while (ime != NULL) {
		imptmp = ISC_LIST_NEXT(ime, next);
		res = dns_c_ipmatchelement_delete(ipml->mem, &ime);
		if (res != ISC_R_SUCCESS) {
			break;
		}
		ime = imptmp;
	}

	return (res);
}

isc_result_t
dns_c_ipmatchlist_copy(isc_mem_t *mem,
		       dns_c_ipmatchlist_t **dest, dns_c_ipmatchlist_t *src)
{
	dns_c_ipmatchelement_t *ime;
	dns_c_ipmatchelement_t *ptr;
	dns_c_ipmatchlist_t *newlist;
	isc_result_t result;

	REQUIRE(mem != NULL);
	REQUIRE(dest != NULL);
	REQUIRE(DNS_C_IPMLIST_VALID(src));

	*dest = NULL;

	result = dns_c_ipmatchlist_new(mem, &newlist);
	if (result != ISC_R_SUCCESS) {
		return (result);
	}

	ime = ISC_LIST_HEAD(src->elements);
	while (ime != NULL) {
		result = dns_c_ipmatchelement_copy(mem, &ptr, ime);
		if (result != ISC_R_SUCCESS) {
			dns_c_ipmatchlist_detach(&newlist);
			return (result);
		}

		ISC_LIST_APPEND(newlist->elements, ptr, next);

		ime = ISC_LIST_NEXT(ime, next);
	}

	*dest = newlist;

	return (ISC_R_SUCCESS);
}

isc_boolean_t
dns_c_ipmatchlist_equal(dns_c_ipmatchlist_t *l1, dns_c_ipmatchlist_t *l2) {
	dns_c_ipmatchelement_t *e1, *e2;

	REQUIRE(l1 == NULL || DNS_C_IPMLIST_VALID(l1));
	REQUIRE(l2 == NULL || DNS_C_IPMLIST_VALID(l2));

	if (l1 == NULL && l2 == NULL)
		return (ISC_TRUE);
	if (l1 != NULL || l2 != NULL)
		return (ISC_FALSE);

	e1 = ISC_LIST_HEAD(l1->elements);
	e2 = ISC_LIST_HEAD(l2->elements);
	while (e1 != NULL && e2 != NULL) {
		if (!dns_c_ipmatchelement_equal(e1, e2))
			return (ISC_FALSE);
		e1 = ISC_LIST_NEXT(e1, next);
		e2 = ISC_LIST_NEXT(e2, next);
	}

	if (l1 != NULL || l2 != NULL)
		return (ISC_FALSE);
	return (ISC_TRUE);
}

isc_result_t
dns_c_ipmatchlist_append(dns_c_ipmatchlist_t *dest,
			 dns_c_ipmatchlist_t *src,
			 isc_boolean_t negate)
{
	dns_c_ipmatchelement_t *ime;
	dns_c_ipmatchelement_t *ime_copy;
	isc_result_t result = ISC_R_SUCCESS;

	REQUIRE(DNS_C_IPMLIST_VALID(dest));
	REQUIRE(DNS_C_IPMLIST_VALID(src));

	ime = ISC_LIST_HEAD(src->elements);
	while (ime != NULL) {
		result = dns_c_ipmatchelement_copy(dest->mem,
						   &ime_copy,
						   ime);
		if (result != ISC_R_SUCCESS) {
			break;
		}

		if (negate) {
			dns_c_ipmatch_negate(ime_copy);
		}

		ISC_LIST_APPEND(dest->elements, ime_copy, next);

		ime = ISC_LIST_NEXT(ime, next);
	}

	return (result);
}

isc_result_t
dns_c_ipmatchelement_print(FILE *fp, int indent, dns_c_ipmatchelement_t *ipme)
{
	int bits;

	REQUIRE(fp != NULL);
	REQUIRE(DNS_C_IPMELEM_VALID(ipme));

	if (dns_c_ipmatchelement_isneg(ipme) &&
	    ipme->type != dns_c_ipmatch_any) {
		/* a '!any' element gets printed as 'none' */
		fputc('!', fp);
	} else {
		fputc(' ', fp);
	}

	switch (ipme->type) {
	case dns_c_ipmatch_pattern:
		dns_c_print_ipaddr(fp, &ipme->u.direct.address);

		bits = ipme->u.direct.mask;
		if (bits > 0) {
			isc_uint32_t fam =
				ipme->u.direct.address.type.sa.sa_family;
			if ((fam == AF_INET && bits < 32) ||
			    (fam == AF_INET6 && bits < 128)) {
				fprintf(fp, "/%d", bits);
			}
		}
		break;

	case dns_c_ipmatch_indirect:
		if (ipme->u.indirect.refname.base != NULL) {
			fprintf(fp, "%s", ipme->u.indirect.refname.base);
		} else {
			dns_c_ipmatchlist_print(fp, indent,
						ipme->u.indirect.list);
		}
		break;

	case dns_c_ipmatch_key:
		fprintf(fp, "key %s", ipme->u.key);
		break;

	case dns_c_ipmatch_localhost:
		fprintf(fp, "localhost");
		break;

	case dns_c_ipmatch_localnets:
		fprintf(fp, "localnets");
		break;

	case dns_c_ipmatch_any:
		if (dns_c_ipmatchelement_isneg(ipme)) {
			fprintf(fp, "none");
		} else {
			fprintf(fp, "any");
		}
		break;

	case dns_c_ipmatch_none:
		isc_log_write(dns_lctx, DNS_LOGCATEGORY_CONFIG,
			      DNS_LOGMODULE_CONFIG, ISC_LOG_CRITICAL,
			      "dns_ipmatch_none element type");
		return (ISC_R_FAILURE);

	case dns_c_ipmatch_acl:
		fprintf(fp, "%s", ipme->u.aclname);
		break;
	}

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_c_ipmatchlist_print(FILE *fp, int indent, dns_c_ipmatchlist_t *ml) {
	dns_c_ipmatchelement_t *ipme ;

	REQUIRE(DNS_C_IPMLIST_VALID(ml));
	REQUIRE(fp != NULL);

	/* no indent on first line. */
	fprintf(fp, "{\n");
	ipme = ISC_LIST_HEAD(ml->elements);
	if (ipme == NULL) {
		dns_c_printtabs(fp, indent);
		fprintf(fp,
			"/* this list intentionally left blank */\n");
	} else {
		while (ipme != NULL) {
			dns_c_printtabs(fp, indent);
			dns_c_ipmatchelement_print(fp, indent + 1, ipme);
			fprintf(fp, ";\n");

			ipme = ISC_LIST_NEXT(ipme, next);
		}
	}

	dns_c_printtabs(fp, indent - 1);
	fprintf(fp, "}");

	return (ISC_R_SUCCESS);
}


isc_boolean_t
dns_c_ipmatchlist_walk(dns_c_ipmatchlist_t *list, dns_c_ipmlwalker func)
{
	isc_boolean_t retval = ISC_TRUE;
	dns_c_ipmatchelement_t *ipme ;

	REQUIRE(DNS_C_IPMLIST_VALID(list));

	ipme = ISC_LIST_HEAD(list->elements);
	while (retval == ISC_TRUE && ipme != NULL) {
		switch (ipme->type) {
		case dns_c_ipmatch_pattern:
		case dns_c_ipmatch_key:
		case dns_c_ipmatch_localhost:
		case dns_c_ipmatch_localnets:
		case dns_c_ipmatch_any:
		case dns_c_ipmatch_none:
			retval = ISC_TF(retval && (*func)(ipme));
			break;

		case dns_c_ipmatch_indirect:
			retval = ISC_TF(retval &&
					dns_c_ipmatchlist_walk(ipme->
							       u.indirect.list,
							       func));
			break;

		default:
			break;
		}

		ipme = ISC_LIST_NEXT(ipme, next);
	}

	return (retval);
}




isc_result_t
dns_c_iplist_new(isc_mem_t *mem, int length, dns_c_iplist_t **newlist) {
	dns_c_iplist_t *list;
	size_t bytes;
	int i;
	REQUIRE(mem != NULL);
	REQUIRE(length > 0);
	REQUIRE(newlist != NULL);

	list = isc_mem_get(mem, sizeof(*list));
	if (list == NULL) {
		return (ISC_R_NOMEMORY);
	}

	bytes = sizeof(isc_sockaddr_t) * length;
	list->ips = isc_mem_get(mem, bytes);
	if (list->ips == NULL) {
		isc_mem_put(mem, list, sizeof(*list));
		return (ISC_R_NOMEMORY);
	}
	memset(list->ips, 0x0, bytes);

	bytes = sizeof(dns_name_t *) * length;
	list->keys = isc_mem_get(mem, bytes);
	if (list->keys == NULL) {
		isc_mem_put(mem, list->ips, sizeof(isc_sockaddr_t) * length);
		isc_mem_put(mem, list, sizeof(*list));
		return (ISC_R_NOMEMORY);
	}
	for (i = 0 ; i < length ; i++)
		list->keys[i] = NULL;

	list->magic = DNS_C_IPLIST_MAGIC;
	list->size = length;
	list->nextidx = 0;
	list->mem = mem;
	list->refcount = 1;

	*newlist = list;

	return (ISC_R_SUCCESS);
}

isc_result_t
dns_c_iplist_detach(dns_c_iplist_t **list) {
	dns_c_iplist_t *l ;
	unsigned int i;


	REQUIRE(list != NULL);
	REQUIRE(*list != NULL);

	l = *list;

	REQUIRE(DNS_C_IPLIST_VALID(l));

	INSIST(l->refcount > 0);
	l->refcount--;

	if (l->refcount == 0) {
		for (i = 0 ; i < l->size ; i++) {
			if (l->keys[i] != NULL) {
				dns_name_free(l->keys[i], l->mem);
				isc_mem_put(l->mem, l->keys[i],
					    sizeof(dns_name_t));
				l->keys[i] = NULL;
			}
		}
		isc_mem_put(l->mem, l->keys, sizeof(dns_name_t *) * l->size);
		isc_mem_put(l->mem, l->ips, sizeof(isc_sockaddr_t) * l->size);
		isc_mem_put(l->mem, l, sizeof(*l));
	}

	*list = NULL;

	return (ISC_R_SUCCESS);
}

isc_boolean_t
dns_c_iplist_haskeys(dns_c_iplist_t *list)
{
	unsigned int i;

	if (list->keys == NULL)
		return (ISC_FALSE);

	for (i = 0 ; i < list->nextidx ; i++) {
		if (list->keys[i] != NULL)
			return (ISC_TRUE);
	}

	return (ISC_FALSE);
}

void
dns_c_iplist_attach(dns_c_iplist_t *source, dns_c_iplist_t **target) {
	REQUIRE(DNS_C_IPLIST_VALID(source));
	INSIST(source->refcount > 0);

	source->refcount++;
	*target = source;
}

isc_result_t
dns_c_iplist_copy(isc_mem_t *mem, dns_c_iplist_t **dest, dns_c_iplist_t *src) {
	dns_c_iplist_t *newl;
	isc_result_t res;
	isc_uint32_t i;

	REQUIRE(dest != NULL);
	REQUIRE(DNS_C_IPLIST_VALID(src));

	res = dns_c_iplist_new(mem, src->size, &newl);
	if (res != ISC_R_SUCCESS)
		return (res);

	for (i = 0 ; i < src->nextidx ; i++) {
		newl->ips[i] = src->ips[i];
		newl->keys[i] = NULL;
		if (src->keys[i] != NULL) {
			newl->keys[i] = isc_mem_get(mem, sizeof(dns_name_t));
			if (newl->keys[i] == NULL) {
				dns_c_iplist_detach(&newl);
				return (ISC_R_NOMEMORY);
			} else {
				res = dns_name_dup(src->keys[i], mem,
						   newl->keys[i]);
				if (res != ISC_R_SUCCESS) {
					dns_c_iplist_detach(&newl);
					return (res);
				}
			}
		}
	}

	newl->nextidx = src->nextidx;

	*dest = newl;

	return (ISC_R_SUCCESS);
}

isc_boolean_t
dns_c_iplist_equal(dns_c_iplist_t *list1, dns_c_iplist_t *list2) {
	isc_uint32_t i;

	REQUIRE(DNS_C_IPLIST_VALID(list1));
	REQUIRE(DNS_C_IPLIST_VALID(list2));

	if (list1->nextidx != list2->nextidx)
		return (ISC_FALSE);

	for (i = 0 ; i < list1->nextidx ; i++) {
		if (!isc_sockaddr_equal(&list1->ips[i], &list2->ips[i]))
			return (ISC_FALSE);

		if ((list1->keys[i] == NULL && list2->keys[i] != NULL) ||
		    (list1->keys[i] != NULL && list2->keys[i] == NULL))
			return (ISC_FALSE);

		if (list1->keys[i] != NULL &&
		    !dns_name_equal(list1->keys[i], list2->keys[i]))
			return (ISC_FALSE);
	}

	return (ISC_TRUE);
}

void
dns_c_iplist_printfully(FILE *fp, int indent, isc_boolean_t porttoo,
			dns_c_iplist_t *list)
{
	isc_uint32_t i;
	in_port_t port;
	in_port_t tmpport;
	isc_boolean_t athead = ISC_TRUE;

	REQUIRE(DNS_C_IPLIST_VALID(list));

	if (list->nextidx == 0) {
		fputc('{', fp);
		fputc('\n', fp);
		dns_c_printtabs(fp, indent);
		fprintf(fp, "/* no ip addresses defined */\n");
		dns_c_printtabs(fp, indent - 1);
		fputc('}', fp);
	} else {
		if (porttoo) {
			port = isc_sockaddr_getport(&list->ips[0]);

			for (i = 0 ; i < list->nextidx ; i++) {
				tmpport = isc_sockaddr_getport(&list->ips[i]);
				if (tmpport != port) {
					athead = ISC_FALSE;
				}
			}

			if (athead) {
				fprintf(fp, "port %d ", port);
			}
		}

		fputc('{', fp);
		fputc('\n', fp);

		for (i = 0 ; i < list->nextidx ; i++) {
			dns_c_printtabs(fp, indent);
			dns_c_print_ipaddr(fp, &list->ips[i]);
			if (!athead) {
				fprintf(fp, " port %d",
					isc_sockaddr_getport(&list->ips[i]));
			}
			if (list->keys[i] != NULL) {
				fprintf(fp, " key \"");
				dns_name_print(list->keys[i], fp);
				fprintf(fp, "\" ");
			}
			fprintf(fp, ";\n");
		}
		dns_c_printtabs(fp, indent - 1);
		fputc('}', fp);
	}

	fputc('\n', fp);
}

void
dns_c_iplist_print(FILE *fp, int indent, dns_c_iplist_t *list) {
	dns_c_iplist_printfully(fp, indent, ISC_FALSE, list);
}

isc_result_t
dns_c_iplist_append(dns_c_iplist_t *list, isc_sockaddr_t newaddr,
		    const char *key)
{
	isc_uint32_t i;
	isc_result_t res;

	REQUIRE(DNS_C_IPLIST_VALID(list));

	for (i = 0 ; i < list->nextidx ; i++) {
		if (memcmp(&list->ips[i], &newaddr, sizeof(newaddr)) == 0) {
			break;
		}
	}

	if (i < list->nextidx)
		return (ISC_R_FAILURE);

	if (list->nextidx == list->size) {
		isc_sockaddr_t *newlist;
		dns_name_t     **newkeys;
		size_t newbytes;
		size_t oldbytes = list->size * sizeof(list->ips[0]);
		size_t newsize = list->size + 10;

		newbytes = sizeof(list->ips[0]) * newsize;
		newlist = isc_mem_get(list->mem, newbytes);
		if (newlist == NULL)
			return (ISC_R_NOMEMORY);

		memset(newlist, 0x0, newbytes);
		memcpy(newlist, list->ips, oldbytes);

		isc_mem_put(list->mem, list->ips, oldbytes);
		list->ips = newlist;

		oldbytes = sizeof(dns_name_t *) * list->size;
		newbytes = sizeof(dns_name_t *) * newsize;
		newkeys = isc_mem_get(list->mem, newbytes);
		if (newkeys == NULL)
			return (ISC_R_NOMEMORY);

		for (i = 0 ; i < list->size ; i++) {
			newkeys[i] = list->keys[i];
			list->keys[i] = NULL;
		}
		for (i = list->size ; i < newsize ; i++)
			newkeys[i] = NULL;

		isc_mem_put(list->mem, list->keys, oldbytes);
		list->keys = newkeys;

		i = list->size;
		list->size = newsize;
	}

	list->ips[i] = newaddr;
	list->nextidx++;

	res = ISC_R_SUCCESS;

	if (key != NULL) {
		if (list->keys[i] != NULL) {
			dns_name_free(list->keys[i], list->mem);
			isc_mem_put(list->mem, list->keys,
				    sizeof(dns_name_t));
			list->keys[i] = NULL;
		}

		res = dns_c_charptoname(list->mem, key, &list->keys[i]);
	}

	return (res);
}



isc_result_t
dns_c_iplist_remove(dns_c_iplist_t *list, isc_sockaddr_t newaddr) {
	isc_uint32_t i;

	REQUIRE(DNS_C_IPLIST_VALID(list));

	for (i = 0 ; i < list->nextidx ; i++) {
		if (memcmp(&list->ips[i], &newaddr, sizeof(newaddr)) == 0) {
			break;
		}
	}

	if (i == list->nextidx) {
		return (ISC_R_FAILURE);
	}

	list->nextidx--;

	if (list->keys[i] != NULL) {
		dns_name_reset(list->keys[i]);
		isc_mem_put(list->mem, list->keys[i], sizeof(dns_name_t));
	}

	for ( /* nothing */ ; i < list->nextidx ; i++) {
		list->ips[i] = list->ips[i + 1];
		list->keys[i] = list->keys[i + 1];
	}

	return (ISC_R_SUCCESS);
}

/*
 * Check that the address given is a network address with the given number
 * of high order bits.
 */
static isc_result_t
checkmask(isc_sockaddr_t *address, isc_uint32_t bits) {
	if (bits > 0) {
		if (address->type.sa.sa_family == AF_INET) {
			isc_uint32_t mask;

			if (bits > 32) {
				return (ISC_R_FAILURE);
			}

			mask = ntohl(0xffffffffU << (32 - bits));

			if ((mask & address->type.sin.sin_addr.s_addr) !=
			    address->type.sin.sin_addr.s_addr) {
				return (ISC_R_FAILURE);
			}
		} else if (address->type.sa.sa_family == AF_INET6) {
			struct in6_addr iaddr;
			unsigned char *maskp;
			unsigned char *addrp;
			int i;

			if (bits > 128) {
				return (ISC_R_FAILURE);
			}

			if (bits2v6mask(&iaddr, bits) != ISC_R_SUCCESS) {
				return (ISC_R_FAILURE);
			}

			addrp = (unsigned char *)&address->type.sin6.sin6_addr;
			maskp = (unsigned char *)&iaddr;
			for (i = 0 ; i < 16 ; i++) {
				if ((addrp[i] & maskp[i]) != addrp[i]) {
					return (ISC_R_FAILURE);
				}
			}
		}
	}

	return (ISC_R_SUCCESS);
}

/*
 * Create a 128 bits mask in network byte order in the the IPv6 address
 * section of the sockaddr. The bits argument is the number of high bits
 * that are to be set to 1.
 */
static isc_result_t
bits2v6mask(struct in6_addr *addr, isc_uint32_t bits) {
	int i;
	isc_uint32_t bitmask[4];
	char addrbuff [ sizeof("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") + 1 ];

	INSIST(bits <= 128);

	/* Break the 128 bits up into 32-bit sections */
	bitmask[0] = bitmask[1] = bitmask[2] = bitmask[3] = 0U;

	if (bits >= 32) {
		bitmask[0] = 0xffffffffU;
	} else if (bits > 0) {
		bitmask[0] = 0xffffffffU << (32 - bits);
	}

	if (bits >= 64) {
		bitmask[1] = 0xffffffffU;
	} else if (bits > 32) {
		bitmask[1] = 0xffffffffU << (64 - bits);
	}

	if (bits >= 96) {
		bitmask[2] = 0xffffffffU;
		bitmask[3] = 0xffffffffU << (128 - bits);
	} else if (bits > 64) {
		bitmask[2] = 0xffffffffU << (96 - bits);
	}

	memset(addr, 0x0, sizeof(*addr));

	sprintf(addrbuff, "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
		(((bitmask[0] & 0xffff0000U) >> 16) & 0xffffU),
		(bitmask[0] & 0xffff),
		(((bitmask[1] & 0xffff0000U) >> 16) & 0xffffU),
		(bitmask[1] & 0xffff),
		(((bitmask[2] & 0xffff0000U) >> 16) & 0xffffU),
		(bitmask[2] & 0xffff),
		(((bitmask[3] & 0xffff0000U) >> 16) & 0xffffU),
		(bitmask[3] & 0xffff));

	i = inet_pton(AF_INET6, addrbuff, addr);

	return (i == 1 ? ISC_R_SUCCESS : ISC_R_FAILURE);
}



