root/crypto/dist/ipsec-tools/src/racoon/isakmp_cfg.c
/*      $NetBSD: isakmp_cfg.c,v 1.31 2025/03/08 16:39:08 christos 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>

#include <utmpx.h>
#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 vchar_t *isakmp_cfg_addrnet4(struct ph1handle *, 
                                 struct isakmp_data *, in_addr_t *, 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 *);
static 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(struct ph1handle *iph1, vchar_t *msg)
{
        struct isakmp *packet;
        struct isakmp_gen *ph;
        size_t 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(struct ph1handle *iph1, uint32_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;

        case ISAKMP_CFG_REPLY:
                return isakmp_cfg_reply(iph1, attrpl);

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

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

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

int
isakmp_cfg_reply(struct ph1handle *iph1, struct isakmp_pl_attr *attrpl)
{
        struct isakmp_data *attr;
        size_t tlen;
        size_t alen;
        char *npp;
        int type;
        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 (iph1->approval->authmethod) {
                case OAKLEY_ATTR_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: 
                case OAKLEY_ATTR_AUTH_METHOD_RSASIG:
                        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_phase1(iph1, EVT_PHASE1_MODE_CFG, buf);
                        vfree(buf);
                }
        }
#endif

        return 0;
}

int
isakmp_cfg_request(struct ph1handle *iph1, struct isakmp_pl_attr *attrpl)
{
        struct isakmp_data *attr;
        size_t 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 (iph1->approval->authmethod) {
                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: 
                case OAKLEY_ATTR_AUTH_METHOD_RSASIG:
                        script_hook(iph1, SCRIPT_PHASE1_UP);
                        break;
                default:
                        break;
                }
        }
        
end:
        vfree(payload);

        return error;
}

int
isakmp_cfg_set(struct ph1handle *iph1, struct isakmp_pl_attr *attrpl)
{
        struct isakmp_data *attr;
        size_t 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 ||
                    iph1->status == PHASE1ST_DYING)
                        isakmp_info_send_d1(iph1);
                remph1(iph1);
                delph1(iph1);
                iph1 = NULL;
        }

        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(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(struct ph1handle *iph1, struct isakmp_data *attr)
{
        int type;
        int confsource;

        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);

        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);

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

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

        case INTERNAL_IP4_SUBNET:
                if(isakmp_cfg_config.splitnet_count > 0){
                        return isakmp_cfg_addrnet4(iph1, attr,
                                                    &isakmp_cfg_config.splitnet_list->network.addr4.s_addr,
                                                    &isakmp_cfg_config.splitnet_list->network.mask4.s_addr);
                }else{
                        plog(LLV_INFO, LOCATION, NULL,
                             "%s requested but no splitnet in configuration\n",
                             s_isakmp_cfg_type(type));
                }
                break;

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

#if 0
static vchar_t *
isakmp_cfg_void(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

/*ARGSUSED*/
vchar_t *
isakmp_cfg_copy(struct ph1handle *iph1 __unused, 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;
}

/*ARGSUSED*/
vchar_t *
isakmp_cfg_short(struct ph1handle *iph1 __unused, 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;
}

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

        if (!len)
                return NULL;

        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(struct ph1handle *iph1, struct isakmp_data *attr,
    const char *string)
{
        size_t len = strlen(string);
        return isakmp_cfg_varlen(iph1, attr, string, len);
}

/*ARGSUSED*/
static vchar_t *
isakmp_cfg_addr4(struct ph1handle *iph1 __unused, 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;
}

/*ARGSUSED*/
static vchar_t *
isakmp_cfg_addrnet4(struct ph1handle *iph1 __unused, struct isakmp_data *attr,
    in_addr_t *addr, in_addr_t *mask)
{
        vchar_t *buffer;
        struct isakmp_data *new;
        size_t len;
        in_addr_t netbuff[2];

        len = sizeof(netbuff);
        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);
        netbuff[0]=*addr;
        netbuff[1]=*mask;
        memcpy(new + 1, netbuff, len);
        
        return buffer;
}


/*ARGSUSED*/
static vchar_t *
isakmp_cfg_addr4_list(struct ph1handle *iph1 __unused, 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(struct ph1handle *iph1, uint32_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(struct ph1handle *iph1, vchar_t *payload, uint32_t np,
    int flags, int new_exchange)
{
        struct ph2handle *iph2 = NULL;
        vchar_t *hash = NULL;
        struct isakmp *isakmp;
        struct isakmp_gen *gen;
        char *p;
        size_t 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;
        }

        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(iph1, 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);

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


void 
isakmp_cfg_rmstate(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(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(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(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(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(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(struct ph1handle *iph1, int inout)
{
        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(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

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

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

        memset(&ut, 0, sizeof ut);
        gettimeofday((struct timeval *)&ut.ut_tv, NULL);
        snprintf(ut.ut_id, sizeof ut.ut_id, TERMSPEC, port);

        switch (inout) {
        case ISAKMP_CFG_LOGIN:
                ut.ut_type = USER_PROCESS;
                strncpy(ut.ut_user, usr, sizeof ut.ut_user);

                GETNAMEINFO_NULL(raddr, addr);
                strncpy(ut.ut_host, addr, sizeof ut.ut_host);

                plog(LLV_INFO, LOCATION, NULL,
                        "Accounting : '%s' logging on '%s' from %s.\n",
                        ut.ut_user, ut.ut_id, addr);

                pututxline(&ut);

                break;
        case ISAKMP_CFG_LOGOUT: 
                ut.ut_type = DEAD_PROCESS;

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

                pututxline(&ut);

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

        return 0;
}
        
int 
isakmp_cfg_getconfig(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((uint16_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(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(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(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(char *dest, int count, void *addr, int withmask)
{
        int i;
        size_t 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);
                addr = (char *)addr + l;
                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(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];

        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;
        }

        /* Default 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(int size)
{
        struct isakmp_cfg_port *new_pool;
        size_t len;
        size_t 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 %zu 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(int cold)
{
        int i;

        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
        int error;
        if (cold == ISAKMP_CFG_INIT_COLD) {
                if ((error = isakmp_cfg_resize_pool(ISAKMP_CFG_MAX_CNX)) != 0)
                        return error;
        }
#endif

        return 0;
}