/*	$NetBSD: db_sec.c,v 1.1.2.1 1999/12/04 16:53:50 he Exp $	*/


#if !defined(lint) && !defined(SABER)
static const char rcsid[] = "Id: db_sec.c,v 8.30 1999/10/15 21:06:49 vixie Exp";
#endif /* not lint */

/*
 * Copyright (c) 1986, 1990
 *    The Regents of the University of California.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 * 	This product includes software developed by the University of
 * 	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Portions Copyright (c) 1993 by Digital Equipment Corporation.
 * 
 * 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, and that
 * the name of Digital Equipment Corporation not be used in advertising or
 * publicity pertaining to distribution of the document or software without
 * specific, written prior permission.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
 * CORPORATION 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.
 */

/*
 * Portions Copyright (c) 1996-1999 by 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.
 */

#include "port_before.h"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>

#include <ctype.h>
#include <resolv.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <time.h>

#include <isc/eventlib.h>
#include <isc/logging.h>
#include <isc/memcluster.h>
#include <isc/tree.h>

#include <isc/dst.h>

#include "port_after.h"

#include "named.h"

struct zpubkey {
	struct dst_key *zpk_key; /* Should be DST_KEY */
	char *zpk_name;
	struct zpubkey *zpk_next;
};

typedef struct zpubkey *zpubkey_list;

static int nxt_match_rrset(struct databuf *dp, struct db_rrset *rrset);

/*
 * A converted databuf is a stripped down databuf after converting the 
 * data to wire format.
 */
struct converted_databuf {
	struct converted_databuf *cd_next;
	u_char *cd_data;
	int cd_size, cd_alloc;
};

/* All of the trusted keys and zone keys */
static tree *trusted_keys = NULL;

static int
compare_pubkey (struct zpubkey *zpk1, struct zpubkey *zpk2) {
	char ta[NS_MAXDNAME], tb[NS_MAXDNAME];

	if (ns_makecanon(zpk1->zpk_name, ta, sizeof ta) < 0 ||
	    ns_makecanon(zpk2->zpk_name, tb, sizeof tb) < 0)
                return (-1);
        return (strcasecmp(ta, tb));
}

static struct zpubkey *
tree_srch_pubkey (const char *name) {
	struct zpubkey tkey, *key;

	tkey.zpk_name = (char *) name;
	if (trusted_keys == NULL) {
		tree_init(&trusted_keys);
		return (NULL);
	}
	key = (struct zpubkey *)tree_srch(&trusted_keys, compare_pubkey,
					  &tkey);
	return (key);
}

static DST_KEY *
find_public_key (const char *name, u_int16_t key_id) {
	struct namebuf *knp;
	struct hashbuf *htp;
	struct databuf *dp;
	const char *fname;
	DST_KEY *key;

	ns_debug(ns_log_default, 5, "find_public_key(%s, %d)", name, key_id);

	htp = hashtab;
	knp = nlookup (name, &htp, &fname, 0);
	if (fname != name)
		/* The name doesn't exist, so there's no key */
		return (NULL);

	for (dp = knp->n_data; dp != NULL; dp = dp->d_next) {
		if (dp->d_type != ns_t_key || dp->d_secure < DB_S_SECURE)
			continue;
		key = dst_dnskey_to_key(name, dp->d_data, dp->d_size);
		/* XXX what about multiple keys with same footprint? */
		if (key) {
			if (key->dk_id == ntohs(key_id))
				return (key);
			else
				dst_free_key(key);
		}
	}
	return (NULL);
}


static DST_KEY *
find_trusted_key (const char *name, u_int16_t key_id) {
	struct zpubkey *zpk;
	zpubkey_list keylist = tree_srch_pubkey (name);

	ns_debug(ns_log_default, 5, "find_trusted_key(%s, %d)", name, key_id);

	for (zpk = keylist; zpk; zpk = zpk->zpk_next)
		if (zpk->zpk_key->dk_id == ntohs(key_id))
			return (zpk->zpk_key);

	return (NULL);
}

