/*	$KAME: common.c,v 1.129 2005/09/16 11:30:13 suz Exp $	*/
/*
 * Copyright (C) 1998 and 1999 WIDE Project.
 * 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. Neither the name of the project 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 PROJECT 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 PROJECT 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.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/stat.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#include <net/if.h>
#include <netinet/in.h>
#ifdef __KAME__
#include <net/if_types.h>
#ifdef __FreeBSD__
#include <net/if_var.h>
#endif
#include <net/if_dl.h>
#endif
#ifdef __linux__
#include <linux/if_packet.h>
#endif
#include <net/if_arp.h>
#ifdef __sun__
#include <sys/sockio.h>
#include <sys/dlpi.h>
#include <stropts.h>
#include <fcntl.h>
#include <libdevinfo.h>
#endif

#ifdef __KAME__
#include <netinet6/in6_var.h>
#endif

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <syslog.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <err.h>
#include <netdb.h>
#include <ifaddrs.h>

#include <dhcp6.h>
#include <config.h>
#include <common.h>
#include <timer.h>

#ifdef __linux__
/* from /usr/include/linux/ipv6.h */

struct in6_ifreq {
	struct in6_addr ifr6_addr;
	u_int32_t ifr6_prefixlen;
	unsigned int ifr6_ifindex;
};
#endif

#define MAXDNAME 255

int foreground;
int debug_thresh;

static int dhcp6_count_list __P((struct dhcp6_list *));
static int in6_matchflags __P((struct sockaddr *, char *, int));
static ssize_t dnsencode __P((const char *, char *, size_t));
static char *dnsdecode __P((u_char **, u_char *, char *, size_t));
static int copyout_option __P((char *, char *, struct dhcp6_listval *));
static int copyin_option __P((int, struct dhcp6opt *, struct dhcp6opt *,
    struct dhcp6_list *));
static int copy_option __P((u_int16_t, u_int16_t, void *, struct dhcp6opt **,
    struct dhcp6opt *, int *));
static ssize_t gethwid __P((char *, int, const char *, u_int16_t *));
static char *sprint_uint64 __P((char *, int, u_int64_t));
static char *sprint_auth __P((struct dhcp6_optinfo *));

int
dhcp6_copy_list(dst, src)
	struct dhcp6_list *dst, *src;
{
	struct dhcp6_listval *ent;

	for (ent = TAILQ_FIRST(src); ent; ent = TAILQ_NEXT(ent, link)) {
		if (dhcp6_add_listval(dst, ent->type,
		    &ent->uv, &ent->sublist) == NULL)
			goto fail;
	}

	return (0);

  fail:
	dhcp6_clear_list(dst);
	return (-1);
}

void
dhcp6_move_list(dst, src)
	struct dhcp6_list *dst, *src;
{
	struct dhcp6_listval *v;

	while ((v = TAILQ_FIRST(src)) != NULL) {
		TAILQ_REMOVE(src, v, link);
		TAILQ_INSERT_TAIL(dst, v, link);
	}
}

void
dhcp6_clear_list(head)
	struct dhcp6_list *head;
{
	struct dhcp6_listval *v;

	while ((v = TAILQ_FIRST(head)) != NULL) {
		TAILQ_REMOVE(head, v, link);
		dhcp6_clear_listval(v);
	}

	return;
}

static int
dhcp6_count_list(head)
	struct dhcp6_list *head;
{
	struct dhcp6_listval *v;
	int i;

	for (i = 0, v = TAILQ_FIRST(head); v; v = TAILQ_NEXT(v, link))
		i++;

	return (i);
}

void
dhcp6_clear_listval(lv)
	struct dhcp6_listval *lv;
{
	dhcp6_clear_list(&lv->sublist);
	switch (lv->type) {
	case DHCP6_LISTVAL_VBUF:
		dhcp6_vbuf_free(&lv->val_vbuf);
		break;
	default:		/* nothing to do */
		break;
	}
	free(lv);
}

/*
 * Note: this function only searches for the first entry that matches
 * VAL.  It also does not care about sublists.
 */
struct dhcp6_listval *
dhcp6_find_listval(head, type, val, option)
	struct dhcp6_list *head;
	dhcp6_listval_type_t type;
	void *val;
	int option;
{
	struct dhcp6_listval *lv;

	for (lv = TAILQ_FIRST(head); lv; lv = TAILQ_NEXT(lv, link)) {
		if (lv->type != type)
			continue;

		switch(type) {
		case DHCP6_LISTVAL_NUM:
			if (lv->val_num == *(int *)val)
				return (lv);
			break;
		case DHCP6_LISTVAL_STCODE:
			if (lv->val_num16 == *(u_int16_t *)val)
				return (lv);
			break;
		case DHCP6_LISTVAL_ADDR6:
			if (IN6_ARE_ADDR_EQUAL(&lv->val_addr6,
			    (struct in6_addr *)val)) {
				return (lv);
			}
			break;
		case DHCP6_LISTVAL_PREFIX6:
			if ((option & MATCHLIST_PREFIXLEN) &&
			    lv->val_prefix6.plen ==
			    ((struct dhcp6_prefix *)val)->plen) {
				return (lv);
			} else if (IN6_ARE_ADDR_EQUAL(&lv->val_prefix6.addr,
			    &((struct dhcp6_prefix *)val)->addr) &&
			    lv->val_prefix6.plen ==
			    ((struct dhcp6_prefix *)val)->plen) {
				return (lv);
			}
			break;
		case DHCP6_LISTVAL_STATEFULADDR6:
			if (IN6_ARE_ADDR_EQUAL(&lv->val_statefuladdr6.addr,
			    &((struct dhcp6_prefix *)val)->addr)) {
				return (lv);
			}
			break;
		case DHCP6_LISTVAL_IAPD:
		case DHCP6_LISTVAL_IANA:
			if (lv->val_ia.iaid ==
			    ((struct dhcp6_ia *)val)->iaid) {
				return (lv);
			}
			break;
		case DHCP6_LISTVAL_VBUF:
			if (dhcp6_vbuf_cmp(&lv->val_vbuf,
			    (struct dhcp6_vbuf *)val) == 0) {
				return (lv);
			}
			break;
		}
	}

	return (NULL);
}

struct dhcp6_listval *
dhcp6_add_listval(head, type, val, sublist)
	struct dhcp6_list *head, *sublist;
	dhcp6_listval_type_t type;
	void *val;
{
	struct dhcp6_listval *lv = NULL;

	if ((lv = malloc(sizeof(*lv))) == NULL) {
		dprintf(LOG_ERR, FNAME,
		    "failed to allocate memory for list entry");
		goto fail;
	}
	memset(lv, 0, sizeof(*lv));
	lv->type = type;
	TAILQ_INIT(&lv->sublist);

	switch(type) {
	case DHCP6_LISTVAL_NUM:
		lv->val_num = *(int *)val;
		break;
	case DHCP6_LISTVAL_STCODE:
		lv->val_num16 = *(u_int16_t *)val;
		break;
	case DHCP6_LISTVAL_ADDR6:
		lv->val_addr6 = *(struct in6_addr *)val;
		break;
	case DHCP6_LISTVAL_PREFIX6:
		lv->val_prefix6 = *(struct dhcp6_prefix *)val;
		break;
	case DHCP6_LISTVAL_STATEFULADDR6:
		lv->val_statefuladdr6 = *(struct dhcp6_statefuladdr *)val;
		break;
	case DHCP6_LISTVAL_IAPD:
	case DHCP6_LISTVAL_IANA:
		lv->val_ia = *(struct dhcp6_ia *)val;
		break;
	case DHCP6_LISTVAL_VBUF:
		if (dhcp6_vbuf_copy(&lv->val_vbuf, (struct dhcp6_vbuf *)val))
			goto fail;
		break;
	default:
		dprintf(LOG_ERR, FNAME,
		    "unexpected list value type (%d)", type);
		goto fail;
	}

	if (sublist && dhcp6_copy_list(&lv->sublist, sublist))
		goto fail;

	TAILQ_INSERT_TAIL(head, lv, link);

	return (lv);

  fail:
	if (lv)
		free(lv);

	return (NULL);
}

int
dhcp6_vbuf_copy(dst, src)
	struct dhcp6_vbuf *dst, *src;
{
	dst->dv_buf = malloc(src->dv_len);
	if (dst->dv_buf == NULL)
		return (-1);

	dst->dv_len = src->dv_len;
	memcpy(dst->dv_buf, src->dv_buf, dst->dv_len);

	return (0);
}

void
dhcp6_vbuf_free(vbuf)
	struct dhcp6_vbuf *vbuf;
{
	free(vbuf->dv_buf);

	vbuf->dv_len = 0;
	vbuf->dv_buf = NULL;
}

int
dhcp6_vbuf_cmp(vb1, vb2)
	struct dhcp6_vbuf *vb1, *vb2;
{
	if (vb1->dv_len != vb2->dv_len)
		return (vb1->dv_len - vb2->dv_len);

	return (memcmp(vb1->dv_buf, vb2->dv_buf, vb1->dv_len));
}

static int
dhcp6_get_addr(optlen, cp, type, list)
	int optlen;
	void *cp;
	dhcp6_listval_type_t type;
	struct dhcp6_list *list;
{
	void *val;

	if (optlen % sizeof(struct in6_addr) || optlen == 0) {
		dprintf(LOG_INFO, FNAME,
		    "malformed DHCP option: type %d, len %d", type, optlen);
		return -1;
	}
	for (val = cp; val < cp + optlen; val += sizeof(struct in6_addr)) {
		struct in6_addr valaddr;

		memcpy(&valaddr, val, sizeof(valaddr));
		if (dhcp6_find_listval(list,
		    DHCP6_LISTVAL_ADDR6, &valaddr, 0)) {
			dprintf(LOG_INFO, FNAME, "duplicated %s address (%s)",
			    dhcp6optstr(type), in6addr2str(&valaddr, 0));
			continue;
		}

		if (dhcp6_add_listval(list, DHCP6_LISTVAL_ADDR6,
		    &valaddr, NULL) == NULL) {
			dprintf(LOG_ERR, FNAME,
			    "failed to copy %s address", dhcp6optstr(type));
			return -1;
		}
	}

	return 0;
}

static int
dhcp6_set_addr(type, list, p, optep, len)
	dhcp6_listval_type_t type;
	struct dhcp6_list *list;
	struct dhcp6opt **p, *optep;
	int *len;
{
	struct in6_addr *in6;
	char *tmpbuf;
	struct dhcp6_listval *d;
	int optlen;

	if (TAILQ_EMPTY(list))
		return 0;

	tmpbuf = NULL;
	optlen = dhcp6_count_list(list) * sizeof(struct in6_addr);
	if ((tmpbuf = malloc(optlen)) == NULL) {
		dprintf(LOG_ERR, FNAME,
		    "memory allocation failed for %s options",
		    dhcp6optstr(type));
		return -1;
	}
	in6 = (struct in6_addr *)tmpbuf;
	for (d = TAILQ_FIRST(list); d; d = TAILQ_NEXT(d, link), in6++)
		memcpy(in6, &d->val_addr6, sizeof(*in6));
	if (copy_option(type, optlen, tmpbuf, p, optep, len) != 0) {
		free(tmpbuf);
		return -1;
	}

	free(tmpbuf);
	return 0;
}

static int
dhcp6_get_domain(optlen, cp, type, list)
	int optlen;
	void *cp;
	dhcp6_listval_type_t type;
	struct dhcp6_list *list;
{
	void *val;

	val = cp;
	while (val < cp + optlen) {
		struct dhcp6_vbuf vb;
		char name[MAXDNAME + 1];

		if (dnsdecode((u_char **)(void *)&val,
		    (u_char *)(cp + optlen), name, sizeof(name)) == NULL) {
			dprintf(LOG_INFO, FNAME, "failed to "
			    "decode a %s domain name",
			    dhcp6optstr(type));
			dprintf(LOG_INFO, FNAME,
			    "malformed DHCP option: type %d, len %d",
			     type, optlen);
			return -1;
		}

		vb.dv_len = strlen(name) + 1;
		vb.dv_buf = name;

		if (dhcp6_add_listval(list,
		    DHCP6_LISTVAL_VBUF, &vb, NULL) == NULL) {
			dprintf(LOG_ERR, FNAME, "failed to "
			    "copy a %s domain name", dhcp6optstr(type));
			return -1;
		}
	}

	return 0;
}

static int
dhcp6_set_domain(type, list, p, optep, len)
	dhcp6_listval_type_t type;
	struct dhcp6_list *list;
	struct dhcp6opt **p, *optep;
	int *len;
{
	int optlen = 0;
	struct dhcp6_listval *d;
	char *tmpbuf;
	char name[MAXDNAME], *cp, *ep;

	if (TAILQ_EMPTY(list))
		return 0;

	for (d = TAILQ_FIRST(list); d; d = TAILQ_NEXT(d, link))
		optlen += (d->val_vbuf.dv_len + 1);

	if (optlen == 0) {
		return 0;
	}

	tmpbuf = NULL;
	if ((tmpbuf = malloc(optlen)) == NULL) {
		dprintf(LOG_ERR, FNAME, "memory allocation failed for "
		    "%s domain options", dhcp6optstr(type));
		return -1;
	}
	cp = tmpbuf;
	ep = cp + optlen;
	for (d = TAILQ_FIRST(list); d; d = TAILQ_NEXT(d, link)) {
		int nlen;

		nlen = dnsencode((const char *)d->val_vbuf.dv_buf,
		    name, sizeof (name));
		if (nlen < 0) {
			dprintf(LOG_ERR, FNAME,
			    "failed to encode a %s domain name",
			    dhcp6optstr(type));
			free(tmpbuf);
			return -1;
		}
		if (ep - cp < nlen) {
			dprintf(LOG_ERR, FNAME,
			    "buffer length for %s domain name is too short",
			    dhcp6optstr(type));
			free(tmpbuf);
			return -1;
		}
		memcpy(cp, name, nlen);
		cp += nlen;
	}
	if (copy_option(type, cp - tmpbuf, tmpbuf, p, optep, len) != 0) {
		free(tmpbuf);
		return -1;
	}
	free(tmpbuf);

	return 0;
}

