cc.c   [plain text]


/*
 * Portions Copyright (C) 2004-2007, 2012  Internet Systems Consortium, Inc. ("ISC")
 * Portions Copyright (C) 2001-2003  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and/or 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 ISC AND NOMINUM DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC 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) 2001  Nominum, Inc.
 *
 * Permission to use, copy, modify, and/or 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 ISC AND NOMINUM DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC 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: cc.c,v 1.18 2007/08/28 07:20:43 tbox Exp $ */

/*! \file */

#include <config.h>

#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <isc/assertions.h>
#include <isc/hmacmd5.h>
#include <isc/print.h>
#include <isc/stdlib.h>

#include <isccc/alist.h>
#include <isccc/base64.h>
#include <isccc/cc.h>
#include <isccc/result.h>
#include <isccc/sexpr.h>
#include <isccc/symtab.h>
#include <isccc/symtype.h>
#include <isccc/util.h>

#define MAX_TAGS		256
#define DUP_LIFETIME		900

typedef isccc_sexpr_t *sexpr_ptr;

static unsigned char auth_hmd5[] = {
	0x05, 0x5f, 0x61, 0x75, 0x74, 0x68,		/*%< len + _auth */
	ISCCC_CCMSGTYPE_TABLE,				/*%< message type */
	0x00, 0x00, 0x00, 0x20,				/*%< length == 32 */
	0x04, 0x68, 0x6d, 0x64, 0x35,			/*%< len + hmd5 */
	ISCCC_CCMSGTYPE_BINARYDATA,			/*%< message type */
	0x00, 0x00, 0x00, 0x16,				/*%< length == 22 */
	/*
	 * The base64 encoding of one of our HMAC-MD5 signatures is
	 * 22 bytes.
	 */
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

#define HMD5_OFFSET	21		/*%< 21 = 6 + 1 + 4 + 5 + 1 + 4 */
#define HMD5_LENGTH	22

static isc_result_t
table_towire(isccc_sexpr_t *alist, isccc_region_t *target);

static isc_result_t
list_towire(isccc_sexpr_t *alist, isccc_region_t *target);

static isc_result_t
value_towire(isccc_sexpr_t *elt, isccc_region_t *target)
{
	size_t len;
	unsigned char *lenp;
	isccc_region_t *vr;
	isc_result_t result;

	if (isccc_sexpr_binaryp(elt)) {
		vr = isccc_sexpr_tobinary(elt);
		len = REGION_SIZE(*vr);
		if (REGION_SIZE(*target) < 1 + 4 + len)
			return (ISC_R_NOSPACE);
		PUT8(ISCCC_CCMSGTYPE_BINARYDATA, target->rstart);
		PUT32(len, target->rstart);
		if (REGION_SIZE(*target) < len)
			return (ISC_R_NOSPACE);
		PUT_MEM(vr->rstart, len, target->rstart);
	} else if (isccc_alist_alistp(elt)) {
		if (REGION_SIZE(*target) < 1 + 4)
			return (ISC_R_NOSPACE);
		PUT8(ISCCC_CCMSGTYPE_TABLE, target->rstart);
		/*
		 * Emit a placeholder length.
		 */
		lenp = target->rstart;
		PUT32(0, target->rstart);
		/*
		 * Emit the table.
		 */
		result = table_towire(elt, target);
		if (result != ISC_R_SUCCESS)
			return (result);
		len = (size_t)(target->rstart - lenp);
		/*
		 * 'len' is 4 bytes too big, since it counts
		 * the placeholder length too.  Adjust and
		 * emit.
		 */
		INSIST(len >= 4U);
		len -= 4;
		PUT32(len, lenp);
	} else if (isccc_sexpr_listp(elt)) {
		if (REGION_SIZE(*target) < 1 + 4)
			return (ISC_R_NOSPACE);
		PUT8(ISCCC_CCMSGTYPE_LIST, target->rstart);
		/*
		 * Emit a placeholder length and count.
		 */
		lenp = target->rstart;
		PUT32(0, target->rstart);
		/*
		 * Emit the list.
		 */
		result = list_towire(elt, target);
		if (result != ISC_R_SUCCESS)
			return (result);
		len = (size_t)(target->rstart - lenp);
		/*
		 * 'len' is 4 bytes too big, since it counts
		 * the placeholder length.  Adjust and emit.
		 */
		INSIST(len >= 4U);
		len -= 4;
		PUT32(len, lenp);
	}

	return (ISC_R_SUCCESS);
}

static isc_result_t
table_towire(isccc_sexpr_t *alist, isccc_region_t *target)
{
	isccc_sexpr_t *kv, *elt, *k, *v;
	char *ks;
	isc_result_t result;
	size_t len;

	for (elt = isccc_alist_first(alist);
	     elt != NULL;
	     elt = ISCCC_SEXPR_CDR(elt)) {
		kv = ISCCC_SEXPR_CAR(elt);
		k = ISCCC_SEXPR_CAR(kv);
		ks = isccc_sexpr_tostring(k);
		v = ISCCC_SEXPR_CDR(kv);
		len = strlen(ks);
		INSIST(len <= 255U);
		/*
		 * Emit the key name.
		 */
		if (REGION_SIZE(*target) < 1 + len)
			return (ISC_R_NOSPACE);
		PUT8(len, target->rstart);
		PUT_MEM(ks, len, target->rstart);
		/*
		 * Emit the value.
		 */
		result = value_towire(v, target);
		if (result != ISC_R_SUCCESS)
			return (result);
	}

	return (ISC_R_SUCCESS);
}

static isc_result_t
list_towire(isccc_sexpr_t *list, isccc_region_t *target)
{
	isc_result_t result;

	while (list != NULL) {
		result = value_towire(ISCCC_SEXPR_CAR(list), target);
		if (result != ISC_R_SUCCESS)
			return (result);
		list = ISCCC_SEXPR_CDR(list);
	}

	return (ISC_R_SUCCESS);
}

static isc_result_t
sign(unsigned char *data, unsigned int length, unsigned char *hmd5,
     isccc_region_t *secret)
{
	isc_hmacmd5_t ctx;
	isc_result_t result;
	isccc_region_t source, target;
	unsigned char digest[ISC_MD5_DIGESTLENGTH];
	unsigned char digestb64[ISC_MD5_DIGESTLENGTH * 4];

	isc_hmacmd5_init(&ctx, secret->rstart, REGION_SIZE(*secret));
	isc_hmacmd5_update(&ctx, data, length);
	isc_hmacmd5_sign(&ctx, digest);
	source.rstart = digest;
	source.rend = digest + ISC_MD5_DIGESTLENGTH;
	target.rstart = digestb64;
	target.rend = digestb64 + ISC_MD5_DIGESTLENGTH * 4;
	result = isccc_base64_encode(&source, 64, "", &target);
	if (result != ISC_R_SUCCESS)
		return (result);
	PUT_MEM(digestb64, HMD5_LENGTH, hmd5);

	return (ISC_R_SUCCESS);
}

isc_result_t
isccc_cc_towire(isccc_sexpr_t *alist, isccc_region_t *target,
	      isccc_region_t *secret)
{
	unsigned char *hmd5_rstart, *signed_rstart;
	isc_result_t result;

	if (REGION_SIZE(*target) < 4 + sizeof(auth_hmd5))
		return (ISC_R_NOSPACE);
	/*
	 * Emit protocol version.
	 */
	PUT32(1, target->rstart);
	if (secret != NULL) {
		/*
		 * Emit _auth section with zeroed HMAC-MD5 signature.
		 * We'll replace the zeros with the real signature once
		 * we know what it is.
		 */
		hmd5_rstart = target->rstart + HMD5_OFFSET;
		PUT_MEM(auth_hmd5, sizeof(auth_hmd5), target->rstart);
	} else
		hmd5_rstart = NULL;
	signed_rstart = target->rstart;
	/*
	 * Delete any existing _auth section so that we don't try
	 * to encode it.
	 */
	isccc_alist_delete(alist, "_auth");
	/*
	 * Emit the message.
	 */
	result = table_towire(alist, target);
	if (result != ISC_R_SUCCESS)
		return (result);
	if (secret != NULL)
		return (sign(signed_rstart, (target->rstart - signed_rstart),
			     hmd5_rstart, secret));
	return (ISC_R_SUCCESS);
}

static isc_result_t
verify(isccc_sexpr_t *alist, unsigned char *data, unsigned int length,
       isccc_region_t *secret)
{
	isc_hmacmd5_t ctx;
	isccc_region_t source;
	isccc_region_t target;
	isc_result_t result;
	isccc_sexpr_t *_auth, *hmd5;
	unsigned char digest[ISC_MD5_DIGESTLENGTH];
	unsigned char digestb64[ISC_MD5_DIGESTLENGTH * 4];

	/*
	 * Extract digest.
	 */
	_auth = isccc_alist_lookup(alist, "_auth");
	if (_auth == NULL)
		return (ISC_R_FAILURE);
	hmd5 = isccc_alist_lookup(_auth, "hmd5");
	if (hmd5 == NULL)
		return (ISC_R_FAILURE);
	/*
	 * Compute digest.
	 */
	isc_hmacmd5_init(&ctx, secret->rstart, REGION_SIZE(*secret));
	isc_hmacmd5_update(&ctx, data, length);
	isc_hmacmd5_sign(&ctx, digest);
	source.rstart = digest;
	source.rend = digest + ISC_MD5_DIGESTLENGTH;
	target.rstart = digestb64;
	target.rend = digestb64 + ISC_MD5_DIGESTLENGTH * 4;
	result = isccc_base64_encode(&source, 64, "", &target);
	if (result != ISC_R_SUCCESS)
		return (result);
	/*
	 * Strip trailing == and NUL terminate target.
	 */
	target.rstart -= 2;
	*target.rstart++ = '\0';
	/*
	 * Verify.
	 */
	if (strcmp((char *)digestb64, isccc_sexpr_tostring(hmd5)) != 0)
		return (ISCCC_R_BADAUTH);

	return (ISC_R_SUCCESS);
}

static isc_result_t
table_fromwire(isccc_region_t *source, isccc_region_t *secret,
	       isccc_sexpr_t **alistp);

static isc_result_t
list_fromwire(isccc_region_t *source, isccc_sexpr_t **listp);

static isc_result_t
value_fromwire(isccc_region_t *source, isccc_sexpr_t **valuep)
{
	unsigned int msgtype;
	isc_uint32_t len;
	isccc_sexpr_t *value;
	isccc_region_t active;
	isc_result_t result;

	if (REGION_SIZE(*source) < 1 + 4)
		return (ISC_R_UNEXPECTEDEND);
	GET8(msgtype, source->rstart);
	GET32(len, source->rstart);
	if (REGION_SIZE(*source) < len)
		return (ISC_R_UNEXPECTEDEND);
	active.rstart = source->rstart;
	active.rend = active.rstart + len;
	source->rstart = active.rend;
	if (msgtype == ISCCC_CCMSGTYPE_BINARYDATA) {
		value = isccc_sexpr_frombinary(&active);
		if (value != NULL) {
			*valuep = value;
			result = ISC_R_SUCCESS;
		} else
			result = ISC_R_NOMEMORY;
	} else if (msgtype == ISCCC_CCMSGTYPE_TABLE)
		result = table_fromwire(&active, NULL, valuep);
	else if (msgtype == ISCCC_CCMSGTYPE_LIST)
		result = list_fromwire(&active, valuep);
	else
		result = ISCCC_R_SYNTAX;

	return (result);
}

static isc_result_t
table_fromwire(isccc_region_t *source, isccc_region_t *secret,
	       isccc_sexpr_t **alistp)
{
	char key[256];
	isc_uint32_t len;
	isc_result_t result;
	isccc_sexpr_t *alist, *value;
	isc_boolean_t first_tag;
	unsigned char *checksum_rstart;

	REQUIRE(alistp != NULL && *alistp == NULL);

	checksum_rstart = NULL;
	first_tag = ISC_TRUE;
	alist = isccc_alist_create();
	if (alist == NULL)
		return (ISC_R_NOMEMORY);

	while (!REGION_EMPTY(*source)) {
		GET8(len, source->rstart);
		if (REGION_SIZE(*source) < len) {
			result = ISC_R_UNEXPECTEDEND;
			goto bad;
		}
		GET_MEM(key, len, source->rstart);
		key[len] = '\0';	/* Ensure NUL termination. */
		value = NULL;
		result = value_fromwire(source, &value);
		if (result != ISC_R_SUCCESS)
			goto bad;
		if (isccc_alist_define(alist, key, value) == NULL) {
			result = ISC_R_NOMEMORY;
			goto bad;
		}
		if (first_tag && secret != NULL && strcmp(key, "_auth") == 0)
			checksum_rstart = source->rstart;
		first_tag = ISC_FALSE;
	}

	*alistp = alist;

	if (secret != NULL) {
		if (checksum_rstart != NULL)
			result = verify(alist, checksum_rstart,
					(source->rend - checksum_rstart),
					secret);
		else
			result = ISCCC_R_BADAUTH;
	} else
		result = ISC_R_SUCCESS;

 bad:
	if (result != ISC_R_SUCCESS)
		isccc_sexpr_free(&alist);

	return (result);
}

static isc_result_t
list_fromwire(isccc_region_t *source, isccc_sexpr_t **listp)
{
	isccc_sexpr_t *list, *value;
	isc_result_t result;

	list = NULL;
	while (!REGION_EMPTY(*source)) {
		value = NULL;
		result = value_fromwire(source, &value);
		if (result != ISC_R_SUCCESS) {
			isccc_sexpr_free(&list);
			return (result);
		}
		if (isccc_sexpr_addtolist(&list, value) == NULL) {
			isccc_sexpr_free(&value);
			isccc_sexpr_free(&list);
			return (result);
		}
	}

	*listp = list;

	return (ISC_R_SUCCESS);
}

isc_result_t
isccc_cc_fromwire(isccc_region_t *source, isccc_sexpr_t **alistp,
		isccc_region_t *secret)
{
	unsigned int size;
	isc_uint32_t version;

	size = REGION_SIZE(*source);
	if (size < 4)
		return (ISC_R_UNEXPECTEDEND);
	GET32(version, source->rstart);
	if (version != 1)
		return (ISCCC_R_UNKNOWNVERSION);

	return (table_fromwire(source, secret, alistp));
}

static isc_result_t
createmessage(isc_uint32_t version, const char *from, const char *to,
	      isc_uint32_t serial, isccc_time_t now,
	      isccc_time_t expires, isccc_sexpr_t **alistp,
	      isc_boolean_t want_expires)
{
	isccc_sexpr_t *alist, *_ctrl, *_data;
	isc_result_t result;

	REQUIRE(alistp != NULL && *alistp == NULL);

	if (version != 1)
		return (ISCCC_R_UNKNOWNVERSION);

	alist = isccc_alist_create();
	if (alist == NULL)
		return (ISC_R_NOMEMORY);

	result = ISC_R_NOMEMORY;

	_ctrl = isccc_alist_create();
	if (_ctrl == NULL)
		goto bad;
	if (isccc_alist_define(alist, "_ctrl", _ctrl) == NULL) {
		isccc_sexpr_free(&_ctrl);
		goto bad;
	}

	_data = isccc_alist_create();
	if (_data == NULL)
		goto bad;
	if (isccc_alist_define(alist, "_data", _data) == NULL) {
		isccc_sexpr_free(&_data);
		goto bad;
	}

	if (isccc_cc_defineuint32(_ctrl, "_ser", serial) == NULL ||
	    isccc_cc_defineuint32(_ctrl, "_tim", now) == NULL ||
	    (want_expires &&
	     isccc_cc_defineuint32(_ctrl, "_exp", expires) == NULL))
		goto bad;
	if (from != NULL &&
	    isccc_cc_definestring(_ctrl, "_frm", from) == NULL)
		goto bad;
	if (to != NULL &&
	    isccc_cc_definestring(_ctrl, "_to", to) == NULL)
		goto bad;

	*alistp = alist;

	return (ISC_R_SUCCESS);

 bad:
	isccc_sexpr_free(&alist);

	return (result);
}

isc_result_t
isccc_cc_createmessage(isc_uint32_t version, const char *from, const char *to,
		     isc_uint32_t serial, isccc_time_t now,
		     isccc_time_t expires, isccc_sexpr_t **alistp)
{
	return (createmessage(version, from, to, serial, now, expires,
			      alistp, ISC_TRUE));
}

isc_result_t
isccc_cc_createack(isccc_sexpr_t *message, isc_boolean_t ok,
		 isccc_sexpr_t **ackp)
{
	char *_frm, *_to;
	isc_uint32_t serial;
	isccc_sexpr_t *ack, *_ctrl;
	isc_result_t result;
	isccc_time_t t;

	REQUIRE(ackp != NULL && *ackp == NULL);

	_ctrl = isccc_alist_lookup(message, "_ctrl");
	if (_ctrl == NULL ||
	    isccc_cc_lookupuint32(_ctrl, "_ser", &serial) != ISC_R_SUCCESS ||
	    isccc_cc_lookupuint32(_ctrl, "_tim", &t) != ISC_R_SUCCESS)
		return (ISC_R_FAILURE);
	/*
	 * _frm and _to are optional.
	 */
	_frm = NULL;
	(void)isccc_cc_lookupstring(_ctrl, "_frm", &_frm);
	_to = NULL;
	(void)isccc_cc_lookupstring(_ctrl, "_to", &_to);
	/*
	 * Create the ack.
	 */
	ack = NULL;
	result = createmessage(1, _to, _frm, serial, t, 0, &ack, ISC_FALSE);
	if (result != ISC_R_SUCCESS)
		return (result);

	_ctrl = isccc_alist_lookup(ack, "_ctrl");
	if (_ctrl == NULL)
		return (ISC_R_FAILURE);
	if (isccc_cc_definestring(ack, "_ack", (ok) ? "1" : "0") == NULL) {
		result = ISC_R_NOMEMORY;
		goto bad;
	}

	*ackp = ack;

	return (ISC_R_SUCCESS);

 bad:
	isccc_sexpr_free(&ack);

	return (result);
}

isc_boolean_t
isccc_cc_isack(isccc_sexpr_t *message)
{
	isccc_sexpr_t *_ctrl;

	_ctrl = isccc_alist_lookup(message, "_ctrl");
	if (_ctrl == NULL)
		return (ISC_FALSE);
	if (isccc_cc_lookupstring(_ctrl, "_ack", NULL) == ISC_R_SUCCESS)
		return (ISC_TRUE);
	return (ISC_FALSE);
}

isc_boolean_t
isccc_cc_isreply(isccc_sexpr_t *message)
{
	isccc_sexpr_t *_ctrl;

	_ctrl = isccc_alist_lookup(message, "_ctrl");
	if (_ctrl == NULL)
		return (ISC_FALSE);
	if (isccc_cc_lookupstring(_ctrl, "_rpl", NULL) == ISC_R_SUCCESS)
		return (ISC_TRUE);
	return (ISC_FALSE);
}

isc_result_t
isccc_cc_createresponse(isccc_sexpr_t *message, isccc_time_t now,
		      isccc_time_t expires, isccc_sexpr_t **alistp)
{
	char *_frm, *_to, *type;
	isc_uint32_t serial;
	isccc_sexpr_t *alist, *_ctrl, *_data;
	isc_result_t result;

	REQUIRE(alistp != NULL && *alistp == NULL);

	_ctrl = isccc_alist_lookup(message, "_ctrl");
	_data = isccc_alist_lookup(message, "_data");
	if (_ctrl == NULL ||
	    _data == NULL ||
	    isccc_cc_lookupuint32(_ctrl, "_ser", &serial) != ISC_R_SUCCESS ||
	    isccc_cc_lookupstring(_data, "type", &type) != ISC_R_SUCCESS)
		return (ISC_R_FAILURE);
	/*
	 * _frm and _to are optional.
	 */
	_frm = NULL;
	(void)isccc_cc_lookupstring(_ctrl, "_frm", &_frm);
	_to = NULL;
	(void)isccc_cc_lookupstring(_ctrl, "_to", &_to);
	/*
	 * Create the response.
	 */
	alist = NULL;
	result = isccc_cc_createmessage(1, _to, _frm, serial, now, expires,
					 &alist);
	if (result != ISC_R_SUCCESS)
		return (result);
	_ctrl = isccc_alist_lookup(alist, "_ctrl");
	if (_ctrl == NULL)
		return (ISC_R_FAILURE);
	_data = isccc_alist_lookup(alist, "_data");
	if (_data == NULL)
		return (ISC_R_FAILURE);
	if (isccc_cc_definestring(_ctrl, "_rpl", "1") == NULL ||
	    isccc_cc_definestring(_data, "type", type) == NULL) {
		isccc_sexpr_free(&alist);
		return (ISC_R_NOMEMORY);
	}

	*alistp = alist;

	return (ISC_R_SUCCESS);
}

isccc_sexpr_t *
isccc_cc_definestring(isccc_sexpr_t *alist, const char *key, const char *str)
{
	size_t len;
	isccc_region_t r;

	len = strlen(str);
	DE_CONST(str, r.rstart);
	r.rend = r.rstart + len;

	return (isccc_alist_definebinary(alist, key, &r));
}

isccc_sexpr_t *
isccc_cc_defineuint32(isccc_sexpr_t *alist, const char *key, isc_uint32_t i)
{
	char b[100];
	size_t len;
	isccc_region_t r;

	snprintf(b, sizeof(b), "%u", i);
	len = strlen(b);
	r.rstart = (unsigned char *)b;
	r.rend = (unsigned char *)b + len;

	return (isccc_alist_definebinary(alist, key, &r));
}

isc_result_t
isccc_cc_lookupstring(isccc_sexpr_t *alist, const char *key, char **strp)
{
	isccc_sexpr_t *kv, *v;

	kv = isccc_alist_assq(alist, key);
	if (kv != NULL) {
		v = ISCCC_SEXPR_CDR(kv);
		if (isccc_sexpr_binaryp(v)) {
			if (strp != NULL)
				*strp = isccc_sexpr_tostring(v);
			return (ISC_R_SUCCESS);
		} else
			return (ISC_R_EXISTS);
	}

	return (ISC_R_NOTFOUND);
}

isc_result_t
isccc_cc_lookupuint32(isccc_sexpr_t *alist, const char *key,
		       isc_uint32_t *uintp)
{
	isccc_sexpr_t *kv, *v;

	kv = isccc_alist_assq(alist, key);
	if (kv != NULL) {
		v = ISCCC_SEXPR_CDR(kv);
		if (isccc_sexpr_binaryp(v)) {
			if (uintp != NULL)
				*uintp = (isc_uint32_t)
					strtoul(isccc_sexpr_tostring(v),
						NULL, 10);
			return (ISC_R_SUCCESS);
		} else
			return (ISC_R_EXISTS);
	}

	return (ISC_R_NOTFOUND);
}

static void
symtab_undefine(char *key, unsigned int type, isccc_symvalue_t value,
		void *arg)
{
	UNUSED(type);
	UNUSED(value);
	UNUSED(arg);

	free(key);
}

static isc_boolean_t
symtab_clean(char *key, unsigned int type, isccc_symvalue_t value,
	     void *arg)
{
	isccc_time_t *now;

	UNUSED(key);
	UNUSED(type);

	now = arg;

	if (*now < value.as_uinteger)
		return (ISC_FALSE);
	if ((*now - value.as_uinteger) < DUP_LIFETIME)
		return (ISC_FALSE);
	return (ISC_TRUE);
}

isc_result_t
isccc_cc_createsymtab(isccc_symtab_t **symtabp)
{
	return (isccc_symtab_create(11897, symtab_undefine, NULL, ISC_FALSE,
				  symtabp));
}

void
isccc_cc_cleansymtab(isccc_symtab_t *symtab, isccc_time_t now)
{
	isccc_symtab_foreach(symtab, symtab_clean, &now);
}

static isc_boolean_t
has_whitespace(const char *str)
{
	char c;

	if (str == NULL)
		return (ISC_FALSE);
	while ((c = *str++) != '\0') {
		if (c == ' ' || c == '\t' || c == '\n')
			return (ISC_TRUE);
	}
	return (ISC_FALSE);
}

isc_result_t
isccc_cc_checkdup(isccc_symtab_t *symtab, isccc_sexpr_t *message,
		isccc_time_t now)
{
	const char *_frm;
	const char *_to;
	char *_ser, *_tim, *tmp;
	isc_result_t result;
	char *key;
	size_t len;
	isccc_symvalue_t value;
	isccc_sexpr_t *_ctrl;

	_ctrl = isccc_alist_lookup(message, "_ctrl");
	if (_ctrl == NULL ||
	    isccc_cc_lookupstring(_ctrl, "_ser", &_ser) != ISC_R_SUCCESS ||
	    isccc_cc_lookupstring(_ctrl, "_tim", &_tim) != ISC_R_SUCCESS)
		return (ISC_R_FAILURE);
	/*
	 * _frm and _to are optional.
	 */
	if (isccc_cc_lookupstring(_ctrl, "_frm", &tmp) != ISC_R_SUCCESS)
		_frm = "";
	else
		_frm = tmp;
	if (isccc_cc_lookupstring(_ctrl, "_to", &tmp) != ISC_R_SUCCESS)
		_to = "";
	else
		_to = tmp;
	/*
	 * Ensure there is no newline in any of the strings.  This is so
	 * we can write them to a file later.
	 */
	if (has_whitespace(_frm) || has_whitespace(_to) ||
	    has_whitespace(_ser) || has_whitespace(_tim))
		return (ISC_R_FAILURE);
	len = strlen(_frm) + strlen(_to) + strlen(_ser) + strlen(_tim) + 4;
	key = malloc(len);
	if (key == NULL)
		return (ISC_R_NOMEMORY);
	snprintf(key, len, "%s;%s;%s;%s", _frm, _to, _ser, _tim);
	value.as_uinteger = now;
	result = isccc_symtab_define(symtab, key, ISCCC_SYMTYPE_CCDUP, value,
				   isccc_symexists_reject);
	if (result != ISC_R_SUCCESS) {
		free(key);
		return (result);
	}

	return (ISC_R_SUCCESS);
}