int
add_trusted_key (const char *name, const int flags, const int proto,
		 const int alg, const char *str)
{
	zpubkey_list keylist;
	struct zpubkey *zpk;
	u_char buf[1024];
	int n;

	keylist = tree_srch_pubkey (name);

	zpk = (struct zpubkey *) memget (sizeof (struct zpubkey));
	if (zpk == NULL)
		ns_panic(ns_log_default, 1,
			 "add_trusted_key: memget failed(%s)", name);
	n = b64_pton(str, buf, sizeof(buf));
	if (n < 0)
		goto failure;
	zpk->zpk_key = dst_buffer_to_key(name, alg, flags, proto, buf, n);
	if (zpk->zpk_key == NULL) {
		ns_warning(ns_log_default,
			   "add_trusted_key: dst_buffer_to_key(%s) failed",
			   name);
		goto failure;
	}
	zpk->zpk_name = zpk->zpk_key->dk_key_name;
	zpk->zpk_next = NULL;

	if (keylist == NULL) {
		if (tree_add (&trusted_keys, compare_pubkey, zpk, NULL) == NULL)
			goto failure;
	}
	else {
		struct zpubkey *tkey = keylist;
		while (tkey->zpk_next)
			tkey = tkey->zpk_next;
		tkey->zpk_next = zpk;
	}

	return (1);
 failure:
	memput(zpk, sizeof (struct zpubkey));
	return (0);
}

/* Can the signer sign records for this name?  This is a heuristic. */
static int
can_sign(const char *name, const char *signer) {
	return (ns_samedomain(name, signer) &&
		dn_count_labels(name) - dn_count_labels(signer) <= 2);
}

static int
rrset_set_security(struct db_rrset *rrset, int slev) {
	struct dnode *dnp;

	for (dnp = rrset->rr_list; dnp != NULL; dnp = dnp->dn_next)
		dnp->dp->d_secure = slev;
	for (dnp = rrset->rr_sigs; dnp != NULL; dnp = dnp->dn_next)
		dnp->dp->d_secure = slev;
	return (slev);
}

static int
convert_databuf(struct databuf *dp, struct converted_databuf *cdp) {
	u_char *bp = cdp->cd_data;
	u_char *cp = dp->d_data;
	u_char *eob = cdp->cd_data + cdp->cd_alloc;
	int len;
	u_char buf[MAXDNAME];

	switch (dp->d_type) {
	    case ns_t_soa:
	    case ns_t_minfo:
	    case ns_t_rp:
		if (eob - bp < strlen((char *)cp) + 1)
			return (-1);
		if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
			return (-1);
		len = ns_name_ntol(buf, bp, eob - bp);
		if (len < 0)
			return (-1);
		bp += len;
		cp += strlen((char *)cp) + 1;

		if (eob - bp < strlen((char *)cp) + 1)
			return (-1);
		if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
			return (-1);
		len = ns_name_ntol(buf, bp, eob - bp);
		if (len < 0)
			return (-1);
		bp += len;
		cp += strlen((char *)cp) + 1;

		if (dp->d_type == ns_t_soa) {
			if (eob - bp < 5 * INT32SZ)
				return (-1);
			memcpy(bp, cp, 5 * INT32SZ);
			bp += (5 * INT32SZ);
			cp += (5 * INT32SZ);
		}

		break;

	    case ns_t_ns:
	    case ns_t_cname:
	    case ns_t_mb:
	    case ns_t_mg:
	    case ns_t_mr:
	    case ns_t_ptr:
	    case ns_t_nxt:
		if (eob - bp < strlen((char *)cp) + 1)
			return (-1);
		if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
			return (-1);
		len = ns_name_ntol(buf, bp, eob - bp);
		if (len < 0)
			return (-1);
		bp += len;
		cp += (len = strlen((char *)cp) + 1);

		if (dp->d_type == ns_t_nxt) {
			if (eob - bp < dp->d_size - len)
				return (-1);
			memcpy(bp, cp, dp->d_size - len);
			bp += (dp->d_size - len);
			cp += (dp->d_size - len);
		}
		break;

	    case ns_t_srv:
		if (eob - bp < 2 * INT16SZ)
			return (-1);
		memcpy(bp, cp, 2 * INT16SZ);
		bp += (2 * INT16SZ);
		cp += (2 * INT16SZ);
		/* no break */
	    case ns_t_rt:
	    case ns_t_mx:
	    case ns_t_afsdb:
	    case ns_t_px:
		if (eob - bp < INT16SZ)
			return (-1);
		memcpy (bp, cp, INT16SZ);
		bp += INT16SZ;
		cp += INT16SZ;

		if (eob - bp < strlen((char *)cp) + 1)
			return (-1);
		if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
			return (-1);
		len = ns_name_ntol(buf, bp, eob - bp);
		if (len < 0)
			return (-1);
		bp += len;
		cp += strlen((char *)cp) + 1;

		if (dp->d_type == ns_t_px) {
			if (eob - bp < strlen((char *)cp) + 1)
				return (-1);
			if (ns_name_pton((char *)cp, buf, sizeof buf) < 0)
				return (-1);
			len = ns_name_ntol(buf, bp, eob - bp);
			if (len < 0)
				return (-1);
			bp += len;
			cp += strlen((char *)cp) + 1;
		}
		break;

	    default:
		if (eob - bp < dp->d_size)
			return (-1);
		memcpy(bp, cp, dp->d_size);
		bp += dp->d_size;
	}
	cdp->cd_size = bp - cdp->cd_data;
	return (cdp->cd_size);
}