struct dhcp6_event *
dhcp6_create_event(ifp, state)
	struct dhcp6_if *ifp;
	int state;
{
	struct dhcp6_event *ev;

	if ((ev = malloc(sizeof(*ev))) == NULL) {
		dprintf(LOG_ERR, FNAME,
		    "failed to allocate memory for an event");
		return (NULL);
	}
	memset(ev, 0, sizeof(*ev));
	ev->ifp = ifp;
	ev->state = state;
	TAILQ_INIT(&ev->data_list);

	return (ev);
}

void
dhcp6_remove_event(ev)
	struct dhcp6_event *ev;
{
	struct dhcp6_serverinfo *sp, *sp_next;

	dprintf(LOG_DEBUG, FNAME, "removing an event on %s, state=%s",
	    ev->ifp->ifname, dhcp6_event_statestr(ev));

	dhcp6_remove_evdata(ev);

	duidfree(&ev->serverid);

	if (ev->timer)
		dhcp6_remove_timer(&ev->timer);
	TAILQ_REMOVE(&ev->ifp->event_list, ev, link);

	for (sp = ev->servers; sp; sp = sp_next) {
		sp_next = sp->next;

		dprintf(LOG_DEBUG, FNAME, "removing server (ID: %s)",
		    duidstr(&sp->optinfo.serverID));
		dhcp6_clear_options(&sp->optinfo);
		if (sp->authparam != NULL)
			free(sp->authparam);
		free(sp);
	}

	if (ev->authparam != NULL)
		free(ev->authparam);

	free(ev);
}

void
dhcp6_remove_evdata(ev)
	struct dhcp6_event *ev;
{
	struct dhcp6_eventdata *evd;

	while ((evd = TAILQ_FIRST(&ev->data_list)) != NULL) {
		TAILQ_REMOVE(&ev->data_list, evd, link);
		if (evd->destructor)
			(*evd->destructor)(evd);
		free(evd);
	}
}

struct authparam *
new_authparam(proto, alg, rdm)
	int proto, alg, rdm;
{
	struct authparam *authparam;

	if ((authparam = malloc(sizeof(*authparam))) == NULL)
		return (NULL);

	memset(authparam, 0, sizeof(*authparam));

	authparam->authproto = proto;
	authparam->authalgorithm = alg;
	authparam->authrdm = rdm;
	authparam->key = NULL;
	authparam->flags |= AUTHPARAM_FLAGS_NOPREVRD;
	authparam->prevrd = 0;

	return (authparam);
}

struct authparam *
copy_authparam(authparam)
	struct authparam *authparam;
{
	struct authparam *dst;

	if ((dst = malloc(sizeof(*dst))) == NULL)
		return (NULL);

	memcpy(dst, authparam, sizeof(*dst));

	return (dst);
}

/*
 * Home-brew function of a 64-bit version of ntohl.
 * XXX: is there any standard for this?
 */
#if (BYTE_ORDER == LITTLE_ENDIAN)
static __inline u_int64_t
ntohq(u_int64_t x)
{
	return (u_int64_t)ntohl((u_int32_t)(x >> 32)) |
	    (int64_t)ntohl((u_int32_t)(x & 0xffffffff)) << 32;
}
#else	/* (BYTE_ORDER == LITTLE_ENDIAN) */
#define ntohq(x) (x)
#endif

int
dhcp6_auth_replaycheck(method, prev, current)
	int method;
	u_int64_t prev, current;
{
	char bufprev[] = "ffff ffff ffff ffff";
	char bufcurrent[] = "ffff ffff ffff ffff";

	if (method != DHCP6_AUTHRDM_MONOCOUNTER) {
		dprintf(LOG_ERR, FNAME, "unsupported replay detection "
		    "method (%d)", method);
		return (-1);
	}

	(void)sprint_uint64(bufprev, sizeof(bufprev), prev);
	(void)sprint_uint64(bufcurrent, sizeof(bufcurrent), current);
	dprintf(LOG_DEBUG, FNAME, "previous: %s, current: %s",
	    bufprev, bufcurrent);

	prev = ntohq(prev);
	current = ntohq(current);

	/*
	 * we call the singular point guilty, since we cannot guess
	 * whether the serial number is increasing or not.
	 */
        if (prev == (current ^ 0x8000000000000000ULL)) {
		dprintf(LOG_INFO, FNAME, "detected a singular point");
		return (1);
	}

	return (((int64_t)(current - prev) > 0) ? 0 : 1);
}

int
getifaddr(addr, ifnam, prefix, plen, strong, ignoreflags)
	struct in6_addr *addr;
	char *ifnam;
	struct in6_addr *prefix;
	int plen;
	int strong;		/* if strong host model is required or not */
	int ignoreflags;
{
	struct ifaddrs *ifap, *ifa;
	struct sockaddr_in6 sin6;
	int error = -1;

	if (getifaddrs(&ifap) != 0) {
		dprintf(LOG_WARNING, FNAME,
			"getifaddrs failed: %s", strerror(errno));
		return (-1);
	}

	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		int s1, s2;

		if (strong && strcmp(ifnam, ifa->ifa_name) != 0)
			continue;

		/* in any case, ignore interfaces in different scope zones. */
		if ((s1 = in6_addrscopebyif(prefix, ifnam)) < 0 ||
		    (s2 = in6_addrscopebyif(prefix, ifa->ifa_name)) < 0 ||
		     s1 != s2)
			continue;

		if (ifa->ifa_addr->sa_family != AF_INET6)
			continue;
#ifdef HAVE_SA_LEN
		if (ifa->ifa_addr->sa_len > sizeof(sin6))
			continue;
#endif

		if (in6_matchflags(ifa->ifa_addr, ifa->ifa_name, ignoreflags))
			continue;

		memcpy(&sin6, ifa->ifa_addr, sysdep_sa_len(ifa->ifa_addr));
#ifdef __KAME__
		if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) {
			sin6.sin6_addr.s6_addr[2] = 0;
			sin6.sin6_addr.s6_addr[3] = 0;
		}
#endif
		if (plen % 8 == 0) {
			if (memcmp(&sin6.sin6_addr, prefix, plen / 8) != 0)
				continue;
		} else {
			struct in6_addr a, m;
			int i;

			memcpy(&a, &sin6.sin6_addr, sizeof(sin6.sin6_addr));
			memset(&m, 0, sizeof(m));
			memset(&m, 0xff, plen / 8);
			m.s6_addr[plen / 8] = (0xff00 >> (plen % 8)) & 0xff;
			for (i = 0; i < sizeof(a); i++)
				a.s6_addr[i] &= m.s6_addr[i];

			if (memcmp(&a, prefix, plen / 8) != 0 ||
			    a.s6_addr[plen / 8] !=
			    (prefix->s6_addr[plen / 8] & m.s6_addr[plen / 8]))
				continue;
		}
		memcpy(addr, &sin6.sin6_addr, sizeof(sin6.sin6_addr));
#ifdef __KAME__
		if (IN6_IS_ADDR_LINKLOCAL(addr))
			addr->s6_addr[2] = addr->s6_addr[3] = 0; 
#endif
		error = 0;
		break;
	}

	freeifaddrs(ifap);
	return (error);
}

int
getifidfromaddr(addr, ifidp)
	struct in6_addr *addr;
	unsigned int *ifidp;
{
	struct ifaddrs *ifap, *ifa;
	struct sockaddr_in6 *sa6;
	unsigned int ifid;
	int retval = -1;

	if (getifaddrs(&ifap) != 0) {
		dprintf(LOG_WARNING, FNAME,
			"getifaddrs failed: %s", strerror(errno));
		return (-1);
	}

	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr->sa_family != AF_INET6)
			continue;

		sa6 = (struct sockaddr_in6 *)ifa->ifa_addr;
		if (IN6_ARE_ADDR_EQUAL(addr, &sa6->sin6_addr))
			break;
	}

	if (ifa != NULL) {
		if ((ifid = if_nametoindex(ifa->ifa_name)) == 0) {
			dprintf(LOG_ERR, FNAME,
			    "if_nametoindex failed for %s", ifa->ifa_name);
			goto end;
		}
		retval = 0;
		*ifidp = ifid;
	}

  end:
	freeifaddrs(ifap);
	return (retval);
}

int
in6_addrscopebyif(addr, ifnam)
	struct in6_addr *addr;
	char *ifnam;
{
	u_int ifindex; 

	if ((ifindex = if_nametoindex(ifnam)) == 0)
		return (-1);

	if (IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_MC_LINKLOCAL(addr))
		return (ifindex);

	if (IN6_IS_ADDR_SITELOCAL(addr) || IN6_IS_ADDR_MC_SITELOCAL(addr))
		return (1);	/* XXX */

	if (IN6_IS_ADDR_MC_ORGLOCAL(addr))
		return (1);	/* XXX */

	return (1);		/* treat it as global */
}

int
transmit_sa(s, sa, buf, len)
	int s;
	struct sockaddr *sa;
	char *buf;
	size_t len;
{
	int error;

	error = sendto(s, buf, len, 0, sa, sysdep_sa_len(sa));

	return (error != len) ? -1 : 0;
}

long
random_between(x, y)
	long x;
	long y;
{
	long ratio;

	ratio = 1 << 16;
	while ((y - x) * ratio < (y - x))
		ratio = ratio / 2;
	return (x + ((y - x) * (ratio - 1) / random() & (ratio - 1)));
}

int
prefix6_mask(in6, plen)
	struct in6_addr *in6;
	int plen;
{
	struct sockaddr_in6 mask6;
	int i;

	if (sa6_plen2mask(&mask6, plen))
		return (-1);

	for (i = 0; i < 16; i++)
		in6->s6_addr[i] &= mask6.sin6_addr.s6_addr[i];

	return (0);
}

int
sa6_plen2mask(sa6, plen)
	struct sockaddr_in6 *sa6;
	int plen;
{
	u_char *cp;

	if (plen < 0 || plen > 128)
		return (-1);

	memset(sa6, 0, sizeof(*sa6));
	sa6->sin6_family = AF_INET6;
#ifdef HAVE_SA_LEN
	sa6->sin6_len = sizeof(*sa6);
#endif

	for (cp = (u_char *)&sa6->sin6_addr; plen > 7; plen -= 8)
		*cp++ = 0xff;
	*cp = 0xff << (8 - plen);

	return (0);
}

char *
addr2str(sa)
	struct sockaddr *sa;
{
	static char addrbuf[8][NI_MAXHOST];
	static int round = 0;
	char *cp;

	round = (round + 1) & 7;
	cp = addrbuf[round];

	getnameinfo(sa, sysdep_sa_len(sa), cp, NI_MAXHOST,
	    NULL, 0, NI_NUMERICHOST);

	return (cp);
}

char *
in6addr2str(in6, scopeid)
	struct in6_addr *in6;
	int scopeid;
{
	struct sockaddr_in6 sa6;

	memset(&sa6, 0, sizeof(sa6));
	sa6.sin6_family = AF_INET6;
#ifdef HAVE_SA_LEN
	sa6.sin6_len = sizeof(sa6);
#endif
	sa6.sin6_addr = *in6;
	sa6.sin6_scope_id = scopeid;

	return (addr2str((struct sockaddr *)&sa6));
}

/* return IPv6 address scope type. caller assumes that smaller is narrower. */
int
in6_scope(addr)
	struct in6_addr *addr;
{
	int scope;

	if (addr->s6_addr[0] == 0xfe) {
		scope = addr->s6_addr[1] & 0xc0;

		switch (scope) {
		case 0x80:
			return (2); /* link-local */
			break;
		case 0xc0:
			return (5); /* site-local */
			break;
		default:
			return (14); /* global: just in case */
			break;
		}
	}

	/* multicast scope. just return the scope field */
	if (addr->s6_addr[0] == 0xff)
		return (addr->s6_addr[1] & 0x0f);

	if (bcmp(&in6addr_loopback, addr, sizeof(addr) - 1) == 0) {
		if (addr->s6_addr[15] == 1) /* loopback */
			return (1);
		if (addr->s6_addr[15] == 0) /* unspecified */
			return (0); /* XXX: good value? */
	}

	return (14);		/* global */
}

static int
in6_matchflags(addr, ifnam, flags)
	struct sockaddr *addr;
	char *ifnam;
	int flags;
{
#ifdef __KAME__
	int s;
	struct in6_ifreq ifr6;

	if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
		warn("in6_matchflags: socket(DGRAM6)");
		return (-1);
	}
	memset(&ifr6, 0, sizeof(ifr6));
	strncpy(ifr6.ifr_name, ifnam, sizeof(ifr6.ifr_name));
	ifr6.ifr_addr = *(struct sockaddr_in6 *)addr;

	if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) < 0) {
		warn("in6_matchflags: ioctl(SIOCGIFAFLAG_IN6, %s)",
		     addr2str(addr));
		close(s);
		return (-1);
	}

	close(s);

	return (ifr6.ifr_ifru.ifru_flags6 & flags);
#else
	return (0);
#endif
}

int
get_duidll(duid)	
struct duid *duid;
{
	u_int16_t len = 0, hwtype;
	struct dhcp6opt_duid_type3 *dp; /* we support the type3 DUID */
	char tmpbuf[256]={0};	/* DUID should be no more than 256 bytes */
	int l;

	if ((l = gethwid(tmpbuf, sizeof(tmpbuf), NULL, &hwtype)) < 0) {
		dprintf(LOG_INFO, FNAME,
			"failed to get a hardware address");
		return (-1);
	}

	len = l + sizeof(struct dhcp6opt_duid_type3);
	memset(duid, 0, sizeof(*duid));
	duid->duid_len = len;

	if ((duid->duid_id = (char *)malloc(len)) == NULL) {
		dprintf(LOG_ERR, FNAME, "failed to allocate memory");
		goto fail;
	}

