/*	$NetBSD: isakmp_cfg.c,v 1.12.6.4 2008/11/27 15:25:20 vanhu Exp $	*/

/* Id: isakmp_cfg.c,v 1.55 2006/08/22 18:17:17 manubsd Exp */

/*
 * Copyright (C) 2004-2006 Emmanuel Dreyfus
 * 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 "config.h"

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

#ifndef ANDROID_PATCHED
#include <utmp.h>
#endif
#if defined(__APPLE__) && defined(__MACH__)
#include <util.h>
#endif

#ifdef __FreeBSD__
# include <libutil.h>
#endif
#ifdef __NetBSD__
#  include <util.h>
#endif

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

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.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 <netdb.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_STDINT_H
#include <stdint.h>
#endif
#include <ctype.h>
#include <resolv.h>

#ifdef HAVE_LIBRADIUS
#include <sys/utsname.h>
#include <radlib.h>
#endif

#include "var.h"
#include "misc.h"
#include "vmbuf.h"
#include "plog.h"
#include "sockmisc.h"
#include "schedule.h"
#include "debug.h"

#include "isakmp_var.h"
#include "isakmp.h"
#include "handler.h"
#include "evt.h"
#include "throttle.h"
#include "remoteconf.h"
#include "crypto_openssl.h"
#include "isakmp_inf.h"
#include "isakmp_xauth.h"
#include "isakmp_unity.h"
#include "isakmp_cfg.h"
#include "strnames.h"
#include "admin.h"
#include "privsep.h"

struct isakmp_cfg_config isakmp_cfg_config;

static vchar_t *buffer_cat(vchar_t *s, vchar_t *append);
static vchar_t *isakmp_cfg_net(struct ph1handle *, struct isakmp_data *);
#if 0
static vchar_t *isakmp_cfg_void(struct ph1handle *, struct isakmp_data *);
#endif
static vchar_t *isakmp_cfg_addr4(struct ph1handle *, 
				 struct isakmp_data *, in_addr_t *);
static void isakmp_cfg_getaddr4(struct isakmp_data *, struct in_addr *);
static vchar_t *isakmp_cfg_addr4_list(struct ph1handle *,
				      struct isakmp_data *, in_addr_t *, int);
static void isakmp_cfg_appendaddr4(struct isakmp_data *, 
				   struct in_addr *, int *, int);
static void isakmp_cfg_getstring(struct isakmp_data *,char *);
void isakmp_cfg_iplist_to_str(char *, int, void *, int);

#define ISAKMP_CFG_LOGIN	1
#define ISAKMP_CFG_LOGOUT	2
static int isakmp_cfg_accounting(struct ph1handle *, int);
#ifdef HAVE_LIBRADIUS
static int isakmp_cfg_accounting_radius(struct ph1handle *, int);
#endif

/* 
 * Handle an ISAKMP config mode packet
 * We expect HDR, HASH, ATTR
 */
void
isakmp_cfg_r(iph1, msg)
	struct ph1handle *iph1;
	vchar_t *msg;
{
	struct isakmp *packet;
	struct isakmp_gen *ph;
	int tlen;
	char *npp;
	int np;
	vchar_t *dmsg;
	struct isakmp_ivm *ivm;

	/* Check that the packet is long enough to have a header */
	if (msg->l < sizeof(*packet)) {
	     plog(LLV_ERROR, LOCATION, NULL, "Unexpected short packet\n");
	     return;
	}

	packet = (struct isakmp *)msg->v;

	/* Is it encrypted? It should be encrypted */
	if ((packet->flags & ISAKMP_FLAG_E) == 0) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "User credentials sent in cleartext!\n");
		return;
	}

	/* 
	 * Decrypt the packet. If this is the beginning of a new
	 * exchange, reinitialize the IV
	 */
	if (iph1->mode_cfg->ivm == NULL ||
	    iph1->mode_cfg->last_msgid != packet->msgid )
		iph1->mode_cfg->ivm = 
		    isakmp_cfg_newiv(iph1, packet->msgid);
	ivm = iph1->mode_cfg->ivm;

	dmsg = oakley_do_decrypt(iph1, msg, ivm->iv, ivm->ive);
	if (dmsg == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "failed to decrypt message\n");
		return;
	}

	plog(LLV_DEBUG, LOCATION, NULL, "MODE_CFG packet\n");
	plogdump(LLV_DEBUG, dmsg->v, dmsg->l);

	/* Now work with the decrypted packet */
	packet = (struct isakmp *)dmsg->v;
	tlen = dmsg->l - sizeof(*packet);
	ph = (struct isakmp_gen *)(packet + 1);

	np = packet->np;
	while ((tlen > 0) && (np != ISAKMP_NPTYPE_NONE)) {
		/* Check that the payload header fits in the packet */
		if (tlen < sizeof(*ph)) {
			 plog(LLV_WARNING, LOCATION, NULL, 
			      "Short payload header\n");
			 goto out;
		}

		/* Check that the payload fits in the packet */
		if (tlen < ntohs(ph->len)) {
			plog(LLV_WARNING, LOCATION, NULL, 
			      "Short payload\n");
			goto out;
		}
		
		plog(LLV_DEBUG, LOCATION, NULL, "Seen payload %d\n", np);
		plogdump(LLV_DEBUG, ph, ntohs(ph->len));

		switch(np) {
		case ISAKMP_NPTYPE_HASH: {
			vchar_t *check;
			vchar_t *payload;
			size_t plen;
			struct isakmp_gen *nph;

			plen = ntohs(ph->len);
			nph = (struct isakmp_gen *)((char *)ph + plen);
			plen = ntohs(nph->len);

			if ((payload = vmalloc(plen)) == NULL) {
				plog(LLV_ERROR, LOCATION, NULL, 
				    "Cannot allocate memory\n");
				goto out;
			}
			memcpy(payload->v, nph, plen);

			if ((check = oakley_compute_hash1(iph1, 
			    packet->msgid, payload)) == NULL) {
				plog(LLV_ERROR, LOCATION, NULL, 
				    "Cannot compute hash\n");
				vfree(payload);
				goto out;
			}

			if (memcmp(ph + 1, check->v, check->l) != 0) {
				plog(LLV_ERROR, LOCATION, NULL, 
				    "Hash verification failed\n");
				vfree(payload);
				vfree(check);
				goto out;
			}
			vfree(payload);
			vfree(check);
			break;
		}
		case ISAKMP_NPTYPE_ATTR: {
			struct isakmp_pl_attr *attrpl;

			attrpl = (struct isakmp_pl_attr *)ph;
			isakmp_cfg_attr_r(iph1, packet->msgid, attrpl);

			break;
		}
		default:
			 plog(LLV_WARNING, LOCATION, NULL, 
			      "Unexpected next payload %d\n", np);
			 /* Skip to the next payload */
			 break;
		}

		/* Move to the next payload */
		np = ph->np;
		tlen -= ntohs(ph->len);
		npp = (char *)ph;
		ph = (struct isakmp_gen *)(npp + ntohs(ph->len));
	}

out:
	vfree(dmsg);
}

int
isakmp_cfg_attr_r(iph1, msgid, attrpl) 
	struct ph1handle *iph1;
	u_int32_t msgid;
	struct isakmp_pl_attr *attrpl;
{
	int type = attrpl->type;

	plog(LLV_DEBUG, LOCATION, NULL,
	     "Configuration exchange type %s\n", s_isakmp_cfg_ptype(type));
	switch (type) {
	case ISAKMP_CFG_ACK:
		/* ignore, but this is the time to reinit the IV */
		oakley_delivm(iph1->mode_cfg->ivm);
		iph1->mode_cfg->ivm = NULL;
		return 0;
		break;

	case ISAKMP_CFG_REPLY:
		return isakmp_cfg_reply(iph1, attrpl);
		break;

	case ISAKMP_CFG_REQUEST:
		iph1->msgid = msgid;
		return isakmp_cfg_request(iph1, attrpl);
		break;

	case ISAKMP_CFG_SET:
		iph1->msgid = msgid;
		return isakmp_cfg_set(iph1, attrpl);
		break;

	default:
		plog(LLV_WARNING, LOCATION, NULL,
		     "Unepected configuration exchange type %d\n", type);
		return -1;
		break;
	}

	return 0;
}

int
isakmp_cfg_reply(iph1, attrpl)
	struct ph1handle *iph1;
	struct isakmp_pl_attr *attrpl;
{
	struct isakmp_data *attr;
	int tlen;
	size_t alen;
	char *npp;
	int type;
	struct sockaddr_in *sin;
	int error;