static int
digest_rr(char *envelope, int elen, struct converted_databuf *cdp,
	  char *buffer, int blen)
{
	char *bp = buffer, *eob = buffer + blen;

	if (eob - bp < elen)
		return (-1);
	memcpy (bp, envelope, elen);
	bp += elen;

	if (eob - bp < INT16SZ)
		return (-1);
	PUTSHORT(cdp->cd_size, bp);

	if (eob - bp < cdp->cd_size)
		return (-1);
	memcpy (bp, cdp->cd_data, cdp->cd_size);
	bp += cdp->cd_size;

	return (bp - buffer);
}

/* Sorts the converted databuf in the list */
static void
insert_converted_databuf(struct converted_databuf *cdp,
			struct converted_databuf **clist)
{
	struct converted_databuf *tcdp, *next;
	int t;

#define compare_cdatabuf(c1, c2, t) \
	(t = memcmp(c1->cd_data, c2->cd_data, MIN(c1->cd_size, c2->cd_size)), \
	 t == 0 ? c1->cd_size - c2->cd_size : t)

	if (*clist == NULL) {
		*clist = cdp;
		return;
	}

	tcdp = *clist;
	if (compare_cdatabuf(cdp, tcdp, t) < 0) {
		cdp->cd_next = tcdp;
		*clist = cdp;
		return;
	}

	next = tcdp->cd_next;
	while (next) {
		if (compare_cdatabuf(cdp, next, t) < 0) {
			cdp->cd_next = next;
			tcdp->cd_next = cdp;
			return;
		}
		tcdp = next;
		next = next->cd_next;
	}
	tcdp->cd_next = cdp;
#undef compare_cdatabuf
}

static void
free_clist(struct converted_databuf *clist) {
	struct converted_databuf *cdp;

	while (clist != NULL) {
		cdp = clist;
		clist = clist->cd_next;
		memput(cdp->cd_data, cdp->cd_alloc);
		memput(cdp, sizeof(struct converted_databuf));
	}
}

/* Removes all empty nodes from an rrset's SIG list. */
static void
rrset_trim_sigs(struct db_rrset *rrset) {
	struct dnode *dnp, *odnp, *ndnp;

	odnp = NULL;
	dnp = rrset->rr_sigs;
	while (dnp != NULL) {
		if (dnp->dp != NULL) {
			odnp = dnp;
			dnp = dnp->dn_next;
		}
		else {
			if (odnp != NULL)
				odnp->dn_next = dnp->dn_next;
			else
				rrset->rr_sigs = dnp->dn_next;
			ndnp = dnp->dn_next;
			memput(dnp, sizeof(struct dnode));
			dnp = ndnp;
		}
	}
}