	/* copy (and fill) the ID */
	dp = (struct dhcp6opt_duid_type3 *)duid->duid_id;
	dp->dh6_duid3_type = htons(3); /* type 3 */
	dp->dh6_duid3_hwtype = htons(hwtype);
	memcpy((void *)(dp + 1), tmpbuf, (len - sizeof(*dp)));

	dprintf(LOG_DEBUG, FNAME, "generated a new DUID-LL: %s %d",
		    duidstr(duid), (len - sizeof(*dp)));

	return (0);

	fail:
	if (duid->duid_id) {
		free(duid->duid_id);
		duid->duid_id = NULL; /* for safety */
	}
	return (-1);
}
	

int
get_duid(idfile, duid)
	char *idfile;
	struct duid *duid;
{
	FILE *fp = NULL;
	u_int16_t len = 0, hwtype;
	struct dhcp6opt_duid_type1 *dp; /* we only support the type1 DUID */
	char tmpbuf[256]={0};	/* DUID should be no more than 256 bytes */

	if ((fp = fopen(idfile, "r")) == NULL && errno != ENOENT)
		dprintf(LOG_NOTICE, FNAME, "failed to open DUID file: %s",
		    idfile);

	if (fp) {
		/* decode length */
		if (fread(&len, sizeof(len), 1, fp) != 1) {
			dprintf(LOG_ERR, FNAME, "DUID file corrupted");
			goto fail;
		}
	} else {
		int l;

		if ((l = gethwid(tmpbuf, sizeof(tmpbuf), NULL, &hwtype)) < 0) {
			dprintf(LOG_INFO, FNAME,
			    "failed to get a hardware address");
			goto fail;
		}

		len = l + sizeof(struct dhcp6opt_duid_type1);
	}

	memset(duid, 0, sizeof(*duid));
	duid->duid_len = len;
	if ((duid->duid_id = (char *)malloc(len)) == NULL) {
		dprintf(LOG_ERR, FNAME, "failed to allocate memory");
		goto fail;
	}

	/* copy (and fill) the ID */
	if (fp) {
		if (fread(duid->duid_id, len, 1, fp) != 1) {
			dprintf(LOG_ERR, FNAME, "DUID file corrupted");
			goto fail;
		}

		dprintf(LOG_DEBUG, FNAME,
		    "extracted an existing DUID from %s: %s",
		    idfile, duidstr(duid));
	} else {
	    u_int64_t t64;

		dp = (struct dhcp6opt_duid_type1 *)duid->duid_id;
		dp->dh6_duid1_type = htons(1); /* type 1 */
		dp->dh6_duid1_hwtype = htons(hwtype);
		/* time is Jan 1, 2000 (UTC), modulo 2^32 */
		t64 = (u_int64_t)(time(NULL) - 946684800);
		dp->dh6_duid1_time = htonl((u_long)(t64 & 0xffffffff));
		memcpy((void *)(dp + 1), tmpbuf, (len - sizeof(*dp)));

		dprintf(LOG_DEBUG, FNAME, "generated a new DUID-LLT: %s %d",
		    duidstr(duid), (len - sizeof(*dp)));
	}

	/* save the (new) ID to the file for next time */
	if (!fp) {
		if ((fp = fopen(idfile, "w+")) == NULL) {
			dprintf(LOG_ERR, FNAME,
			    "failed to open DUID file for save");
			goto fail;
		}
		if ((fwrite(&len, sizeof(len), 1, fp)) != 1) {
			dprintf(LOG_ERR, FNAME, "failed to save DUID");
			goto fail;
		}
		if ((fwrite(duid->duid_id, len, 1, fp)) != 1) {
			dprintf(LOG_ERR, FNAME, "failed to save DUID");
			goto fail;
		}

		dprintf(LOG_DEBUG, FNAME, "saved generated DUID to %s",
		    idfile);
	}

	if (fp) {
		FILE *fpo = NULL;
		
		if ((fpo = fopen("/etc/dhcp6/dhcp6c_db", "w+")) != NULL) {
			
			fprintf(fpo, "DUID: %s\n", duidstr(duid));
			
			if (fpo)
				fclose(fpo);

		} else
			dprintf(LOG_ERR, FNAME, "failed to open /etc/dhcp6/dhcp6c_duid for save");
	}

	if (fp)
		fclose(fp);
	
	return (0);

  fail:
	if (fp)
		fclose(fp);
	if (duid->duid_id) {
		free(duid->duid_id);
		duid->duid_id = NULL; /* for safety */
	}
	return (-1);
}

#ifdef __sun__
struct hwparms {
	char *buf;
	u_int16_t *hwtypep;
	ssize_t retval;
};

static ssize_t
getifhwaddr(const char *ifname, char *buf, u_int16_t *hwtypep, int ppa)
{
	int fd, flags;
	char fname[MAXPATHLEN], *cp;
	struct strbuf putctl;
	struct strbuf getctl;
	long getbuf[1024];
	dl_info_req_t dlir;
	dl_phys_addr_req_t dlpar;
	dl_phys_addr_ack_t *dlpaa;

	dprintf(LOG_DEBUG, FNAME, "trying %s ppa %d", ifname, ppa);

	if (ifname[0] == '\0')
		return (-1);
	if (ppa >= 0 && !isdigit(ifname[strlen(ifname) - 1]))
		(void) snprintf(fname, sizeof (fname), "/dev/%s%d", ifname,
		    ppa);
	else
		(void) snprintf(fname, sizeof (fname), "/dev/%s", ifname);
	getctl.maxlen = sizeof (getbuf);
	getctl.buf = (char *)getbuf;
	if ((fd = open(fname, O_RDWR)) == -1) {
		dl_attach_req_t dlar;

		cp = fname + strlen(fname) - 1;
		if (!isdigit(*cp))
			return (-1);
		while (cp > fname) {
			if (!isdigit(*cp))
				break;
			cp--;
		}
		if (cp == fname)
			return (-1);
		cp++;
		dlar.dl_ppa = atoi(cp);
		*cp = '\0';
		if ((fd = open(fname, O_RDWR)) == -1)
			return (-1);
		dlar.dl_primitive = DL_ATTACH_REQ;
		putctl.len = sizeof (dlar);
		putctl.buf = (char *)&dlar;
		if (putmsg(fd, &putctl, NULL, 0) == -1) {
			(void) close(fd);
			return (-1);
		}
		flags = 0;
		if (getmsg(fd, &getctl, NULL, &flags) == -1) {
			(void) close(fd);
			return (-1);
		}
		if (getbuf[0] != DL_OK_ACK) {
			(void) close(fd);
			return (-1);
		}
	}
	dlir.dl_primitive = DL_INFO_REQ;
	putctl.len = sizeof (dlir);
	putctl.buf = (char *)&dlir;
	if (putmsg(fd, &putctl, NULL, 0) == -1) {
		(void) close(fd);
		return (-1);
	}
	flags = 0;
	if (getmsg(fd, &getctl, NULL, &flags) == -1) {
		(void) close(fd);
		return (-1);
	}
	if (getbuf[0] != DL_INFO_ACK) {
		(void) close(fd);
		return (-1);
	}
	switch (((dl_info_ack_t *)getbuf)->dl_mac_type) {
	case DL_CSMACD:
	case DL_ETHER:
	case DL_100VG:
	case DL_ETH_CSMA:
	case DL_100BT:
		*hwtypep = ARPHRD_ETHER;
		break;
	default:
		(void) close(fd);
		return (-1);
	}
	dlpar.dl_primitive = DL_PHYS_ADDR_REQ;
	dlpar.dl_addr_type = DL_CURR_PHYS_ADDR;
	putctl.len = sizeof (dlpar);
	putctl.buf = (char *)&dlpar;
	if (putmsg(fd, &putctl, NULL, 0) == -1) {
		(void) close(fd);
		return (-1);
	}
	flags = 0;
	if (getmsg(fd, &getctl, NULL, &flags) == -1) {
		(void) close(fd);
		return (-1);
	}
	if (getbuf[0] != DL_PHYS_ADDR_ACK) {
		(void) close(fd);
		return (-1);
	}
	dlpaa = (dl_phys_addr_ack_t *)getbuf;
	if (dlpaa->dl_addr_length != 6) {
		(void) close(fd);
		return (-1);
	}
	(void) memcpy(buf, (char *)getbuf + dlpaa->dl_addr_offset,
	    dlpaa->dl_addr_length);
	return (dlpaa->dl_addr_length);
}

static int
devfs_handler(di_node_t node, di_minor_t minor, void *arg)
{
	struct hwparms *parms = arg;

	parms->retval = getifhwaddr(di_minor_name(minor), parms->buf,
	    parms->hwtypep, di_instance(node));
	return (parms->retval == -1 ? DI_WALK_CONTINUE : DI_WALK_TERMINATE);
}
#endif

static ssize_t
gethwid(buf, len, ifname, hwtypep)
	char *buf;
	int len;
	const char *ifname;
	u_int16_t *hwtypep;
{
	struct ifaddrs *ifa, *ifap;
#ifdef __KAME__
	struct sockaddr_dl *sdl;
#endif
#ifdef __linux__
	struct sockaddr_ll *sll;
#endif
	ssize_t l;

#ifdef __sun__
	if (ifname == NULL) {
		di_node_t root;
		struct hwparms parms;

		if ((root = di_init("/", DINFOSUBTREE | DINFOMINOR |
		    DINFOPROP)) == DI_NODE_NIL) {
			dprintf(LOG_INFO, FNAME, "di_init failed");
			return (-1);
		}
		parms.buf = buf;
		parms.hwtypep = hwtypep;
		parms.retval = -1;
		(void) di_walk_minor(root, DDI_NT_NET, DI_CHECK_ALIAS, &parms,
		    devfs_handler);
		di_fini(root);
		return (parms.retval);
	} else {
		return (getifhwaddr(ifname, buf, hwtypep, -1));
	}
#endif

	if (getifaddrs(&ifap) < 0)
		return (-1);

	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifname && strcmp(ifa->ifa_name, ifname) != 0)
			continue;
		if (ifa->ifa_addr == NULL)
			continue;
#ifdef __KAME__
		if (ifa->ifa_addr->sa_family != AF_LINK)
			continue;

		sdl = (struct sockaddr_dl *)ifa->ifa_addr;
		if (len < 2 + sdl->sdl_alen)
			goto fail;
		/*
		 * translate interface type to hardware type based on
		 * http://www.iana.org/assignments/arp-parameters
		 */
		switch(sdl->sdl_type) {
		case IFT_ETHER:
#ifdef IFT_IEEE80211
		case IFT_IEEE80211:
#endif
			*hwtypep = ARPHRD_ETHER;
			break;
		default:
			continue; /* XXX */
		}
		dprintf(LOG_DEBUG, FNAME, "found an interface %s for DUID",
		    ifa->ifa_name);
		memcpy(buf, LLADDR(sdl), sdl->sdl_alen);
		l = sdl->sdl_alen; /* sdl will soon be freed */
#endif
#ifdef __linux__
		if (ifa->ifa_addr->sa_family != AF_PACKET)
			continue;

		sll = (struct sockaddr_ll *)ifa->ifa_addr;
		if (sll->sll_hatype != ARPHRD_ETHER)
			continue;
		*hwtypep = ARPHRD_ETHER;
		dprintf(LOG_DEBUG, FNAME, "found an interface %s for DUID",
		    ifa->ifa_name);
		memcpy(buf, sll->sll_addr, sll->sll_halen);
		l = sll->sll_halen; /* sll will soon be freed */
#endif
		freeifaddrs(ifap);
		return (l);
	}

  fail:
	freeifaddrs(ifap);
	return (-1);
}

void
dhcp6_init_options(optinfo)
	struct dhcp6_optinfo *optinfo;
{
	memset(optinfo, 0, sizeof(*optinfo));

	optinfo->pref = DH6OPT_PREF_UNDEF;
	optinfo->elapsed_time = DH6OPT_ELAPSED_TIME_UNDEF;
	optinfo->refreshtime = DH6OPT_REFRESHTIME_UNDEF;

	TAILQ_INIT(&optinfo->iapd_list);
	TAILQ_INIT(&optinfo->iana_list);
	TAILQ_INIT(&optinfo->reqopt_list);
	TAILQ_INIT(&optinfo->stcode_list);
	TAILQ_INIT(&optinfo->sip_list);
	TAILQ_INIT(&optinfo->sipname_list);
	TAILQ_INIT(&optinfo->dns_list);
	TAILQ_INIT(&optinfo->dnsname_list);
	TAILQ_INIT(&optinfo->ntp_list);
	TAILQ_INIT(&optinfo->prefix_list);
	TAILQ_INIT(&optinfo->nis_list);
	TAILQ_INIT(&optinfo->nisname_list);
	TAILQ_INIT(&optinfo->nisp_list);
	TAILQ_INIT(&optinfo->nispname_list);
	TAILQ_INIT(&optinfo->bcmcs_list);
	TAILQ_INIT(&optinfo->bcmcsname_list);

	optinfo->authproto = DHCP6_AUTHPROTO_UNDEF;
	optinfo->authalgorithm = DHCP6_AUTHALG_UNDEF;
	optinfo->authrdm = DHCP6_AUTHRDM_UNDEF;
}

void
dhcp6_clear_options(optinfo)
	struct dhcp6_optinfo *optinfo;
{
	switch (optinfo->authproto) {
	case DHCP6_AUTHPROTO_DELAYED:
		if (optinfo->delayedauth_realmval != NULL) {
			free(optinfo->delayedauth_realmval);
		}
		break;
	}

	duidfree(&optinfo->clientID);
	duidfree(&optinfo->serverID);