	tlen = ntohs(attrpl->h.len);
	attr = (struct isakmp_data *)(attrpl + 1);
	tlen -= sizeof(*attrpl);
	
	while (tlen > 0) {
		type = ntohs(attr->type);

		/* Handle short attributes */
		if ((type & ISAKMP_GEN_MASK) == ISAKMP_GEN_TV) {
			type &= ~ISAKMP_GEN_MASK;

			plog(LLV_DEBUG, LOCATION, NULL,
			     "Short attribute %s = %d\n", 
			     s_isakmp_cfg_type(type), ntohs(attr->lorv));

			switch (type) {
			case XAUTH_TYPE:
				if ((error = xauth_attr_reply(iph1, 
				    attr, ntohs(attrpl->id))) != 0)
					return error;
				break;

			default:
				plog(LLV_WARNING, LOCATION, NULL,
				     "Ignored short attribute %s\n",
				     s_isakmp_cfg_type(type));
				break;
			}

			tlen -= sizeof(*attr);
			attr++;
			continue;
		}

		type = ntohs(attr->type);
		alen = ntohs(attr->lorv);

		/* Check that the attribute fit in the packet */
		if (tlen < alen) {
			plog(LLV_ERROR, LOCATION, NULL,
			     "Short attribute %s\n",
			     s_isakmp_cfg_type(type));
			return -1;
		}

		plog(LLV_DEBUG, LOCATION, NULL,
		     "Attribute %s, len %zu\n", 
		     s_isakmp_cfg_type(type), alen);

		switch(type) {
		case XAUTH_TYPE:
		case XAUTH_USER_NAME:
		case XAUTH_USER_PASSWORD:
		case XAUTH_PASSCODE:
		case XAUTH_MESSAGE:
		case XAUTH_CHALLENGE:
		case XAUTH_DOMAIN:
		case XAUTH_STATUS:
		case XAUTH_NEXT_PIN:
		case XAUTH_ANSWER:
			if ((error = xauth_attr_reply(iph1, 
			    attr, ntohs(attrpl->id))) != 0)
				return error;
			break;
		case INTERNAL_IP4_ADDRESS:
			isakmp_cfg_getaddr4(attr, &iph1->mode_cfg->addr4);
			iph1->mode_cfg->flags |= ISAKMP_CFG_GOT_ADDR4;
			break;
		case INTERNAL_IP4_NETMASK:
			isakmp_cfg_getaddr4(attr, &iph1->mode_cfg->mask4);
			iph1->mode_cfg->flags |= ISAKMP_CFG_GOT_MASK4;
			break;
		case INTERNAL_IP4_DNS:
			isakmp_cfg_appendaddr4(attr, 
			    &iph1->mode_cfg->dns4[iph1->mode_cfg->dns4_index],
			    &iph1->mode_cfg->dns4_index, MAXNS);
			iph1->mode_cfg->flags |= ISAKMP_CFG_GOT_DNS4;
			break;
		case INTERNAL_IP4_NBNS:
			isakmp_cfg_appendaddr4(attr, 
			    &iph1->mode_cfg->wins4[iph1->mode_cfg->wins4_index],
			    &iph1->mode_cfg->wins4_index, MAXNS);
			iph1->mode_cfg->flags |= ISAKMP_CFG_GOT_WINS4;
			break;
		case UNITY_DEF_DOMAIN:
			isakmp_cfg_getstring(attr, 
			    iph1->mode_cfg->default_domain);
			iph1->mode_cfg->flags |= ISAKMP_CFG_GOT_DEFAULT_DOMAIN;
			break;
		case UNITY_SPLIT_INCLUDE:
		case UNITY_LOCAL_LAN:
		case UNITY_SPLITDNS_NAME:
		case UNITY_BANNER:
		case UNITY_SAVE_PASSWD:
		case UNITY_NATT_PORT:
		case UNITY_PFS:
		case UNITY_FW_TYPE:
		case UNITY_BACKUP_SERVERS:
		case UNITY_DDNS_HOSTNAME:
			isakmp_unity_reply(iph1, attr);
			break;
		case INTERNAL_IP4_SUBNET:
		case INTERNAL_ADDRESS_EXPIRY:
		default:
			plog(LLV_WARNING, LOCATION, NULL,
			     "Ignored attribute %s\n",
			     s_isakmp_cfg_type(type));
			break;
		}

		npp = (char *)attr;
		attr = (struct isakmp_data *)(npp + sizeof(*attr) + alen);
		tlen -= (sizeof(*attr) + alen);
	}

	/* 
	 * Call the SA up script hook now that we have the configuration
	 * It is done at the end of phase 1 if ISAKMP mode config is not
	 * requested.
	 */
	
	if ((iph1->status == PHASE1ST_ESTABLISHED) && 
	    iph1->rmconf->mode_cfg) {
		switch (AUTHMETHOD(iph1)) {
		case FICTIVE_AUTH_METHOD_XAUTH_PSKEY_I:
		case OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_I:
		/* Unimplemented */
		case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_I: 
		case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_I: 
		case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_I:
		case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_I: 
		case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_I: 
			script_hook(iph1, SCRIPT_PHASE1_UP);
			break;
		default:
			break;
		}
	}
		

#ifdef ENABLE_ADMINPORT
	{
		vchar_t *buf;

		alen = ntohs(attrpl->h.len) - sizeof(*attrpl);
		if ((buf = vmalloc(alen)) == NULL) {
			plog(LLV_WARNING, LOCATION, NULL, 
			    "Cannot allocate memory: %s\n", strerror(errno));
		} else {
			memcpy(buf->v, attrpl + 1, buf->l);
			EVT_PUSH(iph1->local, iph1->remote, 
			    EVTT_ISAKMP_CFG_DONE, buf);
			vfree(buf);
		}
	}
#endif

	return 0;
}

int
isakmp_cfg_request(iph1, attrpl)
	struct ph1handle *iph1;
	struct isakmp_pl_attr *attrpl;
{
	struct isakmp_data *attr;
	int tlen;
	size_t alen;
	char *npp;
	vchar_t *payload;
	struct isakmp_pl_attr *reply;
	vchar_t *reply_attr;
	int type;
	int error = -1;

	if ((payload = vmalloc(sizeof(*reply))) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot allocate memory\n");
		return -1;
	}
	memset(payload->v, 0, sizeof(*reply));

	tlen = ntohs(attrpl->h.len);
	attr = (struct isakmp_data *)(attrpl + 1);
	tlen -= sizeof(*attrpl);
	
	while (tlen > 0) {
		reply_attr = NULL;
		type = ntohs(attr->type);

		/* Handle short attributes */
		if ((type & ISAKMP_GEN_MASK) == ISAKMP_GEN_TV) {
			type &= ~ISAKMP_GEN_MASK;

			plog(LLV_DEBUG, LOCATION, NULL,
			     "Short attribute %s = %d\n", 
			     s_isakmp_cfg_type(type), ntohs(attr->lorv));

			switch (type) {
			case XAUTH_TYPE:
				reply_attr = isakmp_xauth_req(iph1, attr);
				break;
			default:
				plog(LLV_WARNING, LOCATION, NULL,
				     "Ignored short attribute %s\n",
				     s_isakmp_cfg_type(type));
				break;
			}

			tlen -= sizeof(*attr);
			attr++;

			if (reply_attr != NULL) {
				payload = buffer_cat(payload, reply_attr);
				vfree(reply_attr);
			}

			continue;
		}

		type = ntohs(attr->type);
		alen = ntohs(attr->lorv);

		/* Check that the attribute fit in the packet */
		if (tlen < alen) {
			plog(LLV_ERROR, LOCATION, NULL,
			     "Short attribute %s\n",
			     s_isakmp_cfg_type(type));
			goto end;
		}

		plog(LLV_DEBUG, LOCATION, NULL,
		     "Attribute %s, len %zu\n",
		     s_isakmp_cfg_type(type), alen);

		switch(type) {
		case INTERNAL_IP4_ADDRESS:
		case INTERNAL_IP4_NETMASK:
		case INTERNAL_IP4_DNS:
		case INTERNAL_IP4_NBNS:
		case INTERNAL_IP4_SUBNET:
			reply_attr = isakmp_cfg_net(iph1, attr);
			break;

		case XAUTH_TYPE:
		case XAUTH_USER_NAME:
		case XAUTH_USER_PASSWORD:
		case XAUTH_PASSCODE:
		case XAUTH_MESSAGE:
		case XAUTH_CHALLENGE:
		case XAUTH_DOMAIN:
		case XAUTH_STATUS:
		case XAUTH_NEXT_PIN:
		case XAUTH_ANSWER:
			reply_attr = isakmp_xauth_req(iph1, attr);
			break;

		case APPLICATION_VERSION:
			reply_attr = isakmp_cfg_string(iph1, 
			    attr, ISAKMP_CFG_RACOON_VERSION);
			break;

		case UNITY_BANNER:
		case UNITY_PFS:
		case UNITY_SAVE_PASSWD:
		case UNITY_DEF_DOMAIN:
		case UNITY_DDNS_HOSTNAME:
		case UNITY_FW_TYPE:
		case UNITY_SPLITDNS_NAME:
		case UNITY_SPLIT_INCLUDE:
		case UNITY_LOCAL_LAN:
		case UNITY_NATT_PORT:
		case UNITY_BACKUP_SERVERS:
			reply_attr = isakmp_unity_req(iph1, attr);
			break;

		case INTERNAL_ADDRESS_EXPIRY:
		default:
			plog(LLV_WARNING, LOCATION, NULL,
			     "Ignored attribute %s\n",
			     s_isakmp_cfg_type(type));
			break;
		}

		npp = (char *)attr;
		attr = (struct isakmp_data *)(npp + sizeof(*attr) + alen);
		tlen -= (sizeof(*attr) + alen);

		if (reply_attr != NULL) {
			payload = buffer_cat(payload, reply_attr);
			vfree(reply_attr);
		}

	}