int
verify_set(struct db_rrset *rrset) {
	DST_KEY *key = NULL;
	struct sig_record *sigdata;
	struct dnode *sigdn;
	struct databuf *sigdp;
	time_t now;
	char *signer;
	u_char name_n[MAXDNAME];
	u_char *sig, *eom;
	int trustedkey = 0, siglen, labels, len = 0, ret;
	u_char *buffer = NULL, *bp;
	u_char envelope[MAXDNAME+32], *ep;
	struct dnode *dnp;
	int bufsize = 2048; /* Large enough for MAXDNAME + SIG_HDR_SIZE */
	struct converted_databuf *clist = NULL, *cdp;
	int dnssec_failed = 0, dnssec_succeeded = 0;
	int return_value;
	int i;

	if (rrset == NULL || rrset->rr_name == NULL) {
		ns_warning (ns_log_default, "verify_set: missing rrset/name");
		return (rrset_set_security(rrset, DB_S_FAILED));
	}

	if (rrset->rr_sigs == NULL)
		return (rrset_set_security(rrset, DB_S_INSECURE));

	ns_debug(ns_log_default, 5, "verify_set(%s, %s, %s)", rrset->rr_name,
		 p_type(rrset->rr_type), p_class(rrset->rr_class));

	now = time(NULL);

	for (sigdn = rrset->rr_sigs; sigdn != NULL; sigdn = sigdn->dn_next) {
		u_int32_t namefield;
		struct sig_record sigrec;

		sigdp = sigdn->dp;

		eom = sigdp->d_data + sigdp->d_size;
		if (sigdp->d_size < SIG_HDR_SIZE) {
			return_value = DB_S_FAILED;
			goto end;
		}
		memcpy(&sigrec, sigdp->d_data, SIG_HDR_SIZE);
		sigdata = &sigrec;
		signer = (char *)sigdp->d_data + SIG_HDR_SIZE;
		sig = (u_char *)signer + strlen(signer) + 1;
		siglen = eom - sig;

		/*
		 * Don't verify a set if the SIG inception time is in
		 * the future.  This should be fixed before 2038 (BEW)
		 */
		if (ntohl(sigdata->sig_time_n) > now)
			continue;

		/* An expired set is dropped, but the data is not. */
		if (ntohl(sigdata->sig_exp_n) < now) {
			db_freedata(sigdp);
			sigdn->dp = NULL;
			continue;
		}

		/* Cleanup from the last iteration if we continue'd */
		if (trustedkey == 0 && key != NULL)
			dst_free_key(key);

		key = find_trusted_key(signer, sigdata->sig_keyid_n);

		if (key == NULL) {
			trustedkey = 0;
			key = find_public_key(signer, sigdata->sig_keyid_n);
		}
		else    
			trustedkey = 1;

		/* if we don't have the key, either
		 *   - the data should be considered insecure
		 *   - the sig is not a dnssec signature
		 */
		if (key == NULL)
			continue;

		/* Can a key with this name sign the data? */
		if (!can_sign(rrset->rr_name, signer))
			continue;

		/* Check the protocol and flags of the key */
		if (key->dk_proto != NS_KEY_PROT_DNSSEC &&
		    key->dk_proto != NS_KEY_PROT_ANY)
			continue;
		if (key->dk_flags & NS_KEY_NO_AUTH)
			continue;
		namefield = key->dk_flags & NS_KEY_NAME_TYPE;
		if (namefield == NS_KEY_NAME_USER ||
		    namefield == NS_KEY_NAME_RESERVED)
			continue;
		if (namefield == NS_KEY_NAME_ENTITY &&
		    (key->dk_flags & NS_KEY_SIGNATORYMASK == 0))
			continue;

		/*
		 * If we're still here, we have a non-null key that's either
		 * a zone key or an entity key with signing authority.
		 */

		if (buffer == NULL) {
			bp = buffer = memget(bufsize);
			if (bp == NULL) {
				return_value = DB_S_FAILED;
				goto end;
			}
		}
		else
			bp = buffer;


		/* Digest the fixed portion of the SIG record */
		memcpy(bp, (char *) sigdata, SIG_HDR_SIZE);
		bp += SIG_HDR_SIZE;

		/* Digest the signer's name, canonicalized */
		if (ns_name_pton(signer, name_n, sizeof name_n) < 0) {
			return_value = DB_S_FAILED;
			goto end;
		}
		i = ns_name_ntol(name_n, (u_char *)bp, bufsize - SIG_HDR_SIZE);
		if (i < 0) {
			return_value = DB_S_FAILED;
			goto end;
		}
		bp += i;

		/* create the dns record envelope:
		 *     <name><type><class><Original TTL>
		 */
		if (ns_name_pton(rrset->rr_name, name_n, sizeof name_n) < 0 ||
		    ns_name_ntol(name_n, (u_char *)envelope, sizeof envelope) < 0) {
			return_value = DB_S_FAILED;
			goto end;
		}

		labels = dn_count_labels(rrset->rr_name);
		if (labels > sigdata->sig_labels_n) {
			ep = envelope;
			for (i=0; i < (labels - 1 - sigdata->sig_labels_n); i++)
				ep += (*ep+1);
			i = dn_skipname(ep, envelope + sizeof envelope);
			if (i < 0) {
				return_value = DB_S_FAILED;
				goto end;
			}
			envelope[0] = '\001';
			envelope[1] = '*';
			memmove(envelope + 2, ep, i);
		}
		i = dn_skipname(envelope, envelope + sizeof envelope);
		if (i < 0) {
			return_value = DB_S_FAILED;
			goto end;
		}
		ep = envelope + i;
		PUTSHORT (rrset->rr_type, ep);
		PUTSHORT (rrset->rr_class, ep);
		if (envelope + sizeof(envelope) - ep < INT32SZ) {
			return_value = DB_S_FAILED;
			goto end;
		}
		memcpy (ep, &sigdata->sig_ottl_n, INT32SZ);
		ep += INT32SZ;

		if (clist == NULL) {
			for (dnp = rrset->rr_list;
			     dnp != NULL;
			     dnp = dnp->dn_next)
			{
				struct databuf *dp = dnp->dp;

				cdp = memget(sizeof(struct converted_databuf));
				if (cdp == NULL) {
					return_value = DB_S_FAILED;
					goto end;
				}
				memset(cdp, 0, sizeof(*cdp));
				/* Should be large enough... */
				cdp->cd_alloc = dp->d_size + 8;
				cdp->cd_data = memget(cdp->cd_alloc);
				if (cdp->cd_data == NULL) {
					memput(cdp, sizeof(*cdp));
					return_value = DB_S_FAILED;
					goto end;
				}
				while (convert_databuf(dp, cdp) < 0) {
					memput(cdp->cd_data, cdp->cd_alloc);
					cdp->cd_alloc *= 2;
					cdp->cd_data = memget(cdp->cd_alloc);
					if (cdp->cd_data == NULL) {
						memput(cdp, sizeof(*cdp));
						return_value = DB_S_FAILED;
						goto end;
					}
				}
				insert_converted_databuf(cdp, &clist);
			}
		}

		for (cdp = clist; cdp != NULL; cdp = cdp->cd_next) {
			len = digest_rr((char *)envelope, ep-envelope, cdp,
					(char *)bp, bufsize - (bp - buffer));
			while (len < 0) {
				u_char *newbuf;

				/* Double the buffer size */
				newbuf = memget(bufsize*2);
				if (newbuf == NULL) {
					return_value = DB_S_FAILED;
					goto end;
				}
				memcpy(newbuf, buffer, bp - buffer);
				bp = (bp - buffer) + newbuf;
				memput(buffer, bufsize);
				buffer = newbuf;
				bufsize *= 2;

				len = digest_rr((char *)envelope, ep-envelope,
						cdp, (char *)bp,
						bufsize - (bp - buffer));
			}
			bp += len;
		}

		if (len < 0) {
			return_value = DB_S_FAILED;
			goto end;
		}

		ret = dst_verify_data(SIG_MODE_ALL, key, NULL, buffer,
				      bp - buffer, sig, siglen);

		if (ret < 0) {
			dnssec_failed++;
			db_freedata(sigdp);
			sigdn->dp = NULL;
		}
		else
			dnssec_succeeded++;
	}

end:
	if (dnssec_failed > 0)
		rrset_trim_sigs(rrset);
	if (trustedkey == 0 && key != NULL)
		dst_free_key(key);

	if (dnssec_failed > 0 && dnssec_succeeded == 0) {
		ns_warning (ns_log_default,
			    "verify_set(%s, %s, %s) failed",
			    rrset->rr_name, p_type(rrset->rr_type),
			    p_class(rrset->rr_class));
		return_value = DB_S_FAILED;
	}
	else if (dnssec_succeeded > 0)
		return_value = DB_S_SECURE;
	else
		return_value = DB_S_INSECURE;
	free_clist(clist);
	if (buffer != NULL)
		memput(buffer, bufsize);
	return (rrset_set_security(rrset, return_value));
}