	dhcp6_clear_list(&optinfo->iapd_list);
	dhcp6_clear_list(&optinfo->iana_list);
	dhcp6_clear_list(&optinfo->reqopt_list);
	dhcp6_clear_list(&optinfo->stcode_list);
	dhcp6_clear_list(&optinfo->sip_list);
	dhcp6_clear_list(&optinfo->sipname_list);
	dhcp6_clear_list(&optinfo->dns_list);
	dhcp6_clear_list(&optinfo->dnsname_list);
	dhcp6_clear_list(&optinfo->ntp_list);
	dhcp6_clear_list(&optinfo->prefix_list);
	dhcp6_clear_list(&optinfo->nis_list);
	dhcp6_clear_list(&optinfo->nisname_list);
	dhcp6_clear_list(&optinfo->nisp_list);
	dhcp6_clear_list(&optinfo->nispname_list);
	dhcp6_clear_list(&optinfo->bcmcs_list);
	dhcp6_clear_list(&optinfo->bcmcsname_list);

	if (optinfo->relaymsg_msg != NULL)
		free(optinfo->relaymsg_msg);

	if (optinfo->ifidopt_id != NULL)
		free(optinfo->ifidopt_id);

	dhcp6_init_options(optinfo);
}

int
dhcp6_copy_options(dst, src)
	struct dhcp6_optinfo *dst, *src;
{
	if (duidcpy(&dst->clientID, &src->clientID))
		goto fail;
	if (duidcpy(&dst->serverID, &src->serverID))
		goto fail;
	dst->rapidcommit = src->rapidcommit;

	if (dhcp6_copy_list(&dst->iapd_list, &src->iapd_list))
		goto fail;
	if (dhcp6_copy_list(&dst->iana_list, &src->iana_list))
		goto fail;
	if (dhcp6_copy_list(&dst->reqopt_list, &src->reqopt_list))
		goto fail;
	if (dhcp6_copy_list(&dst->stcode_list, &src->stcode_list))
		goto fail;
	if (dhcp6_copy_list(&dst->sip_list, &src->sip_list))
		goto fail;
	if (dhcp6_copy_list(&dst->sipname_list, &src->sipname_list))
		goto fail;
	if (dhcp6_copy_list(&dst->dns_list, &src->dns_list))
		goto fail;
	if (dhcp6_copy_list(&dst->dnsname_list, &src->dnsname_list))
		goto fail;
	if (dhcp6_copy_list(&dst->ntp_list, &src->ntp_list))
		goto fail;
	if (dhcp6_copy_list(&dst->prefix_list, &src->prefix_list))
		goto fail;
	if (dhcp6_copy_list(&dst->nis_list, &src->nis_list))
		goto fail;
	if (dhcp6_copy_list(&dst->nisname_list, &src->nisname_list))
		goto fail;
	if (dhcp6_copy_list(&dst->nisp_list, &src->nisp_list))
		goto fail;
	if (dhcp6_copy_list(&dst->nispname_list, &src->nispname_list))
		goto fail;
	if (dhcp6_copy_list(&dst->bcmcs_list, &src->bcmcs_list))
		goto fail;
	if (dhcp6_copy_list(&dst->bcmcsname_list, &src->bcmcsname_list))
		goto fail;
	dst->elapsed_time = src->elapsed_time;
	dst->refreshtime = src->refreshtime;
	dst->pref = src->pref;

	if (src->relaymsg_msg != NULL) {
		if ((dst->relaymsg_msg = malloc(src->relaymsg_len)) == NULL)
			goto fail;
		dst->relaymsg_len = src->relaymsg_len;
		memcpy(dst->relaymsg_msg, src->relaymsg_msg,
		    src->relaymsg_len);
	}

	if (src->ifidopt_id != NULL) {
		if ((dst->ifidopt_id = malloc(src->ifidopt_len)) == NULL)
			goto fail;
		dst->ifidopt_len = src->ifidopt_len;
		memcpy(dst->ifidopt_id, src->ifidopt_id, src->ifidopt_len);
	}

	dst->authflags = src->authflags;
	dst->authproto = src->authproto;
	dst->authalgorithm = src->authalgorithm;
	dst->authrdm = src->authrdm;
	dst->authrd = src->authrd;

	switch (src->authproto) {
	case DHCP6_AUTHPROTO_DELAYED:
		dst->delayedauth_keyid = src->delayedauth_keyid;
		dst->delayedauth_offset = src->delayedauth_offset;
		dst->delayedauth_realmlen = src->delayedauth_realmlen;
		if (src->delayedauth_realmval != NULL) {
			if ((dst->delayedauth_realmval =
			    malloc(src->delayedauth_realmlen)) == NULL) {
				goto fail;
			}
			memcpy(dst->delayedauth_realmval,
			    src->delayedauth_realmval,
			    src->delayedauth_realmlen);
		}
		break;
	case DHCP6_AUTHPROTO_RECONFIG:
		dst->reconfigauth_type = src->reconfigauth_type;
		dst->reconfigauth_offset = src->reconfigauth_offset;
		memcpy(dst->reconfigauth_val, src->reconfigauth_val,
		    sizeof(dst->reconfigauth_val));
		break;
	}

	return (0);

  fail:
	/* cleanup temporary resources */
	dhcp6_clear_options(dst);
	return (-1);
}

void save_ia(pfile, ia)
char *pfile;
struct dhcp6_ia *ia;
{
	FILE *fp = NULL;
	u_int16_t len = sizeof(struct dhcp6_ia);

	if ((fp = fopen(pfile, "w+")) == NULL) {
		dprintf(LOG_NOTICE, FNAME, "failed to open IA file: %s", pfile);
		goto fail;
	}

	/* save the prefix to the file for next time */	
	if (fp) {
		if ((fwrite(ia, len, 1, fp)) != 1) {
			dprintf(LOG_ERR, FNAME, "failed to save IA content");
			goto fail;
		}

		dprintf(LOG_DEBUG, FNAME, "saved IA to %s", pfile);
	}


  fail:  
  	if (fp)
		fclose(fp);

}

void save_prefix(pfile, prefix)
char *pfile;
struct dhcp6_prefix *prefix;
{
	FILE *fp = NULL;
	u_int16_t len = sizeof(struct dhcp6_prefix);

	if ((fp = fopen(pfile, "w+")) == NULL) {
		dprintf(LOG_NOTICE, FNAME, "failed to open prefix file: %s", pfile);
		goto fail;
	}

	/* save the prefix to the file for next time */	
	if (fp) {
		if ((fwrite(prefix, len, 1, fp)) != 1) {
			dprintf(LOG_ERR, FNAME, "failed to save prefix content");
			goto fail;
		}

		dprintf(LOG_DEBUG, FNAME, "saved prefix to %s", pfile);
	}


  fail:  
  	if (fp)
		fclose(fp);

}

int
dhcp6_get_options(p, ep, optinfo)
	struct dhcp6opt *p, *ep;
	struct dhcp6_optinfo *optinfo;
{
	struct dhcp6opt *np, opth;
	int i, opt, optlen, reqopts, num;
	u_int16_t num16;
	char *bp, *cp, *val;
	u_int16_t val16;
	u_int32_t val32;
	struct dhcp6opt_ia optia;
	struct dhcp6_ia ia;
	struct dhcp6_list sublist;
	int authinfolen;

	bp = (char *)p;
	for (; p + 1 <= ep; p = np) {
		struct duid duid0;

		/*
		 * get the option header.  XXX: since there is no guarantee
		 * about the header alignment, we need to make a local copy.
		 */
		memcpy(&opth, p, sizeof(opth));
		optlen = ntohs(opth.dh6opt_len);
		opt = ntohs(opth.dh6opt_type);

		cp = (char *)(p + 1);
		np = (struct dhcp6opt *)(cp + optlen);

		dprintf(LOG_DEBUG, FNAME, "get DHCP option %s, len %d",
		    dhcp6optstr(opt), optlen);

		/* option length field overrun */
		if (np > ep) {
			dprintf(LOG_INFO, FNAME, "malformed DHCP options");
			goto fail;
		}

		switch (opt) {
		case DH6OPT_CLIENTID:
			if (optlen == 0)
				goto malformed;
			duid0.duid_len = optlen;
			duid0.duid_id = cp;
			dprintf(LOG_DEBUG, "",
				"  DUID: %s", duidstr(&duid0));
			if (duidcpy(&optinfo->clientID, &duid0)) {
				dprintf(LOG_ERR, FNAME, "failed to copy DUID");
				goto fail;
			}
			break;
		case DH6OPT_SERVERID:
			if (optlen == 0)
				goto malformed;
			duid0.duid_len = optlen;
			duid0.duid_id = cp;
			dprintf(LOG_DEBUG, "", "  DUID: %s", duidstr(&duid0));
			if (duidcpy(&optinfo->serverID, &duid0)) {
				dprintf(LOG_ERR, FNAME, "failed to copy DUID");
				goto fail;
			}
			break;
		case DH6OPT_STATUS_CODE:
			if (optlen < sizeof(u_int16_t))
				goto malformed;
			memcpy(&val16, cp, sizeof(val16));
			num16 = ntohs(val16);
			dprintf(LOG_DEBUG, "", "  status code: %s",
			    dhcp6_stcodestr(num16));

			/* need to check duplication? */

			if (dhcp6_add_listval(&optinfo->stcode_list,
			    DHCP6_LISTVAL_STCODE, &num16, NULL) == NULL) {
				dprintf(LOG_ERR, FNAME, "failed to copy "
				    "status code");
				goto fail;
			}

			break;
		case DH6OPT_ORO:
			if ((optlen % 2) != 0 || optlen == 0)
				goto malformed;
			reqopts = optlen / 2;
			for (i = 0, val = cp; i < reqopts;
			     i++, val += sizeof(u_int16_t)) {
				u_int16_t opttype;

				memcpy(&opttype, val, sizeof(u_int16_t));
				num = (int)ntohs(opttype);

				dprintf(LOG_DEBUG, "",
					"  requested option: %s",
					dhcp6optstr(num));

				if (dhcp6_find_listval(&optinfo->reqopt_list,
				    DHCP6_LISTVAL_NUM, &num, 0)) {
					dprintf(LOG_INFO, FNAME, "duplicated "
					    "option type (%s)",
					    dhcp6optstr(opttype));
					goto nextoption;
				}

				if (dhcp6_add_listval(&optinfo->reqopt_list,
				    DHCP6_LISTVAL_NUM, &num, NULL) == NULL) {
					dprintf(LOG_ERR, FNAME,
					    "failed to copy requested option");
					goto fail;
				}
			  nextoption:
				;
			}
			break;
		case DH6OPT_PREFERENCE:
			if (optlen != 1)
				goto malformed;
			dprintf(LOG_DEBUG, "", "  preference: %d",
			    (int)*(u_char *)cp);
			if (optinfo->pref != DH6OPT_PREF_UNDEF) {
				dprintf(LOG_INFO, FNAME,
				    "duplicated preference option");
			} else
				optinfo->pref = (int)*(u_char *)cp;
			break;
		case DH6OPT_ELAPSED_TIME:
			if (optlen != 2)
				goto malformed;
			memcpy(&val16, cp, sizeof(val16));
			val16 = ntohs(val16);
			dprintf(LOG_DEBUG, "", "  elapsed time: %lu",
			    (u_int32_t)val16);
			if (optinfo->elapsed_time !=
			    DH6OPT_ELAPSED_TIME_UNDEF) {
				dprintf(LOG_INFO, FNAME,
				    "duplicated elapsed time option");
			} else
				optinfo->elapsed_time = val16;
			break;
		case DH6OPT_RELAY_MSG:
			if ((optinfo->relaymsg_msg = malloc(optlen)) == NULL)
				goto fail;
			memcpy(optinfo->relaymsg_msg, cp, optlen);
			optinfo->relaymsg_len = optlen;
			break;
		case DH6OPT_AUTH:
			if (optlen < sizeof(struct dhcp6opt_auth) - 4)
				goto malformed;

			/*
			 * Any DHCP message that includes more than one
			 * authentication option MUST be discarded.
			 * [RFC3315 Section 21.4.2]
			 */
			if (optinfo->authproto != DHCP6_AUTHPROTO_UNDEF) {
				dprintf(LOG_INFO, FNAME, "found more than one "
				    "authentication option");
				goto fail;
			}

			optinfo->authproto = *cp++;
			optinfo->authalgorithm = *cp++;
			optinfo->authrdm = *cp++;
			memcpy(&optinfo->authrd, cp, sizeof(optinfo->authrd));
			cp += sizeof(optinfo->authrd);

			dprintf(LOG_DEBUG, "", "  %s", sprint_auth(optinfo));

			authinfolen =
			    optlen - (sizeof(struct dhcp6opt_auth) - 4);
			switch (optinfo->authproto) {
			case DHCP6_AUTHPROTO_DELAYED:
				if (authinfolen == 0) {
					optinfo->authflags |=
					    DHCP6OPT_AUTHFLAG_NOINFO;
					break;
				}
				/* XXX: should we reject an empty realm? */
				if (authinfolen <
				    sizeof(optinfo->delayedauth_keyid) + 16) {
					goto malformed;
				}

				optinfo->delayedauth_realmlen = authinfolen -
				    (sizeof(optinfo->delayedauth_keyid) + 16);
				optinfo->delayedauth_realmval =
				    malloc(optinfo->delayedauth_realmlen);
				if (optinfo->delayedauth_realmval == NULL) {
					dprintf(LOG_WARNING, FNAME, "failed "
					    "allocate memory for auth realm");
					goto fail;
				}
				memcpy(optinfo->delayedauth_realmval, cp,
				    optinfo->delayedauth_realmlen);
				cp += optinfo->delayedauth_realmlen;

				memcpy(&optinfo->delayedauth_keyid, cp,
				    sizeof(optinfo->delayedauth_keyid));
				optinfo->delayedauth_keyid =
				    ntohl(optinfo->delayedauth_keyid);
				cp += sizeof(optinfo->delayedauth_keyid);

				optinfo->delayedauth_offset = cp - bp;
				cp += 16;

				dprintf(LOG_DEBUG, "", "  auth key ID: %x, "
				    "offset=%d, realmlen=%d",
				    optinfo->delayedauth_keyid,
				    optinfo->delayedauth_offset,
				    optinfo->delayedauth_realmlen);
				break;
#ifdef notyet
			case DHCP6_AUTHPROTO_RECONFIG:
				break;
#endif
			default:
				dprintf(LOG_INFO, FNAME,
				    "unsupported authentication protocol: %d",
				    *cp);
				goto fail;
			}
			break;
		case DH6OPT_RAPID_COMMIT:
			if (optlen != 0)
				goto malformed;
			optinfo->rapidcommit = 1;
			break;
		case DH6OPT_INTERFACE_ID:
			if ((optinfo->ifidopt_id = malloc(optlen)) == NULL)
				goto fail;
			memcpy(optinfo->ifidopt_id, cp, optlen);
			optinfo->ifidopt_len = optlen;
			break;
		case DH6OPT_SIP_SERVER_D:
			if (dhcp6_get_domain(optlen, cp, opt,
			    &optinfo->sipname_list) == -1)
				goto fail;
			break;
		case DH6OPT_DNSNAME:
			if (dhcp6_get_domain(optlen, cp, opt,
			    &optinfo->dnsname_list) == -1)
				goto fail;
			break;
		case DH6OPT_NIS_DOMAIN_NAME:
			if (dhcp6_get_domain(optlen, cp, opt,
			    &optinfo->nisname_list) == -1)
				goto fail;
			break;
		case DH6OPT_NISP_DOMAIN_NAME:
			if (dhcp6_get_domain(optlen, cp, opt,
			    &optinfo->nispname_list) == -1)
				goto fail;
			break;
		case DH6OPT_BCMCS_SERVER_D:
			if (dhcp6_get_domain(optlen, cp, opt,
			    &optinfo->bcmcsname_list) == -1)
				goto fail;
			break;
		case DH6OPT_SIP_SERVER_A:
			if (dhcp6_get_addr(optlen, cp, opt,
			    &optinfo->sip_list) == -1)
				goto fail;
			break;
		case DH6OPT_DNS:
			if (dhcp6_get_addr(optlen, cp, opt,
			    &optinfo->dns_list) == -1)
				goto fail;
			break;
		case DH6OPT_NIS_SERVERS:
			if (dhcp6_get_addr(optlen, cp, opt,
			    &optinfo->nis_list) == -1)
				goto fail;
			break;
		case DH6OPT_NISP_SERVERS:
			if (dhcp6_get_addr(optlen, cp, opt,
			    &optinfo->nisp_list) == -1)
				goto fail;
			break;
		case DH6OPT_BCMCS_SERVER_A:
			if (dhcp6_get_addr(optlen, cp, opt,
			    &optinfo->bcmcs_list) == -1)
				goto fail;
			break;
		case DH6OPT_NTP:
			if (dhcp6_get_addr(optlen, cp, opt,
			    &optinfo->ntp_list) == -1)
				goto fail;
			break;
		case DH6OPT_IA_PD:
			if (optlen + sizeof(struct dhcp6opt) <
			    sizeof(optia))
				goto malformed;
			memcpy(&optia, p, sizeof(optia));
			ia.iaid = ntohl(optia.dh6_ia_iaid);
			ia.t1 = ntohl(optia.dh6_ia_t1);
			ia.t2 = ntohl(optia.dh6_ia_t2);

			dprintf(LOG_DEBUG, "",
			    "  IA_PD: ID=%lu, T1=%lu, T2=%lu",
			    ia.iaid, ia.t1, ia.t2);

			/* duplication check */
			if (dhcp6_find_listval(&optinfo->iapd_list,
			    DHCP6_LISTVAL_IAPD, &ia, 0)) {
				dprintf(LOG_INFO, FNAME,
				    "duplicated IA_PD %lu", ia.iaid);
				break; /* ignore this IA_PD */
			}

			save_ia(IA_FILE, &ia);

			/* take care of sub-options */
			TAILQ_INIT(&sublist);
			if (copyin_option(opt,
			    (struct dhcp6opt *)((char *)p + sizeof(optia)),
			    (struct dhcp6opt *)(cp + optlen), &sublist)) {
				goto fail;
			}

			/* link this option set */
			if (dhcp6_add_listval(&optinfo->iapd_list,
			    DHCP6_LISTVAL_IAPD, &ia, &sublist) == NULL) {
				dhcp6_clear_list(&sublist);
				goto fail;
			}
			dhcp6_clear_list(&sublist);

			break;
		case DH6OPT_REFRESHTIME:
			if (optlen != 4)
				goto malformed;
			memcpy(&val32, cp, sizeof(val32));
			val32 = ntohl(val32);
			dprintf(LOG_DEBUG, "",
			    "   information refresh time: %lu", val32);
			if (val32 < DHCP6_IRT_MINIMUM) {
				/*
				 * A client MUST use the refresh time
				 * IRT_MINIMUM if it receives the option with a
				 * value less than IRT_MINIMUM.
				 * [draft-ietf-dhc-lifetime-02.txt,
				 *  Section 3.2]
				 */
				dprintf(LOG_INFO, FNAME,
				    "refresh time is too small (%d), adjusted",
				    val32);
				val32 = DHCP6_IRT_MINIMUM;
			}
			if (optinfo->refreshtime != DH6OPT_REFRESHTIME_UNDEF) {
				dprintf(LOG_INFO, FNAME,
				    "duplicated refresh time option");
			} else
				optinfo->refreshtime = (int64_t)val32;
			break;
		case DH6OPT_IA_NA:
			if (optlen + sizeof(struct dhcp6opt) <
			    sizeof(optia))
				goto malformed;
			memcpy(&optia, p, sizeof(optia));
			ia.iaid = ntohl(optia.dh6_ia_iaid);
			ia.t1 = ntohl(optia.dh6_ia_t1);
			ia.t2 = ntohl(optia.dh6_ia_t2);

			dprintf(LOG_DEBUG, "",
			    "  IA_NA: ID=%lu, T1=%lu, T2=%lu",
			    ia.iaid, ia.t1, ia.t2);

			/* duplication check */
			if (dhcp6_find_listval(&optinfo->iana_list,
			    DHCP6_LISTVAL_IANA, &ia, 0)) {
				dprintf(LOG_INFO, FNAME,
				    "duplicated IA_NA %lu", ia.iaid);
				break; /* ignore this IA_NA */
			}

			/* take care of sub-options */
			TAILQ_INIT(&sublist);
			if (copyin_option(opt,
			    (struct dhcp6opt *)((char *)p + sizeof(optia)),
			    (struct dhcp6opt *)(cp + optlen), &sublist)) {
				goto fail;
			}

			/* link this option set */
			if (dhcp6_add_listval(&optinfo->iana_list,
			    DHCP6_LISTVAL_IANA, &ia, &sublist) == NULL) {
				dhcp6_clear_list(&sublist);
				goto fail;
			}
			dhcp6_clear_list(&sublist);

			break;
		default:
			/* no option specific behavior */
			dprintf(LOG_INFO, FNAME,
			    "unknown or unexpected DHCP6 option %s, len %d",
			    dhcp6optstr(opt), optlen);
			break;
		}
	}