	reply = (struct isakmp_pl_attr *)payload->v;
	reply->h.len = htons(payload->l);
	reply->type = ISAKMP_CFG_REPLY;
	reply->id = attrpl->id;

	plog(LLV_DEBUG, LOCATION, NULL, 
		    "Sending MODE_CFG REPLY\n");

	error = isakmp_cfg_send(iph1, payload, 
	    ISAKMP_NPTYPE_ATTR, ISAKMP_FLAG_E, 0);

	if (iph1->status == PHASE1ST_ESTABLISHED) {
		switch (AUTHMETHOD(iph1)) {
		case OAKLEY_ATTR_AUTH_METHOD_XAUTH_PSKEY_R:
		case OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_R:
		/* Unimplemented */
		case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_R: 
		case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_R: 
		case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_R:
		case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_R: 
		case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_R: 
			script_hook(iph1, SCRIPT_PHASE1_UP);
			break;
		default:
			break;
		}
	}
	
end:
	vfree(payload);

	return error;
}

int
isakmp_cfg_set(iph1, attrpl)
	struct ph1handle *iph1;
	struct isakmp_pl_attr *attrpl;
{
	struct isakmp_data *attr;
	int tlen;
	size_t alen;
	char *npp;
	vchar_t *payload;
	struct isakmp_pl_attr *reply;
	vchar_t *reply_attr;
	int type;
	int error = -1;

	if ((payload = vmalloc(sizeof(*reply))) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot allocate memory\n");
		return -1;
	}
	memset(payload->v, 0, sizeof(*reply));

	tlen = ntohs(attrpl->h.len);
	attr = (struct isakmp_data *)(attrpl + 1);
	tlen -= sizeof(*attrpl);
	
	/* 
	 * We should send ack for the attributes we accepted 
	 */
	while (tlen > 0) {
		reply_attr = NULL;
		type = ntohs(attr->type);

		plog(LLV_DEBUG, LOCATION, NULL,
		     "Attribute %s\n", 
		     s_isakmp_cfg_type(type & ~ISAKMP_GEN_MASK));
		
		switch (type & ~ISAKMP_GEN_MASK) {
		case XAUTH_STATUS:
			reply_attr = isakmp_xauth_set(iph1, attr);
			break;
		default:
			plog(LLV_DEBUG, LOCATION, NULL,
			     "Unexpected SET attribute %s\n", 
		     	     s_isakmp_cfg_type(type & ~ISAKMP_GEN_MASK));
			break;
		}

		if (reply_attr != NULL) {
			payload = buffer_cat(payload, reply_attr);
			vfree(reply_attr);
		}

		/* 
		 * Move to next attribute. If we run out of the packet, 
		 * tlen becomes negative and we exit. 
		 */
		if ((type & ISAKMP_GEN_MASK) == ISAKMP_GEN_TV) {
			tlen -= sizeof(*attr);
			attr++;
		} else {
			alen = ntohs(attr->lorv);
			tlen -= (sizeof(*attr) + alen);
			npp = (char *)attr;
			attr = (struct isakmp_data *)
			    (npp + sizeof(*attr) + alen);
		}
	}

	reply = (struct isakmp_pl_attr *)payload->v;
	reply->h.len = htons(payload->l);
	reply->type = ISAKMP_CFG_ACK;
	reply->id = attrpl->id;

	plog(LLV_DEBUG, LOCATION, NULL,
		     "Sending MODE_CFG ACK\n");

	error = isakmp_cfg_send(iph1, payload, 
	    ISAKMP_NPTYPE_ATTR, ISAKMP_FLAG_E, 0);

	if (iph1->mode_cfg->flags & ISAKMP_CFG_DELETE_PH1) {
		if (iph1->status == PHASE1ST_ESTABLISHED)
			isakmp_info_send_d1(iph1);
		remph1(iph1);
		delph1(iph1);
		iph1 = NULL;
	}
end:
	vfree(payload);

	/* 
	 * If required, request ISAKMP mode config information
	 */
	if ((iph1 != NULL) && (iph1->rmconf->mode_cfg) && (error == 0))
		error = isakmp_cfg_getconfig(iph1);

	return error;
}


static vchar_t *
buffer_cat(s, append)
	vchar_t *s;
	vchar_t *append;
{
	vchar_t *new;

	new = vmalloc(s->l + append->l);
	if (new == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "Cannot allocate memory\n");
		return s;
	}

	memcpy(new->v, s->v, s->l);
	memcpy(new->v + s->l, append->v, append->l);

	vfree(s);
	return new;
}

static vchar_t *
isakmp_cfg_net(iph1, attr)
	struct ph1handle *iph1;
	struct isakmp_data *attr;
{
	int type;
	int confsource;
	in_addr_t addr4;

	type = ntohs(attr->type);

	/* 
	 * Don't give an address to a peer that did not succeed Xauth
	 */
	if (xauth_check(iph1) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "Attempt to start phase config whereas Xauth failed\n");
		return NULL;
	}

	confsource = isakmp_cfg_config.confsource;
	/*
	 * If we have to fall back to a local
	 * configuration source, we will jump
	 * back to this point.
	 */