static void
rrset_free_partial(struct db_rrset *rrset, int free_data, struct dnode *start) {
	struct dnode *dnp;
	int found_start = 0;

	ns_debug(ns_log_default, 5, "rrset_free(%s)", rrset->rr_name);

	if (start == NULL)
		found_start = 1;

	while (rrset->rr_list) {
		dnp = rrset->rr_list;
		if (dnp == start)
			found_start = 1;
		rrset->rr_list = rrset->rr_list->dn_next;
		if (dnp->dp != NULL && free_data == 1 && found_start == 1)
			db_freedata(dnp->dp);
		memput(dnp, sizeof(struct dnode));
	}
	while (rrset->rr_sigs) {
		dnp = rrset->rr_sigs;
		if (dnp == start)
			found_start = 1;
		rrset->rr_sigs = rrset->rr_sigs->dn_next;
		if (dnp->dp != NULL && free_data == 1 && found_start == 1)
			db_freedata(dnp->dp);
		memput(dnp, sizeof(struct dnode));
	}
}

static void
rrset_free(struct db_rrset *rrset, int free_data) {
	rrset_free_partial(rrset, free_data, NULL);
}

/*
 * This is called when we have an rrset with SIGs and no other data.
 * Returns 1 if we either found the necessary data or if the SIG can be added
 * with no other data.  0 indicates that the SIG cannot be added.
 */