	return (0);

  malformed:
	dprintf(LOG_INFO, FNAME, "malformed DHCP option: type %d, len %d",
	    opt, optlen);
  fail:
	dhcp6_clear_options(optinfo);
	return (-1);
}

static char *
dnsdecode(sp, ep, buf, bufsiz)
	u_char **sp;
	u_char *ep;
	char *buf;
	size_t bufsiz;
{
	int i, l;
	u_char *cp;
	char tmpbuf[MAXDNAME + 1];

	cp = *sp;
	*buf = '\0';
	i = 0;			/* XXX: appease gcc */

	if (cp >= ep)
		return (NULL);
	while (cp < ep) {
		i = *cp;
		if (i == 0 || cp != *sp) {
			if (strlcat((char *)buf, ".", bufsiz) >= bufsiz)
				return (NULL);	/* result overrun */
		}
		if (i == 0)
			break;
		cp++;

		if (i > 0x3f)
			return (NULL); /* invalid label */

		if (i > ep - cp)
			return (NULL); /* source overrun */
		while (i-- > 0 && cp < ep) {
			if (!isprint(*cp)) /* we don't accept non-printables */
				return (NULL);
			l = snprintf(tmpbuf, sizeof(tmpbuf), "%c" , *cp);
			if (l >= sizeof(tmpbuf) || l < 0)
				return (NULL);
			if (strlcat(buf, tmpbuf, bufsiz) >= bufsiz)
				return (NULL); /* result overrun */
			cp++;
		}
	}
	if (i != 0)
		return (NULL);	/* not terminated */
	cp++;
	*sp = cp;
	return (buf);
}

static int
copyin_option(type, p, ep, list)
	int type;
	struct dhcp6opt *p, *ep;
	struct dhcp6_list *list;
{
	int opt, optlen;
	char *cp;
	struct dhcp6opt *np, opth;
	struct dhcp6opt_stcode opt_stcode;
	struct dhcp6opt_ia_pd_prefix opt_iapd_prefix;
	struct dhcp6_prefix iapd_prefix;
	struct dhcp6opt_ia_addr opt_ia_addr;
	struct dhcp6_prefix ia_addr;
	struct dhcp6_list sublist;

	TAILQ_INIT(&sublist);

	for (; p + 1 <= ep; p = np) {
		memcpy(&opth, p, sizeof(opth));
		optlen = ntohs(opth.dh6opt_len);
		opt = ntohs(opth.dh6opt_type);

		cp = (char *)(p + 1);
		np = (struct dhcp6opt *)(cp + optlen);

		dprintf(LOG_DEBUG, FNAME, "get DHCP option %s, len %d",
		    dhcp6optstr(opt), optlen);

		if (np > ep) {
			dprintf(LOG_INFO, FNAME, "malformed DHCP option");
			goto fail;
		}

		switch (opt) {
		case DH6OPT_IA_PD_PREFIX:
			/* check option context */
			if (type != DH6OPT_IA_PD) {
				dprintf(LOG_INFO, FNAME,
				    "%s is an invalid position for %s",
				    dhcp6optstr(type), dhcp6optstr(opt));
				goto fail;
			}
			/* check option length */
			if (optlen + sizeof(opth) < sizeof(opt_iapd_prefix))
				goto malformed;

			/* copy and convert option values */
			memcpy(&opt_iapd_prefix, p, sizeof(opt_iapd_prefix));
			if (opt_iapd_prefix.dh6_iapd_prefix_prefix_len > 128) {
				dprintf(LOG_INFO, FNAME,
				    "invalid prefix length (%d)",
				    opt_iapd_prefix.dh6_iapd_prefix_prefix_len);
				goto malformed;
			}
			iapd_prefix.pltime = ntohl(opt_iapd_prefix.dh6_iapd_prefix_preferred_time);
			iapd_prefix.vltime = ntohl(opt_iapd_prefix.dh6_iapd_prefix_valid_time);
			iapd_prefix.plen =
			    opt_iapd_prefix.dh6_iapd_prefix_prefix_len;
			memcpy(&iapd_prefix.addr,
			    &opt_iapd_prefix.dh6_iapd_prefix_prefix_addr,
			    sizeof(iapd_prefix.addr));
			/* clear padding bits in the prefix address */
			prefix6_mask(&iapd_prefix.addr, iapd_prefix.plen);

			dprintf(LOG_DEBUG, FNAME, "  IA_PD prefix: "
			    "%s/%d pltime=%lu vltime=%lu",
			    in6addr2str(&iapd_prefix.addr, 0),
			    iapd_prefix.plen,
			    iapd_prefix.pltime, iapd_prefix.vltime);

			if (dhcp6_find_listval(list, DHCP6_LISTVAL_PREFIX6,
			    &iapd_prefix, 0)) {
				dprintf(LOG_INFO, FNAME, 
				    "duplicated IA_PD prefix "
				    "%s/%d pltime=%lu vltime=%lu",
				    in6addr2str(&iapd_prefix.addr, 0),
				    iapd_prefix.plen,
				    iapd_prefix.pltime, iapd_prefix.vltime);
				goto nextoption;
			}

			save_prefix(PF_FILE, &iapd_prefix);

			/* take care of sub-options */
			TAILQ_INIT(&sublist);
			if (copyin_option(opt,
			    (struct dhcp6opt *)((char *)p +
			    sizeof(opt_iapd_prefix)), np, &sublist)) {
				goto fail;
			}

			if (dhcp6_add_listval(list, DHCP6_LISTVAL_PREFIX6,
			    &iapd_prefix, &sublist) == NULL) {
				dhcp6_clear_list(&sublist);
				goto fail;
			}
			dhcp6_clear_list(&sublist);
			break;
		case DH6OPT_IAADDR:
			/* check option context */
			if (type != DH6OPT_IA_NA) {
				dprintf(LOG_INFO, FNAME,
				    "%s is an invalid position for %s",
				    dhcp6optstr(type), dhcp6optstr(opt));
				goto fail;
			}
			/* check option length */
			if (optlen + sizeof(opth) < sizeof(opt_ia_addr))
				goto malformed;

			/* copy and convert option values */
			memcpy(&opt_ia_addr, p, sizeof(opt_ia_addr));
			ia_addr.pltime = ntohl(opt_ia_addr.dh6_ia_addr_preferred_time);
			ia_addr.vltime = ntohl(opt_ia_addr.dh6_ia_addr_valid_time);
			memcpy(&ia_addr.addr, &opt_ia_addr.dh6_ia_addr_addr,
			    sizeof(ia_addr.addr));

			dprintf(LOG_DEBUG, FNAME, "  IA_NA address: "
			    "%s pltime=%lu vltime=%lu",
			    in6addr2str(&ia_addr.addr, 0),
			    ia_addr.pltime, ia_addr.vltime);

			if (dhcp6_find_listval(list,
			    DHCP6_LISTVAL_STATEFULADDR6, &ia_addr, 0)) {
				dprintf(LOG_INFO, FNAME, 
				    "duplicated IA_NA address"
				    "%s pltime=%lu vltime=%lu",
				    in6addr2str(&ia_addr.addr, 0),
				    ia_addr.pltime, ia_addr.vltime);
				goto nextoption;
			}

			/* take care of sub-options */
			TAILQ_INIT(&sublist);
			if (copyin_option(opt,
			    (struct dhcp6opt *)((char *)p +
			    sizeof(opt_ia_addr)), np, &sublist)) {
				goto fail;
			}

			if (dhcp6_add_listval(list, DHCP6_LISTVAL_STATEFULADDR6,
			    &ia_addr, &sublist) == NULL) {
				dhcp6_clear_list(&sublist);
				goto fail;
			}
			dhcp6_clear_list(&sublist);
			break;
		case DH6OPT_STATUS_CODE:
			/* check option context */
			if (type != DH6OPT_IA_PD &&
			    type != DH6OPT_IA_PD_PREFIX &&
			    type != DH6OPT_IA_NA &&
			    type != DH6OPT_IAADDR) {
				dprintf(LOG_INFO, FNAME,
				    "%s is an invalid position for %s",
				    dhcp6optstr(type), dhcp6optstr(opt));
				goto nextoption; /* or discard the message? */
			}
			/* check option length */
			if (optlen + sizeof(opth) < sizeof(opt_stcode))
				goto malformed;

			/* copy and convert option values */
			memcpy(&opt_stcode, p, sizeof(opt_stcode));
			opt_stcode.dh6_stcode_code =
			    ntohs(opt_stcode.dh6_stcode_code);

			dprintf(LOG_DEBUG, "", "  status code: %s",
			    dhcp6_stcodestr(opt_stcode.dh6_stcode_code));

			/* duplication check */
			if (dhcp6_find_listval(list, DHCP6_LISTVAL_STCODE,
			    &opt_stcode.dh6_stcode_code, 0)) {
				dprintf(LOG_INFO, FNAME,
				    "duplicated status code (%d)",
				    opt_stcode.dh6_stcode_code);
				goto nextoption;
			}

			/* copy-in the code value */
			if (dhcp6_add_listval(list, DHCP6_LISTVAL_STCODE,
			    &opt_stcode.dh6_stcode_code, NULL) == NULL)
				goto fail;

			break;
		}
	  nextoption:
		;
	}

