blob: ba91f486b08d1e2d734f6b78c3039cb1db0b5729 [file] [log] [blame]
/* $NetBSD: ipsec_doi.c,v 1.46 2010/12/14 17:57:31 tteras Exp $ */
/* Id: ipsec_doi.c,v 1.55 2006/08/17 09:20:41 vanhu Exp */
/*
* Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "config.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include PATH_IPSEC_H
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netdb.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 "var.h"
#include "vmbuf.h"
#include "misc.h"
#include "plog.h"
#include "debug.h"
#include "cfparse_proto.h"
#include "isakmp_var.h"
#include "isakmp.h"
#include "ipsec_doi.h"
#include "oakley.h"
#include "remoteconf.h"
#include "localconf.h"
#include "sockmisc.h"
#include "handler.h"
#include "policy.h"
#include "algorithm.h"
#include "sainfo.h"
#include "proposal.h"
#include "crypto_openssl.h"
#include "strnames.h"
#include "gcmalloc.h"
#ifdef ENABLE_NATT
#include "nattraversal.h"
#endif
#ifdef HAVE_GSSAPI
#include <iconv.h>
#include "gssapi.h"
#ifdef HAVE_ICONV_2ND_CONST
#define __iconv_const const
#else
#define __iconv_const
#endif
#endif
static vchar_t *get_ph1approval __P((struct ph1handle *, u_int32_t, u_int32_t,
struct prop_pair **));
static int get_ph1approvalx __P((struct remoteconf *, void *));
static int t2isakmpsa __P((struct isakmp_pl_t *, struct isakmpsa *, u_int32_t));
static int cmp_aproppair_i __P((struct prop_pair *, struct prop_pair *));
static struct prop_pair *get_ph2approval __P((struct ph2handle *,
struct prop_pair **));
static struct prop_pair *get_ph2approvalx __P((struct ph2handle *,
struct prop_pair *));
static void free_proppair0 __P((struct prop_pair *));
static struct prop_pair ** get_proppair_and_doi_sit __P((vchar_t *, int,
u_int32_t *, u_int32_t *));
static int get_transform
__P((struct isakmp_pl_p *, struct prop_pair **, int *));
static u_int32_t ipsecdoi_set_ld __P((vchar_t *));
static int check_doi __P((u_int32_t));
static int check_situation __P((u_int32_t));
static int check_prot_main __P((int));
static int check_prot_quick __P((int));
static int (*check_protocol[]) __P((int)) = {
check_prot_main, /* IPSECDOI_TYPE_PH1 */
check_prot_quick, /* IPSECDOI_TYPE_PH2 */
};
static int check_spi_size __P((int, int));
static int check_trns_isakmp __P((int));
static int check_trns_ah __P((int));
static int check_trns_esp __P((int));
static int check_trns_ipcomp __P((int));
static int (*check_transform[]) __P((int)) = {
0,
check_trns_isakmp, /* IPSECDOI_PROTO_ISAKMP */
check_trns_ah, /* IPSECDOI_PROTO_IPSEC_AH */
check_trns_esp, /* IPSECDOI_PROTO_IPSEC_ESP */
check_trns_ipcomp, /* IPSECDOI_PROTO_IPCOMP */
};
static int check_attr_isakmp __P((struct isakmp_pl_t *));
static int check_attr_ah __P((struct isakmp_pl_t *));
static int check_attr_esp __P((struct isakmp_pl_t *));
static int check_attr_ipsec __P((int, struct isakmp_pl_t *));
static int check_attr_ipcomp __P((struct isakmp_pl_t *));
static int (*check_attributes[]) __P((struct isakmp_pl_t *)) = {
0,
check_attr_isakmp, /* IPSECDOI_PROTO_ISAKMP */
check_attr_ah, /* IPSECDOI_PROTO_IPSEC_AH */
check_attr_esp, /* IPSECDOI_PROTO_IPSEC_ESP */
check_attr_ipcomp, /* IPSECDOI_PROTO_IPCOMP */
};
static int setph1prop __P((struct isakmpsa *, caddr_t));
static int setph1trns __P((struct isakmpsa *, caddr_t));
static int setph1attr __P((struct isakmpsa *, caddr_t));
static vchar_t *setph2proposal0 __P((const struct ph2handle *,
const struct saprop *, const struct saproto *));
struct ph1approvalx_ctx {
struct prop_pair *p;
struct isakmpsa *sa;
};
/*%%%*/
/*
* check phase 1 SA payload.
* make new SA payload to be replyed not including general header.
* the pointer to one of isakmpsa in proposal is set into iph1->approval.
* OUT:
* positive: the pointer to new buffer of SA payload.
* network byte order.
* NULL : error occurd.
*/
int
ipsecdoi_checkph1proposal(sa, iph1)
vchar_t *sa;
struct ph1handle *iph1;
{
vchar_t *newsa; /* new SA payload approved. */
struct prop_pair **pair;
u_int32_t doitype, sittype;
/* get proposal pair */
pair = get_proppair_and_doi_sit(sa, IPSECDOI_TYPE_PH1,
&doitype, &sittype);
if (pair == NULL)
return -1;
/* check and get one SA for use */
newsa = get_ph1approval(iph1, doitype, sittype, pair);
free_proppair(pair);
if (newsa == NULL)
return -1;
iph1->sa_ret = newsa;
return 0;
}
static void
print_ph1proposal(pair, s)
struct prop_pair *pair;
struct isakmpsa *s;
{
struct isakmp_pl_p *prop = pair->prop;
struct isakmp_pl_t *trns = pair->trns;
plog(LLV_DEBUG, LOCATION, NULL,
"prop#=%d, prot-id=%s, spi-size=%d, #trns=%d\n",
prop->p_no, s_ipsecdoi_proto(prop->proto_id),
prop->spi_size, prop->num_t);
plog(LLV_DEBUG, LOCATION, NULL,
"trns#=%d, trns-id=%s\n",
trns->t_no, s_ipsecdoi_trns(prop->proto_id, trns->t_id));
plog(LLV_DEBUG, LOCATION, NULL,
" lifetime = %ld\n", (long) s->lifetime);
plog(LLV_DEBUG, LOCATION, NULL,
" lifebyte = %zu\n", s->lifebyte);
plog(LLV_DEBUG, LOCATION, NULL,
" enctype = %s\n",
s_oakley_attr_v(OAKLEY_ATTR_ENC_ALG, s->enctype));
plog(LLV_DEBUG, LOCATION, NULL,
" encklen = %d\n", s->encklen);
plog(LLV_DEBUG, LOCATION, NULL,
" hashtype = %s\n",
s_oakley_attr_v(OAKLEY_ATTR_HASH_ALG, s->hashtype));
plog(LLV_DEBUG, LOCATION, NULL,
" authmethod = %s\n",
s_oakley_attr_v(OAKLEY_ATTR_AUTH_METHOD, s->authmethod));
plog(LLV_DEBUG, LOCATION, NULL,
" dh_group = %s\n",
s_oakley_attr_v(OAKLEY_ATTR_GRP_DESC, s->dh_group));
}
/*
* acceptable check for remote configuration.
* return a new SA payload to be reply to peer.
*/
static vchar_t *
get_ph1approval(iph1, doitype, sittype, pair)
struct ph1handle *iph1;
u_int32_t doitype, sittype;
struct prop_pair **pair;
{
vchar_t *newsa;
struct ph1approvalx_ctx ctx;
struct prop_pair *s, *p;
struct rmconfselector rmsel;
struct isakmpsa *sa;
int i;
memset(&rmsel, 0, sizeof(rmsel));
rmsel.remote = iph1->remote;
if (iph1->approval) {
delisakmpsa(iph1->approval);
iph1->approval = NULL;
}
for (i = 0; i < MAXPROPPAIRLEN; i++) {
if (pair[i] == NULL)
continue;
for (s = pair[i]; s; s = s->next) {
/* compare proposal and select one */
for (p = s; p; p = p->tnext) {
struct isakmp_pl_p *prop = p->prop;
sa = newisakmpsa();
ctx.p = p;
ctx.sa = sa;
if (t2isakmpsa(p->trns, sa,
iph1->vendorid_mask) < 0)
continue;
print_ph1proposal(p, sa);
if (iph1->rmconf != NULL) {
if (get_ph1approvalx(iph1->rmconf, &ctx))
goto found;
} else {
if (enumrmconf(&rmsel, get_ph1approvalx, &ctx))
goto found;
}
delisakmpsa(sa);
}
}
}
plog(LLV_ERROR, LOCATION, NULL, "no suitable proposal found.\n");
return NULL;
found:
sa = ctx.sa;
plog(LLV_DEBUG, LOCATION, NULL, "an acceptable proposal found.\n");
/* check DH group settings */
if (sa->dhgrp) {
if (sa->dhgrp->prime && sa->dhgrp->gen1) {
/* it's ok */
goto saok;
}
plog(LLV_WARNING, LOCATION, NULL,
"invalid DH parameter found, use default.\n");
oakley_dhgrp_free(sa->dhgrp);
sa->dhgrp=NULL;
}
if (oakley_setdhgroup(sa->dh_group, &sa->dhgrp) == -1) {
sa->dhgrp = NULL;
delisakmpsa(sa);
return NULL;
}
saok:
#ifdef HAVE_GSSAPI
if (sa->gssid != NULL)
plog(LLV_DEBUG, LOCATION, NULL, "gss id in new sa '%.*s'\n",
(int)sa->gssid->l, sa->gssid->v);
if (iph1->side == INITIATOR) {
if (iph1->rmconf->proposal->gssid != NULL)
iph1->gi_i = vdup(iph1->rmconf->proposal->gssid);
if (sa->gssid != NULL)
iph1->gi_r = vdup(sa->gssid);
} else {
if (sa->gssid != NULL) {
iph1->gi_r = vdup(sa->gssid);
iph1->gi_i = gssapi_get_id(iph1);
}
}
if (iph1->gi_i != NULL)
plog(LLV_DEBUG, LOCATION, NULL, "GIi is %.*s\n",
(int)iph1->gi_i->l, iph1->gi_i->v);
if (iph1->gi_r != NULL)
plog(LLV_DEBUG, LOCATION, NULL, "GIr is %.*s\n",
(int)iph1->gi_r->l, iph1->gi_r->v);
#endif
plog(LLV_DEBUG, LOCATION, NULL, "agreed on %s auth.\n",
s_oakley_attr_method(sa->authmethod));
newsa = get_sabyproppair(doitype, sittype, p);
if (newsa == NULL)
delisakmpsa(sa);
else
iph1->approval = sa;
return newsa;
}
/*
* compare peer's single proposal and all of my proposal.
* and select one if suiatable.
*/
static int
get_ph1approvalx(rmconf, ctx)
struct remoteconf *rmconf;
void *ctx;
{
struct ph1approvalx_ctx *pctx = (struct ph1approvalx_ctx *) ctx;
struct isakmpsa *sa;
/* do the hard work */
sa = checkisakmpsa(rmconf->pcheck_level, pctx->sa, rmconf->proposal);
if (sa == NULL)
return 0;
/* duplicate and modify the found SA to match proposal */
sa = dupisakmpsa(sa);
switch (rmconf->pcheck_level) {
case PROP_CHECK_OBEY:
sa->lifetime = pctx->sa->lifetime;
sa->lifebyte = pctx->sa->lifebyte;
sa->encklen = pctx->sa->encklen;
break;
case PROP_CHECK_CLAIM:
case PROP_CHECK_STRICT:
if (pctx->sa->lifetime < sa->lifetime)
sa->lifetime = pctx->sa->lifetime;
if (pctx->sa->lifebyte < sa->lifebyte)
sa->lifebyte = pctx->sa->lifebyte;
if (pctx->sa->encklen > sa->encklen)
sa->encklen = pctx->sa->encklen;
break;
default:
break;
}
/* replace the proposal with our approval sa */
delisakmpsa(pctx->sa);
pctx->sa = sa;
return 1;
}
/*
* get ISAKMP data attributes
*/
static int
t2isakmpsa(trns, sa, vendorid_mask)
struct isakmp_pl_t *trns;
struct isakmpsa *sa;
u_int32_t vendorid_mask;
{
struct isakmp_data *d, *prev;
int flag, type;
int error = -1;
int life_t;
int keylen = 0;
vchar_t *val = NULL;
int len, tlen;
u_char *p;
tlen = ntohs(trns->h.len) - sizeof(*trns);
prev = (struct isakmp_data *)NULL;
d = (struct isakmp_data *)(trns + 1);
/* default */
life_t = OAKLEY_ATTR_SA_LD_TYPE_DEFAULT;
sa->lifetime = OAKLEY_ATTR_SA_LD_SEC_DEFAULT;
sa->lifebyte = 0;
sa->dhgrp = racoon_calloc(1, sizeof(struct dhgroup));
if (!sa->dhgrp)
goto err;
while (tlen > 0) {
type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
flag = ntohs(d->type) & ISAKMP_GEN_MASK;
plog(LLV_DEBUG, LOCATION, NULL,
"type=%s, flag=0x%04x, lorv=%s\n",
s_oakley_attr(type), flag,
s_oakley_attr_v(type, ntohs(d->lorv)));
/* get variable-sized item */
switch (type) {
case OAKLEY_ATTR_GRP_PI:
case OAKLEY_ATTR_GRP_GEN_ONE:
case OAKLEY_ATTR_GRP_GEN_TWO:
case OAKLEY_ATTR_GRP_CURVE_A:
case OAKLEY_ATTR_GRP_CURVE_B:
case OAKLEY_ATTR_SA_LD:
case OAKLEY_ATTR_GRP_ORDER:
if (flag) { /*TV*/
len = 2;
p = (u_char *)&d->lorv;
} else { /*TLV*/
len = ntohs(d->lorv);
p = (u_char *)(d + 1);
}
val = vmalloc(len);
if (!val)
return -1;
memcpy(val->v, p, len);
break;
default:
break;
}
switch (type) {
case OAKLEY_ATTR_ENC_ALG:
sa->enctype = (u_int16_t)ntohs(d->lorv);
break;
case OAKLEY_ATTR_HASH_ALG:
sa->hashtype = (u_int16_t)ntohs(d->lorv);
break;
case OAKLEY_ATTR_AUTH_METHOD:
sa->authmethod = ntohs(d->lorv);
#ifdef HAVE_GSSAPI
if (sa->authmethod == OAKLEY_ATTR_AUTH_METHOD_GSSAPI_KRB_REAL &&
(vendorid_mask & VENDORID_GSSAPI_MASK))
sa->authmethod = OAKLEY_ATTR_AUTH_METHOD_GSSAPI_KRB;
#endif
break;
case OAKLEY_ATTR_GRP_DESC:
sa->dh_group = (u_int16_t)ntohs(d->lorv);
break;
case OAKLEY_ATTR_GRP_TYPE:
{
int type = (int)ntohs(d->lorv);
if (type == OAKLEY_ATTR_GRP_TYPE_MODP)
sa->dhgrp->type = type;
else
return -1;
break;
}
case OAKLEY_ATTR_GRP_PI:
sa->dhgrp->prime = val;
break;
case OAKLEY_ATTR_GRP_GEN_ONE:
vfree(val);
if (!flag)
sa->dhgrp->gen1 = ntohs(d->lorv);
else {
int len = ntohs(d->lorv);
sa->dhgrp->gen1 = 0;
if (len > 4)
return -1;
memcpy(&sa->dhgrp->gen1, d + 1, len);
sa->dhgrp->gen1 = ntohl(sa->dhgrp->gen1);
}
break;
case OAKLEY_ATTR_GRP_GEN_TWO:
vfree(val);
if (!flag)
sa->dhgrp->gen2 = ntohs(d->lorv);
else {
int len = ntohs(d->lorv);
sa->dhgrp->gen2 = 0;
if (len > 4)
return -1;
memcpy(&sa->dhgrp->gen2, d + 1, len);
sa->dhgrp->gen2 = ntohl(sa->dhgrp->gen2);
}
break;
case OAKLEY_ATTR_GRP_CURVE_A:
sa->dhgrp->curve_a = val;
break;
case OAKLEY_ATTR_GRP_CURVE_B:
sa->dhgrp->curve_b = val;
break;
case OAKLEY_ATTR_SA_LD_TYPE:
{
int type = (int)ntohs(d->lorv);
switch (type) {
case OAKLEY_ATTR_SA_LD_TYPE_SEC:
case OAKLEY_ATTR_SA_LD_TYPE_KB:
life_t = type;
break;
default:
life_t = OAKLEY_ATTR_SA_LD_TYPE_DEFAULT;
break;
}
break;
}
case OAKLEY_ATTR_SA_LD:
if (!prev
|| (ntohs(prev->type) & ~ISAKMP_GEN_MASK) !=
OAKLEY_ATTR_SA_LD_TYPE) {
plog(LLV_ERROR, LOCATION, NULL,
"life duration must follow ltype\n");
break;
}
switch (life_t) {
case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
sa->lifetime = ipsecdoi_set_ld(val);
vfree(val);
if (sa->lifetime == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid life duration.\n");
goto err;
}
break;
case IPSECDOI_ATTR_SA_LD_TYPE_KB:
sa->lifebyte = ipsecdoi_set_ld(val);
vfree(val);
if (sa->lifebyte == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid life duration.\n");
goto err;
}
break;
default:
vfree(val);
plog(LLV_ERROR, LOCATION, NULL,
"invalid life type: %d\n", life_t);
goto err;
}
break;
case OAKLEY_ATTR_KEY_LEN:
{
int len = ntohs(d->lorv);
if (len % 8 != 0) {
plog(LLV_ERROR, LOCATION, NULL,
"keylen %d: not multiple of 8\n",
len);
goto err;
}
sa->encklen = (u_int16_t)len;
keylen++;
break;
}
case OAKLEY_ATTR_PRF:
case OAKLEY_ATTR_FIELD_SIZE:
/* unsupported */
break;
case OAKLEY_ATTR_GRP_ORDER:
sa->dhgrp->order = val;
break;
#ifdef HAVE_GSSAPI
case OAKLEY_ATTR_GSS_ID:
{
int error = -1;
iconv_t cd = (iconv_t) -1;
size_t srcleft, dstleft, rv;
__iconv_const char *src;
char *dst;
int len = ntohs(d->lorv);
/*
* Older verions of racoon just placed the
* ISO-Latin-1 string on the wire directly.
* Check to see if we are configured to be
* compatible with this behavior.
*/
if (lcconf->gss_id_enc == LC_GSSENC_LATIN1) {
if ((sa->gssid = vmalloc(len)) == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to allocate memory\n");
goto out;
}
memcpy(sa->gssid->v, d + 1, len);
plog(LLV_DEBUG, LOCATION, NULL,
"received old-style gss "
"id '%.*s' (len %zu)\n",
(int)sa->gssid->l, sa->gssid->v,
sa->gssid->l);
error = 0;
goto out;
}
/*
* For Windows 2000 compatibility, we expect
* the GSS ID attribute on the wire to be
* encoded in UTF-16LE. Internally, we work
* in ISO-Latin-1. Therefore, we should need
* 1/2 the specified length, which should always
* be a multiple of 2 octets.
*/
cd = iconv_open("latin1", "utf-16le");
if (cd == (iconv_t) -1) {
plog(LLV_ERROR, LOCATION, NULL,
"unable to initialize utf-16le -> latin1 "
"conversion descriptor: %s\n",
strerror(errno));
goto out;
}
if ((sa->gssid = vmalloc(len / 2)) == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to allocate memory\n");
goto out;
}
src = (__iconv_const char *)(d + 1);
srcleft = len;
dst = sa->gssid->v;
dstleft = len / 2;
rv = iconv(cd, (__iconv_const char **)&src, &srcleft,
&dst, &dstleft);
if (rv != 0) {
if (rv == -1) {
plog(LLV_ERROR, LOCATION, NULL,
"unable to convert GSS ID from "
"utf-16le -> latin1: %s\n",
strerror(errno));
} else {
plog(LLV_ERROR, LOCATION, NULL,
"%zd character%s in GSS ID cannot "
"be represented in latin1\n",
rv, rv == 1 ? "" : "s");
}
goto out;
}
/* XXX dstleft should always be 0; assert it? */
sa->gssid->l = (len / 2) - dstleft;
plog(LLV_DEBUG, LOCATION, NULL,
"received gss id '%.*s' (len %zu)\n",
(int)sa->gssid->l, sa->gssid->v, sa->gssid->l);
error = 0;
out:
if (cd != (iconv_t)-1)
(void)iconv_close(cd);
if ((error != 0) && (sa->gssid != NULL)) {
vfree(sa->gssid);
sa->gssid = NULL;
}
break;
}
#endif /* HAVE_GSSAPI */
default:
break;
}
prev = d;
if (flag) {
tlen -= sizeof(*d);
d = (struct isakmp_data *)((char *)d + sizeof(*d));
} else {
tlen -= (sizeof(*d) + ntohs(d->lorv));
d = (struct isakmp_data *)((char *)d + sizeof(*d) + ntohs(d->lorv));
}
}
/* key length must not be specified on some algorithms */
if (keylen) {
if (sa->enctype == OAKLEY_ATTR_ENC_ALG_DES
#ifdef HAVE_OPENSSL_IDEA_H
|| sa->enctype == OAKLEY_ATTR_ENC_ALG_IDEA
#endif
|| sa->enctype == OAKLEY_ATTR_ENC_ALG_3DES) {
plog(LLV_ERROR, LOCATION, NULL,
"keylen must not be specified "
"for encryption algorithm %d\n",
sa->enctype);
return -1;
}
}
return 0;
err:
return error;
}
/*%%%*/
/*
* check phase 2 SA payload and select single proposal.
* make new SA payload to be replyed not including general header.
* This function is called by responder only.
* OUT:
* 0: succeed.
* -1: error occured.
*/
int
ipsecdoi_selectph2proposal(iph2)
struct ph2handle *iph2;
{
struct prop_pair **pair;
struct prop_pair *ret;
u_int32_t doitype, sittype;
/* get proposal pair */
pair = get_proppair_and_doi_sit(iph2->sa, IPSECDOI_TYPE_PH2,
&doitype, &sittype);
if (pair == NULL)
return -1;
/* check and select a proposal. */
ret = get_ph2approval(iph2, pair);
free_proppair(pair);
if (ret == NULL)
return -1;
/* make a SA to be replayed. */
/* SPI must be updated later. */
iph2->sa_ret = get_sabyproppair(doitype, sittype, ret);
free_proppair0(ret);
if (iph2->sa_ret == NULL)
return -1;
return 0;
}
/*
* check phase 2 SA payload returned from responder.
* This function is called by initiator only.
* OUT:
* 0: valid.
* -1: invalid.
*/
int
ipsecdoi_checkph2proposal(iph2)
struct ph2handle *iph2;
{
struct prop_pair **rpair = NULL, **spair = NULL;
struct prop_pair *p;
int i, n, num;
int error = -1;
vchar_t *sa_ret = NULL;
u_int32_t doitype, sittype;
/* get proposal pair of SA sent. */
spair = get_proppair_and_doi_sit(iph2->sa, IPSECDOI_TYPE_PH2,
&doitype, &sittype);
if (spair == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get prop pair.\n");
goto end;
}
/* XXX should check the number of transform */
/* get proposal pair of SA replayed */
rpair = get_proppair(iph2->sa_ret, IPSECDOI_TYPE_PH2);
if (rpair == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get prop pair.\n");
goto end;
}
/* check proposal is only one ? */
n = 0;
num = 0;
for (i = 0; i < MAXPROPPAIRLEN; i++) {
if (rpair[i]) {
n = i;
num++;
}
}
if (num == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"no proposal received.\n");
goto end;
}
if (num != 1) {
plog(LLV_ERROR, LOCATION, NULL,
"some proposals received.\n");
goto end;
}
if (spair[n] == NULL) {
plog(LLV_WARNING, LOCATION, NULL,
"invalid proposal number:%d received.\n", i);
}
if (rpair[n]->tnext != NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"multi transforms replyed.\n");
goto end;
}
if (cmp_aproppair_i(rpair[n], spair[n])) {
plog(LLV_ERROR, LOCATION, NULL,
"proposal mismathed.\n");
goto end;
}
/*
* check and select a proposal.
* ensure that there is no modification of the proposal by
* cmp_aproppair_i()
*/
p = get_ph2approval(iph2, rpair);
if (p == NULL)
goto end;
/* make a SA to be replayed. */
sa_ret = iph2->sa_ret;
iph2->sa_ret = get_sabyproppair(doitype, sittype, p);
free_proppair0(p);
if (iph2->sa_ret == NULL)
goto end;
error = 0;
end:
if (rpair)
free_proppair(rpair);
if (spair)
free_proppair(spair);
if (sa_ret)
vfree(sa_ret);
return error;
}
/*
* compare two prop_pair which is assumed to have same proposal number.
* the case of bundle or single SA, NOT multi transforms.
* a: a proposal that is multi protocols and single transform, usually replyed.
* b: a proposal that is multi protocols and multi transform, usually sent.
* NOTE: this function is for initiator.
* OUT
* 0: equal
* 1: not equal
* XXX cannot understand the comment!
*/
static int
cmp_aproppair_i(a, b)
struct prop_pair *a, *b;
{
struct prop_pair *p, *q, *r;
int len;
for (p = a, q = b; p && q; p = p->next, q = q->next) {
for (r = q; r; r = r->tnext) {
/* compare trns */
if (p->trns->t_no == r->trns->t_no)
break;
}
if (!r) {
/* no suitable transform found */
plog(LLV_ERROR, LOCATION, NULL,
"no suitable transform found.\n");
return -1;
}
/* compare prop */
if (p->prop->p_no != r->prop->p_no) {
plog(LLV_WARNING, LOCATION, NULL,
"proposal #%d mismatched, "
"expected #%d.\n",
r->prop->p_no, p->prop->p_no);
/*FALLTHROUGH*/
}
if (p->prop->proto_id != r->prop->proto_id) {
plog(LLV_ERROR, LOCATION, NULL,
"proto_id mismathed: my:%d peer:%d\n",
r->prop->proto_id, p->prop->proto_id);
return -1;
}
if (p->prop->spi_size != r->prop->spi_size) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid spi size: %d.\n",
p->prop->spi_size);
return -1;
}
/* check #of transforms */
if (p->prop->num_t != 1) {
plog(LLV_WARNING, LOCATION, NULL,
"#of transform is %d, "
"but expected 1.\n", p->prop->num_t);
/*FALLTHROUGH*/
}
if (p->trns->t_id != r->trns->t_id) {
plog(LLV_WARNING, LOCATION, NULL,
"transform number has been modified.\n");
/*FALLTHROUGH*/
}
if (p->trns->reserved != r->trns->reserved) {
plog(LLV_WARNING, LOCATION, NULL,
"reserved field should be zero.\n");
/*FALLTHROUGH*/
}
/* compare attribute */
len = ntohs(r->trns->h.len) - sizeof(*p->trns);
if (memcmp(p->trns + 1, r->trns + 1, len) != 0) {
plog(LLV_WARNING, LOCATION, NULL,
"attribute has been modified.\n");
/*FALLTHROUGH*/
}
}
if ((p && !q) || (!p && q)) {
/* # of protocols mismatched */
plog(LLV_ERROR, LOCATION, NULL,
"#of protocols mismatched.\n");
return -1;
}
return 0;
}
/*
* acceptable check for policy configuration.
* return a new SA payload to be reply to peer.
*/
static struct prop_pair *
get_ph2approval(iph2, pair)
struct ph2handle *iph2;
struct prop_pair **pair;
{
struct prop_pair *ret;
int i;
iph2->approval = NULL;
plog(LLV_DEBUG, LOCATION, NULL,
"begin compare proposals.\n");
for (i = 0; i < MAXPROPPAIRLEN; i++) {
if (pair[i] == NULL)
continue;
plog(LLV_DEBUG, LOCATION, NULL,
"pair[%d]: %p\n", i, pair[i]);
print_proppair(LLV_DEBUG, pair[i]);;
/* compare proposal and select one */
ret = get_ph2approvalx(iph2, pair[i]);
if (ret != NULL) {
/* found */
return ret;
}
}
plog(LLV_ERROR, LOCATION, NULL, "no suitable policy found.\n");
return NULL;
}
/*
* compare my proposal and peers just one proposal.
* set a approval.
*/
static struct prop_pair *
get_ph2approvalx(iph2, pp)
struct ph2handle *iph2;
struct prop_pair *pp;
{
struct prop_pair *ret = NULL;
struct saprop *pr0, *pr = NULL;
struct saprop *q1, *q2;
pr0 = aproppair2saprop(pp);
if (pr0 == NULL)
return NULL;
for (q1 = pr0; q1; q1 = q1->next) {
for (q2 = iph2->proposal; q2; q2 = q2->next) {
plog(LLV_DEBUG, LOCATION, NULL,
"peer's single bundle:\n");
printsaprop0(LLV_DEBUG, q1);
plog(LLV_DEBUG, LOCATION, NULL,
"my single bundle:\n");
printsaprop0(LLV_DEBUG, q2);
pr = cmpsaprop_alloc(iph2->ph1, q1, q2, iph2->side);
if (pr != NULL)
goto found;
plog(LLV_ERROR, LOCATION, NULL,
"not matched\n");
}
}
/* no proposal matching */
err:
flushsaprop(pr0);
return NULL;
found:
flushsaprop(pr0);
plog(LLV_DEBUG, LOCATION, NULL, "matched\n");
iph2->approval = pr;
{
struct saproto *sp;
struct prop_pair *p, *x;
struct prop_pair *n = NULL;
ret = NULL;
for (p = pp; p; p = p->next) {
/*
* find a proposal with matching proto_id.
* we have analyzed validity already, in cmpsaprop_alloc().
*/
for (sp = pr->head; sp; sp = sp->next) {
if (sp->proto_id == p->prop->proto_id)
break;
}
if (!sp)
goto err;
if (sp->head->next)
goto err; /* XXX */
for (x = p; x; x = x->tnext)
if (sp->head->trns_no == x->trns->t_no)
break;
if (!x)
goto err; /* XXX */
n = racoon_calloc(1, sizeof(struct prop_pair));
if (n == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get buffer.\n");
goto err;
}
n->prop = x->prop;
n->trns = x->trns;
/* need to preserve the order */
for (x = ret; x && x->next; x = x->next)
;
if (x && x->prop == n->prop) {
for (/*nothing*/; x && x->tnext; x = x->tnext)
;
x->tnext = n;
} else {
if (x)
x->next = n;
else {
ret = n;
}
}
/* #of transforms should be updated ? */
}
}
return ret;
}
void
free_proppair(pair)
struct prop_pair **pair;
{
int i;
for (i = 0; i < MAXPROPPAIRLEN; i++) {
free_proppair0(pair[i]);
pair[i] = NULL;
}
racoon_free(pair);
}
static void
free_proppair0(pair)
struct prop_pair *pair;
{
struct prop_pair *p, *q, *r, *s;
p = pair;
while (p) {
q = p->next;
r = p;
while (r) {
s = r->tnext;
racoon_free(r);
r = s;
}
p = q;
}
}
/*
* get proposal pairs from SA payload.
* tiny check for proposal payload.
*/
static struct prop_pair **
get_proppair_and_doi_sit(sa, mode, doitype, sittype)
vchar_t *sa;
int mode;
u_int32_t *doitype, *sittype;
{
struct prop_pair **pair = NULL;
int num_p = 0; /* number of proposal for use */
int tlen;
caddr_t bp;
int i;
struct ipsecdoi_sa_b *sab = (struct ipsecdoi_sa_b *)sa->v;
plog(LLV_DEBUG, LOCATION, NULL, "total SA len=%zu\n", sa->l);
plogdump(LLV_DEBUG, sa->v, sa->l);
/* check SA payload size */
if (sa->l < sizeof(*sab)) {
plog(LLV_ERROR, LOCATION, NULL,
"Invalid SA length = %zu.\n", sa->l);
goto bad;
}
/* check DOI */
if (check_doi(ntohl(sab->doi)) < 0)
goto bad;
if (doitype != NULL)
*doitype = ntohl(sab->doi);
/* check SITUATION */
if (check_situation(ntohl(sab->sit)) < 0)
goto bad;
if (sittype != NULL)
*sittype = ntohl(sab->sit);
pair = racoon_calloc(1, MAXPROPPAIRLEN * sizeof(*pair));
if (pair == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get buffer.\n");
goto bad;
}
memset(pair, 0, sizeof(pair));
bp = (caddr_t)(sab + 1);
tlen = sa->l - sizeof(*sab);
{
struct isakmp_pl_p *prop;
int proplen;
vchar_t *pbuf = NULL;
struct isakmp_parse_t *pa;
pbuf = isakmp_parsewoh(ISAKMP_NPTYPE_P, (struct isakmp_gen *)bp, tlen);
if (pbuf == NULL)
goto bad;
for (pa = (struct isakmp_parse_t *)pbuf->v;
pa->type != ISAKMP_NPTYPE_NONE;
pa++) {
/* check the value of next payload */
if (pa->type != ISAKMP_NPTYPE_P) {
plog(LLV_ERROR, LOCATION, NULL,
"Invalid payload type=%u\n", pa->type);
vfree(pbuf);
goto bad;
}
prop = (struct isakmp_pl_p *)pa->ptr;
proplen = pa->len;
plog(LLV_DEBUG, LOCATION, NULL,
"proposal #%u len=%d\n", prop->p_no, proplen);
if (proplen == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid proposal with length %d\n", proplen);
vfree(pbuf);
goto bad;
}
/* check Protocol ID */
if (!check_protocol[mode]) {
plog(LLV_ERROR, LOCATION, NULL,
"unsupported mode %d\n", mode);
continue;
}
if (check_protocol[mode](prop->proto_id) < 0)
continue;
/* check SPI length when IKE. */
if (check_spi_size(prop->proto_id, prop->spi_size) < 0)
continue;
/* get transform */
if (get_transform(prop, pair, &num_p) < 0) {
vfree(pbuf);
goto bad;
}
}
vfree(pbuf);
pbuf = NULL;
}
{
int notrans, nprop;
struct prop_pair *p, *q;
/* check for proposals with no transforms */
for (i = 0; i < MAXPROPPAIRLEN; i++) {
if (!pair[i])
continue;
plog(LLV_DEBUG, LOCATION, NULL, "pair %d:\n", i);
print_proppair(LLV_DEBUG, pair[i]);
notrans = nprop = 0;
for (p = pair[i]; p; p = p->next) {
if (p->trns == NULL) {
notrans++;
break;
}
for (q = p; q; q = q->tnext)
nprop++;
}
#if 0
/*
* XXX at this moment, we cannot accept proposal group
* with multiple proposals. this should be fixed.
*/
if (pair[i]->next) {
plog(LLV_WARNING, LOCATION, NULL,
"proposal #%u ignored "
"(multiple proposal not supported)\n",
pair[i]->prop->p_no);
notrans++;
}
#endif
if (notrans) {
for (p = pair[i]; p; p = q) {
q = p->next;
racoon_free(p);
}
pair[i] = NULL;
num_p--;
} else {
plog(LLV_DEBUG, LOCATION, NULL,
"proposal #%u: %d transform\n",
pair[i]->prop->p_no, nprop);
}
}
}
/* bark if no proposal is found. */
if (num_p <= 0) {
plog(LLV_ERROR, LOCATION, NULL,
"no Proposal found.\n");
goto bad;
}
return pair;
bad:
if (pair != NULL)
racoon_free(pair);
return NULL;
}
struct prop_pair **
get_proppair(sa, mode)
vchar_t *sa;
int mode;
{
return get_proppair_and_doi_sit(sa, mode, NULL, NULL);
}
/*
* check transform payload.
* OUT:
* positive: return the pointer to the payload of valid transform.
* 0 : No valid transform found.
*/
static int
get_transform(prop, pair, num_p)
struct isakmp_pl_p *prop;
struct prop_pair **pair;
int *num_p;
{
int tlen; /* total length of all transform in a proposal */
caddr_t bp;
struct isakmp_pl_t *trns;
int trnslen;
vchar_t *pbuf = NULL;
struct isakmp_parse_t *pa;
struct prop_pair *p = NULL, *q;
int num_t;
bp = (caddr_t)prop + sizeof(struct isakmp_pl_p) + prop->spi_size;
tlen = ntohs(prop->h.len)
- (sizeof(struct isakmp_pl_p) + prop->spi_size);
pbuf = isakmp_parsewoh(ISAKMP_NPTYPE_T, (struct isakmp_gen *)bp, tlen);
if (pbuf == NULL)
return -1;
/* check and get transform for use */
num_t = 0;
for (pa = (struct isakmp_parse_t *)pbuf->v;
pa->type != ISAKMP_NPTYPE_NONE;
pa++) {
num_t++;
/* check the value of next payload */
if (pa->type != ISAKMP_NPTYPE_T) {
plog(LLV_ERROR, LOCATION, NULL,
"Invalid payload type=%u\n", pa->type);
break;
}
trns = (struct isakmp_pl_t *)pa->ptr;
trnslen = pa->len;
plog(LLV_DEBUG, LOCATION, NULL,
"transform #%u len=%u\n", trns->t_no, trnslen);
/* check transform ID */
if (prop->proto_id >= ARRAYLEN(check_transform)) {
plog(LLV_WARNING, LOCATION, NULL,
"unsupported proto_id %u\n",
prop->proto_id);
continue;
}
if (prop->proto_id >= ARRAYLEN(check_attributes)) {
plog(LLV_WARNING, LOCATION, NULL,
"unsupported proto_id %u\n",
prop->proto_id);
continue;
}
if (!check_transform[prop->proto_id]
|| !check_attributes[prop->proto_id]) {
plog(LLV_WARNING, LOCATION, NULL,
"unsupported proto_id %u\n",
prop->proto_id);
continue;
}
if (check_transform[prop->proto_id](trns->t_id) < 0)
continue;
/* check data attributes */
if (check_attributes[prop->proto_id](trns) != 0)
continue;
p = racoon_calloc(1, sizeof(*p));
if (p == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get buffer.\n");
vfree(pbuf);
return -1;
}
p->prop = prop;
p->trns = trns;
/* need to preserve the order */
for (q = pair[prop->p_no]; q && q->next; q = q->next)
;
if (q && q->prop == p->prop) {
for (/*nothing*/; q && q->tnext; q = q->tnext)
;
q->tnext = p;
} else {
if (q)
q->next = p;
else {
pair[prop->p_no] = p;
(*num_p)++;
}
}
}
vfree(pbuf);
return 0;
}
/*
* make a new SA payload from prop_pair.
* NOTE: this function make spi value clear.
*/
vchar_t *
get_sabyproppair(doitype, sittype, pair)
u_int32_t doitype, sittype;
struct prop_pair *pair;
{
vchar_t *newsa;
int newtlen;
u_int8_t *np_p = NULL;
struct prop_pair *p;
int prophlen, trnslen;
caddr_t bp;
newtlen = sizeof(struct ipsecdoi_sa_b);
for (p = pair; p; p = p->next) {
newtlen += sizeof(struct isakmp_pl_p);
newtlen += p->prop->spi_size;
newtlen += ntohs(p->trns->h.len);
}
newsa = vmalloc(newtlen);
if (newsa == NULL) {
plog(LLV_ERROR, LOCATION, NULL, "failed to get newsa.\n");
return NULL;
}
bp = newsa->v;
((struct isakmp_gen *)bp)->len = htons(newtlen);
/* update some of values in SA header */
((struct ipsecdoi_sa_b *)bp)->doi = htonl(doitype);
((struct ipsecdoi_sa_b *)bp)->sit = htonl(sittype);
bp += sizeof(struct ipsecdoi_sa_b);
/* create proposal payloads */
for (p = pair; p; p = p->next) {
prophlen = sizeof(struct isakmp_pl_p)
+ p->prop->spi_size;
trnslen = ntohs(p->trns->h.len);
if (np_p)
*np_p = ISAKMP_NPTYPE_P;
/* create proposal */
memcpy(bp, p->prop, prophlen);
((struct isakmp_pl_p *)bp)->h.np = ISAKMP_NPTYPE_NONE;
((struct isakmp_pl_p *)bp)->h.len = htons(prophlen + trnslen);
((struct isakmp_pl_p *)bp)->num_t = 1;
np_p = &((struct isakmp_pl_p *)bp)->h.np;
memset(bp + sizeof(struct isakmp_pl_p), 0, p->prop->spi_size);
bp += prophlen;
/* create transform */
memcpy(bp, p->trns, trnslen);
((struct isakmp_pl_t *)bp)->h.np = ISAKMP_NPTYPE_NONE;
((struct isakmp_pl_t *)bp)->h.len = htons(trnslen);
bp += trnslen;
}
return newsa;
}
/*
* update responder's spi
*/
int
ipsecdoi_updatespi(iph2)
struct ph2handle *iph2;
{
struct prop_pair **pair, *p;
struct saprop *pp;
struct saproto *pr;
int i;
int error = -1;
u_int8_t *spi;
pair = get_proppair(iph2->sa_ret, IPSECDOI_TYPE_PH2);
if (pair == NULL)
return -1;
for (i = 0; i < MAXPROPPAIRLEN; i++) {
if (pair[i])
break;
}
if (i == MAXPROPPAIRLEN || pair[i]->tnext) {
/* multiple transform must be filtered by selectph2proposal.*/
goto end;
}
pp = iph2->approval;
/* create proposal payloads */
for (p = pair[i]; p; p = p->next) {
/*
* find a proposal/transform with matching proto_id/t_id.
* we have analyzed validity already, in cmpsaprop_alloc().
*/
for (pr = pp->head; pr; pr = pr->next) {
if (p->prop->proto_id == pr->proto_id &&
p->trns->t_id == pr->head->trns_id) {
break;
}
}
if (!pr)
goto end;
/*
* XXX SPI bits are left-filled, for use with IPComp.
* we should be switching to variable-length spi field...
*/
spi = (u_int8_t *)&pr->spi;
spi += sizeof(pr->spi);
spi -= pr->spisize;
memcpy((caddr_t)p->prop + sizeof(*p->prop), spi, pr->spisize);
}
error = 0;
end:
free_proppair(pair);
return error;
}
/*
* make a new SA payload from prop_pair.
*/
vchar_t *
get_sabysaprop(pp0, sa0)
struct saprop *pp0;
vchar_t *sa0;
{
struct prop_pair **pair = NULL;
vchar_t *newsa = NULL;
int newtlen;
u_int8_t *np_p = NULL;
struct prop_pair *p = NULL;
struct saprop *pp;
struct saproto *pr;
struct satrns *tr;
int prophlen, trnslen;
caddr_t bp;
int error = -1;
/* get proposal pair */
pair = get_proppair(sa0, IPSECDOI_TYPE_PH2);
if (pair == NULL)
goto out;
newtlen = sizeof(struct ipsecdoi_sa_b);
for (pp = pp0; pp; pp = pp->next) {
if (pair[pp->prop_no] == NULL)
goto out;
for (pr = pp->head; pr; pr = pr->next) {
newtlen += (sizeof(struct isakmp_pl_p)
+ pr->spisize);
for (tr = pr->head; tr; tr = tr->next) {
for (p = pair[pp->prop_no]; p; p = p->tnext) {
if (tr->trns_no == p->trns->t_no)
break;
}
if (p == NULL)
goto out;
newtlen += ntohs(p->trns->h.len);
}
}
}
newsa = vmalloc(newtlen);
if (newsa == NULL) {
plog(LLV_ERROR, LOCATION, NULL, "failed to get newsa.\n");
goto out;
}
bp = newsa->v;
/* some of values of SA must be updated in the out of this function */
((struct isakmp_gen *)bp)->len = htons(newtlen);
bp += sizeof(struct ipsecdoi_sa_b);
/* create proposal payloads */
for (pp = pp0; pp; pp = pp->next) {
for (pr = pp->head; pr; pr = pr->next) {
prophlen = sizeof(struct isakmp_pl_p)
+ p->prop->spi_size;
for (tr = pr->head; tr; tr = tr->next) {
for (p = pair[pp->prop_no]; p; p = p->tnext) {
if (tr->trns_no == p->trns->t_no)
break;
}
if (p == NULL)
goto out;
trnslen = ntohs(p->trns->h.len);
if (np_p)
*np_p = ISAKMP_NPTYPE_P;
/* create proposal */
memcpy(bp, p->prop, prophlen);
((struct isakmp_pl_p *)bp)->h.np = ISAKMP_NPTYPE_NONE;
((struct isakmp_pl_p *)bp)->h.len = htons(prophlen + trnslen);
((struct isakmp_pl_p *)bp)->num_t = 1;
np_p = &((struct isakmp_pl_p *)bp)->h.np;
bp += prophlen;
/* create transform */
memcpy(bp, p->trns, trnslen);
((struct isakmp_pl_t *)bp)->h.np = ISAKMP_NPTYPE_NONE;
((struct isakmp_pl_t *)bp)->h.len = htons(trnslen);
bp += trnslen;
}
}
}
error = 0;
out:
if (pair != NULL)
racoon_free(pair);
if (error != 0) {
if (newsa != NULL) {
vfree(newsa);
newsa = NULL;
}
}
return newsa;
}
/*
* If some error happens then return 0. Although 0 means that lifetime is zero,
* such a value should not be accepted.
* Also 0 of lifebyte should not be included in a packet although 0 means not
* to care of it.
*/
static u_int32_t
ipsecdoi_set_ld(buf)
vchar_t *buf;
{
u_int32_t ld;
if (buf == 0)
return 0;
switch (buf->l) {
case 2:
ld = ntohs(*(u_int16_t *)buf->v);
break;
case 4:
ld = ntohl(*(u_int32_t *)buf->v);
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"length %zu of life duration "
"isn't supported.\n", buf->l);
return 0;
}
return ld;
}
/*
* parse responder-lifetime attributes from payload
*/
int
ipsecdoi_parse_responder_lifetime(notify, lifetime_sec, lifetime_kb)
struct isakmp_pl_n *notify;
u_int32_t *lifetime_sec;
u_int32_t *lifetime_kb;
{
struct isakmp_data *d;
int flag, type, tlen, ld_type = -1;
u_int16_t lorv;
u_int32_t value;
tlen = ntohs(notify->h.len) - sizeof(*notify) - notify->spi_size;
d = (struct isakmp_data *)((char *)(notify + 1) +
notify->spi_size);
while (tlen >= sizeof(struct isakmp_data)) {
type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
flag = ntohs(d->type) & ISAKMP_GEN_MASK;
lorv = ntohs(d->lorv);
plog(LLV_DEBUG, LOCATION, NULL,
"type=%s, flag=0x%04x, lorv=%s\n",
s_ipsecdoi_attr(type), flag,
s_ipsecdoi_attr_v(type, lorv));
switch (type) {
case IPSECDOI_ATTR_SA_LD_TYPE:
if (! flag) {
plog(LLV_ERROR, LOCATION, NULL,
"must be TV when LD_TYPE.\n");
return -1;
}
ld_type = lorv;
break;
case IPSECDOI_ATTR_SA_LD:
if (flag)
value = lorv;
else if (lorv == 2)
value = ntohs(*(u_int16_t *)(d + 1));
else if (lorv == 4)
value = ntohl(*(u_int32_t *)(d + 1));
else {
plog(LLV_ERROR, LOCATION, NULL,
"payload length %d for lifetime "
"data length is unsupported.\n", lorv);
return -1;
}
switch (ld_type) {
case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
if (lifetime_sec != NULL)
*lifetime_sec = value;
plog(LLV_INFO, LOCATION, NULL,
"received RESPONDER-LIFETIME: %d "
"seconds\n", value);
break;
case IPSECDOI_ATTR_SA_LD_TYPE_KB:
if (lifetime_kb != NULL)
*lifetime_kb = value;
plog(LLV_INFO, LOCATION, NULL,
"received RESPONDER-LIFETIME: %d "
"kbytes\n", value);
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"lifetime data received without "
"lifetime data type.\n");
return -1;
}
break;
}
if (flag) {
tlen -= sizeof(*d);
d = (struct isakmp_data *)((char *)d
+ sizeof(*d));
} else {
tlen -= (sizeof(*d) + lorv);
d = (struct isakmp_data *)((char *)d
+ sizeof(*d) + lorv);
}
}
return 0;
}
/*%%%*/
/*
* check DOI
*/
static int
check_doi(doi)
u_int32_t doi;
{
switch (doi) {
case IPSEC_DOI:
return 0;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid value of DOI 0x%08x.\n", doi);
return -1;
}
/* NOT REACHED */
}
/*
* check situation
*/
static int
check_situation(sit)
u_int32_t sit;
{
switch (sit) {
case IPSECDOI_SIT_IDENTITY_ONLY:
return 0;
case IPSECDOI_SIT_SECRECY:
case IPSECDOI_SIT_INTEGRITY:
plog(LLV_ERROR, LOCATION, NULL,
"situation 0x%08x unsupported yet.\n", sit);
return -1;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid situation 0x%08x.\n", sit);
return -1;
}
/* NOT REACHED */
}
/*
* check protocol id in main mode
*/
static int
check_prot_main(proto_id)
int proto_id;
{
switch (proto_id) {
case IPSECDOI_PROTO_ISAKMP:
return 0;
default:
plog(LLV_ERROR, LOCATION, NULL,
"Illegal protocol id=%u.\n", proto_id);
return -1;
}
/* NOT REACHED */
}
/*
* check protocol id in quick mode
*/
static int
check_prot_quick(proto_id)
int proto_id;
{
switch (proto_id) {
case IPSECDOI_PROTO_IPSEC_AH:
case IPSECDOI_PROTO_IPSEC_ESP:
return 0;
case IPSECDOI_PROTO_IPCOMP:
return 0;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid protocol id %d.\n", proto_id);
return -1;
}
/* NOT REACHED */
}
static int
check_spi_size(proto_id, size)
int proto_id, size;
{
switch (proto_id) {
case IPSECDOI_PROTO_ISAKMP:
if (size != 0) {
/* WARNING */
plog(LLV_WARNING, LOCATION, NULL,
"SPI size isn't zero, but IKE proposal.\n");
}
return 0;
case IPSECDOI_PROTO_IPSEC_AH:
case IPSECDOI_PROTO_IPSEC_ESP:
if (size != 4) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid SPI size=%d for IPSEC proposal.\n",
size);
return -1;
}
return 0;
case IPSECDOI_PROTO_IPCOMP:
if (size != 2 && size != 4) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid SPI size=%d for IPCOMP proposal.\n",
size);
return -1;
}
return 0;
default:
/* ??? */
return -1;
}
/* NOT REACHED */
}
/*
* check transform ID in ISAKMP.
*/
static int
check_trns_isakmp(t_id)
int t_id;
{
switch (t_id) {
case IPSECDOI_KEY_IKE:
return 0;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid transform-id=%u in proto_id=%u.\n",
t_id, IPSECDOI_KEY_IKE);
return -1;
}
/* NOT REACHED */
}
/*
* check transform ID in AH.
*/
static int
check_trns_ah(t_id)
int t_id;
{
switch (t_id) {
case IPSECDOI_AH_MD5:
case IPSECDOI_AH_SHA:
case IPSECDOI_AH_SHA256:
case IPSECDOI_AH_SHA384:
case IPSECDOI_AH_SHA512:
return 0;
case IPSECDOI_AH_DES:
plog(LLV_ERROR, LOCATION, NULL,
"not support transform-id=%u in AH.\n", t_id);
return -1;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid transform-id=%u in AH.\n", t_id);
return -1;
}
/* NOT REACHED */
}
/*
* check transform ID in ESP.
*/
static int
check_trns_esp(t_id)
int t_id;
{
switch (t_id) {
case IPSECDOI_ESP_DES:
case IPSECDOI_ESP_3DES:
case IPSECDOI_ESP_NULL:
case IPSECDOI_ESP_RC5:
case IPSECDOI_ESP_CAST:
case IPSECDOI_ESP_BLOWFISH:
case IPSECDOI_ESP_AES:
case IPSECDOI_ESP_TWOFISH:
case IPSECDOI_ESP_CAMELLIA:
return 0;
case IPSECDOI_ESP_DES_IV32:
case IPSECDOI_ESP_DES_IV64:
case IPSECDOI_ESP_IDEA:
case IPSECDOI_ESP_3IDEA:
case IPSECDOI_ESP_RC4:
plog(LLV_ERROR, LOCATION, NULL,
"not support transform-id=%u in ESP.\n", t_id);
return -1;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid transform-id=%u in ESP.\n", t_id);
return -1;
}
/* NOT REACHED */
}
/*
* check transform ID in IPCOMP.
*/
static int
check_trns_ipcomp(t_id)
int t_id;
{
switch (t_id) {
case IPSECDOI_IPCOMP_OUI:
case IPSECDOI_IPCOMP_DEFLATE:
case IPSECDOI_IPCOMP_LZS:
return 0;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid transform-id=%u in IPCOMP.\n", t_id);
return -1;
}
/* NOT REACHED */
}
/*
* check data attributes in IKE.
*/
static int
check_attr_isakmp(trns)
struct isakmp_pl_t *trns;
{
struct isakmp_data *d;
int tlen;
int flag, type;
u_int16_t lorv;
tlen = ntohs(trns->h.len) - sizeof(struct isakmp_pl_t);
d = (struct isakmp_data *)((caddr_t)trns + sizeof(struct isakmp_pl_t));
while (tlen > 0) {
type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
flag = ntohs(d->type) & ISAKMP_GEN_MASK;
lorv = ntohs(d->lorv);
plog(LLV_DEBUG, LOCATION, NULL,
"type=%s, flag=0x%04x, lorv=%s\n",
s_oakley_attr(type), flag,
s_oakley_attr_v(type, lorv));
/*
* some of the attributes must be encoded in TV.
* see RFC2409 Appendix A "Attribute Classes".
*/
switch (type) {
case OAKLEY_ATTR_ENC_ALG:
case OAKLEY_ATTR_HASH_ALG:
case OAKLEY_ATTR_AUTH_METHOD:
case OAKLEY_ATTR_GRP_DESC:
case OAKLEY_ATTR_GRP_TYPE:
case OAKLEY_ATTR_SA_LD_TYPE:
case OAKLEY_ATTR_PRF:
case OAKLEY_ATTR_KEY_LEN:
case OAKLEY_ATTR_FIELD_SIZE:
if (!flag) { /* TLV*/
plog(LLV_ERROR, LOCATION, NULL,
"oakley attribute %d must be TV.\n",
type);
return -1;
}
break;
}
/* sanity check for TLV. length must be specified. */
if (!flag && lorv == 0) { /*TLV*/
plog(LLV_ERROR, LOCATION, NULL,
"invalid length %d for TLV attribute %d.\n",
lorv, type);
return -1;
}
switch (type) {
case OAKLEY_ATTR_ENC_ALG:
if (!alg_oakley_encdef_ok(lorv)) {
plog(LLV_ERROR, LOCATION, NULL,
"invalied encryption algorithm=%d.\n",
lorv);
return -1;
}
break;
case OAKLEY_ATTR_HASH_ALG:
if (!alg_oakley_hashdef_ok(lorv)) {
plog(LLV_ERROR, LOCATION, NULL,
"invalied hash algorithm=%d.\n",
lorv);
return -1;
}
break;
case OAKLEY_ATTR_AUTH_METHOD:
switch (lorv) {
case OAKLEY_ATTR_AUTH_METHOD_PSKEY:
case OAKLEY_ATTR_AUTH_METHOD_RSASIG:
#ifdef ENABLE_HYBRID
case OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_I:
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_I:
#endif
#if defined(ENABLE_HYBRID) || defined(HAVE_GSSAPI)
/* These two authentication method IDs overlap. */
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_PSKEY_I:
/*case OAKLEY_ATTR_AUTH_METHOD_GSSAPI_KRB:*/
#endif
break;
case OAKLEY_ATTR_AUTH_METHOD_DSSSIG:
#ifdef ENABLE_HYBRID
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_PSKEY_R:
case OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_R:
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_R:
case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_I:
case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_R:
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_I:
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_R:
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_I:
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_R:
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_I:
case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_R:
#endif
case OAKLEY_ATTR_AUTH_METHOD_RSAENC:
case OAKLEY_ATTR_AUTH_METHOD_RSAREV:
plog(LLV_ERROR, LOCATION, NULL,
"auth method %s isn't supported.\n",
s_oakley_attr_method(lorv));
return -1;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid auth method %d.\n",
lorv);
return -1;
}
break;
case OAKLEY_ATTR_GRP_DESC:
if (!alg_oakley_dhdef_ok(lorv)) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid DH group %d.\n",
lorv);
return -1;
}
break;
case OAKLEY_ATTR_GRP_TYPE:
switch (lorv) {
case OAKLEY_ATTR_GRP_TYPE_MODP:
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"unsupported DH group type %d.\n",
lorv);
return -1;
}
break;
case OAKLEY_ATTR_GRP_PI:
case OAKLEY_ATTR_GRP_GEN_ONE:
/* sanity checks? */
break;
case OAKLEY_ATTR_GRP_GEN_TWO:
case OAKLEY_ATTR_GRP_CURVE_A:
case OAKLEY_ATTR_GRP_CURVE_B:
plog(LLV_ERROR, LOCATION, NULL,
"attr type=%u isn't supported.\n", type);
return -1;
case OAKLEY_ATTR_SA_LD_TYPE:
switch (lorv) {
case OAKLEY_ATTR_SA_LD_TYPE_SEC:
case OAKLEY_ATTR_SA_LD_TYPE_KB:
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid life type %d.\n", lorv);
return -1;
}
break;
case OAKLEY_ATTR_SA_LD:
/* should check the value */
break;
case OAKLEY_ATTR_PRF:
case OAKLEY_ATTR_KEY_LEN:
break;
case OAKLEY_ATTR_FIELD_SIZE:
plog(LLV_ERROR, LOCATION, NULL,
"attr type=%u isn't supported.\n", type);
return -1;
case OAKLEY_ATTR_GRP_ORDER:
break;
case OAKLEY_ATTR_GSS_ID:
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid attribute type %d.\n", type);
return -1;
}
if (flag) {
tlen -= sizeof(*d);
d = (struct isakmp_data *)((char *)d
+ sizeof(*d));
} else {
tlen -= (sizeof(*d) + lorv);
d = (struct isakmp_data *)((char *)d
+ sizeof(*d) + lorv);
}
}
return 0;
}
/*
* check data attributes in IPSEC AH/ESP.
*/
static int
check_attr_ah(trns)
struct isakmp_pl_t *trns;
{
return check_attr_ipsec(IPSECDOI_PROTO_IPSEC_AH, trns);
}
static int
check_attr_esp(trns)
struct isakmp_pl_t *trns;
{
return check_attr_ipsec(IPSECDOI_PROTO_IPSEC_ESP, trns);
}
static int
check_attr_ipsec(proto_id, trns)
int proto_id;
struct isakmp_pl_t *trns;
{
struct isakmp_data *d;
int tlen;
int flag, type = 0;
u_int16_t lorv;
int attrseen[16]; /* XXX magic number */
tlen = ntohs(trns->h.len) - sizeof(struct isakmp_pl_t);
d = (struct isakmp_data *)((caddr_t)trns + sizeof(struct isakmp_pl_t));
memset(attrseen, 0, sizeof(attrseen));
while (tlen > 0) {
type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
flag = ntohs(d->type) & ISAKMP_GEN_MASK;
lorv = ntohs(d->lorv);
plog(LLV_DEBUG, LOCATION, NULL,
"type=%s, flag=0x%04x, lorv=%s\n",
s_ipsecdoi_attr(type), flag,
s_ipsecdoi_attr_v(type, lorv));
if (type < sizeof(attrseen)/sizeof(attrseen[0]))
attrseen[type]++;
switch (type) {
case IPSECDOI_ATTR_ENC_MODE:
if (! flag) {
plog(LLV_ERROR, LOCATION, NULL,
"must be TV when ENC_MODE.\n");
return -1;
}
switch (lorv) {
case IPSECDOI_ATTR_ENC_MODE_TUNNEL:
case IPSECDOI_ATTR_ENC_MODE_TRNS:
break;
#ifdef ENABLE_NATT
case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_RFC:
case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_RFC:
case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_DRAFT:
case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_DRAFT:
plog(LLV_DEBUG, LOCATION, NULL,
"UDP encapsulation requested\n");
break;
#endif
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid encryption mode=%u.\n",
lorv);
return -1;
}
break;
case IPSECDOI_ATTR_AUTH:
if (! flag) {
plog(LLV_ERROR, LOCATION, NULL,
"must be TV when AUTH.\n");
return -1;
}
switch (lorv) {
case IPSECDOI_ATTR_AUTH_HMAC_MD5:
if (proto_id == IPSECDOI_PROTO_IPSEC_AH &&
trns->t_id != IPSECDOI_AH_MD5) {
ahmismatch:
plog(LLV_ERROR, LOCATION, NULL,
"auth algorithm %u conflicts "
"with transform %u.\n",
lorv, trns->t_id);
return -1;
}
break;
case IPSECDOI_ATTR_AUTH_HMAC_SHA1:
if (proto_id == IPSECDOI_PROTO_IPSEC_AH) {
if (trns->t_id != IPSECDOI_AH_SHA)
goto ahmismatch;
}
break;
case IPSECDOI_ATTR_AUTH_HMAC_SHA2_256:
if (proto_id == IPSECDOI_PROTO_IPSEC_AH) {
if (trns->t_id != IPSECDOI_AH_SHA256)
goto ahmismatch;
}
break;
case IPSECDOI_ATTR_AUTH_HMAC_SHA2_384:
if (proto_id == IPSECDOI_PROTO_IPSEC_AH) {
if (trns->t_id != IPSECDOI_AH_SHA384)
goto ahmismatch;
}
break;
case IPSECDOI_ATTR_AUTH_HMAC_SHA2_512:
if (proto_id == IPSECDOI_PROTO_IPSEC_AH) {
if (trns->t_id != IPSECDOI_AH_SHA512)
goto ahmismatch;
}
break;
case IPSECDOI_ATTR_AUTH_DES_MAC:
case IPSECDOI_ATTR_AUTH_KPDK:
plog(LLV_ERROR, LOCATION, NULL,
"auth algorithm %u isn't supported.\n",
lorv);
return -1;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid auth algorithm=%u.\n",
lorv);
return -1;
}
break;
case IPSECDOI_ATTR_SA_LD_TYPE:
if (! flag) {
plog(LLV_ERROR, LOCATION, NULL,
"must be TV when LD_TYPE.\n");
return -1;
}
switch (lorv) {
case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
case IPSECDOI_ATTR_SA_LD_TYPE_KB:
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid life type %d.\n", lorv);
return -1;
}
break;
case IPSECDOI_ATTR_SA_LD:
if (flag) {
/* i.e. ISAKMP_GEN_TV */
plog(LLV_DEBUG, LOCATION, NULL,
"life duration was in TLV.\n");
} else {
/* i.e. ISAKMP_GEN_TLV */
if (lorv == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid length of LD\n");
return -1;
}
}
break;
case IPSECDOI_ATTR_GRP_DESC:
if (! flag) {
plog(LLV_ERROR, LOCATION, NULL,
"must be TV when GRP_DESC.\n");
return -1;
}
if (!alg_oakley_dhdef_ok(lorv)) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid group description=%u.\n",
lorv);
return -1;
}
break;
case IPSECDOI_ATTR_KEY_LENGTH:
if (! flag) {
plog(LLV_ERROR, LOCATION, NULL,
"must be TV when KEY_LENGTH.\n");
return -1;
}
break;
#ifdef HAVE_SECCTX
case IPSECDOI_ATTR_SECCTX:
if (flag) {
plog(LLV_ERROR, LOCATION, NULL,
"SECCTX must be in TLV.\n");
return -1;
}
break;
#endif
case IPSECDOI_ATTR_KEY_ROUNDS:
case IPSECDOI_ATTR_COMP_DICT_SIZE:
case IPSECDOI_ATTR_COMP_PRIVALG:
plog(LLV_ERROR, LOCATION, NULL,
"attr type=%u isn't supported.\n", type);
return -1;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid attribute type %d.\n", type);
return -1;
}
if (flag) {
tlen -= sizeof(*d);
d = (struct isakmp_data *)((char *)d
+ sizeof(*d));
} else {
tlen -= (sizeof(*d) + lorv);
d = (struct isakmp_data *)((caddr_t)d
+ sizeof(*d) + lorv);
}
}
if (proto_id == IPSECDOI_PROTO_IPSEC_AH &&
!attrseen[IPSECDOI_ATTR_AUTH]) {
plog(LLV_ERROR, LOCATION, NULL,
"attr AUTH must be present for AH.\n");
return -1;
}
if (proto_id == IPSECDOI_PROTO_IPSEC_ESP &&
trns->t_id == IPSECDOI_ESP_NULL &&
!attrseen[IPSECDOI_ATTR_AUTH]) {
plog(LLV_ERROR, LOCATION, NULL,
"attr AUTH must be present for ESP NULL encryption.\n");
return -1;
}
return 0;
}
static int
check_attr_ipcomp(trns)
struct isakmp_pl_t *trns;
{
struct isakmp_data *d;
int tlen;
int flag, type = 0;
u_int16_t lorv;
int attrseen[16]; /* XXX magic number */
tlen = ntohs(trns->h.len) - sizeof(struct isakmp_pl_t);
d = (struct isakmp_data *)((caddr_t)trns + sizeof(struct isakmp_pl_t));
memset(attrseen, 0, sizeof(attrseen));
while (tlen > 0) {
type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
flag = ntohs(d->type) & ISAKMP_GEN_MASK;
lorv = ntohs(d->lorv);
plog(LLV_DEBUG, LOCATION, NULL,
"type=%d, flag=0x%04x, lorv=0x%04x\n",
type, flag, lorv);
if (type < sizeof(attrseen)/sizeof(attrseen[0]))
attrseen[type]++;
switch (type) {
case IPSECDOI_ATTR_ENC_MODE:
if (! flag) {
plog(LLV_ERROR, LOCATION, NULL,
"must be TV when ENC_MODE.\n");
return -1;
}
switch (lorv) {
case IPSECDOI_ATTR_ENC_MODE_TUNNEL:
case IPSECDOI_ATTR_ENC_MODE_TRNS:
break;
#ifdef ENABLE_NATT
case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_RFC:
case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_RFC:
case IPSECDOI_ATTR_ENC_MODE_UDPTUNNEL_DRAFT:
case IPSECDOI_ATTR_ENC_MODE_UDPTRNS_DRAFT:
plog(LLV_DEBUG, LOCATION, NULL,
"UDP encapsulation requested\n");
break;
#endif
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid encryption mode=%u.\n",
lorv);
return -1;
}
break;
case IPSECDOI_ATTR_SA_LD_TYPE:
if (! flag) {
plog(LLV_ERROR, LOCATION, NULL,
"must be TV when LD_TYPE.\n");
return -1;
}
switch (lorv) {
case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
case IPSECDOI_ATTR_SA_LD_TYPE_KB:
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid life type %d.\n", lorv);
return -1;
}
break;
case IPSECDOI_ATTR_SA_LD:
if (flag) {
/* i.e. ISAKMP_GEN_TV */
plog(LLV_DEBUG, LOCATION, NULL,
"life duration was in TLV.\n");
} else {
/* i.e. ISAKMP_GEN_TLV */
if (lorv == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid length of LD\n");
return -1;
}
}
break;
case IPSECDOI_ATTR_GRP_DESC:
if (! flag) {
plog(LLV_ERROR, LOCATION, NULL,
"must be TV when GRP_DESC.\n");
return -1;
}
if (!alg_oakley_dhdef_ok(lorv)) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid group description=%u.\n",
lorv);
return -1;
}
break;
case IPSECDOI_ATTR_AUTH:
plog(LLV_ERROR, LOCATION, NULL,
"invalid attr type=%u.\n", type);
return -1;
case IPSECDOI_ATTR_KEY_LENGTH:
case IPSECDOI_ATTR_KEY_ROUNDS:
case IPSECDOI_ATTR_COMP_DICT_SIZE:
case IPSECDOI_ATTR_COMP_PRIVALG:
plog(LLV_ERROR, LOCATION, NULL,
"attr type=%u isn't supported.\n", type);
return -1;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid attribute type %d.\n", type);
return -1;
}
if (flag) {
tlen -= sizeof(*d);
d = (struct isakmp_data *)((char *)d
+ sizeof(*d));
} else {
tlen -= (sizeof(*d) + lorv);
d = (struct isakmp_data *)((caddr_t)d
+ sizeof(*d) + lorv);
}
}
#if 0
if (proto_id == IPSECDOI_PROTO_IPCOMP &&
!attrseen[IPSECDOI_ATTR_AUTH]) {
plog(LLV_ERROR, LOCATION, NULL,
"attr AUTH must be present for AH.\n", type);
return -1;
}
#endif
return 0;
}
/* %%% */
/*
* create phase1 proposal from remote configuration.
* NOT INCLUDING isakmp general header of SA payload
*/
vchar_t *
ipsecdoi_setph1proposal(rmconf, props)
struct remoteconf *rmconf;
struct isakmpsa *props;
{
vchar_t *mysa;
int sablen;
/* count total size of SA minus isakmp general header */
/* not including isakmp general header of SA payload */
sablen = sizeof(struct ipsecdoi_sa_b);
sablen += setph1prop(props, NULL);
mysa = vmalloc(sablen);
if (mysa == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to allocate my sa buffer\n");
return NULL;
}
/* create SA payload */
/* not including isakmp general header */
((struct ipsecdoi_sa_b *)mysa->v)->doi = htonl(rmconf->doitype);
((struct ipsecdoi_sa_b *)mysa->v)->sit = htonl(rmconf->sittype);
(void)setph1prop(props, mysa->v + sizeof(struct ipsecdoi_sa_b));
return mysa;
}
static int
setph1prop(props, buf)
struct isakmpsa *props;
caddr_t buf;
{
struct isakmp_pl_p *prop = NULL;
struct isakmpsa *s = NULL;
int proplen, trnslen;
u_int8_t *np_t; /* pointer next trns type in previous header */
int trns_num;
caddr_t p = buf;
proplen = sizeof(*prop);
if (buf) {
/* create proposal */
prop = (struct isakmp_pl_p *)p;
prop->h.np = ISAKMP_NPTYPE_NONE;
prop->p_no = props->prop_no;
prop->proto_id = IPSECDOI_PROTO_ISAKMP;
prop->spi_size = 0;
p += sizeof(*prop);
}
np_t = NULL;
trns_num = 0;
for (s = props; s != NULL; s = s->next) {
if (np_t)
*np_t = ISAKMP_NPTYPE_T;
trnslen = setph1trns(s, p);
proplen += trnslen;
if (buf) {
/* save buffer to pre-next payload */
np_t = &((struct isakmp_pl_t *)p)->h.np;
p += trnslen;
/* count up transform length */
trns_num++;
}
}
/* update proposal length */
if (buf) {
prop->h.len = htons(proplen);
prop->num_t = trns_num;
}
return proplen;
}
static int
setph1trns(sa, buf)
struct isakmpsa *sa;
caddr_t buf;
{
struct isakmp_pl_t *trns = NULL;
int trnslen, attrlen;
caddr_t p = buf;
trnslen = sizeof(*trns);
if (buf) {
/* create transform */
trns = (struct isakmp_pl_t *)p;
trns->h.np = ISAKMP_NPTYPE_NONE;
trns->t_no = sa->trns_no;
trns->t_id = IPSECDOI_KEY_IKE;
p += sizeof(*trns);
}
attrlen = setph1attr(sa, p);
trnslen += attrlen;
if (buf)
p += attrlen;
if (buf)
trns->h.len = htons(trnslen);
return trnslen;
}
static int
setph1attr(sa, buf)
struct isakmpsa *sa;
caddr_t buf;
{
caddr_t p = buf;
int attrlen = 0;
if (sa->lifetime) {
u_int32_t lifetime = htonl((u_int32_t)sa->lifetime);
attrlen += sizeof(struct isakmp_data)
+ sizeof(struct isakmp_data);
if (sa->lifetime > 0xffff)
attrlen += sizeof(lifetime);
if (buf) {
p = isakmp_set_attr_l(p, OAKLEY_ATTR_SA_LD_TYPE,
OAKLEY_ATTR_SA_LD_TYPE_SEC);
if (sa->lifetime > 0xffff) {
p = isakmp_set_attr_v(p, OAKLEY_ATTR_SA_LD,
(caddr_t)&lifetime,
sizeof(lifetime));
} else {
p = isakmp_set_attr_l(p, OAKLEY_ATTR_SA_LD,
sa->lifetime);
}
}
}
if (sa->lifebyte) {
u_int32_t lifebyte = htonl((u_int32_t)sa->lifebyte);
attrlen += sizeof(struct isakmp_data)
+ sizeof(struct isakmp_data);
if (sa->lifebyte > 0xffff)
attrlen += sizeof(lifebyte);
if (buf) {
p = isakmp_set_attr_l(p, OAKLEY_ATTR_SA_LD_TYPE,
OAKLEY_ATTR_SA_LD_TYPE_KB);
if (sa->lifebyte > 0xffff) {
p = isakmp_set_attr_v(p, OAKLEY_ATTR_SA_LD,
(caddr_t)&lifebyte,
sizeof(lifebyte));
} else {
p = isakmp_set_attr_l(p, OAKLEY_ATTR_SA_LD,
sa->lifebyte);
}
}
}
if (sa->enctype) {
attrlen += sizeof(struct isakmp_data);
if (buf)
p = isakmp_set_attr_l(p, OAKLEY_ATTR_ENC_ALG, sa->enctype);
}
if (sa->encklen) {
attrlen += sizeof(struct isakmp_data);
if (buf)
p = isakmp_set_attr_l(p, OAKLEY_ATTR_KEY_LEN, sa->encklen);
}
if (sa->authmethod) {
int authmethod;
authmethod = isakmpsa_switch_authmethod(sa->authmethod);
authmethod &= 0xffff;
attrlen += sizeof(struct isakmp_data);
if (buf)
p = isakmp_set_attr_l(p, OAKLEY_ATTR_AUTH_METHOD, authmethod);
}
if (sa->hashtype) {
attrlen += sizeof(struct isakmp_data);
if (buf)
p = isakmp_set_attr_l(p, OAKLEY_ATTR_HASH_ALG, sa->hashtype);
}
switch (sa->dh_group) {
case OAKLEY_ATTR_GRP_DESC_MODP768:
case OAKLEY_ATTR_GRP_DESC_MODP1024:
case OAKLEY_ATTR_GRP_DESC_MODP1536:
case OAKLEY_ATTR_GRP_DESC_MODP2048:
case OAKLEY_ATTR_GRP_DESC_MODP3072:
case OAKLEY_ATTR_GRP_DESC_MODP4096:
case OAKLEY_ATTR_GRP_DESC_MODP6144:
case OAKLEY_ATTR_GRP_DESC_MODP8192:
/* don't attach group type for known groups */
attrlen += sizeof(struct isakmp_data);
if (buf) {
p = isakmp_set_attr_l(p, OAKLEY_ATTR_GRP_DESC,
sa->dh_group);
}
break;
case OAKLEY_ATTR_GRP_DESC_EC2N155:
case OAKLEY_ATTR_GRP_DESC_EC2N185:
/* don't attach group type for known groups */
attrlen += sizeof(struct isakmp_data);
if (buf) {
p = isakmp_set_attr_l(p, OAKLEY_ATTR_GRP_TYPE,
OAKLEY_ATTR_GRP_TYPE_EC2N);
}
break;
case 0:
default:
break;
}
#ifdef HAVE_GSSAPI
if (sa->authmethod == OAKLEY_ATTR_AUTH_METHOD_GSSAPI_KRB &&
sa->gssid != NULL) {
attrlen += sizeof(struct isakmp_data);
/*
* Older versions of racoon just placed the ISO-Latin-1
* string on the wire directly. Check to see if we are
* configured to be compatible with this behavior. Otherwise,
* we encode the GSS ID as UTF-16LE for Windows 2000
* compatibility, which requires twice the number of octets.
*/
if (lcconf->gss_id_enc == LC_GSSENC_LATIN1)
attrlen += sa->gssid->l;
else
attrlen += sa->gssid->l * 2;
if (buf) {
plog(LLV_DEBUG, LOCATION, NULL, "gss id attr: len %zu, "
"val '%.*s'\n", sa->gssid->l, (int)sa->gssid->l,
sa->gssid->v);
if (lcconf->gss_id_enc == LC_GSSENC_LATIN1) {
p = isakmp_set_attr_v(p, OAKLEY_ATTR_GSS_ID,
(caddr_t)sa->gssid->v,
sa->gssid->l);
} else {
size_t dstleft = sa->gssid->l * 2;
size_t srcleft = sa->gssid->l;
const char *src = (const char *)sa->gssid->v;
char *odst, *dst = racoon_malloc(dstleft);
iconv_t cd;
size_t rv;
cd = iconv_open("utf-16le", "latin1");
if (cd == (iconv_t) -1) {
plog(LLV_ERROR, LOCATION, NULL,
"unable to initialize "
"latin1 -> utf-16le "
"converstion descriptor: %s\n",
strerror(errno));
attrlen -= sa->gssid->l * 2;
goto gssid_done;
}
odst = dst;
rv = iconv(cd, (__iconv_const char **)&src,
&srcleft, &dst, &dstleft);
if (rv != 0) {
if (rv == -1) {
plog(LLV_ERROR, LOCATION, NULL,
"unable to convert GSS ID "
"from latin1 -> utf-16le: "
"%s\n", strerror(errno));
} else {
/* should never happen */
plog(LLV_ERROR, LOCATION, NULL,
"%zd character%s in GSS ID "
"cannot be represented "
"in utf-16le\n",
rv, rv == 1 ? "" : "s");
}
(void) iconv_close(cd);
attrlen -= sa->gssid->l * 2;
goto gssid_done;
}
(void) iconv_close(cd);
/* XXX Check srcleft and dstleft? */
p = isakmp_set_attr_v(p, OAKLEY_ATTR_GSS_ID,
odst, sa->gssid->l * 2);
racoon_free(odst);
}
}
}
gssid_done:
#endif /* HAVE_GSSAPI */
return attrlen;
}
static vchar_t *
setph2proposal0(iph2, pp, pr)
const struct ph2handle *iph2;
const struct saprop *pp;
const struct saproto *pr;
{
vchar_t *p;
struct isakmp_pl_p *prop;
struct isakmp_pl_t *trns;
struct satrns *tr;
int attrlen;
size_t trnsoff;
caddr_t x0, x;
u_int8_t *np_t; /* pointer next trns type in previous header */
const u_int8_t *spi;
#ifdef HAVE_SECCTX
int truectxlen = 0;
#endif
p = vmalloc(sizeof(*prop) + sizeof(pr->spi));
if (p == NULL)
return NULL;
/* create proposal */
prop = (struct isakmp_pl_p *)p->v;
prop->h.np = ISAKMP_NPTYPE_NONE;
prop->p_no = pp->prop_no;
prop->proto_id = pr->proto_id;
prop->num_t = 1;
spi = (const u_int8_t *)&pr->spi;
switch (pr->proto_id) {
case IPSECDOI_PROTO_IPCOMP:
/*
* draft-shacham-ippcp-rfc2393bis-05.txt:
* construct 16bit SPI (CPI).
* XXX we may need to provide a configuration option to
* generate 32bit SPI. otherwise we cannot interoeprate
* with nodes that uses 32bit SPI, in case we are initiator.
*/
prop->spi_size = sizeof(u_int16_t);
spi += sizeof(pr->spi) - sizeof(u_int16_t);
p->l -= sizeof(pr->spi);
p->l += sizeof(u_int16_t);
break;
default:
prop->spi_size = sizeof(pr->spi);
break;
}
memcpy(prop + 1, spi, prop->spi_size);
/* create transform */
trnsoff = sizeof(*prop) + prop->spi_size;
np_t = NULL;
for (tr = pr->head; tr; tr = tr->next) {
switch (pr->proto_id) {
case IPSECDOI_PROTO_IPSEC_ESP:
/*
* don't build a null encryption
* with no authentication transform.
*/
if (tr->trns_id == IPSECDOI_ESP_NULL &&
tr->authtype == IPSECDOI_ATTR_AUTH_NONE)
continue;
break;
}
if (np_t) {
*np_t = ISAKMP_NPTYPE_T;
prop->num_t++;
}
/* get attribute length */
attrlen = 0;
if (pp->lifetime) {
attrlen += sizeof(struct isakmp_data)
+ sizeof(struct isakmp_data);
if (pp->lifetime > 0xffff)
attrlen += sizeof(u_int32_t);
}
if (pp->lifebyte && pp->lifebyte != IPSECDOI_ATTR_SA_LD_KB_MAX) {
attrlen += sizeof(struct isakmp_data)
+ sizeof(struct isakmp_data);
if (pp->lifebyte > 0xffff)
attrlen += sizeof(u_int32_t);
}
attrlen += sizeof(struct isakmp_data); /* enc mode */
if (tr->encklen)
attrlen += sizeof(struct isakmp_data);
switch (pr->proto_id) {
case IPSECDOI_PROTO_IPSEC_ESP:
/* non authentication mode ? */
if (tr->authtype != IPSECDOI_ATTR_AUTH_NONE)
attrlen += sizeof(struct isakmp_data);
break;
case IPSECDOI_PROTO_IPSEC_AH:
if (tr->authtype == IPSECDOI_ATTR_AUTH_NONE) {
plog(LLV_ERROR, LOCATION, NULL,
"no authentication algorithm found "
"but protocol is AH.\n");
vfree(p);
return NULL;
}
attrlen += sizeof(struct isakmp_data);
break;
case IPSECDOI_PROTO_IPCOMP:
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid protocol: %d\n", pr->proto_id);
vfree(p);
return NULL;
}
if (alg_oakley_dhdef_ok(iph2->sainfo->pfs_group))
attrlen += sizeof(struct isakmp_data);
#ifdef HAVE_SECCTX
/* ctx_str is defined as char ctx_str[MAX_CTXSTR_SIZ].
* The string may be smaller than MAX_CTXSTR_SIZ.
*/
if (*pp->sctx.ctx_str) {
truectxlen = sizeof(struct security_ctx) -
(MAX_CTXSTR_SIZE - pp->sctx.ctx_strlen);
attrlen += sizeof(struct isakmp_data) + truectxlen;
}
#endif /* HAVE_SECCTX */
p = vrealloc(p, p->l + sizeof(*trns) + attrlen);
if (p == NULL)
return NULL;
prop = (struct isakmp_pl_p *)p->v;
/* set transform's values */
trns = (struct isakmp_pl_t *)(p->v + trnsoff);
trns->h.np = ISAKMP_NPTYPE_NONE;
trns->t_no = tr->trns_no;
trns->t_id = tr->trns_id;
/* set attributes */
x = x0 = p->v + trnsoff + sizeof(*trns);
if (pp->lifetime) {
x = isakmp_set_attr_l(x, IPSECDOI_ATTR_SA_LD_TYPE,
IPSECDOI_ATTR_SA_LD_TYPE_SEC);
if (pp->lifetime > 0xffff) {
u_int32_t v = htonl((u_int32_t)pp->lifetime);
x = isakmp_set_attr_v(x, IPSECDOI_ATTR_SA_LD,
(caddr_t)&v, sizeof(v));
} else {
x = isakmp_set_attr_l(x, IPSECDOI_ATTR_SA_LD,
pp->lifetime);
}
}
if (pp->lifebyte && pp->lifebyte != IPSECDOI_ATTR_SA_LD_KB_MAX) {
x = isakmp_set_attr_l(x, IPSECDOI_ATTR_SA_LD_TYPE,
IPSECDOI_ATTR_SA_LD_TYPE_KB);
if (pp->lifebyte > 0xffff) {
u_int32_t v = htonl((u_int32_t)pp->lifebyte);
x = isakmp_set_attr_v(x, IPSECDOI_ATTR_SA_LD,
(caddr_t)&v, sizeof(v));
} else {
x = isakmp_set_attr_l(x, IPSECDOI_ATTR_SA_LD,
pp->lifebyte);
}
}
x = isakmp_set_attr_l(x, IPSECDOI_ATTR_ENC_MODE, pr->encmode);
if (tr->encklen)
x = isakmp_set_attr_l(x, IPSECDOI_ATTR_KEY_LENGTH, tr->encklen);
/* mandatory check has done above. */
if ((pr->proto_id == IPSECDOI_PROTO_IPSEC_ESP && tr->authtype != IPSECDOI_ATTR_AUTH_NONE)
|| pr->proto_id == IPSECDOI_PROTO_IPSEC_AH)
x = isakmp_set_attr_l(x, IPSECDOI_ATTR_AUTH, tr->authtype);
if (alg_oakley_dhdef_ok(iph2->sainfo->pfs_group))
x = isakmp_set_attr_l(x, IPSECDOI_ATTR_GRP_DESC,
iph2->sainfo->pfs_group);
#ifdef HAVE_SECCTX
if (*pp->sctx.ctx_str) {
struct security_ctx secctx;
secctx = pp->sctx;
secctx.ctx_strlen = htons(pp->sctx.ctx_strlen);
x = isakmp_set_attr_v(x, IPSECDOI_ATTR_SECCTX,
(caddr_t)&secctx, truectxlen);
}
#endif
/* update length of this transform. */
trns = (struct isakmp_pl_t *)(p->v + trnsoff);
trns->h.len = htons(sizeof(*trns) + attrlen);
/* save buffer to pre-next payload */
np_t = &trns->h.np;
trnsoff += (sizeof(*trns) + attrlen);
}
if (np_t == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"no suitable proposal was created.\n");
return NULL;
}
/* update length of this protocol. */
prop->h.len = htons(p->l);
return p;
}
/*
* create phase2 proposal from policy configuration.
* NOT INCLUDING isakmp general header of SA payload.
* This function is called by initiator only.
*/
int
ipsecdoi_setph2proposal(iph2)
struct ph2handle *iph2;
{
struct saprop *proposal, *a;
struct saproto *b = NULL;
vchar_t *q;
struct ipsecdoi_sa_b *sab;
struct isakmp_pl_p *prop;
size_t propoff; /* for previous field of type of next payload. */
proposal = iph2->proposal;
iph2->sa = vmalloc(sizeof(*sab));
if (iph2->sa == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to allocate my sa buffer\n");
return -1;
}
/* create SA payload */
sab = (struct ipsecdoi_sa_b *)iph2->sa->v;
sab->doi = htonl(IPSEC_DOI);
sab->sit = htonl(IPSECDOI_SIT_IDENTITY_ONLY); /* XXX configurable ? */
prop = NULL;
propoff = 0;
for (a = proposal; a; a = a->next) {
for (b = a->head; b; b = b->next) {
#ifdef ENABLE_NATT
if (iph2->ph1->natt_flags & NAT_DETECTED) {
int udp_diff = iph2->ph1->natt_options->mode_udp_diff;
plog (LLV_INFO, LOCATION, NULL,
"NAT detected -> UDP encapsulation "
"(ENC_MODE %d->%d).\n",
b->encmode,
b->encmode+udp_diff);
/* Tunnel -> UDP-Tunnel, Transport -> UDP_Transport */
b->encmode += udp_diff;
b->udp_encap = 1;
}
#endif
q = setph2proposal0(iph2, a, b);
if (q == NULL) {
VPTRINIT(iph2->sa);
return -1;
}
iph2->sa = vrealloc(iph2->sa, iph2->sa->l + q->l);
if (iph2->sa == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to allocate my sa buffer\n");
if (q)
vfree(q);
return -1;
}
memcpy(iph2->sa->v + iph2->sa->l - q->l, q->v, q->l);
if (propoff != 0) {
prop = (struct isakmp_pl_p *)(iph2->sa->v +
propoff);
prop->h.np = ISAKMP_NPTYPE_P;
}
propoff = iph2->sa->l - q->l;
vfree(q);
}
}
return 0;
}
/*
* return 1 if all of the given protocols are transport mode.
*/
int
ipsecdoi_transportmode(pp)
struct saprop *pp;
{
struct saproto *pr = NULL;
for (; pp; pp = pp->next) {
for (pr = pp->head; pr; pr = pr->next) {
if (pr->encmode != IPSECDOI_ATTR_ENC_MODE_TRNS &&
pr->encmode != IPSECDOI_ATTR_ENC_MODE_UDPTRNS_RFC &&
pr->encmode != IPSECDOI_ATTR_ENC_MODE_UDPTRNS_DRAFT)
return 0;
}
}
return 1;
}
int
ipsecdoi_get_defaultlifetime()
{
return IPSECDOI_ATTR_SA_LD_SEC_DEFAULT;
}
int
ipsecdoi_checkalgtypes(proto_id, enc, auth, comp)
int proto_id, enc, auth, comp;
{
#define TMPALGTYPE2STR(n) s_algtype(algclass_ipsec_##n, n)
switch (proto_id) {
case IPSECDOI_PROTO_IPSEC_ESP:
if (enc == 0 || comp != 0) {
plog(LLV_ERROR, LOCATION, NULL,
"illegal algorithm defined "
"ESP enc=%s auth=%s comp=%s.\n",
TMPALGTYPE2STR(enc),
TMPALGTYPE2STR(auth),
TMPALGTYPE2STR(comp));
return -1;
}
break;
case IPSECDOI_PROTO_IPSEC_AH:
if (enc != 0 || auth == 0 || comp != 0) {
plog(LLV_ERROR, LOCATION, NULL,
"illegal algorithm defined "
"AH enc=%s auth=%s comp=%s.\n",
TMPALGTYPE2STR(enc),
TMPALGTYPE2STR(auth),
TMPALGTYPE2STR(comp));
return -1;
}
break;
case IPSECDOI_PROTO_IPCOMP:
if (enc != 0 || auth != 0 || comp == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"illegal algorithm defined "
"IPcomp enc=%s auth=%s comp=%s.\n",
TMPALGTYPE2STR(enc),
TMPALGTYPE2STR(auth),
TMPALGTYPE2STR(comp));
return -1;
}
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid ipsec protocol %d\n", proto_id);
return -1;
}
#undef TMPALGTYPE2STR
return 0;
}
int
ipproto2doi(proto)
int proto;
{
switch (proto) {
case IPPROTO_AH:
return IPSECDOI_PROTO_IPSEC_AH;
case IPPROTO_ESP:
return IPSECDOI_PROTO_IPSEC_ESP;
case IPPROTO_IPCOMP:
return IPSECDOI_PROTO_IPCOMP;
}
return -1; /* XXX */
}
int
doi2ipproto(proto)
int proto;
{
switch (proto) {
case IPSECDOI_PROTO_IPSEC_AH:
return IPPROTO_AH;
case IPSECDOI_PROTO_IPSEC_ESP:
return IPPROTO_ESP;
case IPSECDOI_PROTO_IPCOMP:
return IPPROTO_IPCOMP;
}
return -1; /* XXX */
}
/*
* Check if a subnet id is valid for comparison
* with an address id ( address length mask )
* and compare them
* Return value
* = 0 for match
* = 1 for mismatch
*/
int
ipsecdoi_subnetisaddr_v4( subnet, address )
const vchar_t *subnet;
const vchar_t *address;
{
struct in_addr *mask;
if (address->l != sizeof(struct in_addr))
return 1;
if (subnet->l != (sizeof(struct in_addr)*2))
return 1;
mask = (struct in_addr*)(subnet->v + sizeof(struct in_addr));
if (mask->s_addr!=0xffffffff)
return 1;
return memcmp(subnet->v,address->v,address->l);
}
#ifdef INET6
int
ipsecdoi_subnetisaddr_v6( subnet, address )
const vchar_t *subnet;
const vchar_t *address;
{
struct in6_addr *mask;
int i;
if (address->l != sizeof(struct in6_addr))
return 1;
if (subnet->l != (sizeof(struct in6_addr)*2))
return 1;
mask = (struct in6_addr*)(subnet->v + sizeof(struct in6_addr));
for (i=0; i<16; i++)
if(mask->s6_addr[i]!=0xff)
return 1;
return memcmp(subnet->v,address->v,address->l);
}
#endif
/*
* Check and Compare two IDs
* - specify 0 for exact if wildcards are allowed
* Return value
* = 0 for match
* = 1 for misatch
* = -1 for integrity error
*/
int
ipsecdoi_chkcmpids( idt, ids, exact )
const vchar_t *idt; /* id cmp target */
const vchar_t *ids; /* id cmp source */
int exact;
{
struct ipsecdoi_id_b *id_bt;
struct ipsecdoi_id_b *id_bs;
vchar_t ident_t;
vchar_t ident_s;
int result;
/* handle wildcard IDs */
if (idt == NULL || ids == NULL)
{
if( !exact )
{
plog(LLV_DEBUG, LOCATION, NULL,
"check and compare ids : values matched (ANONYMOUS)\n" );
return 0;
}
else
{
plog(LLV_DEBUG, LOCATION, NULL,
"check and compare ids : value mismatch (ANONYMOUS)\n" );
return -1;
}
}
/* make sure the ids are of the same type */
id_bt = (struct ipsecdoi_id_b *) idt->v;
id_bs = (struct ipsecdoi_id_b *) ids->v;
ident_t.v = idt->v + sizeof(*id_bt);
ident_t.l = idt->l - sizeof(*id_bt);
ident_s.v = ids->v + sizeof(*id_bs);
ident_s.l = ids->l - sizeof(*id_bs);
if (id_bs->type != id_bt->type)
{
/*
* special exception for comparing
* address to subnet id types when
* the netmask is address length
*/
if ((id_bs->type == IPSECDOI_ID_IPV4_ADDR)&&
(id_bt->type == IPSECDOI_ID_IPV4_ADDR_SUBNET)) {
result = ipsecdoi_subnetisaddr_v4(&ident_t,&ident_s);
goto cmpid_result;
}
if ((id_bs->type == IPSECDOI_ID_IPV4_ADDR_SUBNET)&&
(id_bt->type == IPSECDOI_ID_IPV4_ADDR)) {
result = ipsecdoi_subnetisaddr_v4(&ident_s,&ident_t);
goto cmpid_result;
}
#ifdef INET6
if ((id_bs->type == IPSECDOI_ID_IPV6_ADDR)&&
(id_bt->type == IPSECDOI_ID_IPV6_ADDR_SUBNET)) {
result = ipsecdoi_subnetisaddr_v6(&ident_t,&ident_s);
goto cmpid_result;
}
if ((id_bs->type == IPSECDOI_ID_IPV6_ADDR_SUBNET)&&
(id_bt->type == IPSECDOI_ID_IPV6_ADDR)) {
result = ipsecdoi_subnetisaddr_v6(&ident_s,&ident_t);
goto cmpid_result;
}
#endif
plog(LLV_DEBUG, LOCATION, NULL,
"check and compare ids : id type mismatch %s != %s\n",
s_ipsecdoi_ident(id_bs->type),
s_ipsecdoi_ident(id_bt->type));
return 1;
}
if(id_bs->proto_id != id_bt->proto_id){
plog(LLV_DEBUG, LOCATION, NULL,
"check and compare ids : proto_id mismatch %d != %d\n",
id_bs->proto_id, id_bt->proto_id);
return 1;
}
/* compare the ID data. */
switch (id_bt->type) {
case IPSECDOI_ID_DER_ASN1_DN:
case IPSECDOI_ID_DER_ASN1_GN:
/* compare asn1 ids */
result = eay_cmp_asn1dn(&ident_t, &ident_s);
goto cmpid_result;
case IPSECDOI_ID_IPV4_ADDR:
/* validate lengths */
if ((ident_t.l != sizeof(struct in_addr))||
(ident_s.l != sizeof(struct in_addr)))
goto cmpid_invalid;
break;
case IPSECDOI_ID_IPV4_ADDR_SUBNET:
case IPSECDOI_ID_IPV4_ADDR_RANGE:
/* validate lengths */
if ((ident_t.l != (sizeof(struct in_addr)*2))||
(ident_s.l != (sizeof(struct in_addr)*2)))
goto cmpid_invalid;
break;
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR:
/* validate lengths */
if ((ident_t.l != sizeof(struct in6_addr))||
(ident_s.l != sizeof(struct in6_addr)))
goto cmpid_invalid;
break;
case IPSECDOI_ID_IPV6_ADDR_SUBNET:
case IPSECDOI_ID_IPV6_ADDR_RANGE:
/* validate lengths */
if ((ident_t.l != (sizeof(struct in6_addr)*2))||
(ident_s.l != (sizeof(struct in6_addr)*2)))
goto cmpid_invalid;
break;
#endif
case IPSECDOI_ID_FQDN:
case IPSECDOI_ID_USER_FQDN:
case IPSECDOI_ID_KEY_ID:
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"Unhandled id type %i specified for comparison\n",
id_bt->type);
return -1;
}
/* validate matching data and length */
if (ident_t.l == ident_s.l)
result = memcmp(ident_t.v,ident_s.v,ident_t.l);
else
result = 1;
cmpid_result:
/* debug level output */
if(loglevel >= LLV_DEBUG) {
char *idstrt = ipsecdoi_id2str(idt);
char *idstrs = ipsecdoi_id2str(ids);
if (!result)
plog(LLV_DEBUG, LOCATION, NULL,
"check and compare ids : values matched (%s)\n",
s_ipsecdoi_ident(id_bs->type) );
else
plog(LLV_DEBUG, LOCATION, NULL,
"check and compare ids : value mismatch (%s)\n",
s_ipsecdoi_ident(id_bs->type));
plog(LLV_DEBUG, LOCATION, NULL, "cmpid target: \'%s\'\n", idstrt );
plog(LLV_DEBUG, LOCATION, NULL, "cmpid source: \'%s\'\n", idstrs );
racoon_free(idstrs);
racoon_free(idstrt);
}
/* return result */
if( !result )
return 0;
else
return 1;
cmpid_invalid:
/* id integrity error */
plog(LLV_DEBUG, LOCATION, NULL, "check and compare ids : %s integrity error\n",
s_ipsecdoi_ident(id_bs->type));
plog(LLV_DEBUG, LOCATION, NULL, "cmpid target: length = \'%zu\'\n", ident_t.l );
plog(LLV_DEBUG, LOCATION, NULL, "cmpid source: length = \'%zu\'\n", ident_s.l );
return -1;
}
/*
* check the following:
* - In main mode with pre-shared key, only address type can be used.
* - if proper type for phase 1 ?
* - if phase 1 ID payload conformed RFC2407 4.6.2.
* (proto, port) must be (0, 0), (udp, 500) or (udp, [specified]).
* - if ID payload sent from peer is equal to the ID expected by me.
*
* both of "id" and "id_p" should be ID payload without general header,
*/
int
ipsecdoi_checkid1(iph1)
struct ph1handle *iph1;
{
struct ipsecdoi_id_b *id_b;
if (iph1->id_p == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid iph1 passed id_p == NULL\n");
return ISAKMP_INTERNAL_ERROR;
}
if (iph1->id_p->l < sizeof(*id_b)) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid value passed as \"ident\" (len=%lu)\n",
(u_long)iph1->id_p->l);
return ISAKMP_NTYPE_INVALID_ID_INFORMATION;
}
id_b = (struct ipsecdoi_id_b *)iph1->id_p->v;
/* In main mode with pre-shared key, only address type can be used. */
if (iph1->etype == ISAKMP_ETYPE_IDENT &&
iph1->approval->authmethod == OAKLEY_ATTR_AUTH_METHOD_PSKEY) {
if (id_b->type != IPSECDOI_ID_IPV4_ADDR
&& id_b->type != IPSECDOI_ID_IPV6_ADDR) {
plog(LLV_ERROR, LOCATION, NULL,
"Expecting IP address type in main mode, "
"but %s.\n", s_ipsecdoi_ident(id_b->type));
return ISAKMP_NTYPE_INVALID_ID_INFORMATION;
}
}
/* if proper type for phase 1 ? */
switch (id_b->type) {
case IPSECDOI_ID_IPV4_ADDR_SUBNET:
case IPSECDOI_ID_IPV6_ADDR_SUBNET:
case IPSECDOI_ID_IPV4_ADDR_RANGE:
case IPSECDOI_ID_IPV6_ADDR_RANGE:
plog(LLV_WARNING, LOCATION, NULL,
"such ID type %s is not proper.\n",
s_ipsecdoi_ident(id_b->type));
/*FALLTHROUGH*/
}
/* if phase 1 ID payload conformed RFC2407 4.6.2. */
if (id_b->type == IPSECDOI_ID_IPV4_ADDR ||
id_b->type == IPSECDOI_ID_IPV6_ADDR) {
if (id_b->proto_id == 0 && ntohs(id_b->port) != 0) {
plog(LLV_WARNING, LOCATION, NULL,
"protocol ID and Port mismatched. "
"proto_id:%d port:%d\n",
id_b->proto_id, ntohs(id_b->port));
/*FALLTHROUGH*/
} else if (id_b->proto_id == IPPROTO_UDP) {
/*
* copmaring with expecting port.
* always permit if port is equal to PORT_ISAKMP
*/
if (ntohs(id_b->port) != PORT_ISAKMP) {
u_int16_t port;
port = extract_port(iph1->remote);
if (ntohs(id_b->port) != port) {
plog(LLV_WARNING, LOCATION, NULL,
"port %d expected, but %d\n",
port, ntohs(id_b->port));
/*FALLTHROUGH*/
}
}
}
}
/* resolve remote configuration if not done yet */
if (resolveph1rmconf(iph1) < 0)
return ISAKMP_NTYPE_INVALID_ID_INFORMATION;
if (iph1->rmconf == NULL)
return ISAKMP_NTYPE_INVALID_ID_INFORMATION;
return 0;
}
/*
* create ID payload for phase 1 and set into iph1->id.
* NOT INCLUDING isakmp general header.
* see, RFC2407 4.6.2.1
*/
int
ipsecdoi_setid1(iph1)
struct ph1handle *iph1;
{
vchar_t *ret = NULL;
struct ipsecdoi_id_b id_b;
vchar_t *ident = NULL;
struct sockaddr *ipid = NULL;
/* init */
id_b.proto_id = 0;
id_b.port = 0;
ident = NULL;
switch (iph1->rmconf->idvtype) {
case IDTYPE_FQDN:
id_b.type = IPSECDOI_ID_FQDN;
ident = vdup(iph1->rmconf->idv);
break;
case IDTYPE_USERFQDN:
id_b.type = IPSECDOI_ID_USER_FQDN;
ident = vdup(iph1->rmconf->idv);
break;
case IDTYPE_KEYID:
id_b.type = IPSECDOI_ID_KEY_ID;
ident = vdup(iph1->rmconf->idv);
break;
case IDTYPE_ASN1DN:
id_b.type = IPSECDOI_ID_DER_ASN1_DN;
if (iph1->rmconf->idv) {
/* XXX it must be encoded to asn1dn. */
ident = vdup(iph1->rmconf->idv);
} else {
if (oakley_getmycert(iph1) < 0) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get own CERT.\n");
goto err;
}
ident = eay_get_x509asn1subjectname(iph1->cert);
}
break;
case IDTYPE_ADDRESS:
/*
* if the value of the id type was set by the configuration
* file, then use it. otherwise the value is get from local
* ip address by using ike negotiation.
*/
if (iph1->rmconf->idv)
ipid = (struct sockaddr *)iph1->rmconf->idv->v;
/*FALLTHROUGH*/
default:
{
int l;
caddr_t p;
if (ipid == NULL)
ipid = iph1->local;
/* use IP address */
switch (ipid->sa_family) {
case AF_INET:
id_b.type = IPSECDOI_ID_IPV4_ADDR;
l = sizeof(struct in_addr);
p = (caddr_t)&((struct sockaddr_in *)ipid)->sin_addr;
break;
#ifdef INET6
case AF_INET6:
id_b.type = IPSECDOI_ID_IPV6_ADDR;
l = sizeof(struct in6_addr);
p = (caddr_t)&((struct sockaddr_in6 *)ipid)->sin6_addr;
break;
#endif
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid address family.\n");
goto err;
}
id_b.proto_id = IPPROTO_UDP;
id_b.port = htons(PORT_ISAKMP);
ident = vmalloc(l);
if (!ident) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get ID buffer.\n");
return -1;
}
memcpy(ident->v, p, ident->l);
}
}
if (!ident) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get ID buffer.\n");
return -1;
}
ret = vmalloc(sizeof(id_b) + ident->l);
if (ret == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get ID buffer.\n");
goto err;
}
memcpy(ret->v, &id_b, sizeof(id_b));
memcpy(ret->v + sizeof(id_b), ident->v, ident->l);
iph1->id = ret;
plog(LLV_DEBUG, LOCATION, NULL,
"use ID type of %s\n", s_ipsecdoi_ident(id_b.type));
if (ident)
vfree(ident);
return 0;
err:
if (ident)
vfree(ident);
plog(LLV_ERROR, LOCATION, NULL, "failed get my ID\n");
return -1;
}
/* it's only called by cfparse.y. */
int
set_identifier(vpp, type, value)
vchar_t **vpp, *value;
int type;
{
return set_identifier_qual(vpp, type, value, IDQUAL_UNSPEC);
}
int
set_identifier_qual(vpp, type, value, qual)
vchar_t **vpp, *value;
int type;
int qual;
{
vchar_t *new = NULL;
/* simply return if value is null. */
if (!value){
if( type == IDTYPE_FQDN || type == IDTYPE_USERFQDN){
plog(LLV_ERROR, LOCATION, NULL,
"No %s\n", type == IDTYPE_FQDN ? "fqdn":"user fqdn");
return -1;
}
return 0;
}
switch (type) {
case IDTYPE_FQDN:
case IDTYPE_USERFQDN:
if(value->l <= 1){
plog(LLV_ERROR, LOCATION, NULL,
"Empty %s\n", type == IDTYPE_FQDN ? "fqdn":"user fqdn");
return -1;
}
/* length is adjusted since QUOTEDSTRING teminates NULL. */
new = vmalloc(value->l - 1);
if (new == NULL)
return -1;
memcpy(new->v, value->v, new->l);
break;
case IDTYPE_KEYID:
/*
* If no qualifier is specified: IDQUAL_UNSPEC. It means
* to use a file for backward compatibility sake.
*/
switch(qual) {
case IDQUAL_FILE:
case IDQUAL_UNSPEC: {
FILE *fp;
char b[512];
int tlen, len;
fp = fopen(value->v, "r");
if (fp == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"can not open %s\n", value->v);
return -1;
}
tlen = 0;
while ((len = fread(b, 1, sizeof(b), fp)) != 0) {
new = vrealloc(new, tlen + len);
if (!new) {
fclose(fp);
return -1;
}
memcpy(new->v + tlen, b, len);
tlen += len;
}
fclose(fp);
break;
}
case IDQUAL_TAG:
new = vmalloc(value->l - 1);
if (new == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"can not allocate memory");
return -1;
}
memcpy(new->v, value->v, new->l);
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"unknown qualifier");
return -1;
}
break;
case IDTYPE_ADDRESS: {
struct sockaddr *sa;
/* length is adjusted since QUOTEDSTRING teminates NULL. */
if (value->l == 0)
break;
sa = str2saddr(value->v, NULL);
if (sa == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid ip address %s\n", value->v);
return -1;
}
new = vmalloc(sysdep_sa_len(sa));
if (new == NULL) {
racoon_free(sa);
return -1;
}
memcpy(new->v, sa, new->l);
racoon_free(sa);
break;
}
case IDTYPE_ASN1DN:
if (value->v[0] == '~')
/* Hex-encoded ASN1 strings */
new = eay_hex2asn1dn(value->v + 1, - 1);
else
/* DN encoded strings */
new = eay_str2asn1dn(value->v, value->l - 1);
if (new == NULL)
return -1;
if (loglevel >= LLV_DEBUG) {
X509_NAME *xn;
BIO *bio;
unsigned char *ptr = (unsigned char *) new->v, *buf;
size_t len;
char save;
xn = d2i_X509_NAME(NULL, (void *)&ptr, new->l);
bio = BIO_new(BIO_s_mem());
X509_NAME_print_ex(bio, xn, 0, 0);
len = BIO_get_mem_data(bio, &ptr);
save = ptr[len];
ptr[len] = 0;
plog(LLV_DEBUG, LOCATION, NULL, "Parsed DN: %s\n", ptr);
ptr[len] = save;
X509_NAME_free(xn);
BIO_free(bio);
}
break;
}
*vpp = new;
return 0;
}
/*
* create ID payload for phase 2, and set into iph2->id and id_p. There are
* NOT INCLUDING isakmp general header.
* this function is for initiator. responder will get to copy from payload.
* responder ID type is always address type.
* see, RFC2407 4.6.2.1
*/
int
ipsecdoi_setid2(iph2)
struct ph2handle *iph2;
{
struct secpolicy *sp;
/* check there is phase 2 handler ? */
sp = getspbyspid(iph2->spid);
if (sp == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"no policy found for spid:%u.\n", iph2->spid);
return -1;
}
if (!ipsecdoi_transportmode(iph2->proposal))
iph2->id = ipsecdoi_sockaddr2id((struct sockaddr *)&sp->spidx.src,
sp->spidx.prefs, sp->spidx.ul_proto);
else if (iph2->sa_src != NULL) {
/* He have a specific hint indicating that the transport
* mode SA will be negotiated using addresses that differ
* with the one from the SA. We need to indicate that to
* our peer by setting the SA address as ID.
* This is typically the case for the bootstrapping of the
* transport mode SA protecting BU/BA for MIPv6 traffic
*
* --arno*/
iph2->id = ipsecdoi_sockaddr2id(iph2->sa_src,
IPSECDOI_PREFIX_HOST,
sp->spidx.ul_proto);
} else
iph2->id = ipsecdoi_sockaddr2id(iph2->src, IPSECDOI_PREFIX_HOST,
sp->spidx.ul_proto);
if (iph2->id == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get ID for %s\n",
spidx2str(&sp->spidx));
return -1;
}
plog(LLV_DEBUG, LOCATION, NULL, "use local ID type %s\n",
s_ipsecdoi_ident(((struct ipsecdoi_id_b *)iph2->id->v)->type));
/* remote side */
#ifdef ANDROID_PATCHED
if (1)
#else
if (!ipsecdoi_transportmode(iph2->proposal))
#endif
iph2->id_p = ipsecdoi_sockaddr2id((struct sockaddr *)&sp->spidx.dst,
sp->spidx.prefd, sp->spidx.ul_proto);
else if (iph2->sa_dst != NULL) {
/* See comment above for local side. */
iph2->id_p = ipsecdoi_sockaddr2id(iph2->sa_dst,
IPSECDOI_PREFIX_HOST,
sp->spidx.ul_proto);
} else
iph2->id_p = ipsecdoi_sockaddr2id(iph2->dst, IPSECDOI_PREFIX_HOST,
sp->spidx.ul_proto);
if (iph2->id_p == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get ID for %s\n",
spidx2str(&sp->spidx));
VPTRINIT(iph2->id);
return -1;
}
plog(LLV_DEBUG, LOCATION, NULL,
"use remote ID type %s\n",
s_ipsecdoi_ident(((struct ipsecdoi_id_b *)iph2->id_p->v)->type));
return 0;
}
/*
* set address type of ID.
* NOT INCLUDING general header.
*/
vchar_t *
ipsecdoi_sockaddr2id(saddr, prefixlen, ul_proto)
struct sockaddr *saddr;
u_int prefixlen;
u_int ul_proto;
{
vchar_t *new;
int type, len1, len2;
caddr_t sa;
u_short port;
/*
* Q. When type is SUBNET, is it allowed to be ::1/128.
* A. Yes. (consensus at bake-off)
*/
switch (saddr->sa_family) {
case AF_INET:
len1 = sizeof(struct in_addr);
if (prefixlen >= (sizeof(struct in_addr) << 3)) {
type = IPSECDOI_ID_IPV4_ADDR;
len2 = 0;
} else {
type = IPSECDOI_ID_IPV4_ADDR_SUBNET;
len2 = sizeof(struct in_addr);
}
sa = (caddr_t)&((struct sockaddr_in *)(saddr))->sin_addr;
port = ((struct sockaddr_in *)(saddr))->sin_port;
break;
#ifdef INET6
case AF_INET6:
len1 = sizeof(struct in6_addr);
if (prefixlen >= (sizeof(struct in6_addr) << 3)) {
type = IPSECDOI_ID_IPV6_ADDR;
len2 = 0;
} else {
type = IPSECDOI_ID_IPV6_ADDR_SUBNET;
len2 = sizeof(struct in6_addr);
}
sa = (caddr_t)&((struct sockaddr_in6 *)(saddr))->sin6_addr;
port = ((struct sockaddr_in6 *)(saddr))->sin6_port;
break;
#endif
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid family: %d.\n", saddr->sa_family);
return NULL;
}
/* get ID buffer */
new = vmalloc(sizeof(struct ipsecdoi_id_b) + len1 + len2);
if (new == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get ID buffer.\n");
return NULL;
}
memset(new->v, 0, new->l);
/* set the part of header. */
((struct ipsecdoi_id_b *)new->v)->type = type;
/* set ul_proto and port */
/*
* NOTE: we use both IPSEC_ULPROTO_ANY and IPSEC_PORT_ANY as wild card
* because 0 means port number of 0. Instead of 0, we use IPSEC_*_ANY.
*/
((struct ipsecdoi_id_b *)new->v)->proto_id =
ul_proto == IPSEC_ULPROTO_ANY ? 0 : ul_proto;
((struct ipsecdoi_id_b *)new->v)->port =
port == IPSEC_PORT_ANY ? 0 : port;
memcpy(new->v + sizeof(struct ipsecdoi_id_b), sa, len1);
/* set address */
/* set prefix */
if (len2) {
u_char *p = (unsigned char *) new->v +
sizeof(struct ipsecdoi_id_b) + len1;
u_int bits = prefixlen;
while (bits >= 8) {
*p++ = 0xff;
bits -= 8;
}
if (bits > 0)
*p = ~((1 << (8 - bits)) - 1);
}
return new;
}
vchar_t *
ipsecdoi_sockrange2id(laddr, haddr, ul_proto)
struct sockaddr *laddr, *haddr;
u_int ul_proto;
{
vchar_t *new;
int type, len1, len2;
u_short port;
if (laddr->sa_family != haddr->sa_family) {
plog(LLV_ERROR, LOCATION, NULL, "Address family mismatch\n");
return NULL;
}
switch (laddr->sa_family) {
case AF_INET:
type = IPSECDOI_ID_IPV4_ADDR_RANGE;
len1 = sizeof(struct in_addr);
len2 = sizeof(struct in_addr);
break;
#ifdef INET6
case AF_INET6:
type = IPSECDOI_ID_IPV6_ADDR_RANGE;
len1 = sizeof(struct in6_addr);
len2 = sizeof(struct in6_addr);
break;
#endif
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid family: %d.\n", laddr->sa_family);
return NULL;
}
/* get ID buffer */
new = vmalloc(sizeof(struct ipsecdoi_id_b) + len1 + len2);
if (new == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get ID buffer.\n");
return NULL;
}
memset(new->v, 0, new->l);
/* set the part of header. */
((struct ipsecdoi_id_b *)new->v)->type = type;
/* set ul_proto and port */
/*
* NOTE: we use both IPSEC_ULPROTO_ANY and IPSEC_PORT_ANY as wild card
* because 0 means port number of 0. Instead of 0, we use IPSEC_*_ANY.
*/
((struct ipsecdoi_id_b *)new->v)->proto_id =
ul_proto == IPSEC_ULPROTO_ANY ? 0 : ul_proto;
port = ((struct sockaddr_in *)(laddr))->sin_port;
((struct ipsecdoi_id_b *)new->v)->port =
port == IPSEC_PORT_ANY ? 0 : port;
memcpy(new->v + sizeof(struct ipsecdoi_id_b),
(caddr_t)&((struct sockaddr_in *)(laddr))->sin_addr,
len1);
memcpy(new->v + sizeof(struct ipsecdoi_id_b) + len1,
(caddr_t)&((struct sockaddr_in *)haddr)->sin_addr,
len2);
return new;
}
/*
* create sockaddr structure from ID payload (buf).
* buffers (saddr, prefixlen, ul_proto) must be allocated.
* see, RFC2407 4.6.2.1
*/
int
ipsecdoi_id2sockaddr(buf, saddr, prefixlen, ul_proto)
vchar_t *buf;
struct sockaddr *saddr;
u_int8_t *prefixlen;
u_int16_t *ul_proto;
{
struct ipsecdoi_id_b *id_b = NULL;
u_int plen = 0;
if (buf == NULL)
return ISAKMP_INTERNAL_ERROR;
id_b = (struct ipsecdoi_id_b *)buf->v;
/*
* When a ID payload of subnet type with a IP address of full bit
* masked, it has to be processed as host address.
* e.g. below 2 type are same.
* type = ipv6 subnet, data = 2001::1/128
* type = ipv6 address, data = 2001::1
*/
switch (id_b->type) {
case IPSECDOI_ID_IPV4_ADDR:
case IPSECDOI_ID_IPV4_ADDR_SUBNET:
#ifndef __linux__
saddr->sa_len = sizeof(struct sockaddr_in);
#endif
saddr->sa_family = AF_INET;
((struct sockaddr_in *)saddr)->sin_port =
(id_b->port == 0
? IPSEC_PORT_ANY
: id_b->port); /* see sockaddr2id() */
memcpy(&((struct sockaddr_in *)saddr)->sin_addr,
buf->v + sizeof(*id_b), sizeof(struct in_addr));
break;
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR:
case IPSECDOI_ID_IPV6_ADDR_SUBNET:
#ifndef __linux__
saddr->sa_len = sizeof(struct sockaddr_in6);
#endif
saddr->sa_family = AF_INET6;
((struct sockaddr_in6 *)saddr)->sin6_port =
(id_b->port == 0
? IPSEC_PORT_ANY
: id_b->port); /* see sockaddr2id() */
memcpy(&((struct sockaddr_in6 *)saddr)->sin6_addr,
buf->v + sizeof(*id_b), sizeof(struct in6_addr));
((struct sockaddr_in6 *)saddr)->sin6_scope_id =
(IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)saddr)->sin6_addr)
? ((struct sockaddr_in6 *)id_b)->sin6_scope_id
: 0);
break;
#endif
default:
plog(LLV_ERROR, LOCATION, NULL,
"unsupported ID type %d\n", id_b->type);
return ISAKMP_NTYPE_INVALID_ID_INFORMATION;
}
/* get prefix length */
switch (id_b->type) {
case IPSECDOI_ID_IPV4_ADDR:
plen = sizeof(struct in_addr) << 3;
break;
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR:
plen = sizeof(struct in6_addr) << 3;
break;
#endif
case IPSECDOI_ID_IPV4_ADDR_SUBNET:
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR_SUBNET:
#endif
{
u_char *p;
u_int max;
int alen = sizeof(struct in_addr);
switch (id_b->type) {
case IPSECDOI_ID_IPV4_ADDR_SUBNET:
alen = sizeof(struct in_addr);
break;
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR_SUBNET:
alen = sizeof(struct in6_addr);
break;
#endif
}
/* sanity check */
if (buf->l < alen)
return ISAKMP_INTERNAL_ERROR;
/* get subnet mask length */
plen = 0;
max = alen <<3;
p = (unsigned char *) buf->v
+ sizeof(struct ipsecdoi_id_b)
+ alen;
for (; *p == 0xff; p++) {
plen += 8;
if (plen >= max)
break;
}
if (plen < max) {
u_int l = 0;
u_char b = ~(*p);
while (b) {
b >>= 1;
l++;
}
l = 8 - l;
plen += l;
}
}
break;
}
*prefixlen = plen;
*ul_proto = id_b->proto_id == 0
? IPSEC_ULPROTO_ANY
: id_b->proto_id; /* see sockaddr2id() */
return 0;
}
/*
* make printable string from ID payload except of general header.
*/
char *
ipsecdoi_id2str(id)
const vchar_t *id;
{
#define BUFLEN 512
char * ret = NULL;
int len = 0;
char *dat;
static char buf[BUFLEN];
struct ipsecdoi_id_b *id_b = (struct ipsecdoi_id_b *)id->v;
union sockaddr_any saddr;
u_int plen = 0;
switch (id_b->type) {
case IPSECDOI_ID_IPV4_ADDR:
case IPSECDOI_ID_IPV4_ADDR_SUBNET:
case IPSECDOI_ID_IPV4_ADDR_RANGE:
#ifndef __linux__
saddr.sa.sa_len = sizeof(struct sockaddr_in);
#endif
saddr.sa.sa_family = AF_INET;
saddr.sin.sin_port = IPSEC_PORT_ANY;
memcpy(&saddr.sin.sin_addr,
id->v + sizeof(*id_b), sizeof(struct in_addr));
break;
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR:
case IPSECDOI_ID_IPV6_ADDR_SUBNET:
case IPSECDOI_ID_IPV6_ADDR_RANGE:
#ifndef __linux__
saddr.sa.sa_len = sizeof(struct sockaddr_in6);
#endif
saddr.sa.sa_family = AF_INET6;
saddr.sin6.sin6_port = IPSEC_PORT_ANY;
memcpy(&saddr.sin6.sin6_addr,
id->v + sizeof(*id_b), sizeof(struct in6_addr));
saddr.sin6.sin6_scope_id =
(IN6_IS_ADDR_LINKLOCAL(&saddr.sin6.sin6_addr)
? ((struct sockaddr_in6 *)id_b)->sin6_scope_id
: 0);
break;
#endif
}
switch (id_b->type) {
case IPSECDOI_ID_IPV4_ADDR:
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR:
#endif
len = snprintf( buf, BUFLEN, "%s", saddrwop2str(&saddr.sa));
break;
case IPSECDOI_ID_IPV4_ADDR_SUBNET:
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR_SUBNET:
#endif
{
u_char *p;
u_int max;
int alen = sizeof(struct in_addr);
switch (id_b->type) {
case IPSECDOI_ID_IPV4_ADDR_SUBNET:
alen = sizeof(struct in_addr);
break;
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR_SUBNET:
alen = sizeof(struct in6_addr);
break;
#endif
}
/* sanity check */
if (id->l < alen) {
len = 0;
break;
}
/* get subnet mask length */
plen = 0;
max = alen <<3;
p = (unsigned char *) id->v
+ sizeof(struct ipsecdoi_id_b)
+ alen;
for (; *p == 0xff; p++) {
plen += 8;
if (plen >= max)
break;
}
if (plen < max) {
u_int l = 0;
u_char b = ~(*p);
while (b) {
b >>= 1;
l++;
}
l = 8 - l;
plen += l;
}
len = snprintf( buf, BUFLEN, "%s/%i", saddrwop2str(&saddr.sa), plen);
}
break;
case IPSECDOI_ID_IPV4_ADDR_RANGE:
len = snprintf( buf, BUFLEN, "%s-", saddrwop2str(&saddr.sa));
#ifndef __linux__
saddr.sa.sa_len = sizeof(struct sockaddr_in);
#endif
saddr.sa.sa_family = AF_INET;
saddr.sin.sin_port = IPSEC_PORT_ANY;
memcpy(&saddr.sin.sin_addr,
id->v + sizeof(*id_b) + sizeof(struct in_addr),
sizeof(struct in_addr));
len += snprintf(buf + len, BUFLEN - len, "%s", saddrwop2str(&saddr.sa));
break;
#ifdef INET6
case IPSECDOI_ID_IPV6_ADDR_RANGE:
len = snprintf( buf, BUFLEN, "%s-", saddrwop2str(&saddr.sa));
#ifndef __linux__
saddr.sa.sa_len = sizeof(struct sockaddr_in6);
#endif
saddr.sa.sa_family = AF_INET6;
saddr.sin6.sin6_port = IPSEC_PORT_ANY;
memcpy(&saddr.sin6.sin6_addr,
id->v + sizeof(*id_b) + sizeof(struct in6_addr),
sizeof(struct in6_addr));
saddr.sin6.sin6_scope_id =
(IN6_IS_ADDR_LINKLOCAL(&saddr.sin6.sin6_addr)
? ((struct sockaddr_in6 *)id_b)->sin6_scope_id
: 0);
len += snprintf(buf + len, BUFLEN - len, "%s", saddrwop2str(&saddr.sa));
break;
#endif
case IPSECDOI_ID_FQDN:
case IPSECDOI_ID_USER_FQDN:
len = id->l - sizeof(*id_b);
if (len > BUFLEN)
len = BUFLEN;
memcpy(buf, id->v + sizeof(*id_b), len);
break;
case IPSECDOI_ID_DER_ASN1_DN:
case IPSECDOI_ID_DER_ASN1_GN:
{
X509_NAME *xn = NULL;
dat = id->v + sizeof(*id_b);
len = id->l - sizeof(*id_b);
if (d2i_X509_NAME(&xn, (void*) &dat, len) != NULL) {
BIO *bio = BIO_new(BIO_s_mem());
X509_NAME_print_ex(bio, xn, 0, 0);
len = BIO_get_mem_data(bio, &dat);
if (len > BUFLEN)
len = BUFLEN;
memcpy(buf,dat,len);
BIO_free(bio);
X509_NAME_free(xn);
} else {
plog(LLV_ERROR, LOCATION, NULL,
"unable to extract asn1dn from id\n");
len = sprintf(buf, "<ASN1-DN>");
}
break;
}
/* currently unhandled id types */
case IPSECDOI_ID_KEY_ID:
len = sprintf( buf, "<KEY-ID>");
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"unknown ID type %d\n", id_b->type);
}
if (!len)
len = sprintf( buf, "<?>");
ret = racoon_malloc(len+1);
if (ret != NULL) {
memcpy(ret,buf,len);
ret[len]=0;
}
return ret;
}
/*
* set IPsec data attributes into a proposal.
* NOTE: MUST called per a transform.
*/
int
ipsecdoi_t2satrns(t, pp, pr, tr)
struct isakmp_pl_t *t;
struct saprop *pp;
struct saproto *pr;
struct satrns *tr;
{
struct isakmp_data *d, *prev;
int flag, type;
int error = -1;
int life_t;
int tlen;
tr->trns_no = t->t_no;
tr->trns_id = t->t_id;
tlen = ntohs(t->h.len) - sizeof(*t);
prev = (struct isakmp_data *)NULL;
d = (struct isakmp_data *)(t + 1);
/* default */
life_t = IPSECDOI_ATTR_SA_LD_TYPE_DEFAULT;
pp->lifetime = IPSECDOI_ATTR_SA_LD_SEC_DEFAULT;
pp->lifebyte = 0;
tr->authtype = IPSECDOI_ATTR_AUTH_NONE;
while (tlen > 0) {
type = ntohs(d->type) & ~ISAKMP_GEN_MASK;
flag = ntohs(d->type) & ISAKMP_GEN_MASK;
plog(LLV_DEBUG, LOCATION, NULL,
"type=%s, flag=0x%04x, lorv=%s\n",
s_ipsecdoi_attr(type), flag,
s_ipsecdoi_attr_v(type, ntohs(d->lorv)));
switch (type) {
case IPSECDOI_ATTR_SA_LD_TYPE:
{
int type = ntohs(d->lorv);
switch (type) {
case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
case IPSECDOI_ATTR_SA_LD_TYPE_KB:
life_t = type;
break;
default:
plog(LLV_WARNING, LOCATION, NULL,
"invalid life duration type. "
"use default\n");
life_t = IPSECDOI_ATTR_SA_LD_TYPE_DEFAULT;
break;
}
break;
}
case IPSECDOI_ATTR_SA_LD:
if (prev == NULL
|| (ntohs(prev->type) & ~ISAKMP_GEN_MASK) !=
IPSECDOI_ATTR_SA_LD_TYPE) {
plog(LLV_ERROR, LOCATION, NULL,
"life duration must follow ltype\n");
break;
}
{
u_int32_t t;
vchar_t *ld_buf = NULL;
if (flag) {
/* i.e. ISAKMP_GEN_TV */
ld_buf = vmalloc(sizeof(d->lorv));
if (ld_buf == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get LD buffer.\n");
goto end;
}
memcpy(ld_buf->v, &d->lorv, sizeof(d->lorv));
} else {
int len = ntohs(d->lorv);
/* i.e. ISAKMP_GEN_TLV */
ld_buf = vmalloc(len);
if (ld_buf == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to get LD buffer.\n");
goto end;
}
memcpy(ld_buf->v, d + 1, len);
}
switch (life_t) {
case IPSECDOI_ATTR_SA_LD_TYPE_SEC:
t = ipsecdoi_set_ld(ld_buf);
vfree(ld_buf);
if (t == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid life duration.\n");
goto end;
}
/* lifetime must be equal in a proposal. */
if (pp->lifetime == IPSECDOI_ATTR_SA_LD_SEC_DEFAULT)
pp->lifetime = t;
else if (pp->lifetime != t) {
plog(LLV_ERROR, LOCATION, NULL,
"lifetime mismatched "
"in a proposal, "
"prev:%ld curr:%u.\n",
(long)pp->lifetime, t);
goto end;
}
break;
case IPSECDOI_ATTR_SA_LD_TYPE_KB:
t = ipsecdoi_set_ld(ld_buf);
vfree(ld_buf);
if (t == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid life duration.\n");
goto end;
}
/* lifebyte must be equal in a proposal. */
if (pp->lifebyte == 0)
pp->lifebyte = t;
else if (pp->lifebyte != t) {
plog(LLV_ERROR, LOCATION, NULL,
"lifebyte mismatched "
"in a proposal, "
"prev:%d curr:%u.\n",
pp->lifebyte, t);
goto end;
}
break;
default:
vfree(ld_buf);
plog(LLV_ERROR, LOCATION, NULL,
"invalid life type: %d\n", life_t);
goto end;
}
}
break;
case IPSECDOI_ATTR_GRP_DESC:
/*
* RFC2407: 4.5 IPSEC Security Association Attributes
* Specifies the Oakley Group to be used in a PFS QM
* negotiation. For a list of supported values, see
* Appendix A of [IKE].
*/
if (pp->pfs_group == 0)
pp->pfs_group = (u_int16_t)ntohs(d->lorv);
else if (pp->pfs_group != (u_int16_t)ntohs(d->lorv)) {
plog(LLV_ERROR, LOCATION, NULL,
"pfs_group mismatched "
"in a proposal.\n");
goto end;
}
break;
case IPSECDOI_ATTR_ENC_MODE:
if (pr->encmode &&
pr->encmode != (u_int16_t)ntohs(d->lorv)) {
plog(LLV_ERROR, LOCATION, NULL,
"multiple encmode exist "
"in a transform.\n");
goto end;
}
pr->encmode = (u_int16_t)ntohs(d->lorv);
break;
case IPSECDOI_ATTR_AUTH:
if (tr->authtype != IPSECDOI_ATTR_AUTH_NONE) {
plog(LLV_ERROR, LOCATION, NULL,
"multiple authtype exist "
"in a transform.\n");
goto end;
}
tr->authtype = (u_int16_t)ntohs(d->lorv);
break;
case IPSECDOI_ATTR_KEY_LENGTH:
if (pr->proto_id != IPSECDOI_PROTO_IPSEC_ESP) {
plog(LLV_ERROR, LOCATION, NULL,
"key length defined but not ESP");
goto end;
}
tr->encklen = ntohs(d->lorv);
break;
#ifdef HAVE_SECCTX
case IPSECDOI_ATTR_SECCTX:
{
int len = ntohs(d->lorv);
memcpy(&pp->sctx, d + 1, len);
pp->sctx.ctx_strlen = ntohs(pp->sctx.ctx_strlen);
break;
}
#endif /* HAVE_SECCTX */
case IPSECDOI_ATTR_KEY_ROUNDS:
case IPSECDOI_ATTR_COMP_DICT_SIZE:
case IPSECDOI_ATTR_COMP_PRIVALG:
default:
break;
}
prev = d;
if (flag) {
tlen -= sizeof(*d);
d = (struct isakmp_data *)((char *)d + sizeof(*d));
} else {
tlen -= (sizeof(*d) + ntohs(d->lorv));
d = (struct isakmp_data *)((caddr_t)d + sizeof(*d) + ntohs(d->lorv));
}
}
error = 0;
end:
return error;
}
int
ipsecdoi_authalg2trnsid(alg)
int alg;
{
switch (alg) {
case IPSECDOI_ATTR_AUTH_HMAC_MD5:
return IPSECDOI_AH_MD5;
case IPSECDOI_ATTR_AUTH_HMAC_SHA1:
return IPSECDOI_AH_SHA;
case IPSECDOI_ATTR_AUTH_HMAC_SHA2_256:
return IPSECDOI_AH_SHA256;
case IPSECDOI_ATTR_AUTH_HMAC_SHA2_384:
return IPSECDOI_AH_SHA384;
case IPSECDOI_ATTR_AUTH_HMAC_SHA2_512:
return IPSECDOI_AH_SHA512;
case IPSECDOI_ATTR_AUTH_DES_MAC:
return IPSECDOI_AH_DES;
case IPSECDOI_ATTR_AUTH_KPDK:
return IPSECDOI_AH_MD5; /* XXX */
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid authentication algorithm:%d\n", alg);
}
return -1;
}
static int rm_idtype2doi[] = {
255, /* IDTYPE_UNDEFINED, 0 */
IPSECDOI_ID_FQDN, /* IDTYPE_FQDN, 1 */
IPSECDOI_ID_USER_FQDN, /* IDTYPE_USERFQDN, 2 */
IPSECDOI_ID_KEY_ID, /* IDTYPE_KEYID, 3 */
255, /* IDTYPE_ADDRESS, 4
* it expands into 4 types by another function. */
IPSECDOI_ID_DER_ASN1_DN, /* IDTYPE_ASN1DN, 5 */
};
/*
* convert idtype to DOI value.
* OUT 255 : NG
* other: converted.
*/
int
idtype2doi(idtype)
int idtype;
{
if (ARRAYLEN(rm_idtype2doi) > idtype)
return rm_idtype2doi[idtype];
return 255;
}
int
doi2idtype(doi)
int doi;
{
switch(doi) {
case IPSECDOI_ID_FQDN:
return(IDTYPE_FQDN);
case IPSECDOI_ID_USER_FQDN:
return(IDTYPE_USERFQDN);
case IPSECDOI_ID_KEY_ID:
return(IDTYPE_KEYID);
case IPSECDOI_ID_DER_ASN1_DN:
return(IDTYPE_ASN1DN);
case IPSECDOI_ID_IPV4_ADDR:
case IPSECDOI_ID_IPV4_ADDR_SUBNET:
case IPSECDOI_ID_IPV6_ADDR:
case IPSECDOI_ID_IPV6_ADDR_SUBNET:
return(IDTYPE_ADDRESS);
default:
plog(LLV_WARNING, LOCATION, NULL,
"Inproper idtype:%s in this function.\n",
s_ipsecdoi_ident(doi));
return(IDTYPE_ADDRESS); /* XXX */
}
/*NOTREACHED*/
}