static int
attach_data(struct db_rrset *rrset) {
	int type, class;
	struct databuf *dp, *newdp, *sigdp;
	struct dnode *dnp;
	struct namebuf *np;
	struct hashbuf *htp;
	char *signer;
	const char *fname;
	char *name = rrset->rr_name;

	sigdp = rrset->rr_sigs->dp;

	type = SIG_COVERS(sigdp);
	class = sigdp->d_class;
	signer = (char *)(sigdp + SIG_HDR_SIZE);

	/* First, see if the signer can sign data for the name.  If not,
	 * it's not a DNSSEC signature, so we can insert it with no
	 * corresponding data.
	 */
	if (!can_sign(name, signer))
		return (1);

	htp = hashtab;
	np = nlookup (name, &htp, &fname, 0);
	if (fname != name)
		return (0);

	for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
		if (dp->d_type == type && dp->d_class == class) {
			newdp = savedata(class, type, dp->d_ttl, dp->d_data,
					 dp->d_size);
			dnp = (struct dnode *) memget (sizeof (struct dnode));
			if (dnp == NULL)
				ns_panic(ns_log_default, 1,
					 "attach_data: memget failed");
			dnp->dp = newdp;
			dnp->dn_next = rrset->rr_list;
			rrset->rr_list = dnp;
		}
	}
	if (rrset->rr_list != NULL)
		return (1);
	else
		return (0);
}

static int
rrset_db_update(struct db_rrset *rrset, int flags, struct hashbuf **htpp,
		struct sockaddr_in from, int *rrcount)
{
	struct dnode *dnp;
	struct databuf *dp;
	int ret;