	return (0);

  malformed:
	dprintf(LOG_INFO, "", "  malformed DHCP option: type %d", opt);

  fail:
	dhcp6_clear_list(&sublist);
	return (-1);
}

static char *
sprint_uint64(buf, buflen, i64)
	char *buf;
	int buflen;
	u_int64_t i64;
{
	u_int16_t rd0, rd1, rd2, rd3;
	u_int16_t *ptr = (u_int16_t *)(void *)&i64;

	rd0 = ntohs(*ptr++);
	rd1 = ntohs(*ptr++);
	rd2 = ntohs(*ptr++);
	rd3 = ntohs(*ptr);

	snprintf(buf, buflen, "%04x %04x %04x %04x", rd0, rd1, rd2, rd3);

	return (buf);
}

static char *
sprint_auth(optinfo)
	struct dhcp6_optinfo *optinfo;
{
	static char ret[1024];	/* XXX: thread unsafe */
	char *proto, proto0[] = "unknown(255)";
	char *alg, alg0[] = "unknown(255)";
	char *rdm, rdm0[] = "unknown(255)";
	char rd[] = "ffff ffff ffff ffff";

	switch (optinfo->authproto) {
	case DHCP6_AUTHPROTO_DELAYED:
		proto = "delayed";
		break;
	case DHCP6_AUTHPROTO_RECONFIG:
		proto = "reconfig";
		break;
	default:
		snprintf(proto0, sizeof(proto0), "unknown(%d)",
		    optinfo->authproto & 0xff);
		proto = proto0;
		break;
	}

	switch (optinfo->authalgorithm) {
	case DHCP6_AUTHALG_HMACMD5:
		alg = "HMAC-MD5";
		break;
	default:
		snprintf(alg0, sizeof(alg0), "unknown(%d)",
		    optinfo->authalgorithm & 0xff);
		alg = alg0;
		break;
	}

	switch (optinfo->authrdm) {
	case DHCP6_AUTHRDM_MONOCOUNTER:
		rdm = "mono counter";
		break;
	default:
		snprintf(rdm0, sizeof(rdm0), "unknown(%d)", optinfo->authrdm);
		rdm = rdm0;
	}

	(void)sprint_uint64(rd, sizeof(rd), optinfo->authrd);

	snprintf(ret, sizeof(ret), "proto: %s, alg: %s, RDM: %s, RD: %s",
	    proto, alg, rdm, rd);

	return (ret);
}

static int
copy_option(type, len, val, optp, ep, totallenp)
	u_int16_t type, len;
	void *val;
	struct dhcp6opt **optp, *ep;
	int *totallenp;
{
	struct dhcp6opt *opt = *optp, opth;

	if ((void *)ep - (void *)optp < len + sizeof(struct dhcp6opt)) {
		dprintf(LOG_INFO, FNAME,
		    "option buffer short for %s", dhcp6optstr(type));
		return (-1);
	}
	opth.dh6opt_type = htons(type);
	opth.dh6opt_len = htons(len);
	memcpy(opt, &opth, sizeof(opth));
	if (len != 0)
		memcpy(opt + 1, val, len);

	*optp = (struct dhcp6opt *)((char *)(opt + 1) + len);
 	*totallenp += sizeof(struct dhcp6opt) + len;
	dprintf(LOG_DEBUG, FNAME, "set %s (len %d)", dhcp6optstr(type), len);

	return (0);
}

int
dhcp6_set_options(type, optbp, optep, optinfo)
	int type;
	struct dhcp6opt *optbp, *optep;
	struct dhcp6_optinfo *optinfo;
{
	struct dhcp6opt *p = optbp;
	struct dhcp6_listval *stcode, *op;
	int len = 0, optlen;
	char *tmpbuf = NULL;

	if (optinfo->clientID.duid_len) {
		if (copy_option(DH6OPT_CLIENTID, optinfo->clientID.duid_len,
		    optinfo->clientID.duid_id, &p, optep, &len) != 0) {
			goto fail;
		}
	}

	if (optinfo->serverID.duid_len) {
		if (copy_option(DH6OPT_SERVERID, optinfo->serverID.duid_len,
		    optinfo->serverID.duid_id, &p, optep, &len) != 0) {
			goto fail;
		}
	}

	for (op = TAILQ_FIRST(&optinfo->iana_list); op;
	    op = TAILQ_NEXT(op, link)) {
		int optlen;

		tmpbuf = NULL;
		if ((optlen = copyout_option(NULL, NULL, op)) < 0) {
			dprintf(LOG_INFO, FNAME,
			    "failed to count option length");
			goto fail;
		}
		if ((void *)optep - (void *)p < optlen) {
			dprintf(LOG_INFO, FNAME, "short buffer");
			goto fail;
		}
		if ((tmpbuf = malloc(optlen)) == NULL) {
			dprintf(LOG_NOTICE, FNAME,
			    "memory allocation failed for IA_NA options");
			goto fail;
		}
		if (copyout_option(tmpbuf, tmpbuf + optlen, op) < 0) {
			dprintf(LOG_ERR, FNAME,
			    "failed to construct an IA_NA option");
			goto fail;
		}
		memcpy(p, tmpbuf, optlen);
		free(tmpbuf);
		tmpbuf = NULL;
		p = (struct dhcp6opt *)((char *)p + optlen);
		len += optlen;
	}

	if (optinfo->rapidcommit) {
		if (copy_option(DH6OPT_RAPID_COMMIT, 0, NULL, &p,
		    optep, &len) != 0) {
			goto fail;
		}
	}

	if (optinfo->pref != DH6OPT_PREF_UNDEF) {
		u_int8_t p8 = (u_int8_t)optinfo->pref;

		if (copy_option(DH6OPT_PREFERENCE, sizeof(p8), &p8, &p,
		    optep, &len) != 0) {
			goto fail;
		}
	}

	if (optinfo->elapsed_time != DH6OPT_ELAPSED_TIME_UNDEF) {
		u_int16_t p16 = (u_int16_t)optinfo->elapsed_time;

		p16 = htons(p16);
		if (copy_option(DH6OPT_ELAPSED_TIME, sizeof(p16), &p16, &p,
		    optep, &len) != 0) {
			goto fail;
		}
	}

	for (stcode = TAILQ_FIRST(&optinfo->stcode_list); stcode;
	     stcode = TAILQ_NEXT(stcode, link)) {
		u_int16_t code;

		code = htons(stcode->val_num16);
		if (copy_option(DH6OPT_STATUS_CODE, sizeof(code), &code, &p,
		    optep, &len) != 0) {
			goto fail;
		}
	}

	if (!TAILQ_EMPTY(&optinfo->reqopt_list)) {
		struct dhcp6_listval *opt;
		u_int16_t *valp;
		int buflen;

		tmpbuf = NULL;
		buflen = dhcp6_count_list(&optinfo->reqopt_list) *
			sizeof(u_int16_t);
		if ((tmpbuf = malloc(buflen)) == NULL) {
			dprintf(LOG_ERR, FNAME,
			    "memory allocation failed for options");
			goto fail;
		}
		optlen = 0;
		valp = (u_int16_t *)tmpbuf;
		for (opt = TAILQ_FIRST(&optinfo->reqopt_list); opt;
		     opt = TAILQ_NEXT(opt, link)) {
			/*
			 * Information request option can only be specified
			 * in information-request messages.
			 * [draft-ietf-dhc-lifetime-02.txt, Section 3.2]
			 */
			if (opt->val_num == DH6OPT_REFRESHTIME &&
			    type != DH6_INFORM_REQ) {
				dprintf(LOG_DEBUG, FNAME,
				    "refresh time option is not requested "
				    "for %s", dhcp6msgstr(type));
			}

			*valp = htons((u_int16_t)opt->val_num);
			valp++;
			optlen += sizeof(u_int16_t);
		}
		if (optlen > 0 &&
		    copy_option(DH6OPT_ORO, optlen, tmpbuf, &p,
		    optep, &len) != 0) {
			goto fail;
		}
		free(tmpbuf);
		tmpbuf = NULL;
	}

