diff options
Diffstat (limited to 'ipsec-tools/src/racoon/isakmp.c')
-rw-r--r-- | ipsec-tools/src/racoon/isakmp.c | 3698 |
1 files changed, 3698 insertions, 0 deletions
diff --git a/ipsec-tools/src/racoon/isakmp.c b/ipsec-tools/src/racoon/isakmp.c new file mode 100644 index 00000000..2672f7ae --- /dev/null +++ b/ipsec-tools/src/racoon/isakmp.c @@ -0,0 +1,3698 @@ +/* $NetBSD: isakmp.c,v 1.71.2.2 2012/08/29 08:55:26 tteras Exp $ */ + +/* Id: isakmp.c,v 1.74 2006/05/07 21:32:59 manubsd 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 <sys/queue.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include PATH_IPSEC_H + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif +#include <netdb.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <ctype.h> +#ifdef ENABLE_HYBRID +#include <resolv.h> +#endif + +#include "var.h" +#include "misc.h" +#include "vmbuf.h" +#include "plog.h" +#include "sockmisc.h" +#include "schedule.h" +#include "session.h" +#include "debug.h" + +#include "remoteconf.h" +#include "localconf.h" +#include "grabmyaddr.h" +#include "admin.h" +#include "privsep.h" +#include "isakmp_var.h" +#include "isakmp.h" +#include "oakley.h" +#include "evt.h" +#include "handler.h" +#include "ipsec_doi.h" +#include "pfkey.h" +#include "crypto_openssl.h" +#include "policy.h" +#include "algorithm.h" +#include "proposal.h" +#include "sainfo.h" +#include "isakmp_ident.h" +#include "isakmp_agg.h" +#include "isakmp_base.h" +#include "isakmp_quick.h" +#include "isakmp_inf.h" +#include "isakmp_newg.h" +#ifdef ENABLE_HYBRID +#include "vendorid.h" +#include "isakmp_xauth.h" +#include "isakmp_unity.h" +#include "isakmp_cfg.h" +#endif +#ifdef ENABLE_FRAG +#include "isakmp_frag.h" +#endif +#include "strnames.h" + +#include <fcntl.h> + +#ifdef ENABLE_NATT +# include "nattraversal.h" +#endif +# ifdef __linux__ +# include <linux/udp.h> +# include <linux/ip.h> +# ifndef SOL_UDP +# define SOL_UDP 17 +# endif +# endif /* __linux__ */ +# if defined(__NetBSD__) || defined(__FreeBSD__) || \ + (defined(__APPLE__) && defined(__MACH__)) +# include <netinet/in.h> +# include <netinet/udp.h> +# include <netinet/in_systm.h> +# include <netinet/ip.h> +# define SOL_UDP IPPROTO_UDP +# endif /* __NetBSD__ / __FreeBSD__ */ + +static int nostate1 __P((struct ph1handle *, vchar_t *)); +static int nostate2 __P((struct ph2handle *, vchar_t *)); + +extern caddr_t val2str(const char *, size_t); + +static int (*ph1exchange[][2][PHASE1ST_MAX]) + __P((struct ph1handle *, vchar_t *)) = { + /* error */ + { { 0 }, { 0 }, }, + /* Identity Protection exchange */ + { + { nostate1, ident_i1send, nostate1, ident_i2recv, ident_i2send, + ident_i3recv, ident_i3send, ident_i4recv, ident_i4send, nostate1, nostate1,}, + { nostate1, ident_r1recv, ident_r1send, ident_r2recv, ident_r2send, + ident_r3recv, ident_r3send, nostate1, nostate1, nostate1, nostate1, }, + }, + /* Aggressive exchange */ + { + { nostate1, agg_i1send, nostate1, agg_i2recv, agg_i2send, + nostate1, nostate1, nostate1, nostate1, nostate1, nostate1, }, + { nostate1, agg_r1recv, agg_r1send, agg_r2recv, agg_r2send, + nostate1, nostate1, nostate1, nostate1, nostate1, nostate1, }, + }, + /* Base exchange */ + { + { nostate1, base_i1send, nostate1, base_i2recv, base_i2send, + base_i3recv, base_i3send, nostate1, nostate1, nostate1, nostate1, }, + { nostate1, base_r1recv, base_r1send, base_r2recv, base_r2send, + nostate1, nostate1, nostate1, nostate1, nostate1, nostate1, }, + }, +}; + +static int (*ph2exchange[][2][PHASE2ST_MAX]) + __P((struct ph2handle *, vchar_t *)) = { + /* error */ + { { 0 }, { 0 }, }, + /* Quick mode for IKE */ + { + { nostate2, nostate2, quick_i1prep, nostate2, quick_i1send, + quick_i2recv, quick_i2send, quick_i3recv, nostate2, nostate2, }, + { nostate2, quick_r1recv, quick_r1prep, nostate2, quick_r2send, + quick_r3recv, quick_r3prep, quick_r3send, nostate2, nostate2, } + }, +}; + +static u_char r_ck0[] = { 0,0,0,0,0,0,0,0 }; /* used to verify the r_ck. */ + +static int isakmp_main __P((vchar_t *, struct sockaddr *, struct sockaddr *)); +static int ph1_main __P((struct ph1handle *, vchar_t *)); +static int quick_main __P((struct ph2handle *, vchar_t *)); +static int isakmp_ph1begin_r __P((vchar_t *, + struct sockaddr *, struct sockaddr *, u_int8_t)); +static int isakmp_ph2begin_i __P((struct ph1handle *, struct ph2handle *)); +static int isakmp_ph2begin_r __P((struct ph1handle *, vchar_t *)); +static int etypesw1 __P((int)); +static int etypesw2 __P((int)); +static int isakmp_ph1resend __P((struct ph1handle *)); +static int isakmp_ph2resend __P((struct ph2handle *)); + +#ifdef ENABLE_FRAG +static int frag_handler(struct ph1handle *, + vchar_t *, struct sockaddr *, struct sockaddr *); +#endif + +/* + * isakmp packet handler + */ +static int +isakmp_handler(ctx, so_isakmp) + void *ctx; + int so_isakmp; +{ + struct isakmp isakmp; + union { + char buf[sizeof (isakmp) + 4]; + u_int32_t non_esp[2]; + struct { + struct udphdr udp; +#ifdef __linux + struct iphdr ip; +#else + struct ip ip; +#endif + char buf[sizeof(isakmp) + 4]; + } lbuf; + } x; + struct sockaddr_storage remote; + struct sockaddr_storage local; + unsigned int remote_len = sizeof(remote); + unsigned int local_len = sizeof(local); + int len = 0, extralen = 0; + vchar_t *buf = NULL, *tmpbuf = NULL; + int error = -1, res; + + /* read message by MSG_PEEK */ + while ((len = recvfromto(so_isakmp, x.buf, sizeof(x), + MSG_PEEK, (struct sockaddr *)&remote, &remote_len, + (struct sockaddr *)&local, &local_len)) < 0) { + if (errno == EINTR) + continue; + plog(LLV_ERROR, LOCATION, NULL, + "failed to receive isakmp packet: %s\n", + strerror (errno)); + goto end; + } + + /* keep-alive packet - ignore */ + if (len == 1 && (x.buf[0]&0xff) == 0xff) { + /* Pull the keep-alive packet */ + if ((len = recvfrom(so_isakmp, (char *)x.buf, 1, + 0, (struct sockaddr *)&remote, &remote_len)) != 1) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to receive keep alive packet: %s\n", + strerror (errno)); + } + goto end; + } + + /* Lucent IKE in UDP encapsulation */ + { +#ifdef __linux__ + if (ntohs(x.lbuf.udp.dest) == 501) { + extralen += sizeof(x.lbuf.udp) + x.lbuf.ip.ihl; + } +#else + if (ntohs(x.lbuf.udp.uh_dport) == 501) { + extralen += sizeof(x.lbuf.udp) + x.lbuf.ip.ip_hl; + } +#endif + } + +#ifdef ENABLE_NATT + /* we don't know about portchange yet, + look for non-esp marker instead */ + if (x.non_esp[0] == 0 && x.non_esp[1] != 0) + extralen = NON_ESP_MARKER_LEN; +#endif + + /* now we know if there is an extra non-esp + marker at the beginning or not */ + memcpy ((char *)&isakmp, x.buf + extralen, sizeof (isakmp)); + + /* check isakmp header length, as well as sanity of header length */ + if (len < sizeof(isakmp) || ntohl(isakmp.len) < sizeof(isakmp)) { + plog(LLV_ERROR, LOCATION, (struct sockaddr *)&remote, + "packet shorter than isakmp header size (%u, %u, %zu)\n", + len, ntohl(isakmp.len), sizeof(isakmp)); + /* dummy receive */ + if ((len = recvfrom(so_isakmp, (char *)&isakmp, sizeof(isakmp), + 0, (struct sockaddr *)&remote, &remote_len)) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to receive isakmp packet: %s\n", + strerror (errno)); + } + goto end; + } + + /* reject it if the size is tooooo big. */ + if (ntohl(isakmp.len) > 0xffff) { + plog(LLV_ERROR, LOCATION, NULL, + "the length in the isakmp header is too big.\n"); + if ((len = recvfrom(so_isakmp, (char *)&isakmp, sizeof(isakmp), + 0, (struct sockaddr *)&remote, &remote_len)) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to receive isakmp packet: %s\n", + strerror (errno)); + } + goto end; + } + + /* read real message */ + if ((tmpbuf = vmalloc(ntohl(isakmp.len) + extralen)) == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to allocate reading buffer (%u Bytes)\n", + ntohl(isakmp.len) + extralen); + /* dummy receive */ + if ((len = recvfrom(so_isakmp, (char *)&isakmp, sizeof(isakmp), + 0, (struct sockaddr *)&remote, &remote_len)) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to receive isakmp packet: %s\n", + strerror (errno)); + } + goto end; + } + + while ((len = recvfromto(so_isakmp, (char *)tmpbuf->v, tmpbuf->l, + 0, (struct sockaddr *)&remote, &remote_len, + (struct sockaddr *)&local, &local_len)) < 0) { + if (errno == EINTR) + continue; + plog(LLV_ERROR, LOCATION, NULL, + "failed to receive isakmp packet: %s\n", + strerror (errno)); + goto end; + } + + if ((buf = vmalloc(len - extralen)) == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to allocate reading buffer (%u Bytes)\n", + (len - extralen)); + goto end; + } + + memcpy (buf->v, tmpbuf->v + extralen, buf->l); + + len -= extralen; + + if (len != buf->l) { + plog(LLV_ERROR, LOCATION, (struct sockaddr *)&remote, + "received invalid length (%d != %zu), why ?\n", + len, buf->l); + goto end; + } + + plog(LLV_DEBUG, LOCATION, NULL, "===\n"); + plog(LLV_DEBUG, LOCATION, NULL, + "%d bytes message received %s\n", + len, saddr2str_fromto("from %s to %s", + (struct sockaddr *)&remote, + (struct sockaddr *)&local)); + plogdump(LLV_DEBUG, buf->v, buf->l); + + /* avoid packets with malicious port/address */ + if (extract_port((struct sockaddr *)&remote) == 0) { + plog(LLV_ERROR, LOCATION, (struct sockaddr *)&remote, + "src port == 0 (valid as UDP but not with IKE)\n"); + goto end; + } + + /* XXX: check sender whether to be allowed or not to accept */ + + /* XXX: I don't know how to check isakmp half connection attack. */ + + /* simply reply if the packet was processed. */ + res=check_recvdpkt((struct sockaddr *)&remote,(struct sockaddr *)&local, buf); + if (res) { + plog(LLV_NOTIFY, LOCATION, NULL, + "the packet is retransmitted by %s (%d).\n", + saddr2str((struct sockaddr *)&remote), res); + error = 0; + goto end; + } + + /* isakmp main routine */ + if (isakmp_main(buf, (struct sockaddr *)&remote, + (struct sockaddr *)&local) != 0) goto end; + + error = 0; + +end: + if (tmpbuf != NULL) + vfree(tmpbuf); + if (buf != NULL) + vfree(buf); + return error; +} + +/* + * main processing to handle isakmp payload + */ +static int +isakmp_main(msg, remote, local) + vchar_t *msg; + struct sockaddr *remote, *local; +{ + struct isakmp *isakmp = (struct isakmp *)msg->v; + isakmp_index *index = (isakmp_index *)isakmp; + u_int32_t msgid = isakmp->msgid; + struct ph1handle *iph1; + +#ifdef HAVE_PRINT_ISAKMP_C + isakmp_printpacket(msg, remote, local, 0); +#endif + + /* the initiator's cookie must not be zero */ + if (memcmp(&isakmp->i_ck, r_ck0, sizeof(cookie_t)) == 0) { + plog(LLV_ERROR, LOCATION, remote, + "malformed cookie received.\n"); + return -1; + } + + /* Check the Major and Minor Version fields. */ + /* + * XXX Is is right to check version here ? + * I think it may no be here because the version depends + * on exchange status. + */ + if (isakmp->v < ISAKMP_VERSION_NUMBER) { + if (ISAKMP_GETMAJORV(isakmp->v) < ISAKMP_MAJOR_VERSION) { + plog(LLV_ERROR, LOCATION, remote, + "invalid major version %d.\n", + ISAKMP_GETMAJORV(isakmp->v)); + return -1; + } +#if ISAKMP_MINOR_VERSION > 0 + if (ISAKMP_GETMINORV(isakmp->v) < ISAKMP_MINOR_VERSION) { + plog(LLV_ERROR, LOCATION, remote, + "invalid minor version %d.\n", + ISAKMP_GETMINORV(isakmp->v)); + return -1; + } +#endif + } + + /* check the Flags field. */ + /* XXX How is the exclusive check, E and A ? */ + if (isakmp->flags & ~(ISAKMP_FLAG_E | ISAKMP_FLAG_C | ISAKMP_FLAG_A)) { + plog(LLV_ERROR, LOCATION, remote, + "invalid flag 0x%02x.\n", isakmp->flags); + return -1; + } + + /* ignore commit bit. */ + if (ISSET(isakmp->flags, ISAKMP_FLAG_C)) { + if (isakmp->msgid == 0) { + isakmp_info_send_nx(isakmp, remote, local, + ISAKMP_NTYPE_INVALID_FLAGS, NULL); + plog(LLV_ERROR, LOCATION, remote, + "Commit bit on phase1 forbidden.\n"); + return -1; + } + } + + iph1 = getph1byindex(index); + if (iph1 != NULL) { + /* validity check */ + if (memcmp(&isakmp->r_ck, r_ck0, sizeof(cookie_t)) == 0 && + iph1->side == INITIATOR) { + plog(LLV_DEBUG, LOCATION, remote, + "malformed cookie received or " + "the initiator's cookies collide.\n"); + return -1; + } + +#ifdef ENABLE_NATT + /* Floating ports for NAT-T */ + if (NATT_AVAILABLE(iph1) && + ! (iph1->natt_flags & NAT_PORTS_CHANGED) && + ((cmpsaddr(iph1->remote, remote) != CMPSADDR_MATCH) || + (cmpsaddr(iph1->local, local) != CMPSADDR_MATCH))) + { + /* prevent memory leak */ + racoon_free(iph1->remote); + racoon_free(iph1->local); + iph1->remote = NULL; + iph1->local = NULL; + + /* copy-in new addresses */ + iph1->remote = dupsaddr(remote); + if (iph1->remote == NULL) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "phase1 failed: dupsaddr failed.\n"); + remph1(iph1); + delph1(iph1); + return -1; + } + iph1->local = dupsaddr(local); + if (iph1->local == NULL) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "phase1 failed: dupsaddr failed.\n"); + remph1(iph1); + delph1(iph1); + return -1; + } + + /* set the flag to prevent further port floating + (FIXME: should we allow it? E.g. when the NAT gw + is rebooted?) */ + iph1->natt_flags |= NAT_PORTS_CHANGED | NAT_ADD_NON_ESP_MARKER; + + /* print some neat info */ + plog (LLV_INFO, LOCATION, NULL, + "NAT-T: ports changed to: %s\n", + saddr2str_fromto ("%s<->%s", iph1->remote, iph1->local)); + + natt_keepalive_add_ph1 (iph1); + } +#endif + + /* must be same addresses in one stream of a phase at least. */ + if (cmpsaddr(iph1->remote, remote) != CMPSADDR_MATCH) { + char *saddr_db, *saddr_act; + + saddr_db = racoon_strdup(saddr2str(iph1->remote)); + saddr_act = racoon_strdup(saddr2str(remote)); + STRDUP_FATAL(saddr_db); + STRDUP_FATAL(saddr_act); + + plog(LLV_WARNING, LOCATION, remote, + "remote address mismatched. db=%s, act=%s\n", + saddr_db, saddr_act); + + racoon_free(saddr_db); + racoon_free(saddr_act); + } + + /* + * don't check of exchange type here because other type will be + * with same index, for example, informational exchange. + */ + + /* XXX more acceptable check */ + } + + switch (isakmp->etype) { + case ISAKMP_ETYPE_IDENT: + case ISAKMP_ETYPE_AGG: + case ISAKMP_ETYPE_BASE: + /* phase 1 validity check */ + if (isakmp->msgid != 0) { + plog(LLV_ERROR, LOCATION, remote, + "message id should be zero in phase1.\n"); + return -1; + } + + /* search for isakmp status record of phase 1 */ + if (iph1 == NULL) { + /* + * the packet must be the 1st message from a initiator + * or the 2nd message from the responder. + */ + + /* search for phase1 handle by index without r_ck */ + iph1 = getph1byindex0(index); + if (iph1 == NULL) { + /*it must be the 1st message from a initiator.*/ + if (memcmp(&isakmp->r_ck, r_ck0, + sizeof(cookie_t)) != 0) { + + plog(LLV_DEBUG, LOCATION, remote, + "malformed cookie received " + "or the spi expired.\n"); + return -1; + } + + /* it must be responder's 1st exchange. */ + if (isakmp_ph1begin_r(msg, remote, local, + isakmp->etype) < 0) + return -1; + break; + + /*NOTREACHED*/ + } + + /* it must be the 2nd message from the responder. */ + if (iph1->side != INITIATOR) { + plog(LLV_DEBUG, LOCATION, remote, + "malformed cookie received. " + "it has to be as the initiator. %s\n", + isakmp_pindex(&iph1->index, 0)); + return -1; + } + } + + /* + * Don't delete phase 1 handler when the exchange type + * in handler is not equal to packet's one because of no + * authencication completed. + */ + if (iph1->etype != isakmp->etype) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "exchange type is mismatched: " + "db=%s packet=%s, ignore it.\n", + s_isakmp_etype(iph1->etype), + s_isakmp_etype(isakmp->etype)); + return -1; + } + +#ifdef ENABLE_FRAG + if (isakmp->np == ISAKMP_NPTYPE_FRAG) + return frag_handler(iph1, msg, remote, local); +#endif + + /* call main process of phase 1 */ + if (ph1_main(iph1, msg) < 0) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "phase1 negotiation failed.\n"); + remph1(iph1); + delph1(iph1); + return -1; + } + break; + + case ISAKMP_ETYPE_AUTH: + plog(LLV_INFO, LOCATION, remote, + "unsupported exchange %d received.\n", + isakmp->etype); + break; + + case ISAKMP_ETYPE_INFO: + case ISAKMP_ETYPE_ACKINFO: + /* + * iph1 must be present for Information message. + * if iph1 is null then trying to get the phase1 status + * as the packet from responder againt initiator's 1st + * exchange in phase 1. + * NOTE: We think such informational exchange should be ignored. + */ + if (iph1 == NULL) { + iph1 = getph1byindex0(index); + if (iph1 == NULL) { + plog(LLV_ERROR, LOCATION, remote, + "unknown Informational " + "exchange received.\n"); + return -1; + } + if (cmpsaddr(iph1->remote, remote) != CMPSADDR_MATCH) { + plog(LLV_WARNING, LOCATION, remote, + "remote address mismatched. " + "db=%s\n", + saddr2str(iph1->remote)); + } + } + +#ifdef ENABLE_FRAG + if (isakmp->np == ISAKMP_NPTYPE_FRAG) + return frag_handler(iph1, msg, remote, local); +#endif + + if (isakmp_info_recv(iph1, msg) < 0) + return -1; + break; + + case ISAKMP_ETYPE_QUICK: + { + struct ph2handle *iph2; + + if (iph1 == NULL) { + isakmp_info_send_nx(isakmp, remote, local, + ISAKMP_NTYPE_INVALID_COOKIE, NULL); + plog(LLV_ERROR, LOCATION, remote, + "can't start the quick mode, " + "there is no ISAKMP-SA, %s\n", + isakmp_pindex((isakmp_index *)&isakmp->i_ck, + isakmp->msgid)); + return -1; + } +#ifdef ENABLE_HYBRID + /* Reinit the IVM if it's still there */ + if (iph1->mode_cfg && iph1->mode_cfg->ivm) { + oakley_delivm(iph1->mode_cfg->ivm); + iph1->mode_cfg->ivm = NULL; + } +#endif +#ifdef ENABLE_FRAG + if (isakmp->np == ISAKMP_NPTYPE_FRAG) + return frag_handler(iph1, msg, remote, local); +#endif + + /* check status of phase 1 whether negotiated or not. */ + if (iph1->status != PHASE1ST_ESTABLISHED && + iph1->status != PHASE1ST_DYING) { + plog(LLV_ERROR, LOCATION, remote, + "can't start the quick mode, " + "there is no valid ISAKMP-SA, %s\n", + isakmp_pindex(&iph1->index, iph1->msgid)); + return -1; + } + + /* search isakmp phase 2 stauts record. */ + iph2 = getph2bymsgid(iph1, msgid); + if (iph2 == NULL) { + /* it must be new negotiation as responder */ + if (isakmp_ph2begin_r(iph1, msg) < 0) + return -1; + return 0; + /*NOTREACHED*/ + } + + /* commit bit. */ + /* XXX + * we keep to set commit bit during negotiation. + * When SA is configured, bit will be reset. + * XXX + * don't initiate commit bit. should be fixed in the future. + */ + if (ISSET(isakmp->flags, ISAKMP_FLAG_C)) + iph2->flags |= ISAKMP_FLAG_C; + + /* call main process of quick mode */ + if (quick_main(iph2, msg) < 0) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "phase2 negotiation failed.\n"); + remph2(iph2); + delph2(iph2); + return -1; + } + } + break; + + case ISAKMP_ETYPE_NEWGRP: + if (iph1 == NULL) { + plog(LLV_ERROR, LOCATION, remote, + "Unknown new group mode exchange, " + "there is no ISAKMP-SA.\n"); + return -1; + } + +#ifdef ENABLE_FRAG + if (isakmp->np == ISAKMP_NPTYPE_FRAG) + return frag_handler(iph1, msg, remote, local); +#endif + + isakmp_newgroup_r(iph1, msg); + break; + +#ifdef ENABLE_HYBRID + case ISAKMP_ETYPE_CFG: + if (iph1 == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "mode config %d from %s, " + "but we have no ISAKMP-SA.\n", + isakmp->etype, saddr2str(remote)); + return -1; + } + +#ifdef ENABLE_FRAG + if (isakmp->np == ISAKMP_NPTYPE_FRAG) + return frag_handler(iph1, msg, remote, local); +#endif + + isakmp_cfg_r(iph1, msg); + break; +#endif + + case ISAKMP_ETYPE_NONE: + default: + plog(LLV_ERROR, LOCATION, NULL, + "Invalid exchange type %d from %s.\n", + isakmp->etype, saddr2str(remote)); + return -1; + } + + return 0; +} + +/* + * main function of phase 1. + */ +static int +ph1_main(iph1, msg) + struct ph1handle *iph1; + vchar_t *msg; +{ + int error; +#ifdef ENABLE_STATS + struct timeval start, end; +#endif + + /* ignore a packet */ + if (iph1->status >= PHASE1ST_ESTABLISHED) + return 0; + +#ifdef ENABLE_STATS + gettimeofday(&start, NULL); +#endif + /* receive */ + if (ph1exchange[etypesw1(iph1->etype)] + [iph1->side] + [iph1->status] == NULL) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "why isn't the function defined.\n"); + return -1; + } + error = (ph1exchange[etypesw1(iph1->etype)] + [iph1->side] + [iph1->status])(iph1, msg); + if (error != 0) { + + /* XXX + * When an invalid packet is received on phase1, it should + * be selected to process this packet. That is to respond + * with a notify and delete phase 1 handler, OR not to respond + * and keep phase 1 handler. However, in PHASE1ST_START when + * acting as RESPONDER we must not keep phase 1 handler or else + * it will stay forever. + */ + + if (iph1->side == RESPONDER && iph1->status == PHASE1ST_START) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "failed to pre-process ph1 packet (side: %d, status %d).\n", + iph1->side, iph1->status); + return -1; + } else { + /* ignore the error and keep phase 1 handler */ + return 0; + } + } + +#ifndef ENABLE_FRAG + /* free resend buffer */ + if (iph1->sendbuf == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "no buffer found as sendbuf\n"); + return -1; + } +#endif + + VPTRINIT(iph1->sendbuf); + + /* turn off schedule */ + sched_cancel(&iph1->scr); + + /* send */ + plog(LLV_DEBUG, LOCATION, NULL, "===\n"); + if ((ph1exchange[etypesw1(iph1->etype)] + [iph1->side] + [iph1->status])(iph1, msg) != 0) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "failed to process ph1 packet (side: %d, status: %d).\n", + iph1->side, iph1->status); + return -1; + } + +#ifdef ENABLE_STATS + gettimeofday(&end, NULL); + syslog(LOG_NOTICE, "%s(%s): %8.6f", + "phase1", s_isakmp_state(iph1->etype, iph1->side, iph1->status), + timedelta(&start, &end)); +#endif + if (iph1->status == PHASE1ST_ESTABLISHED) { + +#ifdef ENABLE_STATS + gettimeofday(&iph1->end, NULL); + syslog(LOG_NOTICE, "%s(%s): %8.6f", + "phase1", s_isakmp_etype(iph1->etype), + timedelta(&iph1->start, &iph1->end)); +#endif + + /* save created date. */ + (void)time(&iph1->created); + + /* migrate ph2s from dying ph1s */ + migrate_dying_ph12(iph1); + + /* add to the schedule to expire, and seve back pointer. */ + if (ph1_rekey_enabled(iph1)) { + sched_schedule(&iph1->sce, + iph1->approval->lifetime * + PFKEY_SOFT_LIFETIME_RATE / 100, + isakmp_ph1dying_stub); + } else { + sched_schedule(&iph1->sce, iph1->approval->lifetime, + isakmp_ph1expire_stub); + } + +#ifdef ENABLE_HYBRID + if (iph1->mode_cfg->flags & ISAKMP_CFG_VENDORID_XAUTH) { + switch (iph1->approval->authmethod) { + case OAKLEY_ATTR_AUTH_METHOD_HYBRID_RSA_R: + case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_R: + case OAKLEY_ATTR_AUTH_METHOD_XAUTH_PSKEY_R: + case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSASIG_R: + case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_R: + case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_R: + case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_R: + xauth_sendreq(iph1); + /* XXX Don't process INITIAL_CONTACT */ + iph1->rmconf->ini_contact = 0; + break; + default: + break; + } + } +#endif +#ifdef ENABLE_DPD + /* Schedule the r_u_there.... */ + if(iph1->dpd_support && iph1->rmconf->dpd_interval) + isakmp_sched_r_u(iph1, 0); +#endif + + /* INITIAL-CONTACT processing */ + /* don't anything if local test mode. */ + if (!f_local + && iph1->rmconf->ini_contact && !getcontacted(iph1->remote)) { + /* send INITIAL-CONTACT */ + isakmp_info_send_n1(iph1, + ISAKMP_NTYPE_INITIAL_CONTACT, NULL); + /* insert a node into contacted list. */ + if (inscontacted(iph1->remote) == -1) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "failed to add contacted list.\n"); + /* ignore */ + } + } + if (iph1->initial_contact_received) + isakmp_info_recv_initialcontact(iph1, NULL); + + log_ph1established(iph1); + plog(LLV_DEBUG, LOCATION, NULL, "===\n"); + + /* + * SA up shell script hook: do it now,except if + * ISAKMP mode config was requested. In the later + * case it is done when we receive the configuration. + */ + if ((iph1->status == PHASE1ST_ESTABLISHED) && + !iph1->rmconf->mode_cfg) { + switch (iph1->approval->authmethod) { +#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: + /* Unimplemeted... */ + case OAKLEY_ATTR_AUTH_METHOD_HYBRID_DSS_R: + case OAKLEY_ATTR_AUTH_METHOD_XAUTH_DSSSIG_R: + case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAENC_R: + case OAKLEY_ATTR_AUTH_METHOD_XAUTH_RSAREV_R: + break; +#endif + default: + script_hook(iph1, SCRIPT_PHASE1_UP); + break; + } + } + } + + return 0; +} + +/* + * main function of quick mode. + */ +static int +quick_main(iph2, msg) + struct ph2handle *iph2; + vchar_t *msg; +{ + struct isakmp *isakmp = (struct isakmp *)msg->v; + int error; +#ifdef ENABLE_STATS + struct timeval start, end; +#endif + + /* ignore a packet */ + if (iph2->status == PHASE2ST_ESTABLISHED + || iph2->status == PHASE2ST_GETSPISENT) + return 0; + +#ifdef ENABLE_STATS + gettimeofday(&start, NULL); +#endif + + /* receive */ + if (ph2exchange[etypesw2(isakmp->etype)] + [iph2->side] + [iph2->status] == NULL) { + plog(LLV_ERROR, LOCATION, iph2->ph1->remote, + "why isn't the function defined.\n"); + return -1; + } + error = (ph2exchange[etypesw2(isakmp->etype)] + [iph2->side] + [iph2->status])(iph2, msg); + if (error != 0) { + plog(LLV_ERROR, LOCATION, iph2->ph1->remote, + "failed to pre-process ph2 packet (side: %d, status %d).\n", + iph2->side, iph2->status); + if (error == ISAKMP_INTERNAL_ERROR) + return 0; + isakmp_info_send_n1(iph2->ph1, error, NULL); + return -1; + } + + /* when using commit bit, status will be reached here. */ + if (iph2->status == PHASE2ST_ADDSA) + return 0; + + /* free resend buffer */ + if (iph2->sendbuf == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "no buffer found as sendbuf\n"); + return -1; + } + VPTRINIT(iph2->sendbuf); + + /* turn off schedule */ + sched_cancel(&iph2->scr); + + /* send */ + plog(LLV_DEBUG, LOCATION, NULL, "===\n"); + if ((ph2exchange[etypesw2(isakmp->etype)] + [iph2->side] + [iph2->status])(iph2, msg) != 0) { + plog(LLV_ERROR, LOCATION, iph2->ph1->remote, + "failed to process ph2 packet (side: %d, status: %d).\n", + iph2->side, iph2->status); + return -1; + } + +#ifdef ENABLE_STATS + gettimeofday(&end, NULL); + syslog(LOG_NOTICE, "%s(%s): %8.6f", + "phase2", + s_isakmp_state(ISAKMP_ETYPE_QUICK, iph2->side, iph2->status), + timedelta(&start, &end)); +#endif + + return 0; +} + +/* new negotiation of phase 1 for initiator */ +struct ph1handle * +isakmp_ph1begin_i(rmconf, remote, local) + struct remoteconf *rmconf; + struct sockaddr *remote, *local; +{ + struct ph1handle *iph1; +#ifdef ENABLE_STATS + struct timeval start, end; +#endif + + /* get new entry to isakmp status table. */ + iph1 = newph1(); + if (iph1 == NULL) + return NULL; + + iph1->status = PHASE1ST_START; + iph1->rmconf = rmconf; + iph1->side = INITIATOR; + iph1->version = ISAKMP_VERSION_NUMBER; + iph1->msgid = 0; + iph1->flags = 0; + iph1->ph2cnt = 0; +#ifdef HAVE_GSSAPI + iph1->gssapi_state = NULL; +#endif +#ifdef ENABLE_HYBRID + if ((iph1->mode_cfg = isakmp_cfg_mkstate()) == NULL) { + delph1(iph1); + return NULL; + } +#endif +#ifdef ENABLE_FRAG + + if(rmconf->ike_frag == ISAKMP_FRAG_FORCE) + iph1->frag = 1; + else + iph1->frag = 0; + iph1->frag_chain = NULL; +#endif + iph1->approval = NULL; + + /* XXX copy remote address */ + if (copy_ph1addresses(iph1, rmconf, remote, local) < 0) { + delph1(iph1); + return NULL; + } + + (void)insph1(iph1); + + /* start phase 1 exchange */ + iph1->etype = rmconf->etypes->type; + + plog(LLV_DEBUG, LOCATION, NULL, "===\n"); + { + char *a; + + a = racoon_strdup(saddr2str(iph1->local)); + STRDUP_FATAL(a); + + plog(LLV_INFO, LOCATION, NULL, + "initiate new phase 1 negotiation: %s<=>%s\n", + a, saddr2str(iph1->remote)); + racoon_free(a); + } + plog(LLV_INFO, LOCATION, NULL, + "begin %s mode.\n", + s_isakmp_etype(iph1->etype)); + +#ifdef ENABLE_STATS + gettimeofday(&iph1->start, NULL); + gettimeofday(&start, NULL); +#endif + /* start exchange */ + if ((ph1exchange[etypesw1(iph1->etype)] + [iph1->side] + [iph1->status])(iph1, NULL) != 0) { + /* failed to start phase 1 negotiation */ + remph1(iph1); + delph1(iph1); + + return NULL; + } + +#ifdef ENABLE_STATS + gettimeofday(&end, NULL); + syslog(LOG_NOTICE, "%s(%s): %8.6f", + "phase1", + s_isakmp_state(iph1->etype, iph1->side, iph1->status), + timedelta(&start, &end)); +#endif + + return iph1; +} + +/* new negotiation of phase 1 for responder */ +static int +isakmp_ph1begin_r(msg, remote, local, etype) + vchar_t *msg; + struct sockaddr *remote, *local; + u_int8_t etype; +{ + struct isakmp *isakmp = (struct isakmp *)msg->v; + struct ph1handle *iph1; + struct rmconfselector rmsel; +#ifdef ENABLE_STATS + struct timeval start, end; +#endif + + /* check if this etype is allowed */ + memset(&rmsel, 0, sizeof(rmsel)); + rmsel.remote = remote; + if (enumrmconf(&rmsel, check_etypeok, (void *) (intptr_t) etype) == 0) { + plog(LLV_ERROR, LOCATION, remote, + "exchange %s not allowed in any applicable rmconf.\n", + s_isakmp_etype(etype)); + return -1; + } + + /* get new entry to isakmp status table. */ + iph1 = newph1(); + if (iph1 == NULL) + return -1; + + memcpy(&iph1->index.i_ck, &isakmp->i_ck, sizeof(iph1->index.i_ck)); + iph1->status = PHASE1ST_START; + iph1->flags = 0; + iph1->side = RESPONDER; + iph1->etype = etype; + iph1->version = isakmp->v; + iph1->msgid = 0; +#ifdef HAVE_GSSAPI + iph1->gssapi_state = NULL; +#endif +#ifdef ENABLE_HYBRID + if ((iph1->mode_cfg = isakmp_cfg_mkstate()) == NULL) { + delph1(iph1); + return -1; + } +#endif +#ifdef ENABLE_FRAG + iph1->frag = 0; + iph1->frag_chain = NULL; +#endif + iph1->approval = NULL; + +#ifdef ENABLE_NATT + /* RFC3947 says that we MUST accept new phases1 on NAT-T floated port. + * We have to setup this flag now to correctly generate the first reply. + * Don't know if a better check could be done for that ? + */ + if(extract_port(local) == lcconf->port_isakmp_natt) + iph1->natt_flags |= (NAT_PORTS_CHANGED); +#endif + + /* copy remote address; remote and local always contain + * port numbers so rmconf is not needed */ + if (copy_ph1addresses(iph1, NULL, remote, local) < 0) { + delph1(iph1); + return -1; + } + (void)insph1(iph1); + + plog(LLV_DEBUG, LOCATION, NULL, "===\n"); + { + char *a; + + a = racoon_strdup(saddr2str(iph1->local)); + STRDUP_FATAL(a); + + plog(LLV_INFO, LOCATION, NULL, + "respond new phase 1 negotiation: %s<=>%s\n", + a, saddr2str(iph1->remote)); + racoon_free(a); + } + plog(LLV_INFO, LOCATION, NULL, + "begin %s mode.\n", s_isakmp_etype(etype)); + +#ifdef ENABLE_STATS + gettimeofday(&iph1->start, NULL); + gettimeofday(&start, NULL); +#endif + +#ifndef ENABLE_FRAG + + /* start exchange */ + if ((ph1exchange[etypesw1(iph1->etype)] + [iph1->side] + [iph1->status])(iph1, msg) < 0 + || (ph1exchange[etypesw1(iph1->etype)] + [iph1->side] + [iph1->status])(iph1, msg) < 0) { + plog(LLV_ERROR, LOCATION, remote, + "failed to process ph1 packet (side: %d, status: %d).\n", + iph1->side, iph1->status); + remph1(iph1); + delph1(iph1); + return -1; + } + +#ifdef ENABLE_STATS + gettimeofday(&end, NULL); + syslog(LOG_NOTICE, "%s(%s): %8.6f", + "phase1", + s_isakmp_state(iph1->etype, iph1->side, iph1->status), + timedelta(&start, &end)); +#endif + + return 0; + +#else /* ENABLE_FRAG */ + + /* now that we have a phase1 handle, feed back into our + * main receive function to catch fragmented packets + */ + + return isakmp_main(msg, remote, local); + +#endif /* ENABLE_FRAG */ + +} + +/* new negotiation of phase 2 for initiator */ +static int +isakmp_ph2begin_i(iph1, iph2) + struct ph1handle *iph1; + struct ph2handle *iph2; +{ +#ifdef ENABLE_HYBRID + if (xauth_check(iph1) != 0) { + plog(LLV_ERROR, LOCATION, NULL, + "Attempt to start phase 2 whereas Xauth failed\n"); + return -1; + } +#endif + + /* fixup ph2 ports for this ph1 */ + if (extract_port(iph2->src) == 0) + set_port(iph2->src, extract_port(iph1->local)); + if (extract_port(iph2->dst) == 0) + set_port(iph2->dst, extract_port(iph1->remote)); + + /* found ISAKMP-SA. */ + plog(LLV_DEBUG, LOCATION, NULL, "===\n"); + plog(LLV_DEBUG, LOCATION, NULL, "begin QUICK mode.\n"); + { + char *a; + a = racoon_strdup(saddr2str(iph2->src)); + STRDUP_FATAL(a); + + plog(LLV_INFO, LOCATION, NULL, + "initiate new phase 2 negotiation: %s<=>%s\n", + a, saddr2str(iph2->dst)); + racoon_free(a); + } + +#ifdef ENABLE_STATS + gettimeofday(&iph2->start, NULL); +#endif + if (iph2->status != PHASE2ST_EXPIRED) /* Phase 1 is already bound (ongoing rekeying) */ + bindph12(iph1, iph2); + iph2->status = PHASE2ST_STATUS2; + + if ((ph2exchange[etypesw2(ISAKMP_ETYPE_QUICK)] + [iph2->side] + [iph2->status])(iph2, NULL) < 0) { + /* release ipsecsa handler due to internal error. */ + remph2(iph2); + return -1; + } + return 0; +} + +/* new negotiation of phase 2 for responder */ +static int +isakmp_ph2begin_r(iph1, msg) + struct ph1handle *iph1; + vchar_t *msg; +{ + struct isakmp *isakmp = (struct isakmp *)msg->v; + struct ph2handle *iph2 = 0; + int error; +#ifdef ENABLE_STATS + struct timeval start, end; +#endif +#ifdef ENABLE_HYBRID + if (xauth_check(iph1) != 0) { + plog(LLV_ERROR, LOCATION, NULL, + "Attempt to start phase 2 whereas Xauth failed\n"); + return -1; + } +#endif + + iph2 = newph2(); + if (iph2 == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to allocate phase2 entry.\n"); + return -1; + } + + iph2->side = RESPONDER; + iph2->status = PHASE2ST_START; + iph2->flags = isakmp->flags; + iph2->msgid = isakmp->msgid; + iph2->seq = pk_getseq(); + iph2->ivm = oakley_newiv2(iph1, iph2->msgid); + if (iph2->ivm == NULL) { + delph2(iph2); + return -1; + } + iph2->dst = dupsaddr(iph1->remote); /* XXX should be considered */ + if (iph2->dst == NULL) { + delph2(iph2); + return -1; + } + iph2->src = dupsaddr(iph1->local); /* XXX should be considered */ + if (iph2->src == NULL) { + delph2(iph2); + return -1; + } + + /* add new entry to isakmp status table */ + insph2(iph2); + bindph12(iph1, iph2); + + plog(LLV_DEBUG, LOCATION, NULL, "===\n"); + { + char *a; + + a = racoon_strdup(saddr2str(iph2->src)); + STRDUP_FATAL(a); + + plog(LLV_INFO, LOCATION, NULL, + "respond new phase 2 negotiation: %s<=>%s\n", + a, saddr2str(iph2->dst)); + racoon_free(a); + } + +#ifdef ENABLE_STATS + gettimeofday(&start, NULL); +#endif + + error = (ph2exchange[etypesw2(ISAKMP_ETYPE_QUICK)] + [iph2->side] + [iph2->status])(iph2, msg); + if (error != 0) { + plog(LLV_ERROR, LOCATION, iph1->remote, + "failed to pre-process ph2 packet (side: %d, status: %d).\n", + iph2->side, iph2->status); + if (error != ISAKMP_INTERNAL_ERROR) + isakmp_info_send_n1(iph2->ph1, error, NULL); + /* + * release handler because it's wrong that ph2handle is kept + * after failed to check message for responder's. + */ + remph2(iph2); + delph2(iph2); + return -1; + } + + /* send */ + plog(LLV_DEBUG, LOCATION, NULL, "===\n"); + if ((ph2exchange[etypesw2(isakmp->etype)] + [iph2->side] + [iph2->status])(iph2, msg) < 0) { + plog(LLV_ERROR, LOCATION, iph2->ph1->remote, + "failed to process ph2 packet (side: %d, status: %d).\n", + iph2->side, iph2->status); + /* don't release handler */ + return -1; + } +#ifdef ENABLE_STATS + gettimeofday(&end, NULL); + syslog(LOG_NOTICE, "%s(%s): %8.6f", + "phase2", + s_isakmp_state(ISAKMP_ETYPE_QUICK, iph2->side, iph2->status), + timedelta(&start, &end)); +#endif + + return 0; +} + +/* + * parse ISAKMP payloads, without ISAKMP base header. + */ +vchar_t * +isakmp_parsewoh(np0, gen, len) + int np0; + struct isakmp_gen *gen; + int len; +{ + u_char np = np0 & 0xff; + int tlen, plen; + vchar_t *result; + struct isakmp_parse_t *p, *ep; + + plog(LLV_DEBUG, LOCATION, NULL, "begin.\n"); + + /* + * 5 is a magic number, but any value larger than 2 should be fine + * as we do vrealloc() in the following loop. + */ + result = vmalloc(sizeof(struct isakmp_parse_t) * 5); + if (result == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to get buffer.\n"); + return NULL; + } + p = (struct isakmp_parse_t *)result->v; + ep = (struct isakmp_parse_t *)(result->v + result->l - sizeof(*ep)); + + tlen = len; + + /* parse through general headers */ + while (0 < tlen && np != ISAKMP_NPTYPE_NONE) { + if (tlen <= sizeof(struct isakmp_gen)) { + /* don't send information, see isakmp_ident_r1() */ + plog(LLV_ERROR, LOCATION, NULL, + "invalid length of payload\n"); + vfree(result); + return NULL; + } + + plog(LLV_DEBUG, LOCATION, NULL, + "seen nptype=%u(%s)\n", np, s_isakmp_nptype(np)); + + p->type = np; + p->len = ntohs(gen->len); + if (p->len < sizeof(struct isakmp_gen) || p->len > tlen) { + plog(LLV_DEBUG, LOCATION, NULL, + "invalid length of payload\n"); + vfree(result); + return NULL; + } + p->ptr = gen; + p++; + if (ep <= p) { + int off; + + off = p - (struct isakmp_parse_t *)result->v; + result = vrealloc(result, result->l * 2); + if (result == NULL) { + plog(LLV_DEBUG, LOCATION, NULL, + "failed to realloc buffer.\n"); + vfree(result); + return NULL; + } + ep = (struct isakmp_parse_t *) + (result->v + result->l - sizeof(*ep)); + p = (struct isakmp_parse_t *)result->v; + p += off; + } + + np = gen->np; + plen = ntohs(gen->len); + gen = (struct isakmp_gen *)((caddr_t)gen + plen); + tlen -= plen; + } + p->type = ISAKMP_NPTYPE_NONE; + p->len = 0; + p->ptr = NULL; + + plog(LLV_DEBUG, LOCATION, NULL, "succeed.\n"); + + return result; +} + +/* + * parse ISAKMP payloads, including ISAKMP base header. + */ +vchar_t * +isakmp_parse(buf) + vchar_t *buf; +{ + struct isakmp *isakmp = (struct isakmp *)buf->v; + struct isakmp_gen *gen; + int tlen; + vchar_t *result; + u_char np; + + np = isakmp->np; + gen = (struct isakmp_gen *)(buf->v + sizeof(*isakmp)); + tlen = buf->l - sizeof(struct isakmp); + result = isakmp_parsewoh(np, gen, tlen); + + return result; +} + +/* %%% */ +int +isakmp_init() +{ + /* initialize a isakmp status table */ + initph1tree(); + initph2tree(); + initctdtree(); + init_recvdpkt(); + + return 0; +} + +/* + * make strings containing i_cookie + r_cookie + msgid + */ +const char * +isakmp_pindex(index, msgid) + const isakmp_index *index; + const u_int32_t msgid; +{ + static char buf[64]; + const u_char *p; + int i, j; + + memset(buf, 0, sizeof(buf)); + + /* copy index */ + p = (const u_char *)index; + for (j = 0, i = 0; i < sizeof(isakmp_index); i++) { + snprintf((char *)&buf[j], sizeof(buf) - j, "%02x", p[i]); + j += 2; + switch (i) { + case 7: + buf[j++] = ':'; + } + } + + if (msgid == 0) + return buf; + + /* copy msgid */ + snprintf((char *)&buf[j], sizeof(buf) - j, ":%08x", ntohs(msgid)); + + return buf; +} + +/* open ISAKMP sockets. */ +int +isakmp_open(struct sockaddr *addr, int udp_encap) +{ + const int yes = 1; + int ifnum = 0, encap_ifnum = 0, fd; + struct sockaddr_in *sin = (struct sockaddr_in *) addr; +#ifdef INET6 + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) addr; + int pktinfo; +#endif +#ifdef ENABLE_NATT + int option = -1; +#endif + + /* warn if wildcard address - should we forbid this? */ + switch (addr->sa_family) { + case AF_INET: + if (sin->sin_addr.s_addr == 0) + plog(LLV_WARNING, LOCATION, NULL, + "listening to wildcard address," + "broadcast IKE packet may kill you\n"); + break; +#ifdef INET6 + case AF_INET6: + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) { + plog(LLV_DEBUG, LOCATION, NULL, + "ignoring multicast address %s\n", + saddr2str(addr)); + return -1; + } + + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) + plog(LLV_WARNING, LOCATION, NULL, + "listening to wildcard address, " + "broadcast IKE packet may kill you\n"); + break; +#endif + default: + plog(LLV_ERROR, LOCATION, NULL, + "unsupported address family %d\n", + addr->sa_family); + return -1; + } + + if ((fd = privsep_socket(addr->sa_family, SOCK_DGRAM, 0)) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "socket(%s)\n", strerror(errno)); + return -1; + } + close_on_exec(fd); + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + plog(LLV_WARNING, LOCATION, NULL, + "failed to put socket in non-blocking mode\n"); + + /* receive my interface address on inbound packets. */ + switch (addr->sa_family) { + case AF_INET: + if (setsockopt(fd, IPPROTO_IP, +#ifdef __linux__ + IP_PKTINFO, +#else + IP_RECVDSTADDR, +#endif + (const void *) &yes, sizeof(yes)) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "setsockopt IP_RECVDSTADDR (%s)\n", + strerror(errno)); + goto err; + } + +#ifdef ENABLE_NATT + if (udp_encap) + option = UDP_ENCAP_ESPINUDP; +#if defined(ENABLE_NATT_00) || defined(ENABLE_NATT_01) + else + option = UDP_ENCAP_ESPINUDP_NON_IKE; +#endif + if (option == -1) + break; + + if (setsockopt(fd, SOL_UDP, + UDP_ENCAP, &option, + sizeof(option)) < 0) { + plog(LLV_WARNING, LOCATION, NULL, + "setsockopt(%s): UDP_ENCAP %s\n", + option == UDP_ENCAP_ESPINUDP ? "UDP_ENCAP_ESPINUDP" : "UDP_ENCAP_ESPINUDP_NON_IKE", + strerror(errno)); + } else { + plog(LLV_INFO, LOCATION, NULL, + "%s used for NAT-T\n", + saddr2str(addr)); + } +#endif + break; + +#ifdef INET6 + case AF_INET6: +#if defined(INET6_ADVAPI) +#ifdef IPV6_RECVPKTINFO + pktinfo = IPV6_RECVPKTINFO; +#else /* old adv. API */ + pktinfo = IPV6_PKTINFO; +#endif /* IPV6_RECVPKTINFO */ +#else + pktinfo = IPV6_RECVDSTADDR; +#endif + if (setsockopt(fd, IPPROTO_IPV6, pktinfo, + (const void *) &yes, sizeof(yes)) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "setsockopt IPV6_RECVDSTADDR (%d):%s\n", + pktinfo, strerror(errno)); + goto err; + } + +#ifdef IPV6_USE_MIN_MTU + if (setsockopt(fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU, + (void *) &yes, sizeof(yes)) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "setsockopt IPV6_USE_MIN_MTU (%s)\n", + strerror(errno)); + goto err; + } +#endif + break; +#endif + } + + if (setsockopt(fd, SOL_SOCKET, +#ifdef __linux__ + SO_REUSEADDR, +#else + SO_REUSEPORT, +#endif + (void *) &yes, sizeof(yes)) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to set REUSE flag on %s (%s).\n", + saddr2str(addr), strerror(errno)); + goto err; + } + + if (setsockopt_bypass(fd, addr->sa_family) < 0) + goto err; + + if (privsep_bind(fd, addr, sysdep_sa_len(addr)) < 0) { + plog(LLV_ERROR, LOCATION, addr, + "failed to bind to address %s (%s).\n", + saddr2str(addr), strerror(errno)); + goto err; + } + + plog(LLV_INFO, LOCATION, NULL, + "%s used as isakmp port (fd=%d)\n", + saddr2str(addr), fd); + + monitor_fd(fd, isakmp_handler, NULL, 1); + return fd; + +err: + close(fd); + return -1; +} + +void +isakmp_close(int fd) +{ + unmonitor_fd(fd); + close(fd); +} + +int +isakmp_send(iph1, sbuf) + struct ph1handle *iph1; + vchar_t *sbuf; +{ + int len = 0; + int s; + vchar_t *vbuf = NULL, swap; + +#ifdef ENABLE_NATT + size_t extralen = NON_ESP_MARKER_USE(iph1) ? NON_ESP_MARKER_LEN : 0; + + /* Check if NON_ESP_MARKER_LEN is already there (happens when resending packets) + */ + if(extralen == NON_ESP_MARKER_LEN && + *(u_int32_t *)sbuf->v == 0) + extralen = 0; + +#ifdef ENABLE_FRAG + /* + * Do not add the non ESP marker for a packet that will + * be fragmented. The non ESP marker should appear in + * all fragment's packets, but not in the fragmented packet + */ + if (iph1->frag && sbuf->l > ISAKMP_FRAG_MAXLEN) + extralen = 0; +#endif + if (extralen) + plog (LLV_DEBUG, LOCATION, NULL, "Adding NON-ESP marker\n"); + + /* If NAT-T port floating is in use, 4 zero bytes (non-ESP marker) + must added just before the packet itself. For this we must + allocate a new buffer and release it at the end. */ + if (extralen) { + if ((vbuf = vmalloc (sbuf->l + extralen)) == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "vbuf allocation failed\n"); + return -1; + } + *(u_int32_t *)vbuf->v = 0; + memcpy (vbuf->v + extralen, sbuf->v, sbuf->l); + /* ensures that the modified buffer will be sent back to the caller, so + * add_recvdpkt() will add the correct buffer + */ + swap = *sbuf; + *sbuf = *vbuf; + *vbuf = swap; + vfree(vbuf); + } +#endif + + /* select the socket to be sent */ + s = myaddr_getfd(iph1->local); + if (s == -1) + return -1; + + plog (LLV_DEBUG, LOCATION, NULL, "%zu bytes %s\n", sbuf->l, + saddr2str_fromto("from %s to %s", iph1->local, iph1->remote)); + +#ifdef ENABLE_FRAG + if (iph1->frag && sbuf->l > ISAKMP_FRAG_MAXLEN) { + if (isakmp_sendfrags(iph1, sbuf) == -1) { + plog(LLV_ERROR, LOCATION, NULL, + "isakmp_sendfrags failed\n"); + return -1; + } + } else +#endif + { + len = sendfromto(s, sbuf->v, sbuf->l, + iph1->local, iph1->remote, lcconf->count_persend); + + if (len == -1) { + plog(LLV_ERROR, LOCATION, NULL, "sendfromto failed\n"); + return -1; + } + } + + return 0; +} + +/* called from scheduler */ +static void +isakmp_ph1resend_stub(p) + struct sched *p; +{ + struct ph1handle *iph1 = container_of(p, struct ph1handle, scr); + + if (isakmp_ph1resend(iph1) < 0) { + remph1(iph1); + delph1(iph1); + } +} + +static int +isakmp_ph1resend(iph1) + struct ph1handle *iph1; +{ + /* Note: NEVER do the rem/del here, it will be done by the caller or by the _stub function + */ + if (iph1->retry_counter <= 0) { + plog(LLV_ERROR, LOCATION, NULL, + "phase1 negotiation failed due to time up. %s\n", + isakmp_pindex(&iph1->index, iph1->msgid)); + /* XXX is the peer really "dead" here ??? */ + script_hook(iph1, SCRIPT_PHASE1_DEAD); + evt_phase1(iph1, EVT_PHASE1_NO_RESPONSE, NULL); + + return -1; + } + + if (isakmp_send(iph1, iph1->sendbuf) < 0){ + plog(LLV_ERROR, LOCATION, NULL, + "phase1 negotiation failed due to send error. %s\n", + isakmp_pindex(&iph1->index, iph1->msgid)); + evt_phase1(iph1, EVT_PHASE1_NO_RESPONSE, NULL); + return -1; + } + + plog(LLV_DEBUG, LOCATION, NULL, + "resend phase1 packet %s\n", + isakmp_pindex(&iph1->index, iph1->msgid)); + + iph1->retry_counter--; + + sched_schedule(&iph1->scr, lcconf->retry_interval, + isakmp_ph1resend_stub); + + return 0; +} + +int +isakmp_ph1send(iph1) + struct ph1handle *iph1; +{ + iph1->retry_counter = lcconf->retry_counter; + return isakmp_ph1resend(iph1); +} + +/* called from scheduler */ +static void +isakmp_ph2resend_stub(p) + struct sched *p; +{ + struct ph2handle *iph2 = container_of(p, struct ph2handle, scr); + + if (isakmp_ph2resend(iph2) < 0) { + remph2(iph2); + delph2(iph2); + } +} + +static int +isakmp_ph2resend(iph2) + struct ph2handle *iph2; +{ + /* Note: NEVER do the unbind/rem/del here, it will be done by the caller or by the _stub function + */ + if (iph2->ph1->status >= PHASE1ST_EXPIRED) { + plog(LLV_ERROR, LOCATION, NULL, + "phase2 negotiation failed due to phase1 expired. %s\n", + isakmp_pindex(&iph2->ph1->index, iph2->msgid)); + return -1; + } + + if (iph2->retry_counter <= 0) { + plog(LLV_ERROR, LOCATION, NULL, + "phase2 negotiation failed due to time up. %s\n", + isakmp_pindex(&iph2->ph1->index, iph2->msgid)); + evt_phase2(iph2, EVT_PHASE2_NO_RESPONSE, NULL); + unbindph12(iph2); + return -1; + } + + if (isakmp_send(iph2->ph1, iph2->sendbuf) < 0){ + plog(LLV_ERROR, LOCATION, NULL, + "phase2 negotiation failed due to send error. %s\n", + isakmp_pindex(&iph2->ph1->index, iph2->msgid)); + evt_phase2(iph2, EVT_PHASE2_NO_RESPONSE, NULL); + return -1; + } + + plog(LLV_DEBUG, LOCATION, NULL, + "resend phase2 packet %s\n", + isakmp_pindex(&iph2->ph1->index, iph2->msgid)); + + iph2->retry_counter--; + + sched_schedule(&iph2->scr, lcconf->retry_interval, + isakmp_ph2resend_stub); + + return 0; +} + +int +isakmp_ph2send(iph2) + struct ph2handle *iph2; +{ + iph2->retry_counter = lcconf->retry_counter; + return isakmp_ph2resend(iph2); +} + +/* called from scheduler */ +void +isakmp_ph1dying_stub(p) + struct sched *p; +{ + + isakmp_ph1dying(container_of(p, struct ph1handle, sce)); +} + +void +isakmp_ph1dying(iph1) + struct ph1handle *iph1; +{ + struct ph1handle *new_iph1; + struct ph2handle *p; + struct remoteconf *rmconf; + + if (iph1->status >= PHASE1ST_DYING) + return; + + /* Going away in after a while... */ + iph1->status = PHASE1ST_DYING; + + /* Any fresh phase1s? */ + new_iph1 = getph1(iph1, iph1->local, iph1->remote, 1); + if (new_iph1 == NULL) { + LIST_FOREACH(p, &iph1->ph2tree, ph1bind) { + if (p->status != PHASE2ST_ESTABLISHED) + continue; + + plog(LLV_INFO, LOCATION, NULL, + "renegotiating phase1 to %s due to " + "active phase2\n", + saddrwop2str(iph1->remote)); + + if (iph1->side == INITIATOR) + isakmp_ph1begin_i(iph1->rmconf, iph1->remote, + iph1->local); + + break; + } + } else { + migrate_ph12(iph1, new_iph1); + } + + /* Schedule for expiration */ + sched_schedule(&iph1->sce, iph1->approval->lifetime * + (100 - PFKEY_SOFT_LIFETIME_RATE) / 100, + isakmp_ph1expire_stub); +} + +/* called from scheduler */ +void +isakmp_ph1expire_stub(p) + struct sched *p; +{ + isakmp_ph1expire(container_of(p, struct ph1handle, sce)); +} + +void +isakmp_ph1expire(iph1) + struct ph1handle *iph1; +{ + char *src, *dst; + + if (iph1->status < PHASE1ST_EXPIRED) { + src = racoon_strdup(saddr2str(iph1->local)); + dst = racoon_strdup(saddr2str(iph1->remote)); + STRDUP_FATAL(src); + STRDUP_FATAL(dst); + + plog(LLV_INFO, LOCATION, NULL, + "ISAKMP-SA expired %s-%s spi:%s\n", + src, dst, + isakmp_pindex(&iph1->index, 0)); + racoon_free(src); + racoon_free(dst); + iph1->status = PHASE1ST_EXPIRED; + } + + isakmp_ph1delete(iph1); +} + +/* called from scheduler */ +void +isakmp_ph1delete_stub(p) + struct sched *p; +{ + + isakmp_ph1delete(container_of(p, struct ph1handle, sce)); +} + +void +isakmp_ph1delete(iph1) + struct ph1handle *iph1; +{ + struct ph2handle *p, *next; + struct ph1handle *new_iph1; + char *src, *dst; + + /* Migrate established phase2s. Any fresh phase1s? */ + new_iph1 = getph1(iph1, iph1->local, iph1->remote, 1); + if (new_iph1 != NULL) + migrate_ph12(iph1, new_iph1); + + /* Discard any left phase2s */ + for (p = LIST_FIRST(&iph1->ph2tree); p; p = next) { + next = LIST_NEXT(p, ph1bind); + if (p->status == PHASE2ST_ESTABLISHED) + isakmp_info_send_d2(p); + /* remove all ph2 handles, + * as ph1handle will be expired soon + */ + delete_spd(p, 1); + remph2(p); + delph2(p); + } + + src = racoon_strdup(saddr2str(iph1->local)); + dst = racoon_strdup(saddr2str(iph1->remote)); + STRDUP_FATAL(src); + STRDUP_FATAL(dst); + + plog(LLV_INFO, LOCATION, NULL, + "ISAKMP-SA deleted %s-%s spi:%s\n", + src, dst, isakmp_pindex(&iph1->index, 0)); + + evt_phase1(iph1, EVT_PHASE1_DOWN, NULL); + if (new_iph1 == NULL && ph1_rekey_enabled(iph1)) + script_hook(iph1, SCRIPT_PHASE1_DEAD); + + racoon_free(src); + racoon_free(dst); + + remph1(iph1); + delph1(iph1); +} + +/* called from scheduler. + * this function will call only isakmp_ph2delete(). + * phase 2 handler remain forever if kernel doesn't cry a expire of phase 2 SA + * by something cause. That's why this function is called after phase 2 SA + * expires in the userland. + */ +void +isakmp_ph2expire_stub(p) + struct sched *p; +{ + + isakmp_ph2expire(container_of(p, struct ph2handle, sce)); +} + +void +isakmp_ph2expire(iph2) + struct ph2handle *iph2; +{ + char *src, *dst; + + src = racoon_strdup(saddrwop2str(iph2->src)); + dst = racoon_strdup(saddrwop2str(iph2->dst)); + STRDUP_FATAL(src); + STRDUP_FATAL(dst); + + plog(LLV_INFO, LOCATION, NULL, + "phase2 sa expired %s-%s\n", src, dst); + racoon_free(src); + racoon_free(dst); + + iph2->status = PHASE2ST_EXPIRED; + sched_schedule(&iph2->sce, 1, isakmp_ph2delete_stub); +} + +/* called from scheduler */ +void +isakmp_ph2delete_stub(p) + struct sched *p; +{ + + isakmp_ph2delete(container_of(p, struct ph2handle, sce)); +} + +void +isakmp_ph2delete(iph2) + struct ph2handle *iph2; +{ + char *src, *dst; + + src = racoon_strdup(saddrwop2str(iph2->src)); + dst = racoon_strdup(saddrwop2str(iph2->dst)); + STRDUP_FATAL(src); + STRDUP_FATAL(dst); + + plog(LLV_INFO, LOCATION, NULL, + "phase2 sa deleted %s-%s\n", src, dst); + racoon_free(src); + racoon_free(dst); + + remph2(iph2); + delph2(iph2); + + return; +} + +/* %%% + * Interface between PF_KEYv2 and ISAKMP + */ +/* + * receive ACQUIRE from kernel, and begin either phase1 or phase2. + * if phase1 has been finished, begin phase2. + */ +int +isakmp_post_acquire(iph2, iph1hint, nopassive) + struct ph2handle *iph2; + struct ph1handle *iph1hint; + int nopassive; +{ + struct remoteconf *rmconf; + struct ph1handle *iph1 = NULL; + + plog(LLV_DEBUG, LOCATION, NULL, "in post_acquire\n"); + + /* Search appropriate configuration with masking port. Note that + * we always use iph2->dst, and not iph2->sa_dst. + * + * XXX One possible need for using iph2->sa_dst if not NULL would + * be for selecting a remote configuration based on a stable + * address of a mobile node (not a CoA provided by MIGRATE/KMADDRESS + * as iph2->dst hint). This scenario would require additional changes, + * so no need to bother yet. --arno */ + + if (iph1hint == NULL || iph1hint->rmconf == NULL) { + rmconf = getrmconf(iph2->dst, nopassive ? GETRMCONF_F_NO_PASSIVE : 0); + if (rmconf == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "no configuration found for %s.\n", + saddrwop2str(iph2->dst)); + return -1; + } + } else { + rmconf = iph1hint->rmconf; + } + + /* if passive mode, ignore the acquire message */ + if (nopassive && rmconf->passive) { + plog(LLV_DEBUG, LOCATION, NULL, + "because of passive mode, " + "ignore the acquire message for %s.\n", + saddrwop2str(iph2->dst)); + return -1; + } + + /* + * XXX Searching by IP addresses + ports might fail on + * some cases, we should use the ISAKMP identity to search + * matching ISAKMP. + */ + iph1 = getph1(iph1hint, iph2->src, iph2->dst, 0); + + /* no ISAKMP-SA found. */ + if (iph1 == NULL) { + iph2->retry_checkph1 = lcconf->retry_checkph1; + sched_schedule(&iph2->sce, 1, isakmp_chkph1there_stub); + plog(LLV_INFO, LOCATION, NULL, + "IPsec-SA request for %s queued " + "due to no phase1 found.\n", + saddrwop2str(iph2->dst)); + + /* start phase 1 negotiation as a initiator. */ + if (isakmp_ph1begin_i(rmconf, iph2->dst, iph2->src) == NULL) { + sched_cancel(&iph2->sce); + return -1; + } + + return 0; + /*NOTREACHED*/ + } + + /* found ISAKMP-SA, but on negotiation. */ + if (iph1->status < PHASE1ST_ESTABLISHED) { + iph2->retry_checkph1 = lcconf->retry_checkph1; + sched_schedule(&iph2->sce, 1, isakmp_chkph1there_stub); + plog(LLV_INFO, LOCATION, iph2->dst, + "request for establishing IPsec-SA was queued " + "due to no phase1 found.\n"); + return 0; + /*NOTREACHED*/ + } + + /* found established ISAKMP-SA */ + /* i.e. iph1->status == PHASE1ST_ESTABLISHED */ + + /* found ISAKMP-SA. */ + plog(LLV_DEBUG, LOCATION, NULL, "begin QUICK mode.\n"); + + /* begin quick mode */ + if (isakmp_ph2begin_i(iph1, iph2)) + return -1; + + return 0; +} + +int +isakmp_get_sainfo(iph2, sp_out, sp_in) + struct ph2handle *iph2; + struct secpolicy *sp_out, *sp_in; +{ + struct remoteconf *conf; + uint32_t remoteid = 0; + + plog(LLV_DEBUG, LOCATION, NULL, + "new acquire %s\n", spidx2str(&sp_out->spidx)); + + /* get sainfo */ + { + vchar_t *idsrc, *iddst; + + idsrc = ipsecdoi_sockaddr2id((struct sockaddr *)&sp_out->spidx.src, + sp_out->spidx.prefs, sp_out->spidx.ul_proto); + if (idsrc == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to get ID for %s\n", + spidx2str(&sp_out->spidx)); + return -1; + } + iddst = ipsecdoi_sockaddr2id((struct sockaddr *)&sp_out->spidx.dst, + sp_out->spidx.prefd, sp_out->spidx.ul_proto); + if (iddst == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to get ID for %s\n", + spidx2str(&sp_out->spidx)); + vfree(idsrc); + return -1; + } + + conf = getrmconf(iph2->dst, 0); + if (conf != NULL) + remoteid = conf->ph1id; + else + plog(LLV_DEBUG, LOCATION, NULL, "Warning: no valid rmconf !\n"); + + iph2->sainfo = getsainfo(idsrc, iddst, NULL, NULL, remoteid); + vfree(idsrc); + vfree(iddst); + if (iph2->sainfo == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to get sainfo.\n"); + return -1; + /* XXX should use the algorithm list from register message */ + } + + plog(LLV_DEBUG, LOCATION, NULL, + "selected sainfo: %s\n", sainfo2str(iph2->sainfo)); + } + + if (set_proposal_from_policy(iph2, sp_out, sp_in) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to create saprop.\n"); + return -1; + } + + return 0; +} + + +/* + * receive GETSPI from kernel. + */ +int +isakmp_post_getspi(iph2) + struct ph2handle *iph2; +{ +#ifdef ENABLE_STATS + struct timeval start, end; +#endif + + /* don't process it because there is no suitable phase1-sa. */ + if (iph2->ph1->status >= PHASE1ST_EXPIRED) { + plog(LLV_ERROR, LOCATION, iph2->ph1->remote, + "the negotiation is stopped, " + "because there is no suitable ISAKMP-SA.\n"); + return -1; + } + +#ifdef ENABLE_STATS + gettimeofday(&start, NULL); +#endif + if ((ph2exchange[etypesw2(ISAKMP_ETYPE_QUICK)] + [iph2->side] + [iph2->status])(iph2, NULL) != 0) + return -1; +#ifdef ENABLE_STATS + gettimeofday(&end, NULL); + syslog(LOG_NOTICE, "%s(%s): %8.6f", + "phase2", + s_isakmp_state(ISAKMP_ETYPE_QUICK, iph2->side, iph2->status), + timedelta(&start, &end)); +#endif + + return 0; +} + +/* called by scheduler */ +void +isakmp_chkph1there_stub(p) + struct sched *p; +{ + isakmp_chkph1there(container_of(p, struct ph2handle, sce)); +} + +void +isakmp_chkph1there(iph2) + struct ph2handle *iph2; +{ + struct ph1handle *iph1; + + iph2->retry_checkph1--; + if (iph2->retry_checkph1 < 0) { + plog(LLV_ERROR, LOCATION, iph2->dst, + "phase2 negotiation failed " + "due to time up waiting for phase1. %s\n", + sadbsecas2str(iph2->dst, iph2->src, + iph2->satype, 0, 0)); + plog(LLV_INFO, LOCATION, NULL, + "delete phase 2 handler.\n"); + + /* send acquire to kernel as error */ + pk_sendeacquire(iph2); + + remph2(iph2); + delph2(iph2); + + return; + } + + /* Search isakmp status table by address and port */ + iph1 = getph1byaddr(iph2->src, iph2->dst, 0); + + /* XXX Even if ph1 as responder is there, should we not start + * phase 2 negotiation ? */ + if (iph1 != NULL + && iph1->status == PHASE1ST_ESTABLISHED) { + /* found isakmp-sa */ + + plog(LLV_DEBUG2, LOCATION, NULL, "CHKPH1THERE: got a ph1 handler, setting ports.\n"); + plog(LLV_DEBUG2, LOCATION, NULL, "iph1->local: %s\n", saddr2str(iph1->local)); + plog(LLV_DEBUG2, LOCATION, NULL, "iph1->remote: %s\n", saddr2str(iph1->remote)); + plog(LLV_DEBUG2, LOCATION, NULL, "before:\n"); + plog(LLV_DEBUG2, LOCATION, NULL, "src: %s\n", saddr2str(iph2->src)); + plog(LLV_DEBUG2, LOCATION, NULL, "dst: %s\n", saddr2str(iph2->dst)); + set_port(iph2->src, extract_port(iph1->local)); + set_port(iph2->dst, extract_port(iph1->remote)); + plog(LLV_DEBUG2, LOCATION, NULL, "After:\n"); + plog(LLV_DEBUG2, LOCATION, NULL, "src: %s\n", saddr2str(iph2->src)); + plog(LLV_DEBUG2, LOCATION, NULL, "dst: %s\n", saddr2str(iph2->dst)); + + /* begin quick mode */ + (void)isakmp_ph2begin_i(iph1, iph2); + return; + } + + plog(LLV_DEBUG2, LOCATION, NULL, "CHKPH1THERE: no established ph1 handler found\n"); + + /* no isakmp-sa found */ + sched_schedule(&iph2->sce, 1, isakmp_chkph1there_stub); + + return; +} + +/* copy variable data into ALLOCATED buffer. */ +caddr_t +isakmp_set_attr_v(buf, type, val, len) + caddr_t buf; + int type; + caddr_t val; + int len; +{ + struct isakmp_data *data; + + data = (struct isakmp_data *)buf; + data->type = htons((u_int16_t)type | ISAKMP_GEN_TLV); + data->lorv = htons((u_int16_t)len); + memcpy(data + 1, val, len); + + return buf + sizeof(*data) + len; +} + +/* copy fixed length data into ALLOCATED buffer. */ +caddr_t +isakmp_set_attr_l(buf, type, val) + caddr_t buf; + int type; + u_int32_t val; +{ + struct isakmp_data *data; + + data = (struct isakmp_data *)buf; + data->type = htons((u_int16_t)type | ISAKMP_GEN_TV); + data->lorv = htons((u_int16_t)val); + + return buf + sizeof(*data); +} + +/* add a variable data attribute to the buffer by reallocating it. */ +vchar_t * +isakmp_add_attr_v(buf0, type, val, len) + vchar_t *buf0; + int type; + caddr_t val; + int len; +{ + vchar_t *buf = NULL; + struct isakmp_data *data; + int tlen; + int oldlen = 0; + + tlen = sizeof(*data) + len; + + if (buf0) { + oldlen = buf0->l; + buf = vrealloc(buf0, oldlen + tlen); + } else + buf = vmalloc(tlen); + if (!buf) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to get a attribute buffer.\n"); + return NULL; + } + + data = (struct isakmp_data *)(buf->v + oldlen); + data->type = htons((u_int16_t)type | ISAKMP_GEN_TLV); + data->lorv = htons((u_int16_t)len); + memcpy(data + 1, val, len); + + return buf; +} + +/* add a fixed data attribute to the buffer by reallocating it. */ +vchar_t * +isakmp_add_attr_l(buf0, type, val) + vchar_t *buf0; + int type; + u_int32_t val; +{ + vchar_t *buf = NULL; + struct isakmp_data *data; + int tlen; + int oldlen = 0; + + tlen = sizeof(*data); + + if (buf0) { + oldlen = buf0->l; + buf = vrealloc(buf0, oldlen + tlen); + } else + buf = vmalloc(tlen); + if (!buf) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to get a attribute buffer.\n"); + return NULL; + } + + data = (struct isakmp_data *)(buf->v + oldlen); + data->type = htons((u_int16_t)type | ISAKMP_GEN_TV); + data->lorv = htons((u_int16_t)val); + + return buf; +} + +/* + * calculate cookie and set. + */ +int +isakmp_newcookie(place, remote, local) + caddr_t place; + struct sockaddr *remote; + struct sockaddr *local; +{ + vchar_t *buf = NULL, *buf2 = NULL; + char *p; + int blen; + int alen; + caddr_t sa1, sa2; + time_t t; + int error = -1; + u_short port; + + + if (remote->sa_family != local->sa_family) { + plog(LLV_ERROR, LOCATION, NULL, + "address family mismatch, remote:%d local:%d\n", + remote->sa_family, local->sa_family); + goto end; + } + switch (remote->sa_family) { + case AF_INET: + alen = sizeof(struct in_addr); + sa1 = (caddr_t)&((struct sockaddr_in *)remote)->sin_addr; + sa2 = (caddr_t)&((struct sockaddr_in *)local)->sin_addr; + break; +#ifdef INET6 + case AF_INET6: + alen = sizeof(struct in6_addr); + sa1 = (caddr_t)&((struct sockaddr_in6 *)remote)->sin6_addr; + sa2 = (caddr_t)&((struct sockaddr_in6 *)local)->sin6_addr; + break; +#endif + default: + plog(LLV_ERROR, LOCATION, NULL, + "invalid family: %d\n", remote->sa_family); + goto end; + } + blen = (alen + sizeof(u_short)) * 2 + + sizeof(time_t) + lcconf->secret_size; + buf = vmalloc(blen); + if (buf == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to get a cookie.\n"); + goto end; + } + p = buf->v; + + /* copy my address */ + memcpy(p, sa1, alen); + p += alen; + port = ((struct sockaddr_in *)remote)->sin_port; + memcpy(p, &port, sizeof(u_short)); + p += sizeof(u_short); + + /* copy target address */ + memcpy(p, sa2, alen); + p += alen; + port = ((struct sockaddr_in *)local)->sin_port; + memcpy(p, &port, sizeof(u_short)); + p += sizeof(u_short); + + /* copy time */ + t = time(0); + memcpy(p, (caddr_t)&t, sizeof(t)); + p += sizeof(t); + + /* copy random value */ + buf2 = eay_set_random(lcconf->secret_size); + if (buf2 == NULL) + goto end; + memcpy(p, buf2->v, lcconf->secret_size); + p += lcconf->secret_size; + vfree(buf2); + + buf2 = eay_sha1_one(buf); + memcpy(place, buf2->v, sizeof(cookie_t)); + + sa1 = val2str(place, sizeof (cookie_t)); + plog(LLV_DEBUG, LOCATION, NULL, "new cookie:\n%s\n", sa1); + racoon_free(sa1); + + error = 0; +end: + if (buf != NULL) + vfree(buf); + if (buf2 != NULL) + vfree(buf2); + return error; +} + +/* + * save partner's(payload) data into phhandle. + */ +int +isakmp_p2ph(buf, gen) + vchar_t **buf; + struct isakmp_gen *gen; +{ + /* XXX to be checked in each functions for logging. */ + if (*buf) { + plog(LLV_WARNING, LOCATION, NULL, + "ignore this payload, same payload type exist.\n"); + return -1; + } + + *buf = vmalloc(ntohs(gen->len) - sizeof(*gen)); + if (*buf == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to get buffer.\n"); + return -1; + } + memcpy((*buf)->v, gen + 1, (*buf)->l); + + return 0; +} + +u_int32_t +isakmp_newmsgid2(iph1) + struct ph1handle *iph1; +{ + u_int32_t msgid2; + + do { + msgid2 = eay_random(); + } while (getph2bymsgid(iph1, msgid2)); + + return msgid2; +} + +/* + * set values into allocated buffer of isakmp header for phase 1 + */ +static caddr_t +set_isakmp_header(vbuf, iph1, nptype, etype, flags, msgid) + vchar_t *vbuf; + struct ph1handle *iph1; + int nptype; + u_int8_t etype; + u_int8_t flags; + u_int32_t msgid; +{ + struct isakmp *isakmp; + + if (vbuf->l < sizeof(*isakmp)) + return NULL; + + isakmp = (struct isakmp *)vbuf->v; + + memcpy(&isakmp->i_ck, &iph1->index.i_ck, sizeof(cookie_t)); + memcpy(&isakmp->r_ck, &iph1->index.r_ck, sizeof(cookie_t)); + isakmp->np = nptype; + isakmp->v = iph1->version; + isakmp->etype = etype; + isakmp->flags = flags; + isakmp->msgid = msgid; + isakmp->len = htonl(vbuf->l); + + return vbuf->v + sizeof(*isakmp); +} + +/* + * set values into allocated buffer of isakmp header for phase 1 + */ +caddr_t +set_isakmp_header1(vbuf, iph1, nptype) + vchar_t *vbuf; + struct ph1handle *iph1; + int nptype; +{ + return set_isakmp_header (vbuf, iph1, nptype, iph1->etype, iph1->flags, iph1->msgid); +} + +/* + * set values into allocated buffer of isakmp header for phase 2 + */ +caddr_t +set_isakmp_header2(vbuf, iph2, nptype) + vchar_t *vbuf; + struct ph2handle *iph2; + int nptype; +{ + return set_isakmp_header (vbuf, iph2->ph1, nptype, ISAKMP_ETYPE_QUICK, iph2->flags, iph2->msgid); +} + +/* + * set values into allocated buffer of isakmp payload. + */ +caddr_t +set_isakmp_payload(buf, src, nptype) + caddr_t buf; + vchar_t *src; + int nptype; +{ + struct isakmp_gen *gen; + caddr_t p = buf; + + plog(LLV_DEBUG, LOCATION, NULL, "add payload of len %zu, next type %d\n", + src->l, nptype); + + gen = (struct isakmp_gen *)p; + gen->np = nptype; + gen->len = htons(sizeof(*gen) + src->l); + p += sizeof(*gen); + memcpy(p, src->v, src->l); + p += src->l; + + return p; +} + +static int +etypesw1(etype) + int etype; +{ + switch (etype) { + case ISAKMP_ETYPE_IDENT: + return 1; + case ISAKMP_ETYPE_AGG: + return 2; + case ISAKMP_ETYPE_BASE: + return 3; + default: + return 0; + } + /*NOTREACHED*/ +} + +static int +etypesw2(etype) + int etype; +{ + switch (etype) { + case ISAKMP_ETYPE_QUICK: + return 1; + default: + return 0; + } + /*NOTREACHED*/ +} + +#ifdef HAVE_PRINT_ISAKMP_C +/* for print-isakmp.c */ +char *snapend; +extern void isakmp_print __P((const u_char *, u_int, const u_char *)); + +char *getname __P((const u_char *)); +#ifdef INET6 +char *getname6 __P((const u_char *)); +#endif +int safeputchar __P((int)); + +/* + * Return a name for the IP address pointed to by ap. This address + * is assumed to be in network byte order. + */ +char * +getname(ap) + const u_char *ap; +{ + struct sockaddr_in addr; + static char ntop_buf[NI_MAXHOST]; + + memset(&addr, 0, sizeof(addr)); +#ifndef __linux__ + addr.sin_len = sizeof(struct sockaddr_in); +#endif + addr.sin_family = AF_INET; + memcpy(&addr.sin_addr, ap, sizeof(addr.sin_addr)); + if (getnameinfo((struct sockaddr *)&addr, sizeof(addr), + ntop_buf, sizeof(ntop_buf), NULL, 0, + NI_NUMERICHOST | niflags)) + strlcpy(ntop_buf, "?", sizeof(ntop_buf)); + + return ntop_buf; +} + +#ifdef INET6 +/* + * Return a name for the IP6 address pointed to by ap. This address + * is assumed to be in network byte order. + */ +char * +getname6(ap) + const u_char *ap; +{ + struct sockaddr_in6 addr; + static char ntop_buf[NI_MAXHOST]; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_len = sizeof(struct sockaddr_in6); + addr.sin6_family = AF_INET6; + memcpy(&addr.sin6_addr, ap, sizeof(addr.sin6_addr)); + if (getnameinfo((struct sockaddr *)&addr, addr.sin6_len, + ntop_buf, sizeof(ntop_buf), NULL, 0, + NI_NUMERICHOST | niflags)) + strlcpy(ntop_buf, "?", sizeof(ntop_buf)); + + return ntop_buf; +} +#endif /* INET6 */ + +int +safeputchar(c) + int c; +{ + unsigned char ch; + + ch = (unsigned char)(c & 0xff); + if (c < 0x80 && isprint(c)) + return printf("%c", c & 0xff); + else + return printf("\\%03o", c & 0xff); +} + +void +isakmp_printpacket(msg, from, my, decoded) + vchar_t *msg; + struct sockaddr *from; + struct sockaddr *my; + int decoded; +{ +#ifdef YIPS_DEBUG + struct timeval tv; + int s; + char hostbuf[NI_MAXHOST]; + char portbuf[NI_MAXSERV]; + struct isakmp *isakmp; + vchar_t *buf; +#endif + + if (loglevel < LLV_DEBUG) + return; + +#ifdef YIPS_DEBUG + plog(LLV_DEBUG, LOCATION, NULL, "begin.\n"); + + gettimeofday(&tv, NULL); + s = tv.tv_sec % 3600; + printf("%02d:%02d.%06u ", s / 60, s % 60, (u_int32_t)tv.tv_usec); + + if (from) { + if (getnameinfo(from, sysdep_sa_len(from), hostbuf, sizeof(hostbuf), + portbuf, sizeof(portbuf), + NI_NUMERICHOST | NI_NUMERICSERV | niflags)) { + strlcpy(hostbuf, "?", sizeof(hostbuf)); + strlcpy(portbuf, "?", sizeof(portbuf)); + } + printf("%s:%s", hostbuf, portbuf); + } else + printf("?"); + printf(" -> "); + if (my) { + if (getnameinfo(my, sysdep_sa_len(my), hostbuf, sizeof(hostbuf), + portbuf, sizeof(portbuf), + NI_NUMERICHOST | NI_NUMERICSERV | niflags)) { + strlcpy(hostbuf, "?", sizeof(hostbuf)); + strlcpy(portbuf, "?", sizeof(portbuf)); + } + printf("%s:%s", hostbuf, portbuf); + } else + printf("?"); + printf(": "); + + buf = vdup(msg); + if (!buf) { + printf("(malloc fail)\n"); + return; + } + if (decoded) { + isakmp = (struct isakmp *)buf->v; + if (isakmp->flags & ISAKMP_FLAG_E) { +#if 0 + int pad; + pad = *(u_char *)(buf->v + buf->l - 1); + if (buf->l < pad && 2 < vflag) + printf("(wrong padding)"); +#endif + isakmp->flags &= ~ISAKMP_FLAG_E; + } + } + + snapend = buf->v + buf->l; + isakmp_print(buf->v, buf->l, NULL); + vfree(buf); + printf("\n"); + fflush(stdout); + + return; +#endif +} +#endif /*HAVE_PRINT_ISAKMP_C*/ + +int +copy_ph1addresses(iph1, rmconf, remote, local) + struct ph1handle *iph1; + struct remoteconf *rmconf; + struct sockaddr *remote, *local; +{ + u_int16_t port; + + /* address portion must be grabbed from real remote address "remote" */ + iph1->remote = dupsaddr(remote); + if (iph1->remote == NULL) + return -1; + + /* + * if remote has no port # (in case of initiator - from ACQUIRE msg) + * - if remote.conf specifies port #, use that + * - if remote.conf does not, use 500 + * if remote has port # (in case of responder - from recvfrom(2)) + * respect content of "remote". + */ + if (extract_port(iph1->remote) == 0) { + port = 0; + if (rmconf != NULL) + port = extract_port(rmconf->remote); + if (port == 0) + port = PORT_ISAKMP; + set_port(iph1->remote, port); + } + + if (local == NULL) + iph1->local = getlocaladdr(iph1->remote); + else + iph1->local = dupsaddr(local); + if (iph1->local == NULL) + return -1; + + if (extract_port(iph1->local) == 0) { + port = myaddr_getsport(iph1->local); + if (port == 0) + port = PORT_ISAKMP; + set_port(iph1->local, port); + } + +#ifdef ENABLE_NATT + if (extract_port(iph1->local) == lcconf->port_isakmp_natt) { + plog(LLV_DEBUG, LOCATION, NULL, "Marking ports as changed\n"); + iph1->natt_flags |= NAT_ADD_NON_ESP_MARKER; + } +#endif + + return 0; +} + +static int +nostate1(iph1, msg) + struct ph1handle *iph1; + vchar_t *msg; +{ + plog(LLV_ERROR, LOCATION, iph1->remote, "wrong state %u.\n", + iph1->status); + return -1; +} + +static int +nostate2(iph2, msg) + struct ph2handle *iph2; + vchar_t *msg; +{ + plog(LLV_ERROR, LOCATION, iph2->ph1->remote, "wrong state %u.\n", + iph2->status); + return -1; +} + +void +log_ph1established(iph1) + const struct ph1handle *iph1; +{ + char *src, *dst; + + src = racoon_strdup(saddr2str(iph1->local)); + dst = racoon_strdup(saddr2str(iph1->remote)); + STRDUP_FATAL(src); + STRDUP_FATAL(dst); + + plog(LLV_INFO, LOCATION, NULL, + "ISAKMP-SA established %s-%s spi:%s\n", + src, dst, + isakmp_pindex(&iph1->index, 0)); + + evt_phase1(iph1, EVT_PHASE1_UP, NULL); + if(!iph1->rmconf->mode_cfg) + evt_phase1(iph1, EVT_PHASE1_MODE_CFG, NULL); + + racoon_free(src); + racoon_free(dst); + + return; +} + +struct payload_list * +isakmp_plist_append_full (struct payload_list *plist, vchar_t *payload, + u_int8_t payload_type, u_int8_t free_payload) +{ + if (! plist) { + plist = racoon_malloc (sizeof (struct payload_list)); + plist->prev = NULL; + } + else { + plist->next = racoon_malloc (sizeof (struct payload_list)); + plist->next->prev = plist; + plist = plist->next; + } + + plist->next = NULL; + plist->payload = payload; + plist->payload_type = payload_type; + plist->free_payload = free_payload; + + return plist; +} + +vchar_t * +isakmp_plist_set_all (struct payload_list **plist, struct ph1handle *iph1) +{ + struct payload_list *ptr = *plist, *first; + size_t tlen = sizeof (struct isakmp), n = 0; + vchar_t *buf = NULL; + char *p; + + /* Seek to the first item. */ + while (ptr->prev) ptr = ptr->prev; + first = ptr; + + /* Compute the whole length. */ + while (ptr) { + tlen += ptr->payload->l + sizeof (struct isakmp_gen); + ptr = ptr->next; + } + + buf = vmalloc(tlen); + if (buf == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "failed to get buffer to send.\n"); + goto end; + } + + ptr = first; + + p = set_isakmp_header1(buf, iph1, ptr->payload_type); + if (p == NULL) + goto end; + + while (ptr) + { + p = set_isakmp_payload (p, ptr->payload, ptr->next ? ptr->next->payload_type : ISAKMP_NPTYPE_NONE); + first = ptr; + ptr = ptr->next; + if (first->free_payload) + vfree(first->payload); + racoon_free (first); + /* ptr->prev = NULL; first = NULL; ... omitted. */ + n++; + } + + *plist = NULL; + + return buf; +end: + if (buf != NULL) + vfree(buf); + return NULL; +} + +#ifdef ENABLE_FRAG +int +frag_handler(iph1, msg, remote, local) + struct ph1handle *iph1; + vchar_t *msg; + struct sockaddr *remote; + struct sockaddr *local; +{ + vchar_t *newmsg; + + if (isakmp_frag_extract(iph1, msg) == 1) { + if ((newmsg = isakmp_frag_reassembly(iph1)) == NULL) { + plog(LLV_ERROR, LOCATION, remote, + "Packet reassembly failed\n"); + return -1; + } + return isakmp_main(newmsg, remote, local); + } + + return 0; +} +#endif + +void +script_hook(iph1, script) + struct ph1handle *iph1; + int script; +{ +#define IP_MAX 40 +#define PORT_MAX 6 + char addrstr[IP_MAX]; + char portstr[PORT_MAX]; + char **envp = NULL; + int envc = 1; + char **c; + + if (iph1 == NULL || + iph1->rmconf == NULL || + iph1->rmconf->script[script] == NULL) + return; + +#ifdef ENABLE_HYBRID + (void)isakmp_cfg_setenv(iph1, &envp, &envc); +#endif + + /* local address */ + GETNAMEINFO(iph1->local, addrstr, portstr); + + if (script_env_append(&envp, &envc, "LOCAL_ADDR", addrstr) != 0) { + plog(LLV_ERROR, LOCATION, NULL, "Cannot set LOCAL_ADDR\n"); + goto out; + } + + if (script_env_append(&envp, &envc, "LOCAL_PORT", portstr) != 0) { + plog(LLV_ERROR, LOCATION, NULL, "Cannot set LOCAL_PORT\n"); + goto out; + } + + /* Peer address */ + if (iph1->remote != NULL) { + GETNAMEINFO(iph1->remote, addrstr, portstr); + + if (script_env_append(&envp, &envc, + "REMOTE_ADDR", addrstr) != 0) { + plog(LLV_ERROR, LOCATION, NULL, + "Cannot set REMOTE_ADDR\n"); + goto out; + } + + if (script_env_append(&envp, &envc, + "REMOTE_PORT", portstr) != 0) { + plog(LLV_ERROR, LOCATION, NULL, + "Cannot set REMOTEL_PORT\n"); + goto out; + } + } + + /* Peer identity. */ + if (iph1->id_p != NULL) { + if (script_env_append(&envp, &envc, "REMOTE_ID", + ipsecdoi_id2str(iph1->id_p)) != 0) { + plog(LLV_ERROR, LOCATION, NULL, + "Cannot set REMOTE_ID\n"); + goto out; + } + } + + if (privsep_script_exec(iph1->rmconf->script[script]->v, + script, envp) != 0) + plog(LLV_ERROR, LOCATION, NULL, + "Script %s execution failed\n", script_names[script]); + +out: + for (c = envp; *c; c++) + racoon_free(*c); + + racoon_free(envp); + + return; +} + +int +script_env_append(envp, envc, name, value) + char ***envp; + int *envc; + char *name; + char *value; +{ + char *envitem; + char **newenvp; + int newenvc; + + envitem = racoon_malloc(strlen(name) + 1 + strlen(value) + 1); + if (envitem == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "Cannot allocate memory: %s\n", strerror(errno)); + return -1; + } + sprintf(envitem, "%s=%s", name, value); + + newenvc = (*envc) + 1; + newenvp = racoon_realloc(*envp, newenvc * sizeof(char *)); + if (newenvp == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "Cannot allocate memory: %s\n", strerror(errno)); + racoon_free(envitem); + return -1; + } + + newenvp[newenvc - 2] = envitem; + newenvp[newenvc - 1] = NULL; + + *envp = newenvp; + *envc = newenvc; + return 0; +} + +int +script_exec(script, name, envp) + char *script; + int name; + char *const envp[]; +{ + char *argv[] = { NULL, NULL, NULL }; + + argv[0] = script; + argv[1] = script_names[name]; + argv[2] = NULL; + + switch (fork()) { + case 0: + execve(argv[0], argv, envp); + plog(LLV_ERROR, LOCATION, NULL, + "execve(\"%s\") failed: %s\n", + argv[0], strerror(errno)); + _exit(1); + break; + case -1: + plog(LLV_ERROR, LOCATION, NULL, + "Cannot fork: %s\n", strerror(errno)); + return -1; + break; + default: + break; + } + return 0; + +} + +void +purge_remote(iph1) + struct ph1handle *iph1; +{ + vchar_t *buf = NULL; + struct sadb_msg *msg, *next, *end; + struct sadb_sa *sa; + struct sockaddr *src, *dst; + caddr_t mhp[SADB_EXT_MAX + 1]; + u_int proto_id; + struct ph2handle *iph2; + struct ph1handle *new_iph1; + + plog(LLV_INFO, LOCATION, NULL, + "purging ISAKMP-SA spi=%s.\n", + isakmp_pindex(&(iph1->index), iph1->msgid)); + + /* Mark as expired. */ + iph1->status = PHASE1ST_EXPIRED; + + /* Check if we have another, still valid, phase1 SA. */ + new_iph1 = getph1(iph1, iph1->local, iph1->remote, GETPH1_F_ESTABLISHED); + + /* + * Delete all orphaned or binded to the deleting ph1handle phase2 SAs. + * Keep all others phase2 SAs. + */ + buf = pfkey_dump_sadb(SADB_SATYPE_UNSPEC); + if (buf == NULL) { + plog(LLV_DEBUG, LOCATION, NULL, + "pfkey_dump_sadb returned nothing.\n"); + return; + } + + msg = (struct sadb_msg *)buf->v; + end = (struct sadb_msg *)(buf->v + buf->l); + + while (msg < end) { + if ((msg->sadb_msg_len << 3) < sizeof(*msg)) + break; + next = (struct sadb_msg *)((caddr_t)msg + (msg->sadb_msg_len << 3)); + if (msg->sadb_msg_type != SADB_DUMP) { + msg = next; + continue; + } + + if (pfkey_align(msg, mhp) || pfkey_check(mhp)) { + plog(LLV_ERROR, LOCATION, NULL, + "pfkey_check (%s)\n", ipsec_strerror()); + msg = next; + continue; + } + + sa = (struct sadb_sa *)(mhp[SADB_EXT_SA]); + if (!sa || + !mhp[SADB_EXT_ADDRESS_SRC] || + !mhp[SADB_EXT_ADDRESS_DST]) { + msg = next; + continue; + } + pk_fixup_sa_addresses(mhp); + src = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_SRC]); + dst = PFKEY_ADDR_SADDR(mhp[SADB_EXT_ADDRESS_DST]); + + if (sa->sadb_sa_state != SADB_SASTATE_LARVAL && + sa->sadb_sa_state != SADB_SASTATE_MATURE && + sa->sadb_sa_state != SADB_SASTATE_DYING) { + msg = next; + continue; + } + + /* + * check in/outbound SAs. + * Select only SAs where src == local and dst == remote (outgoing) + * or src == remote and dst == local (incoming). + */ + if ((cmpsaddr(iph1->local, src) != CMPSADDR_MATCH || + cmpsaddr(iph1->remote, dst) != CMPSADDR_MATCH) && + (cmpsaddr(iph1->local, dst) != CMPSADDR_MATCH || + cmpsaddr(iph1->remote, src) != CMPSADDR_MATCH)) { + msg = next; + continue; + } + + proto_id = pfkey2ipsecdoi_proto(msg->sadb_msg_satype); + iph2 = getph2bysaidx(src, dst, proto_id, sa->sadb_sa_spi); + + /* Check if there is another valid ISAKMP-SA */ + if (new_iph1 != NULL) { + + if (iph2 == NULL) { + /* No handler... still send a pfkey_delete message, but log this !*/ + plog(LLV_INFO, LOCATION, NULL, + "Unknown IPsec-SA spi=%u, hmmmm?\n", + ntohl(sa->sadb_sa_spi)); + }else{ + + /* + * If we have a new ph1, do not purge IPsec-SAs binded + * to a different ISAKMP-SA + */ + if (iph2->ph1 != NULL && iph2->ph1 != iph1){ + msg = next; + continue; + } + + /* If the ph2handle is established, do not purge IPsec-SA */ + if (iph2->status == PHASE2ST_ESTABLISHED || + iph2->status == PHASE2ST_EXPIRED) { + + plog(LLV_INFO, LOCATION, NULL, + "keeping IPsec-SA spi=%u - found valid ISAKMP-SA spi=%s.\n", + ntohl(sa->sadb_sa_spi), + isakmp_pindex(&(new_iph1->index), new_iph1->msgid)); + msg = next; + continue; + } + } + } + + + pfkey_send_delete(lcconf->sock_pfkey, + msg->sadb_msg_satype, + IPSEC_MODE_ANY, + src, dst, sa->sadb_sa_spi); + + /* delete a relative phase 2 handle. */ + if (iph2 != NULL) { + delete_spd(iph2, 0); + remph2(iph2); + delph2(iph2); + } + + plog(LLV_INFO, LOCATION, NULL, + "purged IPsec-SA spi=%u.\n", + ntohl(sa->sadb_sa_spi)); + + msg = next; + } + + if (buf) + vfree(buf); + + /* Mark the phase1 handler as EXPIRED */ + plog(LLV_INFO, LOCATION, NULL, + "purged ISAKMP-SA spi=%s.\n", + isakmp_pindex(&(iph1->index), iph1->msgid)); + + isakmp_ph1delete(iph1); +} + +void +delete_spd(iph2, created) + struct ph2handle *iph2; + u_int64_t created; +{ + struct policyindex spidx; + struct sockaddr_storage addr; + u_int8_t pref; + struct sockaddr *src; + struct sockaddr *dst; + int error; + int idi2type = 0;/* switch whether copy IDs into id[src,dst]. */ + + if (iph2 == NULL) + return; + + /* Delete the SPD entry if we generated it + */ + if (! iph2->generated_spidx ) + return; + + src = iph2->src; + dst = iph2->dst; + + plog(LLV_INFO, LOCATION, NULL, + "deleting a generated policy.\n"); + + memset(&spidx, 0, sizeof(spidx)); + iph2->spidx_gen = (caddr_t )&spidx; + + /* make inbound policy */ + iph2->src = dst; + iph2->dst = src; + spidx.dir = IPSEC_DIR_INBOUND; + spidx.ul_proto = 0; + + /* + * Note: code from get_proposal_r + */ + +#define _XIDT(d) ((struct ipsecdoi_id_b *)(d)->v)->type + + /* + * make destination address in spidx from either ID payload + * or phase 1 address into a address in spidx. + */ + if (iph2->id != NULL + && (_XIDT(iph2->id) == IPSECDOI_ID_IPV4_ADDR + || _XIDT(iph2->id) == IPSECDOI_ID_IPV6_ADDR + || _XIDT(iph2->id) == IPSECDOI_ID_IPV4_ADDR_SUBNET + || _XIDT(iph2->id) == IPSECDOI_ID_IPV6_ADDR_SUBNET)) { + /* get a destination address of a policy */ + error = ipsecdoi_id2sockaddr(iph2->id, + (struct sockaddr *)&spidx.dst, + &spidx.prefd, &spidx.ul_proto); + if (error) + goto purge; + +#ifdef INET6 + /* + * get scopeid from the SA address. + * note that the phase 1 source address is used as + * a destination address to search for a inbound + * policy entry because rcoon is responder. + */ + if (_XIDT(iph2->id) == IPSECDOI_ID_IPV6_ADDR) { + if ((error = + setscopeid((struct sockaddr *)&spidx.dst, + iph2->src)) != 0) + goto purge; + } +#endif + + if (_XIDT(iph2->id) == IPSECDOI_ID_IPV4_ADDR + || _XIDT(iph2->id) == IPSECDOI_ID_IPV6_ADDR) + idi2type = _XIDT(iph2->id); + + } else { + + plog(LLV_DEBUG, LOCATION, NULL, + "get a destination address of SP index " + "from phase1 address " + "due to no ID payloads found " + "OR because ID type is not address.\n"); + + /* + * copy the SOURCE address of IKE into the + * DESTINATION address of the key to search the + * SPD because the direction of policy is inbound. + */ + memcpy(&spidx.dst, iph2->src, sysdep_sa_len(iph2->src)); + switch (spidx.dst.ss_family) { + case AF_INET: + spidx.prefd = + sizeof(struct in_addr) << 3; + break; +#ifdef INET6 + case AF_INET6: + spidx.prefd = + sizeof(struct in6_addr) << 3; + break; +#endif + default: + spidx.prefd = 0; + break; + } + } + + /* make source address in spidx */ + if (iph2->id_p != NULL + && (_XIDT(iph2->id_p) == IPSECDOI_ID_IPV4_ADDR + || _XIDT(iph2->id_p) == IPSECDOI_ID_IPV6_ADDR + || _XIDT(iph2->id_p) == IPSECDOI_ID_IPV4_ADDR_SUBNET + || _XIDT(iph2->id_p) == IPSECDOI_ID_IPV6_ADDR_SUBNET)) { + /* get a source address of inbound SA */ + error = ipsecdoi_id2sockaddr(iph2->id_p, + (struct sockaddr *)&spidx.src, + &spidx.prefs, &spidx.ul_proto); + if (error) + goto purge; + +#ifdef INET6 + /* + * get scopeid from the SA address. + * for more detail, see above of this function. + */ + if (_XIDT(iph2->id_p) == IPSECDOI_ID_IPV6_ADDR) { + error = + setscopeid((struct sockaddr *)&spidx.src, + iph2->dst); + if (error) + goto purge; + } +#endif + + /* make sa_[src,dst] if both ID types are IP address and same */ + if (_XIDT(iph2->id_p) == idi2type + && spidx.dst.ss_family == spidx.src.ss_family) { + iph2->sa_src = + dupsaddr((struct sockaddr *)&spidx.dst); + if (iph2->sa_src == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "allocation failed\n"); + goto purge; + } + iph2->sa_dst = + dupsaddr((struct sockaddr *)&spidx.src); + if (iph2->sa_dst == NULL) { + plog(LLV_ERROR, LOCATION, NULL, + "allocation failed\n"); + goto purge; + } + } + + } else { + plog(LLV_DEBUG, LOCATION, NULL, + "get a source address of SP index " + "from phase1 address " + "due to no ID payloads found " + "OR because ID type is not address.\n"); + + /* see above comment. */ + memcpy(&spidx.src, iph2->dst, sysdep_sa_len(iph2->dst)); + switch (spidx.src.ss_family) { + case AF_INET: + spidx.prefs = + sizeof(struct in_addr) << 3; + break; +#ifdef INET6 + case AF_INET6: + spidx.prefs = + sizeof(struct in6_addr) << 3; + break; +#endif + default: + spidx.prefs = 0; + break; + } + } + +#undef _XIDT + + plog(LLV_DEBUG, LOCATION, NULL, + "get a src address from ID payload " + "%s prefixlen=%u ul_proto=%u\n", + saddr2str((struct sockaddr *)&spidx.src), + spidx.prefs, spidx.ul_proto); + plog(LLV_DEBUG, LOCATION, NULL, + "get dst address from ID payload " + "%s prefixlen=%u ul_proto=%u\n", + saddr2str((struct sockaddr *)&spidx.dst), + spidx.prefd, spidx.ul_proto); + + /* + * convert the ul_proto if it is 0 + * because 0 in ID payload means a wild card. + */ + if (spidx.ul_proto == 0) + spidx.ul_proto = IPSEC_ULPROTO_ANY; + +#undef _XIDT + + /* Check if the generated SPD has the same timestamp as the SA. + * If timestamps are different, this means that the SPD entry has been + * refreshed by another SA, and should NOT be deleted with the current SA. + */ + if( created ){ + struct secpolicy *p; + + p = getsp(&spidx); + if(p != NULL){ + /* just do no test if p is NULL, because this probably just means + * that the policy has already be deleted for some reason. + */ + if(p->spidx.created != created) + goto purge; + } + } + + /* End of code from get_proposal_r + */ + + if (pk_sendspddelete(iph2) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "pfkey spddelete(inbound) failed.\n"); + }else{ + plog(LLV_DEBUG, LOCATION, NULL, + "pfkey spddelete(inbound) sent.\n"); + } + +#ifdef HAVE_POLICY_FWD + /* make forward policy if required */ + if (tunnel_mode_prop(iph2->approval)) { + spidx.dir = IPSEC_DIR_FWD; + if (pk_sendspddelete(iph2) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "pfkey spddelete(forward) failed.\n"); + }else{ + plog(LLV_DEBUG, LOCATION, NULL, + "pfkey spddelete(forward) sent.\n"); + } + } +#endif + + /* make outbound policy */ + iph2->src = src; + iph2->dst = dst; + spidx.dir = IPSEC_DIR_OUTBOUND; + addr = spidx.src; + spidx.src = spidx.dst; + spidx.dst = addr; + pref = spidx.prefs; + spidx.prefs = spidx.prefd; + spidx.prefd = pref; + + if (pk_sendspddelete(iph2) < 0) { + plog(LLV_ERROR, LOCATION, NULL, + "pfkey spddelete(outbound) failed.\n"); + }else{ + plog(LLV_DEBUG, LOCATION, NULL, + "pfkey spddelete(outbound) sent.\n"); + } +purge: + iph2->spidx_gen=NULL; +} + + +#ifdef INET6 +u_int32_t +setscopeid(sp_addr0, sa_addr0) + struct sockaddr *sp_addr0, *sa_addr0; +{ + struct sockaddr_in6 *sp_addr, *sa_addr; + + sp_addr = (struct sockaddr_in6 *)sp_addr0; + sa_addr = (struct sockaddr_in6 *)sa_addr0; + + if (!IN6_IS_ADDR_LINKLOCAL(&sp_addr->sin6_addr) + && !IN6_IS_ADDR_SITELOCAL(&sp_addr->sin6_addr) + && !IN6_IS_ADDR_MULTICAST(&sp_addr->sin6_addr)) + return 0; + + /* this check should not be here ? */ + if (sa_addr->sin6_family != AF_INET6) { + plog(LLV_ERROR, LOCATION, NULL, + "can't get scope ID: family mismatch\n"); + return -1; + } + + if (!IN6_IS_ADDR_LINKLOCAL(&sa_addr->sin6_addr)) { + plog(LLV_ERROR, LOCATION, NULL, + "scope ID is not supported except of lladdr.\n"); + return -1; + } + + sp_addr->sin6_scope_id = sa_addr->sin6_scope_id; + + return 0; +} +#endif |