	/* If we have any unattached SIG records that are DNSSEC signatures,
	 * don't cache them unless we already have the corresponding data.
	 * If we do cache unattached SIGs, we run into problems later if we
	 * have a SIG X and get a query for type X.
	 */ 
	if (rrset->rr_list == NULL) {
		if (attach_data(rrset) == 0) {
			rrset_free(rrset, 1);
			return (OK);
		}

		if (rrset->rr_list != NULL &&
		    verify_set(rrset) == DB_S_FAILED)
		{
			rrset_free(rrset, 1);
			return (OK);
		}
	}

	for (dnp = rrset->rr_list; dnp != NULL; dnp = dnp->dn_next) {
		dp = dnp->dp;
		ret = db_update(rrset->rr_name, dp, dp, NULL,
				flags, (*htpp), from);
		if (ret != OK) {
			/* XXX Probably should do rollback.  */
			db_err(ret, rrset->rr_name, dp->d_type,
			       dnp->file, dnp->line);
			if (ret != DATAEXISTS) {
				rrset_free_partial(rrset, 1, dnp);
				return (ret);
			}
			db_freedata(dp);
		}
		if (rrcount != NULL)
			(*rrcount)++;
		dnp->dp = NULL;
	}
	for (dnp = rrset->rr_sigs; dnp != NULL; dnp = dnp->dn_next) {
		dp = dnp->dp;
		if (dp == NULL) /* verifyset() can remove sigs */
			continue;
		ret = db_update(rrset->rr_name, dp, dp, NULL,
				flags, (*htpp), from);
		if (ret != OK) {
			/* XXX Probably should do rollback.  */
			db_err(ret, rrset->rr_name, dp->d_type,
			       dnp->file, dnp->line);
			if (ret != DATAEXISTS) {
				rrset_free_partial(rrset, 1, dnp);
				return (ret);
			}
			db_freedata(dp);
		}
		if (rrcount != NULL)
			(*rrcount)++;
		dnp->dp = NULL;
	}
	rrset_free(rrset, 0);
	return (OK);
}

static int
rr_in_set(struct databuf *rr, struct dnode *set) {
	struct dnode *dnp;

	if (set == NULL)
		return (0);

	for(dnp = set; dnp != NULL; dnp = dnp->dn_next) {
		if (dnp->dp->d_size == rr->d_size &&
		    memcmp(dnp->dp->d_data, rr->d_data, dnp->dp->d_size) == 0)
			return (1);
	}
	return (0);
}

static int
add_to_rrset_list(struct db_rrset **rrsets, char *name, struct databuf *dp,
		  int line, const char *file)
{
	struct db_rrset *rrset = *rrsets;
	struct dnode *dnp;

	while (rrset != NULL) {
		if (rrset->rr_type != ns_t_nxt || dp->d_type != ns_t_nxt) {
			if (dp->d_type == ns_t_sig) {
				if (SIG_COVERS(dp) == rrset->rr_type)
					break;
			} else {
				if (dp->d_type == rrset->rr_type)
					break;
			}
		}
		else if (nxt_match_rrset(dp, rrset))
			break;
		rrset = rrset->rr_next;
	}

	if (rrset != NULL) {
		if ((dp->d_type == ns_t_sig && rr_in_set(dp, rrset->rr_sigs)) ||
		    (dp->d_type != ns_t_sig && rr_in_set(dp, rrset->rr_list)))
		{
			db_freedata(dp);
			return (DATAEXISTS);
		}
	} else {
		rrset = (struct db_rrset *) memget(sizeof(struct db_rrset));
		if (rrset == NULL)
			 ns_panic(ns_log_default, 1,
				  "add_to_rrset_list: memget failed(%s)", name);
		memset(rrset, 0, sizeof(struct db_rrset));
		rrset->rr_name = savestr(name, 1);
		rrset->rr_class = dp->d_class;
		if (dp->d_type == ns_t_sig)
			rrset->rr_type = SIG_COVERS(dp);
		else
			rrset->rr_type = dp->d_type;
		rrset->rr_next = *rrsets;
		*rrsets = rrset;
	}

	dnp = (struct dnode *) memget(sizeof(struct dnode));
	if (dnp == NULL)
		ns_panic(ns_log_default, 1,
			 "add_to_rrset_list: memget failed(%s)", name);
	memset(dnp, 0, sizeof(struct dnode));
	dnp->dp = dp;
	if (dp->d_type == ns_t_sig) {
		if (rrset->rr_sigs != NULL) {
			struct dnode *fdnp;

			/* Preserve the order of the RRs */
			/* Add this one to the end of the list */
			for (fdnp = rrset->rr_sigs;
			     fdnp->dn_next != NULL;
			     fdnp = fdnp->dn_next)
				/* NULL */ ;
			fdnp->dn_next = dnp;
		} else
			rrset->rr_sigs = dnp;
	} else {
		if (rrset->rr_list != NULL) {
			struct dnode *fdnp;

			/* Preserve the order of the RRs */
			/* Add this one to the end of the list */
			for (fdnp = rrset->rr_list;
			     fdnp->dn_next != NULL;
			     fdnp = fdnp->dn_next)
				/* NULL */ ;
			fdnp->dn_next = dnp;
		} else
			rrset->rr_list = dnp;
	}
	dnp->file = (char *) file;
	dnp->line = line;
	return (0);
}