	if (dhcp6_set_domain(DH6OPT_SIP_SERVER_D, &optinfo->sipname_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_addr(DH6OPT_SIP_SERVER_A, &optinfo->sip_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_addr(DH6OPT_DNS, &optinfo->dns_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_domain(DH6OPT_DNSNAME, &optinfo->dnsname_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_addr(DH6OPT_NIS_SERVERS, &optinfo->nis_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_addr(DH6OPT_NISP_SERVERS, &optinfo->nisp_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_domain(DH6OPT_NIS_DOMAIN_NAME, &optinfo->nisname_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_domain(DH6OPT_NISP_DOMAIN_NAME, &optinfo->nispname_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_addr(DH6OPT_NTP, &optinfo->ntp_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_domain(DH6OPT_BCMCS_SERVER_D, &optinfo->bcmcsname_list,
	    &p, optep, &len) != 0)
		goto fail;

	if (dhcp6_set_addr(DH6OPT_BCMCS_SERVER_A, &optinfo->bcmcs_list,
	    &p, optep, &len) != 0)
		goto fail;

	for (op = TAILQ_FIRST(&optinfo->iapd_list); op;
	    op = TAILQ_NEXT(op, link)) {
		int optlen;

		tmpbuf = NULL;
		if ((optlen = copyout_option(NULL, NULL, op)) < 0) {
			dprintf(LOG_INFO, FNAME,
			    "failed to count option length");
			goto fail;
		}
		if ((void *)optep - (void *)p < optlen) {
			dprintf(LOG_INFO, FNAME, "short buffer");
			goto fail;
		}
		if ((tmpbuf = malloc(optlen)) == NULL) {
			dprintf(LOG_NOTICE, FNAME,
			    "memory allocation failed for IA_PD options");
			goto fail;
		}
		if (copyout_option(tmpbuf, tmpbuf + optlen, op) < 0) {
			dprintf(LOG_ERR, FNAME,
			    "failed to construct an IA_PD option");
			goto fail;
		}
		memcpy(p, tmpbuf, optlen);
		free(tmpbuf);
		tmpbuf = NULL;
		p = (struct dhcp6opt *)((char *)p + optlen);
		len += optlen;
	}

	if (optinfo->relaymsg_len) {
		if (copy_option(DH6OPT_RELAY_MSG, optinfo->relaymsg_len,
		    optinfo->relaymsg_msg, &p, optep, &len) != 0) {
			goto fail;
		}
	}

	if (optinfo->ifidopt_id) {
		if (copy_option(DH6OPT_INTERFACE_ID, optinfo->ifidopt_len,
		    optinfo->ifidopt_id, &p, optep, &len) != 0) {
			goto fail;
		}
	}

	if (optinfo->refreshtime != DH6OPT_REFRESHTIME_UNDEF) {
		u_int32_t p32 = (u_int32_t)optinfo->refreshtime;

		p32 = htonl(p32);
		if (copy_option(DH6OPT_REFRESHTIME, sizeof(p32), &p32, &p,
		    optep, &len) != 0) {
			goto fail;
		}
	}

	if (optinfo->authproto != DHCP6_AUTHPROTO_UNDEF) {
		struct dhcp6opt_auth *auth;
		int authlen;
		char *authinfo;

		authlen = sizeof(*auth);
		if (!(optinfo->authflags & DHCP6OPT_AUTHFLAG_NOINFO)) {
			switch (optinfo->authproto) {
			case DHCP6_AUTHPROTO_DELAYED:
				/* Realm + key ID + HMAC-MD5 */
				authlen += optinfo->delayedauth_realmlen +
				    sizeof(optinfo->delayedauth_keyid) + 16;
				break;
#ifdef notyet
			case DHCP6_AUTHPROTO_RECONFIG:
				/* type + key-or-HAMC */
				authlen += 17;
				break;
#endif
			default:
				dprintf(LOG_ERR, FNAME,
				    "unexpected authentication protocol");
				goto fail;
			}
		}
		if ((auth = malloc(authlen)) == NULL) {
			dprintf(LOG_WARNING, FNAME, "failed to allocate "
			    "memory for authentication information");
			goto fail;
		}

		memset(auth, 0, authlen);
		/* copy_option will take care of type and len later */
		auth->dh6_auth_proto = (u_int8_t)optinfo->authproto;
		auth->dh6_auth_alg = (u_int8_t)optinfo->authalgorithm;
		auth->dh6_auth_rdm = (u_int8_t)optinfo->authrdm;
		memcpy(auth->dh6_auth_rdinfo, &optinfo->authrd,
		    sizeof(auth->dh6_auth_rdinfo));

		if (!(optinfo->authflags & DHCP6OPT_AUTHFLAG_NOINFO)) {
			u_int32_t p32;

			switch (optinfo->authproto) {
			case DHCP6_AUTHPROTO_DELAYED:
				authinfo = (char *)(auth + 1);

				/* copy realm */
				memcpy(authinfo, optinfo->delayedauth_realmval,
				    optinfo->delayedauth_realmlen);
				authinfo += optinfo->delayedauth_realmlen;

				/* copy key ID (need memcpy for alignment) */
				p32 = htonl(optinfo->delayedauth_keyid);
				memcpy(authinfo, &p32, sizeof(p32));

				/*
				 * Set the offset so that the caller can
				 * calculate the HMAC.
				 */
				optinfo->delayedauth_offset =
				    ((char *)p - (char *)optbp) + authlen - 16;

				dprintf(LOG_DEBUG, FNAME,
				    "key ID %x, offset %d",
				    optinfo->delayedauth_keyid,
				    optinfo->delayedauth_offset); 
				break;
#ifdef notyet
			case DHCP6_AUTHPROTO_RECONFIG:
#endif
			default:
				dprintf(LOG_ERR, FNAME,
				    "unexpected authentication protocol");
				free(auth);
				goto fail;
			}
		}

		if (copy_option(DH6OPT_AUTH, authlen - 4,
		    &auth->dh6_auth_proto, &p, optep, &len) != 0) {
			goto fail;
		}
		free(auth);
	}

	return (len);

  fail:
	if (tmpbuf)
		free(tmpbuf);
	return (-1);
}
#undef COPY_OPTION

static ssize_t
dnsencode(name, buf, buflen)
	const char *name;
	char *buf;
	size_t buflen;
{
	char *cp, *ep;
	const char *p, *q;
	int i;
	int namelen = strlen(name);

	cp = buf;
	ep = cp + buflen;

	/* if not certain about my name, return an empty buffer */
	if (namelen == 0)
		return (0);

	p = name;
	while (cp < ep && p < name + namelen) {
		i = 0;
		for (q = p; q < name + namelen && *q && *q != '.'; q++)
			i++;
		/* result does not fit into buf */
		if (cp + i + 1 >= ep)
			goto fail;
		/*
		 * DNS label length restriction, RFC1035 page 8.
		 * "i == 0" case is included here to avoid returning
		 * 0-length label on "foo..bar".
		 */
		if (i <= 0 || i >= 64)
			goto fail;
		*cp++ = i;
		if (!isalpha(p[0]) || !isalnum(p[i - 1]))
			goto fail;
		while (i > 0) {
			if (!isalnum(*p) && *p != '-')
				goto fail;
			if (isupper(*p))
				*cp++ = tolower(*p++);
			else
				*cp++ = *p++;
			i--;
		}
		p = q;
		if (p < name + namelen && *p == '.')
			p++;
	}
	/* termination */
	if (cp + 1 >= ep)
		goto fail;
	*cp++ = '\0';
	return (cp - buf);

 fail:
	return (-1);
}

/*
 * Construct a DHCPv6 option along with sub-options in the wire format.
 * If the packet buffer is NULL, just calculate the length of the option
 * (and sub-options) so that the caller can allocate a buffer to store the
 * option(s).
 * This function basically assumes that the caller prepares enough buffer to
 * store all the options.  However, it also takes the buffer end and checks
 * the possibility of overrun for safety.
 */
static int
copyout_option(p, ep, optval)
	char *p, *ep;
	struct dhcp6_listval *optval;
{
	struct dhcp6opt *opt;
	struct dhcp6opt_stcode stcodeopt;
	struct dhcp6opt_ia ia;
	struct dhcp6opt_ia_pd_prefix pd_prefix;
	struct dhcp6opt_ia_addr ia_addr;
	char *subp;
	struct dhcp6_listval *subov;
	int optlen, headlen, sublen, opttype;

	/* check invariant for safety */
	if (p && ep <= p)
		return (-1);

	/* first, detect the length of the option head */
	switch(optval->type) {
	case DHCP6_LISTVAL_IAPD:
		memset(&ia, 0, sizeof(ia));
		headlen = sizeof(ia);
		opttype = DH6OPT_IA_PD;
		opt = (struct dhcp6opt *)(void *)&ia;
		break;
	case DHCP6_LISTVAL_IANA:
		memset(&ia, 0, sizeof(ia));
		headlen = sizeof(ia);
		opttype = DH6OPT_IA_NA;
		opt = (struct dhcp6opt *)(void *)&ia;
		break;
	case DHCP6_LISTVAL_ADDR6:
		memset(&pd_prefix, 0, sizeof(pd_prefix));
		headlen = sizeof(pd_prefix);
		opttype = DH6OPT_IA_PD_PREFIX;
		opt = (struct dhcp6opt *)&pd_prefix;
		break;
	case DHCP6_LISTVAL_PREFIX6:
		memset(&pd_prefix, 0, sizeof(pd_prefix));
		headlen = sizeof(pd_prefix);
		opttype = DH6OPT_IA_PD_PREFIX;
		opt = (struct dhcp6opt *)&pd_prefix;
		break;
	case DHCP6_LISTVAL_STATEFULADDR6:
		memset(&ia_addr, 0, sizeof(ia_addr));
		headlen = sizeof(ia_addr);
		opttype = DH6OPT_IAADDR;
		opt = (struct dhcp6opt *)&ia_addr;
		break;
	case DHCP6_LISTVAL_STCODE:
		memset(&stcodeopt, 0, sizeof(stcodeopt));
		headlen = sizeof(stcodeopt);
		opttype = DH6OPT_STATUS_CODE;
		opt = (struct dhcp6opt *)(void *)&stcodeopt;
		break;
	default:
		/*
		 * we encounter an unknown option.  this should be an internal
		 * error.
		 */
		dprintf(LOG_ERR, FNAME, "unknown option: code %d",
		    optval->type);
		return (-1);
	}

	/* then, calculate the length of and/or fill in the sub-options */
	subp = NULL;
	sublen = 0;
	if (p)
		subp = p + headlen;
	for (subov = TAILQ_FIRST(&optval->sublist); subov;
	    subov = TAILQ_NEXT(subov, link)) {
		int s;

		if ((s = copyout_option(subp, ep, subov)) < 0)
			return (-1);
		if (p)
			subp += s;
		sublen += s;
	}

	/* finally, deal with the head part again */
	optlen = headlen + sublen;
	if (!p)
		return(optlen);

	dprintf(LOG_DEBUG, FNAME, "set %s", dhcp6optstr(opttype));
	if (ep - p < headlen) /* check it just in case */
		return (-1);

	/* fill in the common part */
	opt->dh6opt_type = htons(opttype);
	opt->dh6opt_len = htons(optlen - sizeof(struct dhcp6opt));

	/* fill in type specific fields */
	switch(optval->type) {
	case DHCP6_LISTVAL_IAPD:
		ia.dh6_ia_iaid = htonl(optval->val_ia.iaid);
		ia.dh6_ia_t1 = htonl(optval->val_ia.t1);
		ia.dh6_ia_t2 = htonl(optval->val_ia.t2);
		break;
	case DHCP6_LISTVAL_IANA:
		ia.dh6_ia_iaid = htonl(optval->val_ia.iaid);
		ia.dh6_ia_t1 = htonl(optval->val_ia.t1);
		ia.dh6_ia_t2 = htonl(optval->val_ia.t2);
		break;
	case DHCP6_LISTVAL_PREFIX6:
		pd_prefix.dh6_iapd_prefix_preferred_time =
		    htonl(optval->val_prefix6.pltime);
		pd_prefix.dh6_iapd_prefix_valid_time =
		    htonl(optval->val_prefix6.vltime);
		pd_prefix.dh6_iapd_prefix_prefix_len =
		    optval->val_prefix6.plen;
		/* XXX: prefix_addr is badly aligned, so we need memcpy */
		memcpy(&pd_prefix.dh6_iapd_prefix_prefix_addr,
		    &optval->val_prefix6.addr, sizeof(struct in6_addr));
		break;
	case DHCP6_LISTVAL_STATEFULADDR6:
		ia_addr.dh6_ia_addr_preferred_time =
		    htonl(optval->val_statefuladdr6.pltime);
		ia_addr.dh6_ia_addr_valid_time =
		    htonl(optval->val_statefuladdr6.vltime);
		ia_addr.dh6_ia_addr_addr = optval->val_statefuladdr6.addr;
		break;
	case DHCP6_LISTVAL_STCODE:
		stcodeopt.dh6_stcode_code = htons(optval->val_num16);
		break;
	default:
		/*
		 * XXX: this case should be rejected at the beginning of this
		 * function.
		 */
		return (-1);
	}

	/* copyout the data (p must be non NULL at this point) */
	memcpy(p, opt, headlen);
	return (optlen);
}

void
dhcp6_set_timeoparam(ev)
	struct dhcp6_event *ev;
{
	ev->retrans = 0;
	ev->init_retrans = 0;
	ev->max_retrans_cnt = 0;
	ev->max_retrans_dur = 0;
	ev->max_retrans_time = 0;

	switch(ev->state) {
	case DHCP6S_SOLICIT:
		ev->init_retrans = SOL_TIMEOUT;
		ev->max_retrans_time = SOL_MAX_RT;
		break;
	case DHCP6S_INFOREQ:
		ev->init_retrans = INF_TIMEOUT;
		ev->max_retrans_time = INF_MAX_RT;
		break;
	case DHCP6S_REQUEST:
		ev->init_retrans = REQ_TIMEOUT;
		ev->max_retrans_time = REQ_MAX_RT;
		ev->max_retrans_cnt = REQ_MAX_RC;
		break;
	case DHCP6S_RENEW:
		ev->init_retrans = REN_TIMEOUT;
		ev->max_retrans_time = REN_MAX_RT;
		break;
	case DHCP6S_REBIND:
		ev->init_retrans = REB_TIMEOUT;
		ev->max_retrans_time = REB_MAX_RT;
		break;
	case DHCP6S_RELEASE:
		ev->init_retrans = REL_TIMEOUT;
		ev->max_retrans_cnt = REL_MAX_RC;
		break;
	default:
		dprintf(LOG_ERR, FNAME, "unexpected event state %d on %s",
		    ev->state, ev->ifp->ifname);
		exit(1);
	}
}

void
dhcp6_reset_timer(ev)
	struct dhcp6_event *ev;
{
	double n, r;
	char *statestr;
	struct timeval interval;

	switch(ev->state) {
	case DHCP6S_INIT:
		/*
		 * The first Solicit message from the client on the interface
		 * MUST be delayed by a random amount of time between
		 * 0 and SOL_MAX_DELAY.
		 * [RFC3315 17.1.2]
		 * XXX: a random delay is also necessary before the first
		 * information-request message.  Fortunately, the parameters
		 * and the algorithm for these two cases are the same.
		 * [RFC3315 18.1.5]
		 */
		ev->retrans = (random() % (SOL_MAX_DELAY));
		break;
	default:
		if (ev->state == DHCP6S_SOLICIT && ev->timeouts == 0) {
			/*
			 * The first RT MUST be selected to be strictly
			 * greater than IRT by choosing RAND to be strictly
			 * greater than 0.
			 * [RFC3315 17.1.2]
			 */
			r = (double)((random() % 1000) + 1) / 10000;
			n = ev->init_retrans + r * ev->init_retrans;
		} else {
			r = (double)((random() % 2000) - 1000) / 10000;

			if (ev->timeouts == 0) {
				n = ev->init_retrans + r * ev->init_retrans;
			} else
				n = 2 * ev->retrans + r * ev->retrans;
		}
		if (ev->max_retrans_time && n > ev->max_retrans_time)
			n = ev->max_retrans_time + r * ev->max_retrans_time;
		ev->retrans = (long)n;
		break;
	}

	interval.tv_sec = (ev->retrans * 1000) / 1000000;
	interval.tv_usec = (ev->retrans * 1000) % 1000000;
	dhcp6_set_timer(&interval, ev->timer);

	statestr = dhcp6_event_statestr(ev);

	dprintf(LOG_DEBUG, FNAME, "reset a timer on %s, "
		"state=%s, timeo=%d, retrans=%d",
		ev->ifp->ifname, statestr, ev->timeouts, ev->retrans);
}

int
duidcpy(dd, ds)
	struct duid *dd, *ds;
{
	dd->duid_len = ds->duid_len;
	if ((dd->duid_id = malloc(dd->duid_len)) == NULL) {
		dprintf(LOG_ERR, FNAME, "memory allocation failed");
		return (-1);
	}
	memcpy(dd->duid_id, ds->duid_id, dd->duid_len);

	return (0);
}

int
duidcmp(d1, d2)
	struct duid *d1, *d2;
{
	if (d1->duid_len == d2->duid_len) {
		return (memcmp(d1->duid_id, d2->duid_id, d1->duid_len));
	} else
		return (-1);
}

void
duidfree(duid)
	struct duid *duid;
{
	if (duid->duid_id)
		free(duid->duid_id);
	duid->duid_id = NULL;
	duid->duid_len = 0;
}

/*
 * Provide an NTP-format timestamp as a replay detection counter
 * as mentioned in RFC3315.
 */
#define JAN_1970        2208988800UL        /* 1970 - 1900 in seconds */
int
get_rdvalue(rdm, rdvalue, rdsize)
	int rdm;
	void *rdvalue;
	size_t rdsize;
{
#if defined(HAVE_CLOCK_GETTIME)
	struct timespec tp;
	double nsec;
#else
	struct timeval tv;
#endif
	u_int32_t u32, l32;

	if (rdm != DHCP6_AUTHRDM_MONOCOUNTER) {
		dprintf(LOG_INFO, FNAME, "unsupported RDM (%d)", rdm);
		return (-1);
	}
	if (rdsize != sizeof(u_int64_t)) {
		dprintf(LOG_INFO, FNAME, "unsupported RD size (%d)", rdsize);
		return (-1);
	}

#if defined(HAVE_CLOCK_GETTIME)
	if (clock_gettime(CLOCK_REALTIME, &tp)) {
		dprintf(LOG_WARNING, FNAME, "clock_gettime failed: %s",
		    strerror(errno));
		return (-1);
	}

	u32 = (u_int32_t)tp.tv_sec;
	u32 += JAN_1970;

	nsec = (double)tp.tv_nsec / 1e9 * 0x100000000ULL;
	/* nsec should be smaller than 2^32 */
	l32 = (u_int32_t)nsec;
#else
	if (gettimeofday(&tv, NULL) != 0) {
		dprintf(LOG_WARNING, FNAME, "gettimeofday failed: %s",
		    strerror(errno));
		return (-1);
	}
	u32 = (u_int32_t)tv.tv_sec;
	u32 += JAN_1970;
	l32 = (u_int32_t)tv.tv_usec;
#endif /* HAVE_CLOCK_GETTIME */

	u32 = htonl(u32);
	l32 = htonl(l32);

	memcpy(rdvalue, &u32, sizeof(u32));
	memcpy((char *)rdvalue + sizeof(u32), &l32, sizeof(l32));

	return (0);
}

char *
dhcp6optstr(type)
	int type;
{
	static char genstr[sizeof("opt_65535") + 1]; /* XXX thread unsafe */

	if (type > 65535)
		return ("INVALID option");

	switch(type) {
	case DH6OPT_CLIENTID:
		return ("client ID");
	case DH6OPT_SERVERID:
		return ("server ID");
	case DH6OPT_IA_NA:
		return ("identity association");
	case DH6OPT_IA_TA:
		return ("IA for temporary");
	case DH6OPT_IAADDR:
		return ("IA address");
	case DH6OPT_ORO:
		return ("option request");
	case DH6OPT_PREFERENCE:
		return ("preference");
	case DH6OPT_ELAPSED_TIME:
		return ("elapsed time");
	case DH6OPT_RELAY_MSG:
		return ("relay message");
	case DH6OPT_AUTH:
		return ("authentication");
	case DH6OPT_UNICAST:
		return ("server unicast");
	case DH6OPT_STATUS_CODE:
		return ("status code");
	case DH6OPT_RAPID_COMMIT:
		return ("rapid commit");
	case DH6OPT_USER_CLASS:
		return ("user class");
	case DH6OPT_VENDOR_CLASS:
		return ("vendor class");
	case DH6OPT_VENDOR_OPTS:
		return ("vendor specific info");
	case DH6OPT_INTERFACE_ID:
		return ("interface ID");
	case DH6OPT_RECONF_MSG:
		return ("reconfigure message");
	case DH6OPT_SIP_SERVER_D:
		return ("SIP domain name");
	case DH6OPT_SIP_SERVER_A:
		return ("SIP server address");
	case DH6OPT_DNS:
		return ("DNS");
	case DH6OPT_DNSNAME:
		return ("domain search list");
	case DH6OPT_NTP:
		return ("NTP server");
	case DH6OPT_IA_PD:
		return ("IA_PD");
	case DH6OPT_IA_PD_PREFIX:
		return ("IA_PD prefix");
	case DH6OPT_REFRESHTIME:
		return ("information refresh time");
	case DH6OPT_NIS_SERVERS:
		return ("NIS servers");
	case DH6OPT_NISP_SERVERS:
		return ("NIS+ servers");
	case DH6OPT_NIS_DOMAIN_NAME:
		return ("NIS domain name");
	case DH6OPT_NISP_DOMAIN_NAME:
		return ("NIS+ domain name");
	case DH6OPT_BCMCS_SERVER_D:
		return ("BCMCS domain name");
	case DH6OPT_BCMCS_SERVER_A:
		return ("BCMCS server address");
	case DH6OPT_GEOCONF_CIVIC:
		return ("Geoconf Civic");
	case DH6OPT_REMOTE_ID:
		return ("remote ID");
	case DH6OPT_SUBSCRIBER_ID:
		return ("subscriber ID");
	case DH6OPT_CLIENT_FQDN:
		return ("client FQDN");
	default:
		snprintf(genstr, sizeof(genstr), "opt_%d", type);
		return (genstr);
	}
}

char *
dhcp6msgstr(type)
	int type;
{
	static char genstr[sizeof("msg255") + 1]; /* XXX thread unsafe */

	if (type > 255)
		return ("INVALID msg");

	switch(type) {
	case DH6_SOLICIT:
		return ("solicit");
	case DH6_ADVERTISE:
		return ("advertise");
	case DH6_REQUEST:
		return ("request");
	case DH6_CONFIRM:
		return ("confirm");
	case DH6_RENEW:
		return ("renew");
	case DH6_REBIND:
		return ("rebind");
	case DH6_REPLY:
		return ("reply");
	case DH6_RELEASE:
		return ("release");
	case DH6_DECLINE:
		return ("decline");
	case DH6_RECONFIGURE:
		return ("reconfigure");
	case DH6_INFORM_REQ:
		return ("information request");
	case DH6_RELAY_FORW:
		return ("relay-forward");
	case DH6_RELAY_REPLY:
		return ("relay-reply");
	default:
		snprintf(genstr, sizeof(genstr), "msg%d", type);
		return (genstr);
	}
}

char *
dhcp6_stcodestr(code)
	u_int16_t code;
{
	static char genstr[sizeof("code255") + 1]; /* XXX thread unsafe */

	if (code > 255)
		return ("INVALID code");

	switch(code) {
	case DH6OPT_STCODE_SUCCESS:
		return ("success");
	case DH6OPT_STCODE_UNSPECFAIL:
		return ("unspec failure");
	case DH6OPT_STCODE_NOADDRSAVAIL:
		return ("no addresses");
	case DH6OPT_STCODE_NOBINDING:
		return ("no binding");
	case DH6OPT_STCODE_NOTONLINK:
		return ("not on-link");
	case DH6OPT_STCODE_USEMULTICAST:
		return ("use multicast");
	case DH6OPT_STCODE_NOPREFIXAVAIL:
		return ("no prefixes");
	default:
		snprintf(genstr, sizeof(genstr), "code%d", code);
		return (genstr);
	}
}

char *
duidstr(duid)
	struct duid *duid;
{
	int i, n;
	char *cp, *ep;
	static char duidstr[sizeof("xx:") * 128 + sizeof("...")];

	cp = duidstr;
	ep = duidstr + sizeof(duidstr);
	for (i = 0; i < duid->duid_len && i <= 128; i++) {
		n = snprintf(cp, ep - cp, "%s%02x", i == 0 ? "" : ":",
		    duid->duid_id[i] & 0xff);
		if (n < 0)
			return NULL;
		cp += n;
	}
	if (i < duid->duid_len)
		snprintf(cp, ep - cp, "%s", "...");

	return (duidstr);
}

char *dhcp6_event_statestr(ev)
	struct dhcp6_event *ev;
{
	switch(ev->state) {
	case DHCP6S_INIT:
		return ("INIT");
	case DHCP6S_SOLICIT:
		return ("SOLICIT");
	case DHCP6S_INFOREQ:
		return ("INFOREQ");
	case DHCP6S_REQUEST:
		return ("REQUEST");
	case DHCP6S_RENEW:
		return ("RENEW");
	case DHCP6S_REBIND:
		return ("REBIND");
	case DHCP6S_RELEASE:
		return ("RELEASE");
	case DHCP6S_IDLE:
		return ("IDLE");
	default:
		return ("???"); /* XXX */
	}
}

void
setloglevel(debuglevel)
	int debuglevel;
{
	if (foreground) {
		switch(debuglevel) {
		case 0:
			debug_thresh = LOG_ERR;
			break;
		case 1:
			debug_thresh = LOG_INFO;
			break;
		default:
			debug_thresh = LOG_DEBUG;
			break;
		}
	} else {
		switch(debuglevel) {
		case 0:
			setlogmask(LOG_UPTO(LOG_ERR));
			break;
		case 1:
			setlogmask(LOG_UPTO(LOG_INFO));
			break;
		}
	}
}

void
dprintf(int level, const char *fname, const char *fmt, ...)
{
	va_list ap;
	char logbuf[LINE_MAX];
	int printfname = 1;

	va_start(ap, fmt);
	vsnprintf(logbuf, sizeof(logbuf), fmt, ap);

	if (*fname == '\0')
		printfname = 0;

	if (foreground && debug_thresh >= level) {
		time_t now;
		struct tm *tm_now;
		const char *month[] = {
			"Jan", "Feb", "Mar", "Apr", "May", "Jun",
			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
		};

		if ((now = time(NULL)) < 0)
			exit(1); /* XXX */
		tm_now = localtime(&now);
		fprintf(stderr, "%3s/%02d/%04d %02d:%02d:%02d: %s%s%s\n",
		    month[tm_now->tm_mon], tm_now->tm_mday,
		    tm_now->tm_year + 1900,
		    tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
		    fname, printfname ? ": " : "",
		    logbuf);
	} else
		syslog(level, "%s%s%s", fname, printfname ? ": " : "", logbuf);
}

int
ifaddrconf(cmd, ifname, addr, plen, pltime, vltime)
	ifaddrconf_cmd_t cmd;
	char *ifname;
	struct sockaddr_in6 *addr;
	int plen;
	int pltime;
	int vltime;
{
#ifdef __KAME__
	struct in6_aliasreq req;
#endif
#ifdef __linux__
	struct in6_ifreq req;
	struct ifreq ifr;
#endif
#ifdef __sun__
	struct lifreq req;
#endif
	unsigned long ioctl_cmd;
	char *cmdstr;
	int s;			/* XXX overhead */

	switch(cmd) {
	case IFADDRCONF_ADD:
		cmdstr = "add";
#ifdef __KAME__
		ioctl_cmd = SIOCAIFADDR_IN6;
#endif
#ifdef __linux__
		ioctl_cmd = SIOCSIFADDR;
#endif
#ifdef __sun__
		ioctl_cmd = SIOCLIFADDIF;
#endif
		break;
	case IFADDRCONF_REMOVE:
		cmdstr = "remove";
#ifdef __KAME__
		ioctl_cmd = SIOCDIFADDR_IN6;
#endif
#ifdef __linux__
		ioctl_cmd = SIOCDIFADDR;
#endif
#ifdef __sun__
		ioctl_cmd = SIOCLIFREMOVEIF;
#endif
		break;
	default:
		return (-1);
	}

	if ((s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
		dprintf(LOG_ERR, FNAME, "can't open a temporary socket: %s",
		    strerror(errno));
		return (-1);
	}

	memset(&req, 0, sizeof(req));
#ifdef __KAME__
	req.ifra_addr = *addr;
	memcpy(req.ifra_name, ifname, sizeof(req.ifra_name));
	(void)sa6_plen2mask(&req.ifra_prefixmask, plen);
	/* XXX: should lifetimes be calculated based on the lease duration? */
	req.ifra_lifetime.ia6t_vltime = vltime;
	req.ifra_lifetime.ia6t_pltime = pltime;
#endif
#ifdef __linux__
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
	if (ioctl(s, SIOGIFINDEX, &ifr) < 0) {
		dprintf(LOG_NOTICE, FNAME, "failed to get the index of %s: %s",
		    ifname, strerror(errno));
		close(s); 
		return (-1); 
	}
	memcpy(&req.ifr6_addr, &addr->sin6_addr, sizeof(struct in6_addr));
	req.ifr6_prefixlen = plen;
	req.ifr6_ifindex = ifr.ifr_ifindex;
#endif
#ifdef __sun__
	strncpy(req.lifr_name, ifname, sizeof (req.lifr_name));
#endif

	if (ioctl(s, ioctl_cmd, &req)) {
		dprintf(LOG_NOTICE, FNAME, "failed to %s an address on %s: %s",
		    cmdstr, ifname, strerror(errno));
		close(s);
		return (-1);
	}

#ifdef __sun__
	memcpy(&req.lifr_addr, addr, sizeof (*addr));
	if (ioctl(s, SIOCSLIFADDR, &req) == -1) {
		dprintf(LOG_NOTICE, FNAME, "failed to %s new address on %s: %s",
		    cmdstr, ifname, strerror(errno));
		close(s);
		return (-1);
	}
#endif

	dprintf(LOG_DEBUG, FNAME, "%s an address %s/%d on %s", cmdstr,
	    addr2str((struct sockaddr *)addr), plen, ifname);

	close(s);
	return (0);
}

int
safefile(path)
	const char *path;
{
	struct stat s;
	uid_t myuid;

	/* no setuid */
	if (getuid() != geteuid()) {
		dprintf(LOG_NOTICE, FNAME,
		    "setuid'ed execution not allowed");
		return (-1);
	}

	if (lstat(path, &s) != 0) {
		dprintf(LOG_NOTICE, FNAME, "lstat failed: %s",
		    strerror(errno));
		return (-1);
	}

	/* the file must be owned by the running uid */
	myuid = getuid();
	if (s.st_uid != myuid) {
		dprintf(LOG_NOTICE, FNAME, "%s has invalid owner uid", path);
		return (-1);
	}

	switch (s.st_mode & S_IFMT) {
	case S_IFREG:
		break;
	default:
		dprintf(LOG_NOTICE, FNAME, "%s is an invalid file type 0x%o",
		    path, (s.st_mode & S_IFMT));
		return (-1);
	}

	return (0);
}