retry_source:

	switch(type) {
	case INTERNAL_IP4_ADDRESS:
		switch(confsource) {
#ifdef HAVE_LIBLDAP
		case ISAKMP_CFG_CONF_LDAP:
			if (iph1->mode_cfg->flags & ISAKMP_CFG_ADDR4_EXTERN)
			    break;
			plog(LLV_INFO, LOCATION, NULL, 
			    "No IP from LDAP, using local pool\n");
			/* FALLTHROUGH */
			confsource = ISAKMP_CFG_CONF_LOCAL;
			goto retry_source;
#endif
#ifdef HAVE_LIBRADIUS
		case ISAKMP_CFG_CONF_RADIUS:
			if ((iph1->mode_cfg->flags & ISAKMP_CFG_ADDR4_EXTERN)
			    && (iph1->mode_cfg->addr4.s_addr != htonl(-2)))
			    /*
			     * -2 is 255.255.255.254, RADIUS uses that
			     * to instruct the NAS to use a local pool
			     */
			    break;
			plog(LLV_INFO, LOCATION, NULL, 
			    "No IP from RADIUS, using local pool\n");
			/* FALLTHROUGH */
			confsource = ISAKMP_CFG_CONF_LOCAL;
			goto retry_source;
#endif
		case ISAKMP_CFG_CONF_LOCAL:
			if (isakmp_cfg_getport(iph1) == -1) {
				plog(LLV_ERROR, LOCATION, NULL, 
				    "Port pool depleted\n");
				break;
			}

			iph1->mode_cfg->addr4.s_addr = 
			    htonl(ntohl(isakmp_cfg_config.network4) 
			    + iph1->mode_cfg->port);
			iph1->mode_cfg->flags |= ISAKMP_CFG_ADDR4_LOCAL;
			break;

		default:
			plog(LLV_ERROR, LOCATION, NULL, 
			    "Unexpected confsource\n");
		}
			
		if (isakmp_cfg_accounting(iph1, ISAKMP_CFG_LOGIN) != 0)
			plog(LLV_ERROR, LOCATION, NULL, "Accounting failed\n");

		return isakmp_cfg_addr4(iph1, 
		    attr, &iph1->mode_cfg->addr4.s_addr);
		break;

	case INTERNAL_IP4_NETMASK:
		switch(confsource) {
#ifdef HAVE_LIBLDAP
		case ISAKMP_CFG_CONF_LDAP:
			if (iph1->mode_cfg->flags & ISAKMP_CFG_MASK4_EXTERN)
				break;
			plog(LLV_INFO, LOCATION, NULL, 
			    "No mask from LDAP, using local pool\n");
			/* FALLTHROUGH */
			confsource = ISAKMP_CFG_CONF_LOCAL;
			goto retry_source;
#endif
#ifdef HAVE_LIBRADIUS
		case ISAKMP_CFG_CONF_RADIUS:
			if (iph1->mode_cfg->flags & ISAKMP_CFG_MASK4_EXTERN)
				break;
			plog(LLV_INFO, LOCATION, NULL, 
			    "No mask from RADIUS, using local pool\n");
			/* FALLTHROUGH */
			confsource = ISAKMP_CFG_CONF_LOCAL;
			goto retry_source;
#endif
		case ISAKMP_CFG_CONF_LOCAL:
			iph1->mode_cfg->mask4.s_addr 
			    = isakmp_cfg_config.netmask4;
			iph1->mode_cfg->flags |= ISAKMP_CFG_MASK4_LOCAL;
			break;

		default:
			plog(LLV_ERROR, LOCATION, NULL, 
			    "Unexpected confsource\n");
		}
		return isakmp_cfg_addr4(iph1, attr, 
		    &iph1->mode_cfg->mask4.s_addr);
		break;

	case INTERNAL_IP4_DNS:
		return isakmp_cfg_addr4_list(iph1, 
		    attr, &isakmp_cfg_config.dns4[0], 
		    isakmp_cfg_config.dns4_index);
		break;

	case INTERNAL_IP4_NBNS:
		return isakmp_cfg_addr4_list(iph1, 
		    attr, &isakmp_cfg_config.nbns4[0], 
		    isakmp_cfg_config.nbns4_index);
		break;

	case INTERNAL_IP4_SUBNET:
		return isakmp_cfg_addr4(iph1, 
		    attr, &isakmp_cfg_config.network4);
		break;

	default:
		plog(LLV_ERROR, LOCATION, NULL, "Unexpected type %d\n", type);
		break;
	}
	return NULL;
}

#if 0
static vchar_t *
isakmp_cfg_void(iph1, attr)
	struct ph1handle *iph1;
	struct isakmp_data *attr;
{
	vchar_t *buffer;
	struct isakmp_data *new;

	if ((buffer = vmalloc(sizeof(*attr))) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot allocate memory\n");
		return NULL;
	}

	new = (struct isakmp_data *)buffer->v;

	new->type = attr->type;
	new->lorv = htons(0);

	return buffer;
}
#endif

vchar_t *
isakmp_cfg_copy(iph1, attr)
	struct ph1handle *iph1;
	struct isakmp_data *attr;
{
	vchar_t *buffer;
	size_t len = 0;

	if ((ntohs(attr->type) & ISAKMP_GEN_MASK) == ISAKMP_GEN_TLV)
		len = ntohs(attr->lorv);

	if ((buffer = vmalloc(sizeof(*attr) + len)) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot allocate memory\n");
		return NULL;
	}

	memcpy(buffer->v, attr, sizeof(*attr) + ntohs(attr->lorv));

	return buffer;
}

vchar_t *
isakmp_cfg_short(iph1, attr, value)
	struct ph1handle *iph1;
	struct isakmp_data *attr;
	int value;
{
	vchar_t *buffer;
	struct isakmp_data *new;
	int type;

	if ((buffer = vmalloc(sizeof(*attr))) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot allocate memory\n");
		return NULL;
	}

	new = (struct isakmp_data *)buffer->v;
	type = ntohs(attr->type) & ~ISAKMP_GEN_MASK;

	new->type = htons(type | ISAKMP_GEN_TV);
	new->lorv = htons(value);

	return buffer;
}

vchar_t *
isakmp_cfg_varlen(iph1, attr, string, len)
	struct ph1handle *iph1;
	struct isakmp_data *attr;
	char *string;
	size_t len;
{
	vchar_t *buffer;
	struct isakmp_data *new;
	char *data;

	if ((buffer = vmalloc(sizeof(*attr) + len)) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot allocate memory\n");
		return NULL;
	}

	new = (struct isakmp_data *)buffer->v;

	new->type = attr->type;
	new->lorv = htons(len);
	data = (char *)(new + 1);

	memcpy(data, string, len);
	
	return buffer;
}
vchar_t *
isakmp_cfg_string(iph1, attr, string)
	struct ph1handle *iph1;
	struct isakmp_data *attr;
	char *string;
{
	size_t len = strlen(string);
	return isakmp_cfg_varlen(iph1, attr, string, len);
}

static vchar_t *
isakmp_cfg_addr4(iph1, attr, addr)
	struct ph1handle *iph1;
	struct isakmp_data *attr;
	in_addr_t *addr;
{
	vchar_t *buffer;
	struct isakmp_data *new;
	size_t len;

	len = sizeof(*addr);
	if ((buffer = vmalloc(sizeof(*attr) + len)) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot allocate memory\n");
		return NULL;
	}

	new = (struct isakmp_data *)buffer->v;

	new->type = attr->type;
	new->lorv = htons(len);
	memcpy(new + 1, addr, len);
	
	return buffer;
}

static vchar_t *
isakmp_cfg_addr4_list(iph1, attr, addr, nbr)
	struct ph1handle *iph1;
	struct isakmp_data *attr;
	in_addr_t *addr;
	int nbr;
{
	int error = -1;
	vchar_t *buffer = NULL;
	vchar_t *bufone = NULL;
	struct isakmp_data *new;
	size_t len;
	int i;

	len = sizeof(*addr);
	if ((buffer = vmalloc(0)) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot allocate memory\n");
		goto out;
	}
	for(i = 0; i < nbr; i++) {
		if ((bufone = vmalloc(sizeof(*attr) + len)) == NULL) {
			plog(LLV_ERROR, LOCATION, NULL, 
			    "Cannot allocate memory\n");
			goto out;
		}
		new = (struct isakmp_data *)bufone->v;
		new->type = attr->type;
		new->lorv = htons(len);
		memcpy(new + 1, &addr[i], len);
		new += (len + sizeof(*attr));
		buffer = buffer_cat(buffer, bufone);
		vfree(bufone);
	}

	error = 0;

out:
	if ((error != 0) && (buffer != NULL)) {
		vfree(buffer);
		buffer = NULL;
	}

	return buffer;
}

struct isakmp_ivm *
isakmp_cfg_newiv(iph1, msgid)
	struct ph1handle *iph1;
	u_int32_t msgid;
{
	struct isakmp_cfg_state *ics = iph1->mode_cfg;

	if (ics == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "isakmp_cfg_newiv called without mode config state\n");
		return NULL;
	}

	if (ics->ivm != NULL)
		oakley_delivm(ics->ivm);

	ics->ivm = oakley_newiv2(iph1, msgid);
	ics->last_msgid = msgid;

	return ics->ivm;
}

/* Derived from isakmp_info_send_common */
int
isakmp_cfg_send(iph1, payload, np, flags, new_exchange)
	struct ph1handle *iph1;
	vchar_t *payload;
	u_int32_t np;
	int flags;
	int new_exchange;
{
	struct ph2handle *iph2 = NULL;
	vchar_t *hash = NULL;
	struct isakmp *isakmp;
	struct isakmp_gen *gen;
	char *p;
	int tlen;
	int error = -1;
	struct isakmp_cfg_state *ics = iph1->mode_cfg;

	/* Check if phase 1 is established */
	if ((iph1->status != PHASE1ST_ESTABLISHED) || 
	    (iph1->local == NULL) ||
	    (iph1->remote == NULL)) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "ISAKMP mode config exchange with immature phase 1\n");
		goto end;
	}

	/* add new entry to isakmp status table */
	iph2 = newph2();
	if (iph2 == NULL)
		goto end;

	iph2->dst = dupsaddr(iph1->remote);
	if (iph2->dst == NULL) {
		delph2(iph2);
		goto end;
	}
	iph2->src = dupsaddr(iph1->local);
	if (iph2->src == NULL) {
		delph2(iph2);
		goto end;
	}