static int
update_rrset_list(struct db_rrset **rrsets, int flags, struct hashbuf **htpp,
		  struct sockaddr_in from, int *rrcount)
{
	struct db_rrset *rrset = *rrsets, *next = NULL, *last = NULL;
	int result = 0, tresult, cnameandother = 0;

	while (rrset != NULL) {
		if (rrset->rr_type == ns_t_key)
			break;
		last = rrset;
		rrset = rrset->rr_next;
	}

	if (rrset != NULL && last != NULL) {
		last->rr_next = rrset->rr_next;
		rrset->rr_next = *rrsets;
		*rrsets = rrset;
	}

	rrset = *rrsets;

	while (rrset != NULL) {
		if (verify_set(rrset) > DB_S_FAILED) {
			ns_debug(ns_log_default, 10,
				 "update_rrset_list(%s, %s): set verified",
				  rrset->rr_name, p_type(rrset->rr_type));
			tresult = rrset_db_update(rrset, flags, htpp,
						  from, rrcount);
			if (tresult == CNAMEANDOTHER)
				cnameandother++;
			if (tresult != OK)
				result = tresult;
		}
		else {
			rrset_free(rrset, 1);
			result = DNSSECFAIL;
		}
		freestr(rrset->rr_name);
		next = rrset->rr_next;
		memput(rrset, sizeof(struct db_rrset));
		rrset = next;
	}
	*rrsets = NULL;
	if (cnameandother != 0)
		return (CNAMEANDOTHER);
	return (result);
}

int
db_set_update(char *name, struct databuf *dp, void **state,
	      int flags, struct hashbuf **htpp, struct sockaddr_in from,
	      int *rrcount, int line, const char *file)
{
	struct db_rrset **rrsets;
	struct db_rrset *rrset;
	int result = 0;

	ns_debug(ns_log_default, 5, "db_set_update(%s)",
		 (name == NULL) ? "<NULL>" : (*name == 0) ? "." : name);

	if (state == NULL)
		ns_panic(ns_log_default, 1,
			 "Called db_set_update with state == NULL");

	rrsets = (struct db_rrset **) state;

	if (*rrsets != NULL) {
		rrset = *rrsets;
		if (rrset->rr_name != NULL && dp != NULL &&
		    name != NULL && ns_samename(name, rrset->rr_name) == 1 &&
		    dp->d_class == rrset->rr_class)
			return (add_to_rrset_list(rrsets, name, dp,
						  line, file));
	}

	if (*rrsets != NULL)
		result = update_rrset_list(rrsets, flags, htpp, from, rrcount);

	if (dp != NULL) {
		ns_debug(ns_log_default, 10,
			 "db_set_update(%s), creating new list", name);

		(void) add_to_rrset_list(rrsets, name, dp, line, file);
	}
	return (result);
}

static int
nxt_match_rrset(struct databuf *dp, struct db_rrset *rrset) {
	if (rrset->rr_list != NULL)
		return (nxtmatch(rrset->rr_name, dp, rrset->rr_list->dp));
	else
		return (nxtmatch(rrset->rr_name, dp, rrset->rr_sigs->dp));
}