#if (!defined(ENABLE_NATT)) || (defined(BROKEN_NATT))
	if (set_port(iph2->dst, 0) == NULL ||
	    set_port(iph2->src, 0) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
		     "invalid family: %d\n", iph1->remote->sa_family);
		delph2(iph2);
		goto end;
	}
#endif
	iph2->ph1 = iph1;
	iph2->side = INITIATOR;
	iph2->status = PHASE2ST_START;

	if (new_exchange)
		iph2->msgid = isakmp_newmsgid2(iph1);
	else
		iph2->msgid = iph1->msgid;

	/* get IV and HASH(1) if skeyid_a was generated. */
	if (iph1->skeyid_a != NULL) {
		if (new_exchange) {
			if (isakmp_cfg_newiv(iph1, iph2->msgid) == NULL) {
				delph2(iph2);
				goto end;
			}
		}

		/* generate HASH(1) */
		hash = oakley_compute_hash1(iph2->ph1, iph2->msgid, payload);
		if (hash == NULL) {
			delph2(iph2);
			goto end;
		}

		/* initialized total buffer length */
		tlen = hash->l;
		tlen += sizeof(*gen);
	} else {
		/* IKE-SA is not established */
		hash = NULL;

		/* initialized total buffer length */
		tlen = 0;
	}
	if ((flags & ISAKMP_FLAG_A) == 0)
		iph2->flags = (hash == NULL ? 0 : ISAKMP_FLAG_E);
	else
		iph2->flags = (hash == NULL ? 0 : ISAKMP_FLAG_A);

	insph2(iph2);
	bindph12(iph1, iph2);

	tlen += sizeof(*isakmp) + payload->l;

	/* create buffer for isakmp payload */
	iph2->sendbuf = vmalloc(tlen);
	if (iph2->sendbuf == NULL) { 
		plog(LLV_ERROR, LOCATION, NULL,
			"failed to get buffer to send.\n");
		goto err;
	}

	/* create isakmp header */
	isakmp = (struct isakmp *)iph2->sendbuf->v;
	memcpy(&isakmp->i_ck, &iph1->index.i_ck, sizeof(cookie_t));
	memcpy(&isakmp->r_ck, &iph1->index.r_ck, sizeof(cookie_t));
	isakmp->np = hash == NULL ? (np & 0xff) : ISAKMP_NPTYPE_HASH;
	isakmp->v = iph1->version;
	isakmp->etype = ISAKMP_ETYPE_CFG;
	isakmp->flags = iph2->flags;
	memcpy(&isakmp->msgid, &iph2->msgid, sizeof(isakmp->msgid));
	isakmp->len = htonl(tlen);
	p = (char *)(isakmp + 1);

	/* create HASH payload */
	if (hash != NULL) {
		gen = (struct isakmp_gen *)p;
		gen->np = np & 0xff;
		gen->len = htons(sizeof(*gen) + hash->l);
		p += sizeof(*gen);
		memcpy(p, hash->v, hash->l);
		p += hash->l;
	}

	/* add payload */
	memcpy(p, payload->v, payload->l);
	p += payload->l;

#ifdef HAVE_PRINT_ISAKMP_C
	isakmp_printpacket(iph2->sendbuf, iph1->local, iph1->remote, 1);
#endif
	
	plog(LLV_DEBUG, LOCATION, NULL, "MODE_CFG packet to send\n");
	plogdump(LLV_DEBUG, iph2->sendbuf->v, iph2->sendbuf->l);

	/* encoding */
	if (ISSET(isakmp->flags, ISAKMP_FLAG_E)) {
		vchar_t *tmp;

		tmp = oakley_do_encrypt(iph2->ph1, iph2->sendbuf, 
			ics->ivm->ive, ics->ivm->iv);
		VPTRINIT(iph2->sendbuf);
		if (tmp == NULL)
			goto err;
		iph2->sendbuf = tmp;
	}

	/* HDR*, HASH(1), ATTR */
	if (isakmp_send(iph2->ph1, iph2->sendbuf) < 0) {
		VPTRINIT(iph2->sendbuf);
		goto err;
	}

	plog(LLV_DEBUG, LOCATION, NULL,
		"sendto mode config %s.\n", s_isakmp_nptype(np));

	/*
	 * XXX We might need to resend the message...
	 */

	error = 0;
	VPTRINIT(iph2->sendbuf);

err:
	if (iph2->sendbuf != NULL)
		vfree(iph2->sendbuf);

	unbindph12(iph2);
	remph2(iph2);
	delph2(iph2);
end:
	if (hash)
		vfree(hash);
	return error;
}


void 
isakmp_cfg_rmstate(iph1)
	struct ph1handle *iph1;
{
	struct isakmp_cfg_state *state = iph1->mode_cfg;

	if (isakmp_cfg_accounting(iph1, ISAKMP_CFG_LOGOUT) != 0)
		plog(LLV_ERROR, LOCATION, NULL, "Accounting failed\n");

	if (state->flags & ISAKMP_CFG_PORT_ALLOCATED)
		isakmp_cfg_putport(iph1, state->port);	

	/* Delete the IV if it's still there */
	if(iph1->mode_cfg->ivm) {
		oakley_delivm(iph1->mode_cfg->ivm);
		iph1->mode_cfg->ivm = NULL;
	}

	/* Free any allocated splitnet lists */
	if(iph1->mode_cfg->split_include != NULL)
		splitnet_list_free(iph1->mode_cfg->split_include,
			&iph1->mode_cfg->include_count);
	if(iph1->mode_cfg->split_local != NULL)
		splitnet_list_free(iph1->mode_cfg->split_local,
			&iph1->mode_cfg->local_count);

	xauth_rmstate(&state->xauth);

	racoon_free(state);
	iph1->mode_cfg = NULL;

	return;
}

struct isakmp_cfg_state *
isakmp_cfg_mkstate(void) 
{
	struct isakmp_cfg_state *state;

	if ((state = racoon_malloc(sizeof(*state))) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "Cannot allocate memory for mode config state\n");
		return NULL;
	}
	memset(state, 0, sizeof(*state));

	return state;
}

int 
isakmp_cfg_getport(iph1)
	struct ph1handle *iph1;
{
	unsigned int i;
	size_t size = isakmp_cfg_config.pool_size;

	if (iph1->mode_cfg->flags & ISAKMP_CFG_PORT_ALLOCATED)
		return iph1->mode_cfg->port;

	if (isakmp_cfg_config.port_pool == NULL) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "isakmp_cfg_config.port_pool == NULL\n");
		return -1;
	}

	for (i = 0; i < size; i++) {
		if (isakmp_cfg_config.port_pool[i].used == 0)
			break;
	}

	if (i == size) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "No more addresses available\n");
			return -1;
	}

	isakmp_cfg_config.port_pool[i].used = 1;

	plog(LLV_INFO, LOCATION, NULL, "Using port %d\n", i);

	iph1->mode_cfg->flags |= ISAKMP_CFG_PORT_ALLOCATED;
	iph1->mode_cfg->port = i;

	return i;
}

int 
isakmp_cfg_putport(iph1, index)
	struct ph1handle *iph1;
	unsigned int index;
{
	if (isakmp_cfg_config.port_pool == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "isakmp_cfg_config.port_pool == NULL\n");
		return -1;
	}

	if (isakmp_cfg_config.port_pool[index].used == 0) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "Attempt to release an unallocated address (port %d)\n",
		    index);
		return -1;
	}

#ifdef HAVE_LIBPAM
	/* Cleanup PAM status associated with the port */
	if (isakmp_cfg_config.authsource == ISAKMP_CFG_AUTH_PAM)
		privsep_cleanup_pam(index);
#endif
	isakmp_cfg_config.port_pool[index].used = 0;
	iph1->mode_cfg->flags &= ISAKMP_CFG_PORT_ALLOCATED;

	plog(LLV_INFO, LOCATION, NULL, "Released port %d\n", index);

	return 0;
}

#ifdef HAVE_LIBPAM
void
cleanup_pam(port)
	int port;
{
	if (isakmp_cfg_config.port_pool[port].pam != NULL) {
		pam_end(isakmp_cfg_config.port_pool[port].pam, PAM_SUCCESS);
		isakmp_cfg_config.port_pool[port].pam = NULL;
	}

	return;
}
#endif

/* Accounting, only for RADIUS or PAM */
static int
isakmp_cfg_accounting(iph1, inout)
	struct ph1handle *iph1;
	int inout;
{
#ifdef HAVE_LIBPAM
	if (isakmp_cfg_config.accounting == ISAKMP_CFG_ACCT_PAM)
		return privsep_accounting_pam(iph1->mode_cfg->port, 
		    inout);
#endif 
#ifdef HAVE_LIBRADIUS
	if (isakmp_cfg_config.accounting == ISAKMP_CFG_ACCT_RADIUS)
		return isakmp_cfg_accounting_radius(iph1, inout);
#endif
	if (isakmp_cfg_config.accounting == ISAKMP_CFG_ACCT_SYSTEM)
		return privsep_accounting_system(iph1->mode_cfg->port,
			iph1->remote, iph1->mode_cfg->login, inout);
	return 0;
}

#ifdef HAVE_LIBPAM
int 
isakmp_cfg_accounting_pam(port, inout)
	int port;
	int inout;
{
	int error = 0;
	pam_handle_t *pam;

	if (isakmp_cfg_config.port_pool == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "isakmp_cfg_config.port_pool == NULL\n");
		return -1;
	}
	
	pam = isakmp_cfg_config.port_pool[port].pam;
	if (pam == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "pam handle is NULL\n");
		return -1;
	}

	switch (inout) {
	case ISAKMP_CFG_LOGIN:
		error = pam_open_session(pam, 0);
		break;
	case ISAKMP_CFG_LOGOUT:
		error = pam_close_session(pam, 0);
		pam_end(pam, error);
		isakmp_cfg_config.port_pool[port].pam = NULL;
		break;
	default:
		plog(LLV_ERROR, LOCATION, NULL, "Unepected inout\n");
		break;
	}
	
	if (error != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "pam_open_session/pam_close_session failed: %s\n",
		    pam_strerror(pam, error)); 
		return -1;
        }

	return 0;
}
#endif /* HAVE_LIBPAM */

#ifdef HAVE_LIBRADIUS
static int
isakmp_cfg_accounting_radius(iph1, inout)
	struct ph1handle *iph1;
	int inout;
{
	/* For first time use, initialize Radius */
	if (radius_acct_state == NULL) {
		if ((radius_acct_state = rad_acct_open()) == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
			    "Cannot init librradius\n");
			return -1;
		}

		if (rad_config(radius_acct_state, NULL) != 0) {
			 plog(LLV_ERROR, LOCATION, NULL,
			     "Cannot open librarius config file: %s\n",
			     rad_strerror(radius_acct_state));
			  rad_close(radius_acct_state);
			  radius_acct_state = NULL;
			  return -1;
		}
	}

	if (rad_create_request(radius_acct_state, 
	    RAD_ACCOUNTING_REQUEST) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_create_request failed: %s\n",
		    rad_strerror(radius_acct_state));
		return -1;
	}

	if (rad_put_string(radius_acct_state, RAD_USER_NAME, 
	    iph1->mode_cfg->login) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_put_string failed: %s\n",
		    rad_strerror(radius_acct_state));
		return -1;
	}

	switch (inout) {
	case ISAKMP_CFG_LOGIN:
		inout = RAD_START;
		break;
	case ISAKMP_CFG_LOGOUT:
		inout = RAD_STOP;
		break;
	default:
		plog(LLV_ERROR, LOCATION, NULL, "Unepected inout\n");
		break;
	}

	if (rad_put_addr(radius_acct_state, 
	    RAD_FRAMED_IP_ADDRESS, iph1->mode_cfg->addr4) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_put_addr failed: %s\n",
		    rad_strerror(radius_acct_state));
		return -1;
	}

	if (rad_put_addr(radius_acct_state, 
	    RAD_LOGIN_IP_HOST, iph1->mode_cfg->addr4) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_put_addr failed: %s\n",
		    rad_strerror(radius_acct_state));
		return -1;
	}

	if (rad_put_int(radius_acct_state, RAD_ACCT_STATUS_TYPE, inout) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_put_int failed: %s\n",
		    rad_strerror(radius_acct_state));
		return -1;
	}

	if (isakmp_cfg_radius_common(radius_acct_state, 
	    iph1->mode_cfg->port) != 0)
		return -1;

	if (rad_send_request(radius_acct_state) != RAD_ACCOUNTING_RESPONSE) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_send_request failed: %s\n",
		    rad_strerror(radius_acct_state));
		return -1;
	}

	return 0;
}
#endif /* HAVE_LIBRADIUS */

/*
 * Attributes common to all RADIUS requests
 */
#ifdef HAVE_LIBRADIUS
int
isakmp_cfg_radius_common(radius_state, port)
	struct rad_handle *radius_state;
	int port;
{ 
	struct utsname name;
	static struct hostent *host = NULL;
	struct in_addr nas_addr;

	/* 
	 * Find our own IP by resolving our nodename
	 */
	if (host == NULL) {
		if (uname(&name) != 0) {
			plog(LLV_ERROR, LOCATION, NULL,
			    "uname failed: %s\n", strerror(errno));
			return -1;
		}

		if ((host = gethostbyname(name.nodename)) == NULL) {
			plog(LLV_ERROR, LOCATION, NULL,
			    "gethostbyname failed: %s\n", strerror(errno));
			return -1;
		}
	}

	memcpy(&nas_addr, host->h_addr, sizeof(nas_addr));
	if (rad_put_addr(radius_state, RAD_NAS_IP_ADDRESS, nas_addr) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_put_addr failed: %s\n",
		    rad_strerror(radius_state));
		return -1;
	}

	if (rad_put_int(radius_state, RAD_NAS_PORT, port) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_put_int failed: %s\n",
		    rad_strerror(radius_state));
		return -1;
	}

	if (rad_put_int(radius_state, RAD_NAS_PORT_TYPE, RAD_VIRTUAL) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_put_int failed: %s\n",
		    rad_strerror(radius_state));
		return -1;
	}

	if (rad_put_int(radius_state, RAD_SERVICE_TYPE, RAD_FRAMED) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		    "rad_put_int failed: %s\n",
		    rad_strerror(radius_state));
		return -1;
	}
	
	return 0;
}
#endif

#ifndef ANDROID_PATCHED

/*
	Logs the user into the utmp system files.
*/

int
isakmp_cfg_accounting_system(port, raddr, usr, inout)
	int port;
	struct sockaddr *raddr;
	char *usr;
	int inout;
{
	int error = 0;
	struct utmp ut;
	char term[UT_LINESIZE];
	char addr[NI_MAXHOST];
	
	if (usr == NULL || usr[0]=='\0') {
		plog(LLV_ERROR, LOCATION, NULL,
			"system accounting : no login found\n");
		return -1;
	}

	sprintf(term, TERMSPEC, port);

	switch (inout) {
	case ISAKMP_CFG_LOGIN:
		strncpy(ut.ut_name, usr, UT_NAMESIZE);
		ut.ut_name[UT_NAMESIZE - 1] = '\0';

		strncpy(ut.ut_line, term, UT_LINESIZE);
		ut.ut_line[UT_LINESIZE - 1] = '\0';

		GETNAMEINFO_NULL(raddr, addr);
		strncpy(ut.ut_host, addr, UT_HOSTSIZE);
		ut.ut_host[UT_HOSTSIZE - 1] = '\0';

		ut.ut_time = time(NULL);
 
		plog(LLV_INFO, LOCATION, NULL,
			"Accounting : '%s' logging on '%s' from %s.\n",
			ut.ut_name, ut.ut_line, ut.ut_host);

		login(&ut);

		break;
	case ISAKMP_CFG_LOGOUT:	

		plog(LLV_INFO, LOCATION, NULL,
			"Accounting : '%s' unlogging from '%s'.\n",
			usr, term);

		logout(term);

		break;
	default:
		plog(LLV_ERROR, LOCATION, NULL, "Unepected inout\n");
		break;
	}

	return 0;
}
	
#endif

int 
isakmp_cfg_getconfig(iph1)
	struct ph1handle *iph1;
{
	vchar_t *buffer;
	struct isakmp_pl_attr *attrpl;
	struct isakmp_data *attr;
	size_t len;
	int error;
	int attrcount;
	int i;
	int attrlist[] = {
		INTERNAL_IP4_ADDRESS,
		INTERNAL_IP4_NETMASK,
		INTERNAL_IP4_DNS,
		INTERNAL_IP4_NBNS,
		UNITY_BANNER,
		UNITY_DEF_DOMAIN,
		UNITY_SPLITDNS_NAME,
		UNITY_SPLIT_INCLUDE,
		UNITY_LOCAL_LAN,
		APPLICATION_VERSION,
	};

	attrcount = sizeof(attrlist) / sizeof(*attrlist);
	len = sizeof(*attrpl) + sizeof(*attr) * attrcount;
	    
	if ((buffer = vmalloc(len)) == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot allocate memory\n");
		return -1;
	}

	attrpl = (struct isakmp_pl_attr *)buffer->v;
	attrpl->h.len = htons(len);
	attrpl->type = ISAKMP_CFG_REQUEST;
	attrpl->id = htons((u_int16_t)(eay_random() & 0xffff));

	attr = (struct isakmp_data *)(attrpl + 1);

	for (i = 0; i < attrcount; i++) {
		attr->type = htons(attrlist[i]);
		attr->lorv = htons(0);
		attr++;
	}

	plog(LLV_DEBUG, LOCATION, NULL, 
		    "Sending MODE_CFG REQUEST\n");

	error = isakmp_cfg_send(iph1, buffer,
	    ISAKMP_NPTYPE_ATTR, ISAKMP_FLAG_E, 1);

	vfree(buffer);

	return error;
}

static void
isakmp_cfg_getaddr4(attr, ip)
	struct isakmp_data *attr;
	struct in_addr *ip;
{
	size_t alen = ntohs(attr->lorv);
	in_addr_t *addr;

	if (alen != sizeof(*ip)) {
		plog(LLV_ERROR, LOCATION, NULL, "Bad IPv4 address len\n");
		return;
	}

	addr = (in_addr_t *)(attr + 1);
	ip->s_addr = *addr;

	return;
}

static void
isakmp_cfg_appendaddr4(attr, ip, num, max)
	struct isakmp_data *attr;
	struct in_addr *ip;
	int *num;
	int max;
{
	size_t alen = ntohs(attr->lorv);
	in_addr_t *addr;

	if (alen != sizeof(*ip)) {
		plog(LLV_ERROR, LOCATION, NULL, "Bad IPv4 address len\n");
		return;
	}
	if (*num == max) {
		plog(LLV_ERROR, LOCATION, NULL, "Too many addresses given\n");
		return;
	}

	addr = (in_addr_t *)(attr + 1);
	ip->s_addr = *addr;
	(*num)++;

	return;
}

static void
isakmp_cfg_getstring(attr, str)
	struct isakmp_data *attr;
	char *str;
{
	size_t alen = ntohs(attr->lorv);
	char *src;
	src = (char *)(attr + 1);

	memcpy(str, src, (alen > MAXPATHLEN ? MAXPATHLEN : alen));

	return;
}

#define IP_MAX 40

void
isakmp_cfg_iplist_to_str(dest, count, addr, withmask)
	char *dest;
	int count;
	void *addr;
	int withmask;
{
	int i;
	int p;
	int l;
	struct unity_network tmp;
	for(i = 0, p = 0; i < count; i++) {
		if(withmask == 1)
			l = sizeof(struct unity_network);
		else
			l = sizeof(struct in_addr);
		memcpy(&tmp, addr, l);
#if defined(ANDROID_CHANGES)
		addr = ((uint8_t*) addr) + l;
#else
		addr += l;
#endif
		if((uint32_t)tmp.addr4.s_addr == 0)
			break;
	
		inet_ntop(AF_INET, &tmp.addr4, dest + p, IP_MAX);
		p += strlen(dest + p);
		if(withmask == 1) {
			dest[p] = '/';
			p++;
			inet_ntop(AF_INET, &tmp.mask4, dest + p, IP_MAX);
			p += strlen(dest + p);
		}
		dest[p] = ' ';
		p++;
	}
	if(p > 0)
		dest[p-1] = '\0';
	else
		dest[0] = '\0';
}

int
isakmp_cfg_setenv(iph1, envp, envc)
	struct ph1handle *iph1; 
	char ***envp;
	int *envc;
{
	char addrstr[IP_MAX];
	char addrlist[IP_MAX * MAXNS + MAXNS];
	char *splitlist = addrlist;
	char *splitlist_cidr;
	char defdom[MAXPATHLEN + 1];
	int cidr, tmp;
	char cidrstr[4];
	int i, p;
	int test;

	plog(LLV_DEBUG, LOCATION, NULL, "Starting a script.\n");

	/* 
	 * Internal IPv4 address, either if 
	 * we are a client or a server.
	 */
	if ((iph1->mode_cfg->flags & ISAKMP_CFG_GOT_ADDR4) ||
#ifdef HAVE_LIBLDAP
	    (iph1->mode_cfg->flags & ISAKMP_CFG_ADDR4_EXTERN) ||
#endif
#ifdef HAVE_LIBRADIUS
	    (iph1->mode_cfg->flags & ISAKMP_CFG_ADDR4_EXTERN) ||
#endif
	    (iph1->mode_cfg->flags & ISAKMP_CFG_ADDR4_LOCAL)) {
		inet_ntop(AF_INET, &iph1->mode_cfg->addr4, 
		    addrstr, IP_MAX);
	} else
		addrstr[0] = '\0';

	if (script_env_append(envp, envc, "INTERNAL_ADDR4", addrstr) != 0) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot set INTERNAL_ADDR4\n");
		return -1;
	}

	if (iph1->mode_cfg->xauth.authdata.generic.usr != NULL) {
		if (script_env_append(envp, envc, "XAUTH_USER", 
		    iph1->mode_cfg->xauth.authdata.generic.usr) != 0) {
			plog(LLV_ERROR, LOCATION, NULL, 
			    "Cannot set XAUTH_USER\n");
			return -1;
		}
	}

	/* Internal IPv4 mask */
	if (iph1->mode_cfg->flags & ISAKMP_CFG_GOT_MASK4) 
		inet_ntop(AF_INET, &iph1->mode_cfg->mask4, 
		    addrstr, IP_MAX);
	else
		addrstr[0] = '\0';

	/* 	
	 * During several releases, documentation adverised INTERNAL_NETMASK4
	 * while code was using INTERNAL_MASK4. We now do both.
	 */

	if (script_env_append(envp, envc, "INTERNAL_MASK4", addrstr) != 0) { 
		plog(LLV_ERROR, LOCATION, NULL, "Cannot set INTERNAL_MASK4\n");
		return -1;
	}

	if (script_env_append(envp, envc, "INTERNAL_NETMASK4", addrstr) != 0) { 
		plog(LLV_ERROR, LOCATION, NULL, 
		    "Cannot set INTERNAL_NETMASK4\n");
		return -1;
	}

	tmp = ntohl(iph1->mode_cfg->mask4.s_addr);
	for (cidr = 0; tmp != 0; cidr++)
		tmp <<= 1;
	snprintf(cidrstr, 3, "%d", cidr);

	if (script_env_append(envp, envc, "INTERNAL_CIDR4", cidrstr) != 0) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot set INTERNAL_CIDR4\n");
		return -1;
	}

	/* Internal IPv4 DNS */
	if (iph1->mode_cfg->flags & ISAKMP_CFG_GOT_DNS4) {
		/* First Internal IPv4 DNS (for compatibilty with older code */
		inet_ntop(AF_INET, &iph1->mode_cfg->dns4[0], 
		    addrstr, IP_MAX);

		/* Internal IPv4 DNS - all */
		isakmp_cfg_iplist_to_str(addrlist, iph1->mode_cfg->dns4_index,
			(void *)iph1->mode_cfg->dns4, 0);
	} else {
		addrstr[0] = '\0';
		addrlist[0] = '\0';
	}

	if (script_env_append(envp, envc, "INTERNAL_DNS4", addrstr) != 0) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot set INTERNAL_DNS4\n");
		return -1;
	}
	if (script_env_append(envp, envc, "INTERNAL_DNS4_LIST", addrlist) != 0) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "Cannot set INTERNAL_DNS4_LIST\n");
		return -1;
	}
	
	/* Internal IPv4 WINS */
	if (iph1->mode_cfg->flags & ISAKMP_CFG_GOT_WINS4) {
		/* 
		 * First Internal IPv4 WINS 
		 * (for compatibilty with older code 
		 */
		inet_ntop(AF_INET, &iph1->mode_cfg->wins4[0], 
		    addrstr, IP_MAX);

		/* Internal IPv4 WINS - all */
		isakmp_cfg_iplist_to_str(addrlist, iph1->mode_cfg->wins4_index,
			(void *)iph1->mode_cfg->wins4, 0);
	} else {
		addrstr[0] = '\0';
		addrlist[0] = '\0';
	}

	if (script_env_append(envp, envc, "INTERNAL_WINS4", addrstr) != 0) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "Cannot set INTERNAL_WINS4\n");
		return -1;
	}
	if (script_env_append(envp, envc, 
	    "INTERNAL_WINS4_LIST", addrlist) != 0) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "Cannot set INTERNAL_WINS4_LIST\n");
		return -1;
	}

	/* Deault domain */
	if(iph1->mode_cfg->flags & ISAKMP_CFG_GOT_DEFAULT_DOMAIN) 
		strncpy(defdom, 
		    iph1->mode_cfg->default_domain, 
		    MAXPATHLEN + 1);
	else
		defdom[0] = '\0';
	
	if (script_env_append(envp, envc, "DEFAULT_DOMAIN", defdom) != 0) { 
		plog(LLV_ERROR, LOCATION, NULL, 
		    "Cannot set DEFAULT_DOMAIN\n");
		return -1;
	}

	/* Split networks */
	if (iph1->mode_cfg->flags & ISAKMP_CFG_GOT_SPLIT_INCLUDE) {
		splitlist =
		    splitnet_list_2str(iph1->mode_cfg->split_include, NETMASK);
		splitlist_cidr =
		    splitnet_list_2str(iph1->mode_cfg->split_include, CIDR);
	} else {
		splitlist = addrlist;
		splitlist_cidr = addrlist;
		addrlist[0] = '\0';
	}

	if (script_env_append(envp, envc, "SPLIT_INCLUDE", splitlist) != 0) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot set SPLIT_INCLUDE\n");
		return -1;
	}
	if (script_env_append(envp, envc,
	    "SPLIT_INCLUDE_CIDR", splitlist_cidr) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		     "Cannot set SPLIT_INCLUDE_CIDR\n");
		return -1;
	}
	if (splitlist != addrlist)
		racoon_free(splitlist);
	if (splitlist_cidr != addrlist)
		racoon_free(splitlist_cidr);

	if (iph1->mode_cfg->flags & ISAKMP_CFG_GOT_SPLIT_LOCAL) {
		splitlist =
		    splitnet_list_2str(iph1->mode_cfg->split_local, NETMASK);
		splitlist_cidr =
		    splitnet_list_2str(iph1->mode_cfg->split_local, CIDR);
	} else {
		splitlist = addrlist;
		splitlist_cidr = addrlist;
		addrlist[0] = '\0';
	}

	if (script_env_append(envp, envc, "SPLIT_LOCAL", splitlist) != 0) {
		plog(LLV_ERROR, LOCATION, NULL, "Cannot set SPLIT_LOCAL\n");
		return -1;
	}
	if (script_env_append(envp, envc,
	    "SPLIT_LOCAL_CIDR", splitlist_cidr) != 0) {
		plog(LLV_ERROR, LOCATION, NULL,
		     "Cannot set SPLIT_LOCAL_CIDR\n");
		return -1;
	}
	if (splitlist != addrlist)
		racoon_free(splitlist);
	if (splitlist_cidr != addrlist)
		racoon_free(splitlist_cidr);

	return 0;
}

int
isakmp_cfg_resize_pool(size)
	int size;
{
	struct isakmp_cfg_port *new_pool;
	size_t len;
	int i;

	if (size == isakmp_cfg_config.pool_size)
		return 0;

	plog(LLV_INFO, LOCATION, NULL,
	    "Resize address pool from %zu to %d\n",
	    isakmp_cfg_config.pool_size, size);

	/* If a pool already exists, check if we can shrink it */
	if ((isakmp_cfg_config.port_pool != NULL) &&
	    (size < isakmp_cfg_config.pool_size)) {
		for (i = isakmp_cfg_config.pool_size-1; i >= size; --i) {
			if (isakmp_cfg_config.port_pool[i].used) {
				plog(LLV_ERROR, LOCATION, NULL, 
				    "resize pool from %zu to %d impossible "
				    "port %d is in use\n", 
				    isakmp_cfg_config.pool_size, size, i);
				size = i;
				break;
			}	
		}
	}

	len = size * sizeof(*isakmp_cfg_config.port_pool);
	new_pool = racoon_realloc(isakmp_cfg_config.port_pool, len);
	if (new_pool == NULL) {
		plog(LLV_ERROR, LOCATION, NULL, 
		    "resize pool from %zu to %d impossible: %s",
		    isakmp_cfg_config.pool_size, size, strerror(errno));
		return -1;
	}

	/* If size increase, intialize correctly the new records */
	if (size > isakmp_cfg_config.pool_size) {
		size_t unit;
		size_t old_size;

		unit =  sizeof(*isakmp_cfg_config.port_pool);
		old_size = isakmp_cfg_config.pool_size;

		bzero((char *)new_pool + (old_size * unit), 
		    (size - old_size) * unit);
	}

	isakmp_cfg_config.port_pool = new_pool;
	isakmp_cfg_config.pool_size = size;

	return 0;
}

int
isakmp_cfg_init(cold) 
	int cold;
{
	int i;
	int error;

	isakmp_cfg_config.network4 = (in_addr_t)0x00000000;
	isakmp_cfg_config.netmask4 = (in_addr_t)0x00000000;
	for (i = 0; i < MAXNS; i++)
		isakmp_cfg_config.dns4[i] = (in_addr_t)0x00000000;
	isakmp_cfg_config.dns4_index = 0;
	for (i = 0; i < MAXWINS; i++)
		isakmp_cfg_config.nbns4[i] = (in_addr_t)0x00000000;
	isakmp_cfg_config.nbns4_index = 0;
	if (cold == ISAKMP_CFG_INIT_COLD)
		isakmp_cfg_config.port_pool = NULL;
	isakmp_cfg_config.authsource = ISAKMP_CFG_AUTH_SYSTEM;
	isakmp_cfg_config.groupsource = ISAKMP_CFG_GROUP_SYSTEM;
	if (cold == ISAKMP_CFG_INIT_COLD) {
		if (isakmp_cfg_config.grouplist != NULL) {
			for (i = 0; i < isakmp_cfg_config.groupcount; i++)
				racoon_free(isakmp_cfg_config.grouplist[i]);
			racoon_free(isakmp_cfg_config.grouplist);
		}
	}
	isakmp_cfg_config.grouplist = NULL;
	isakmp_cfg_config.groupcount = 0;
	isakmp_cfg_config.confsource = ISAKMP_CFG_CONF_LOCAL;
	isakmp_cfg_config.accounting = ISAKMP_CFG_ACCT_NONE;
	if (cold == ISAKMP_CFG_INIT_COLD)
		isakmp_cfg_config.pool_size = 0;
	isakmp_cfg_config.auth_throttle = THROTTLE_PENALTY;
	strlcpy(isakmp_cfg_config.default_domain, ISAKMP_CFG_DEFAULT_DOMAIN,
	    MAXPATHLEN);
	strlcpy(isakmp_cfg_config.motd, ISAKMP_CFG_MOTD, MAXPATHLEN);

	if (cold != ISAKMP_CFG_INIT_COLD )
		if (isakmp_cfg_config.splitnet_list != NULL)
			splitnet_list_free(isakmp_cfg_config.splitnet_list,
				&isakmp_cfg_config.splitnet_count);
	isakmp_cfg_config.splitnet_list = NULL;
	isakmp_cfg_config.splitnet_count = 0;
	isakmp_cfg_config.splitnet_type = 0;

	isakmp_cfg_config.pfs_group = 0;
	isakmp_cfg_config.save_passwd = 0;

	if (cold != ISAKMP_CFG_INIT_COLD )
		if (isakmp_cfg_config.splitdns_list != NULL)
			racoon_free(isakmp_cfg_config.splitdns_list);
	isakmp_cfg_config.splitdns_list = NULL;
	isakmp_cfg_config.splitdns_len = 0;

#if 0
	if (cold == ISAKMP_CFG_INIT_COLD) {
		if ((error = isakmp_cfg_resize_pool(ISAKMP_CFG_MAX_CNX)) != 0)
			return error;
	}
#endif

	return 0;
}