#include #ifdef __rtems__ #include #include "rtems-bsd-racoon-namespace.h" #endif /* __rtems__ */ /* $NetBSD: handler.c,v 1.39.2.1 2011/11/17 14:46:31 vanhu Exp $ */ /* Id: handler.c,v 1.28 2006/05/26 12:17:29 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 #include #include #include #include #include #include #include #include "var.h" #include "misc.h" #include "vmbuf.h" #include "plog.h" #include "sockmisc.h" #include "debug.h" #ifdef ENABLE_HYBRID #include #endif #include "schedule.h" #include "grabmyaddr.h" #include "algorithm.h" #include "crypto_openssl.h" #include "policy.h" #include "proposal.h" #include "isakmp_var.h" #include "evt.h" #include "isakmp.h" #ifdef ENABLE_HYBRID #include "isakmp_xauth.h" #include "isakmp_cfg.h" #endif #include "isakmp_inf.h" #include "oakley.h" #include "remoteconf.h" #include "localconf.h" #include "handler.h" #include "gcmalloc.h" #include "nattraversal.h" #include "sainfo.h" #ifdef HAVE_GSSAPI #include "gssapi.h" #endif static LIST_HEAD(_ph1tree_, ph1handle) ph1tree; static LIST_HEAD(_ph2tree_, ph2handle) ph2tree; static LIST_HEAD(_ctdtree_, contacted) ctdtree; static LIST_HEAD(_rcptree_, recvdpkt) rcptree; static struct sched sc_sweep = SCHED_INITIALIZER(); static void del_recvdpkt __P((struct recvdpkt *)); static void rem_recvdpkt __P((struct recvdpkt *)); /* * functions about management of the isakmp status table */ /* %%% management phase 1 handler */ /* * search for isakmpsa handler with isakmp index. */ extern caddr_t val2str(const char *, size_t); /* * Enumerate the Phase 1 tree. * If enum_func() internally return a non-zero value, this specific * error value is returned. 0 is returned if everything went right. * * Note that it is ok for enum_func() to call insph1(). Those inserted * Phase 1 will not interfere with current enumeration process. */ int enumph1(sel, enum_func, enum_arg) struct ph1selector *sel; int (* enum_func)(struct ph1handle *iph1, void *arg); void *enum_arg; { struct ph1handle *p; int ret; LIST_FOREACH(p, &ph1tree, chain) { if (sel != NULL) { if (sel->local != NULL && cmpsaddr(sel->local, p->local) > CMPSADDR_WILDPORT_MATCH) continue; if (sel->remote != NULL && cmpsaddr(sel->remote, p->remote) > CMPSADDR_WILDPORT_MATCH) continue; } if ((ret = enum_func(p, enum_arg)) != 0) return ret; } return 0; } struct ph1handle * getph1byindex(index) isakmp_index *index; { struct ph1handle *p; LIST_FOREACH(p, &ph1tree, chain) { if (p->status >= PHASE1ST_EXPIRED) continue; if (memcmp(&p->index, index, sizeof(*index)) == 0) return p; } return NULL; } /* * search for isakmp handler by i_ck in index. */ struct ph1handle * getph1byindex0(index) isakmp_index *index; { struct ph1handle *p; LIST_FOREACH(p, &ph1tree, chain) { if (p->status >= PHASE1ST_EXPIRED) continue; if (memcmp(&p->index, index, sizeof(cookie_t)) == 0) return p; } return NULL; } /* * search for isakmpsa handler by source and remote address. * don't use port number to search because this function search * with phase 2's destinaion. */ struct ph1handle * getph1(ph1hint, local, remote, flags) struct ph1handle *ph1hint; struct sockaddr *local, *remote; int flags; { struct ph1handle *p; plog(LLV_DEBUG2, LOCATION, NULL, "getph1: start\n"); plog(LLV_DEBUG2, LOCATION, NULL, "local: %s\n", saddr2str(local)); plog(LLV_DEBUG2, LOCATION, NULL, "remote: %s\n", saddr2str(remote)); LIST_FOREACH(p, &ph1tree, chain) { if (p->status >= PHASE1ST_DYING) continue; plog(LLV_DEBUG2, LOCATION, NULL, "p->local: %s\n", saddr2str(p->local)); plog(LLV_DEBUG2, LOCATION, NULL, "p->remote: %s\n", saddr2str(p->remote)); if ((flags & GETPH1_F_ESTABLISHED) && (p->status != PHASE1ST_ESTABLISHED)) { plog(LLV_DEBUG2, LOCATION, NULL, "status %d, skipping\n", p->status); continue; } if (local != NULL && cmpsaddr(local, p->local) == CMPSADDR_MISMATCH) continue; if (remote != NULL && cmpsaddr(remote, p->remote) == CMPSADDR_MISMATCH) continue; if (ph1hint != NULL) { if (ph1hint->id && ph1hint->id->l && p->id && p->id->l && (ph1hint->id->l != p->id->l || memcmp(ph1hint->id->v, p->id->v, p->id->l) != 0)) { plog(LLV_DEBUG2, LOCATION, NULL, "local identity does match hint\n"); continue; } if (ph1hint->id_p && ph1hint->id_p->l && p->id_p && p->id_p->l && (ph1hint->id_p->l != p->id_p->l || memcmp(ph1hint->id_p->v, p->id_p->v, p->id_p->l) != 0)) { plog(LLV_DEBUG2, LOCATION, NULL, "remote identity does match hint\n"); continue; } } plog(LLV_DEBUG2, LOCATION, NULL, "matched\n"); return p; } plog(LLV_DEBUG2, LOCATION, NULL, "no match\n"); return NULL; } int resolveph1rmconf(iph1) struct ph1handle *iph1; { struct remoteconf *rmconf; /* INITIATOR is always expected to know the exact rmconf. */ if (iph1->side == INITIATOR) return 0; rmconf = getrmconf_by_ph1(iph1); if (rmconf == NULL) return -1; if (rmconf == RMCONF_ERR_MULTIPLE) return 1; if (iph1->rmconf != NULL) { if (rmconf != iph1->rmconf) { plog(LLV_ERROR, LOCATION, NULL, "unexpected rmconf switch; killing ph1\n"); return -1; } } else { iph1->rmconf = rmconf; } return 0; } /* * move phase2s from old_iph1 to new_iph1 */ void migrate_ph12(old_iph1, new_iph1) struct ph1handle *old_iph1, *new_iph1; { struct ph2handle *p, *next; /* Relocate phase2s to better phase1s or request a new phase1. */ for (p = LIST_FIRST(&old_iph1->ph2tree); p; p = next) { next = LIST_NEXT(p, ph1bind); if (p->status != PHASE2ST_ESTABLISHED) continue; unbindph12(p); bindph12(new_iph1, p); } } /* * the iph1 is new, migrate all phase2s that belong to a dying or dead ph1 */ void migrate_dying_ph12(iph1) struct ph1handle *iph1; { struct ph1handle *p; LIST_FOREACH(p, &ph1tree, chain) { if (p == iph1) continue; if (p->status < PHASE1ST_DYING) continue; if (cmpsaddr(iph1->local, p->local) == CMPSADDR_MATCH && cmpsaddr(iph1->remote, p->remote) == CMPSADDR_MATCH) migrate_ph12(p, iph1); } } /* * dump isakmp-sa */ vchar_t * dumpph1() { struct ph1handle *iph1; struct ph1dump *pd; int cnt = 0; vchar_t *buf; /* get length of buffer */ LIST_FOREACH(iph1, &ph1tree, chain) cnt++; buf = vmalloc(cnt * sizeof(struct ph1dump)); if (buf == NULL) { plog(LLV_ERROR, LOCATION, NULL, "failed to get buffer\n"); return NULL; } pd = (struct ph1dump *)buf->v; LIST_FOREACH(iph1, &ph1tree, chain) { memcpy(&pd->index, &iph1->index, sizeof(iph1->index)); pd->status = iph1->status; pd->side = iph1->side; memcpy(&pd->remote, iph1->remote, sysdep_sa_len(iph1->remote)); memcpy(&pd->local, iph1->local, sysdep_sa_len(iph1->local)); pd->version = iph1->version; pd->etype = iph1->etype; pd->created = iph1->created; pd->ph2cnt = iph1->ph2cnt; pd++; } return buf; } /* * create new isakmp Phase 1 status record to handle isakmp in Phase1 */ struct ph1handle * newph1() { struct ph1handle *iph1; /* create new iph1 */ iph1 = racoon_calloc(1, sizeof(*iph1)); if (iph1 == NULL) return NULL; iph1->status = PHASE1ST_SPAWN; #ifdef ENABLE_DPD iph1->dpd_support = 0; iph1->dpd_seq = 0; iph1->dpd_fails = 0; #endif evt_list_init(&iph1->evt_listeners); return iph1; } /* * delete new isakmp Phase 1 status record to handle isakmp in Phase1 */ void delph1(iph1) struct ph1handle *iph1; { if (iph1 == NULL) return; /* SA down shell script hook */ script_hook(iph1, SCRIPT_PHASE1_DOWN); evt_list_cleanup(&iph1->evt_listeners); #ifdef ENABLE_NATT if (iph1->natt_flags & NAT_KA_QUEUED) natt_keepalive_remove (iph1->local, iph1->remote); if (iph1->natt_options) { racoon_free(iph1->natt_options); iph1->natt_options = NULL; } #endif #ifdef ENABLE_HYBRID if (iph1->mode_cfg) isakmp_cfg_rmstate(iph1); #endif #ifdef ENABLE_DPD sched_cancel(&iph1->dpd_r_u); #endif sched_cancel(&iph1->sce); sched_cancel(&iph1->scr); if (iph1->remote) { racoon_free(iph1->remote); iph1->remote = NULL; } if (iph1->local) { racoon_free(iph1->local); iph1->local = NULL; } if (iph1->approval) { delisakmpsa(iph1->approval); iph1->approval = NULL; } VPTRINIT(iph1->authstr); VPTRINIT(iph1->sendbuf); VPTRINIT(iph1->dhpriv); VPTRINIT(iph1->dhpub); VPTRINIT(iph1->dhpub_p); VPTRINIT(iph1->dhgxy); VPTRINIT(iph1->nonce); VPTRINIT(iph1->nonce_p); VPTRINIT(iph1->skeyid); VPTRINIT(iph1->skeyid_d); VPTRINIT(iph1->skeyid_a); VPTRINIT(iph1->skeyid_e); VPTRINIT(iph1->key); VPTRINIT(iph1->hash); VPTRINIT(iph1->sig); VPTRINIT(iph1->sig_p); VPTRINIT(iph1->cert); VPTRINIT(iph1->cert_p); VPTRINIT(iph1->crl_p); VPTRINIT(iph1->cr_p); VPTRINIT(iph1->id); VPTRINIT(iph1->id_p); if(iph1->approval != NULL) delisakmpsa(iph1->approval); if (iph1->ivm) { oakley_delivm(iph1->ivm); iph1->ivm = NULL; } VPTRINIT(iph1->sa); VPTRINIT(iph1->sa_ret); #ifdef HAVE_GSSAPI VPTRINIT(iph1->gi_i); VPTRINIT(iph1->gi_r); gssapi_free_state(iph1); #endif racoon_free(iph1); } /* * create new isakmp Phase 1 status record to handle isakmp in Phase1 */ int insph1(iph1) struct ph1handle *iph1; { /* validity check */ if (iph1->remote == NULL) { plog(LLV_ERROR, LOCATION, NULL, "invalid isakmp SA handler. no remote address.\n"); return -1; } LIST_INSERT_HEAD(&ph1tree, iph1, chain); return 0; } void remph1(iph1) struct ph1handle *iph1; { LIST_REMOVE(iph1, chain); } /* * flush isakmp-sa */ void flushph1() { struct ph1handle *p, *next; for (p = LIST_FIRST(&ph1tree); p; p = next) { next = LIST_NEXT(p, chain); /* send delete information */ if (p->status >= PHASE1ST_ESTABLISHED) isakmp_info_send_d1(p); remph1(p); delph1(p); } } void initph1tree() { LIST_INIT(&ph1tree); } int ph1_rekey_enabled(iph1) struct ph1handle *iph1; { if (iph1->rmconf == NULL) return 0; if (iph1->rmconf->rekey == REKEY_FORCE) return 1; #ifdef ENABLE_DPD if (iph1->rmconf->rekey == REKEY_ON && iph1->dpd_support && iph1->rmconf->dpd_interval) return 1; #endif return 0; } /* %%% management phase 2 handler */ int enumph2(sel, enum_func, enum_arg) struct ph2selector *sel; int (*enum_func)(struct ph2handle *ph2, void *arg); void *enum_arg; { struct ph2handle *p; int ret; LIST_FOREACH(p, &ph2tree, chain) { if (sel != NULL) { if (sel->spid != 0 && sel->spid != p->spid) continue; if (sel->src != NULL && cmpsaddr(sel->src, p->src) != CMPSADDR_MATCH) continue; if (sel->dst != NULL && cmpsaddr(sel->dst, p->dst) != CMPSADDR_MATCH) continue; } if ((ret = enum_func(p, enum_arg)) != 0) return ret; } return 0; } /* * search ph2handle with sequence number. */ struct ph2handle * getph2byseq(seq) u_int32_t seq; { struct ph2handle *p; LIST_FOREACH(p, &ph2tree, chain) { if (p->seq == seq) return p; } return NULL; } /* * search ph2handle with message id. */ struct ph2handle * getph2bymsgid(iph1, msgid) struct ph1handle *iph1; u_int32_t msgid; { struct ph2handle *p; LIST_FOREACH(p, &iph1->ph2tree, ph1bind) { if (p->msgid == msgid && p->ph1 == iph1) return p; } return NULL; } /* Note that src and dst are not the selectors of the SP * but the source and destination addresses used for * for SA negotiation (best example is tunnel mode SA * where src and dst are the endpoints). There is at most * a unique match because racoon does not support bundles * which makes that there is at most a single established * SA for a given spid. One could say that src and dst * are in fact useless ... */ struct ph2handle * getph2byid(src, dst, spid) struct sockaddr *src, *dst; u_int32_t spid; { struct ph2handle *p, *next; for (p = LIST_FIRST(&ph2tree); p; p = next) { next = LIST_NEXT(p, chain); if (spid == p->spid && cmpsaddr(src, p->src) <= CMPSADDR_WILDPORT_MATCH && cmpsaddr(dst, p->dst) <= CMPSADDR_WILDPORT_MATCH){ /* Sanity check to detect zombie handlers * XXX Sould be done "somewhere" more interesting, * because we have lots of getph2byxxxx(), but this one * is called by pk_recvacquire(), so is the most important. */ if(p->status < PHASE2ST_ESTABLISHED && p->retry_counter == 0 && p->sce.func == NULL && p->scr.func == NULL) { plog(LLV_DEBUG, LOCATION, NULL, "Zombie ph2 found, expiring it\n"); isakmp_ph2expire(p); }else return p; } } return NULL; } struct ph2handle * getph2bysaddr(src, dst) struct sockaddr *src, *dst; { struct ph2handle *p; LIST_FOREACH(p, &ph2tree, chain) { if (cmpsaddr(src, p->src) <= CMPSADDR_WILDPORT_MATCH && cmpsaddr(dst, p->dst) <= CMPSADDR_WILDPORT_MATCH) return p; } return NULL; } /* * call by pk_recvexpire(). */ struct ph2handle * getph2bysaidx(src, dst, proto_id, spi) struct sockaddr *src, *dst; u_int proto_id; u_int32_t spi; { struct ph2handle *iph2; struct saproto *pr; LIST_FOREACH(iph2, &ph2tree, chain) { if (iph2->proposal == NULL && iph2->approval == NULL) continue; if (iph2->approval != NULL) { for (pr = iph2->approval->head; pr != NULL; pr = pr->next) { if (proto_id != pr->proto_id) break; if (spi == pr->spi || spi == pr->spi_p) return iph2; } } else if (iph2->proposal != NULL) { for (pr = iph2->proposal->head; pr != NULL; pr = pr->next) { if (proto_id != pr->proto_id) break; if (spi == pr->spi) return iph2; } } } return NULL; } /* * create new isakmp Phase 2 status record to handle isakmp in Phase2 */ struct ph2handle * newph2() { struct ph2handle *iph2 = NULL; /* create new iph2 */ iph2 = racoon_calloc(1, sizeof(*iph2)); if (iph2 == NULL) return NULL; iph2->status = PHASE1ST_SPAWN; evt_list_init(&iph2->evt_listeners); return iph2; } /* * initialize ph2handle * NOTE: don't initialize src/dst. * SPI in the proposal is cleared. */ void initph2(iph2) struct ph2handle *iph2; { evt_list_cleanup(&iph2->evt_listeners); unbindph12(iph2); sched_cancel(&iph2->sce); sched_cancel(&iph2->scr); VPTRINIT(iph2->sendbuf); VPTRINIT(iph2->msg1); /* clear spi, keep variables in the proposal */ if (iph2->proposal) { struct saproto *pr; for (pr = iph2->proposal->head; pr != NULL; pr = pr->next) pr->spi = 0; } /* clear approval */ if (iph2->approval) { flushsaprop(iph2->approval); iph2->approval = NULL; } /* clear the generated policy */ if (iph2->spidx_gen) { delsp_bothdir((struct policyindex *)iph2->spidx_gen); racoon_free(iph2->spidx_gen); iph2->spidx_gen = NULL; } if (iph2->pfsgrp) { oakley_dhgrp_free(iph2->pfsgrp); iph2->pfsgrp = NULL; } VPTRINIT(iph2->dhpriv); VPTRINIT(iph2->dhpub); VPTRINIT(iph2->dhpub_p); VPTRINIT(iph2->dhgxy); VPTRINIT(iph2->id); VPTRINIT(iph2->id_p); VPTRINIT(iph2->nonce); VPTRINIT(iph2->nonce_p); VPTRINIT(iph2->sa); VPTRINIT(iph2->sa_ret); if (iph2->ivm) { oakley_delivm(iph2->ivm); iph2->ivm = NULL; } #ifdef ENABLE_NATT if (iph2->natoa_src) { racoon_free(iph2->natoa_src); iph2->natoa_src = NULL; } if (iph2->natoa_dst) { racoon_free(iph2->natoa_dst); iph2->natoa_dst = NULL; } #endif } /* * delete new isakmp Phase 2 status record to handle isakmp in Phase2 */ void delph2(iph2) struct ph2handle *iph2; { initph2(iph2); if (iph2->src) { racoon_free(iph2->src); iph2->src = NULL; } if (iph2->dst) { racoon_free(iph2->dst); iph2->dst = NULL; } if (iph2->sa_src) { racoon_free(iph2->sa_src); iph2->sa_src = NULL; } if (iph2->sa_dst) { racoon_free(iph2->sa_dst); iph2->sa_dst = NULL; } #ifdef ENABLE_NATT if (iph2->natoa_src) { racoon_free(iph2->natoa_src); iph2->natoa_src = NULL; } if (iph2->natoa_dst) { racoon_free(iph2->natoa_dst); iph2->natoa_dst = NULL; } #endif if (iph2->proposal) { flushsaprop(iph2->proposal); iph2->proposal = NULL; } racoon_free(iph2); } /* * create new isakmp Phase 2 status record to handle isakmp in Phase2 */ int insph2(iph2) struct ph2handle *iph2; { LIST_INSERT_HEAD(&ph2tree, iph2, chain); return 0; } void remph2(iph2) struct ph2handle *iph2; { unbindph12(iph2); LIST_REMOVE(iph2, chain); } void initph2tree() { LIST_INIT(&ph2tree); } void flushph2() { struct ph2handle *p, *next; plog(LLV_DEBUG2, LOCATION, NULL, "flushing all ph2 handlers...\n"); for (p = LIST_FIRST(&ph2tree); p; p = next) { next = LIST_NEXT(p, chain); /* send delete information */ if (p->status == PHASE2ST_ESTABLISHED){ plog(LLV_DEBUG2, LOCATION, NULL, "got a ph2 handler to flush...\n"); isakmp_info_send_d2(p); }else{ plog(LLV_DEBUG2, LOCATION, NULL, "skipping ph2 handler (state %d)\n", p->status); } delete_spd(p, 0); remph2(p); delph2(p); } } /* * Delete all Phase 2 handlers for this src/dst/proto. This * is used during INITIAL-CONTACT processing (so no need to * send a message to the peer). */ void deleteallph2(src, dst, proto_id) struct sockaddr *src, *dst; u_int proto_id; { struct ph2handle *iph2, *next; struct saproto *pr; for (iph2 = LIST_FIRST(&ph2tree); iph2 != NULL; iph2 = next) { next = LIST_NEXT(iph2, chain); if (iph2->proposal == NULL && iph2->approval == NULL) continue; if (iph2->approval != NULL) { for (pr = iph2->approval->head; pr != NULL; pr = pr->next) { if (proto_id == pr->proto_id) goto zap_it; } } else if (iph2->proposal != NULL) { for (pr = iph2->proposal->head; pr != NULL; pr = pr->next) { if (proto_id == pr->proto_id) goto zap_it; } } continue; zap_it: remph2(iph2); delph2(iph2); } } /* %%% */ void bindph12(iph1, iph2) struct ph1handle *iph1; struct ph2handle *iph2; { unbindph12(iph2); iph2->ph1 = iph1; iph1->ph2cnt++; LIST_INSERT_HEAD(&iph1->ph2tree, iph2, ph1bind); } void unbindph12(iph2) struct ph2handle *iph2; { if (iph2->ph1 != NULL) { LIST_REMOVE(iph2, ph1bind); iph2->ph1->ph2cnt--; iph2->ph1 = NULL; } } /* %%% management contacted list */ /* * search contacted list. */ struct contacted * getcontacted(remote) struct sockaddr *remote; { struct contacted *p; LIST_FOREACH(p, &ctdtree, chain) { if (cmpsaddr(remote, p->remote) <= CMPSADDR_WILDPORT_MATCH) return p; } return NULL; } /* * create new isakmp Phase 2 status record to handle isakmp in Phase2 */ int inscontacted(remote) struct sockaddr *remote; { struct contacted *new; /* create new iph2 */ new = racoon_calloc(1, sizeof(*new)); if (new == NULL) return -1; new->remote = dupsaddr(remote); if (new->remote == NULL) { plog(LLV_ERROR, LOCATION, NULL, "failed to allocate buffer.\n"); racoon_free(new); return -1; } LIST_INSERT_HEAD(&ctdtree, new, chain); return 0; } void remcontacted(remote) struct sockaddr *remote; { struct contacted *p, *next; for (p = LIST_FIRST(&ctdtree); p; p = next) { next = LIST_NEXT(p, chain); if (cmpsaddr(remote, p->remote) <= CMPSADDR_WILDPORT_MATCH) { LIST_REMOVE(p, chain); racoon_free(p->remote); racoon_free(p); break; } } } void initctdtree() { LIST_INIT(&ctdtree); } /* * check the response has been sent to the peer. when not, simply reply * the buffered packet to the peer. * OUT: * 0: the packet is received at the first time. * 1: the packet was processed before. * 2: the packet was processed before, but the address mismatches. * -1: error happened. */ int check_recvdpkt(remote, local, rbuf) struct sockaddr *remote, *local; vchar_t *rbuf; { vchar_t *hash; struct recvdpkt *r; struct timeval now, diff; int len, s; hash = eay_md5_one(rbuf); if (!hash) { plog(LLV_ERROR, LOCATION, NULL, "failed to allocate buffer.\n"); return -1; } LIST_FOREACH(r, &rcptree, chain) { if (memcmp(hash->v, r->hash->v, r->hash->l) == 0) break; } vfree(hash); /* this is the first time to receive the packet */ if (r == NULL) return 0; /* * the packet was processed before, but the remote address mismatches. */ if (cmpsaddr(remote, r->remote) != CMPSADDR_MATCH) return 2; /* * it should not check the local address because the packet * may arrive at other interface. */ /* check the previous time to send */ sched_get_monotonic_time(&now); timersub(&now, &r->time_send, &diff); if (diff.tv_sec == 0) { plog(LLV_WARNING, LOCATION, NULL, "the packet retransmitted in a short time from %s\n", saddr2str(remote)); /*XXX should it be error ? */ } /* select the socket to be sent */ s = myaddr_getfd(r->local); if (s == -1) return -1; /* resend the packet if needed */ len = sendfromto(s, r->sendbuf->v, r->sendbuf->l, r->local, r->remote, lcconf->count_persend); if (len == -1) { plog(LLV_ERROR, LOCATION, NULL, "sendfromto failed\n"); return -1; } /* check the retry counter */ r->retry_counter--; if (r->retry_counter <= 0) { rem_recvdpkt(r); del_recvdpkt(r); plog(LLV_DEBUG, LOCATION, NULL, "deleted the retransmission packet to %s.\n", saddr2str(remote)); } else r->time_send = now; return 1; } /* * adding a hash of received packet into the received list. */ int add_recvdpkt(remote, local, sbuf, rbuf) struct sockaddr *remote, *local; vchar_t *sbuf, *rbuf; { struct recvdpkt *new = NULL; if (lcconf->retry_counter == 0) { /* no need to add it */ return 0; } new = racoon_calloc(1, sizeof(*new)); if (!new) { plog(LLV_ERROR, LOCATION, NULL, "failed to allocate buffer.\n"); return -1; } new->hash = eay_md5_one(rbuf); if (!new->hash) { plog(LLV_ERROR, LOCATION, NULL, "failed to allocate buffer.\n"); del_recvdpkt(new); return -1; } new->remote = dupsaddr(remote); if (new->remote == NULL) { plog(LLV_ERROR, LOCATION, NULL, "failed to allocate buffer.\n"); del_recvdpkt(new); return -1; } new->local = dupsaddr(local); if (new->local == NULL) { plog(LLV_ERROR, LOCATION, NULL, "failed to allocate buffer.\n"); del_recvdpkt(new); return -1; } new->sendbuf = vdup(sbuf); if (new->sendbuf == NULL) { plog(LLV_ERROR, LOCATION, NULL, "failed to allocate buffer.\n"); del_recvdpkt(new); return -1; } new->retry_counter = lcconf->retry_counter; sched_get_monotonic_time(&new->time_send); LIST_INSERT_HEAD(&rcptree, new, chain); return 0; } void del_recvdpkt(r) struct recvdpkt *r; { if (r->remote) racoon_free(r->remote); if (r->local) racoon_free(r->local); if (r->hash) vfree(r->hash); if (r->sendbuf) vfree(r->sendbuf); racoon_free(r); } void rem_recvdpkt(r) struct recvdpkt *r; { LIST_REMOVE(r, chain); } static void sweep_recvdpkt(dummy) struct sched *dummy; { struct recvdpkt *r, *next; struct timeval now, diff, sweep; sched_get_monotonic_time(&now); /* calculate sweep time; delete entries older than this */ diff.tv_sec = lcconf->retry_counter * lcconf->retry_interval; diff.tv_usec = 0; timersub(&now, &diff, &sweep); for (r = LIST_FIRST(&rcptree); r; r = next) { next = LIST_NEXT(r, chain); if (timercmp(&r->time_send, &sweep, <)) { rem_recvdpkt(r); del_recvdpkt(r); } } sched_schedule(&sc_sweep, diff.tv_sec, sweep_recvdpkt); } void init_recvdpkt() { time_t lt = lcconf->retry_counter * lcconf->retry_interval; LIST_INIT(&rcptree); sched_schedule(&sc_sweep, lt, sweep_recvdpkt); } #ifdef ENABLE_HYBRID /* * Retruns 0 if the address was obtained by ISAKMP mode config, 1 otherwise * This should be in isakmp_cfg.c but ph1tree being private, it must be there */ int exclude_cfg_addr(addr) const struct sockaddr *addr; { struct ph1handle *p; struct sockaddr_in *sin; LIST_FOREACH(p, &ph1tree, chain) { if ((p->mode_cfg != NULL) && (p->mode_cfg->flags & ISAKMP_CFG_GOT_ADDR4) && (addr->sa_family == AF_INET)) { sin = (struct sockaddr_in *)addr; if (sin->sin_addr.s_addr == p->mode_cfg->addr4.s_addr) return 0; } } return 1; } #endif /* * Reload conf code */ static int revalidate_ph2(struct ph2handle *iph2){ struct sainfoalg *alg; int found, check_level; struct sainfo *sainfo; struct saprop *approval; struct ph1handle *iph1; /* * Get the new sainfo using values of the old one */ if (iph2->sainfo != NULL) { iph2->sainfo = getsainfo(iph2->sainfo->idsrc, iph2->sainfo->iddst, iph2->sainfo->id_i, NULL, iph2->sainfo->remoteid); } approval = iph2->approval; sainfo = iph2->sainfo; if (sainfo == NULL) { /* * Sainfo has been removed */ plog(LLV_DEBUG, LOCATION, NULL, "Reload: No sainfo for ph2\n"); return 0; } if (approval == NULL) { /* * XXX why do we have a NULL approval sometimes ??? */ plog(LLV_DEBUG, LOCATION, NULL, "No approval found !\n"); return 0; } /* * Don't care about proposals, should we do something ? * We have to keep iph2->proposal valid at least for initiator, * for pk_sendgetspi() */ plog(LLV_DEBUG, LOCATION, NULL, "active single bundle:\n"); printsaprop0(LLV_DEBUG, approval); /* * Validate approval against sainfo * Note: we must have an updated ph1->rmconf before doing that, * we'll set check_level to EXACT if we don't have a ph1 * XXX try tu find the new remote section to get the new check level ? * XXX lifebyte */ if (iph2->ph1 != NULL) iph1=iph2->ph1; else iph1=getph1byaddr(iph2->src, iph2->dst, 0); if(iph1 != NULL && iph1->rmconf != NULL) { check_level = iph1->rmconf->pcheck_level; } else { if(iph1 != NULL) plog(LLV_DEBUG, LOCATION, NULL, "No phase1 rmconf found !\n"); else plog(LLV_DEBUG, LOCATION, NULL, "No phase1 found !\n"); check_level = PROP_CHECK_EXACT; } switch (check_level) { case PROP_CHECK_OBEY: plog(LLV_DEBUG, LOCATION, NULL, "Reload: OBEY for ph2, ok\n"); return 1; break; case PROP_CHECK_STRICT: /* FALLTHROUGH */ case PROP_CHECK_CLAIM: if (sainfo->lifetime < approval->lifetime) { plog(LLV_DEBUG, LOCATION, NULL, "Reload: lifetime mismatch\n"); return 0; } #if 0 /* Lifebyte is deprecated, just ignore it */ if (sainfo->lifebyte < approval->lifebyte) { plog(LLV_DEBUG, LOCATION, NULL, "Reload: lifebyte mismatch\n"); return 0; } #endif if (sainfo->pfs_group && sainfo->pfs_group != approval->pfs_group) { plog(LLV_DEBUG, LOCATION, NULL, "Reload: PFS group mismatch\n"); return 0; } break; case PROP_CHECK_EXACT: if (sainfo->lifetime != approval->lifetime || #if 0 /* Lifebyte is deprecated, just ignore it */ sainfo->lifebyte != approval->lifebyte || #endif sainfo->pfs_group != iph2->approval->pfs_group) { plog(LLV_DEBUG, LOCATION, NULL, "Reload: lifetime | pfs mismatch\n"); return 0; } break; default: plog(LLV_DEBUG, LOCATION, NULL, "Reload: Shouldn't be here !\n"); return 0; break; } for (alg = sainfo->algs[algclass_ipsec_auth]; alg; alg = alg->next) { if (alg->alg == approval->head->head->authtype) break; } if (alg == NULL) { plog(LLV_DEBUG, LOCATION, NULL, "Reload: alg == NULL (auth)\n"); return 0; } found = 0; for (alg = sainfo->algs[algclass_ipsec_enc]; (found == 0 && alg != NULL); alg = alg->next) { plog(LLV_DEBUG, LOCATION, NULL, "Reload: next ph2 enc alg...\n"); if (alg->alg != approval->head->head->trns_id){ plog(LLV_DEBUG, LOCATION, NULL, "Reload: encmode mismatch (%d / %d)\n", alg->alg, approval->head->head->trns_id); continue; } switch (check_level){ /* PROP_CHECK_STRICT cannot happen here */ case PROP_CHECK_EXACT: if (alg->encklen != approval->head->head->encklen) { plog(LLV_DEBUG, LOCATION, NULL, "Reload: enclen mismatch\n"); continue; } break; case PROP_CHECK_CLAIM: /* FALLTHROUGH */ case PROP_CHECK_STRICT: if (alg->encklen > approval->head->head->encklen) { plog(LLV_DEBUG, LOCATION, NULL, "Reload: enclen mismatch\n"); continue; } break; default: plog(LLV_ERROR, LOCATION, NULL, "unexpected check_level\n"); continue; break; } found = 1; } if (!found){ plog(LLV_DEBUG, LOCATION, NULL, "Reload: No valid enc\n"); return 0; } /* * XXX comp */ plog(LLV_DEBUG, LOCATION, NULL, "Reload: ph2 check ok\n"); return 1; } static void remove_ph2(struct ph2handle *iph2) { u_int32_t spis[2]; if(iph2 == NULL) return; plog(LLV_DEBUG, LOCATION, NULL, "Deleting a Ph2...\n"); if (iph2->status == PHASE2ST_ESTABLISHED) isakmp_info_send_d2(iph2); if(iph2->approval != NULL && iph2->approval->head != NULL){ spis[0]=iph2->approval->head->spi; spis[1]=iph2->approval->head->spi_p; /* purge_ipsec_spi() will do all the work: * - delete SPIs in kernel * - delete generated SPD * - unbind / rem / del ph2 */ purge_ipsec_spi(iph2->dst, iph2->approval->head->proto_id, spis, 2); }else{ remph2(iph2); delph2(iph2); } } static void remove_ph1(struct ph1handle *iph1){ struct ph2handle *iph2, *iph2_next; if(iph1 == NULL) return; plog(LLV_DEBUG, LOCATION, NULL, "Removing PH1...\n"); if (iph1->status == PHASE1ST_ESTABLISHED || iph1->status == PHASE1ST_DYING) { for (iph2 = LIST_FIRST(&iph1->ph2tree); iph2; iph2 = iph2_next) { iph2_next = LIST_NEXT(iph2, ph1bind); remove_ph2(iph2); } isakmp_info_send_d1(iph1); } iph1->status = PHASE1ST_EXPIRED; /* directly call isakmp_ph1delete to avoid as possible a race * condition where we'll try to access iph1->rmconf after it has * freed */ isakmp_ph1delete(iph1); } static int revalidate_ph1tree_rmconf(void) { struct ph1handle *p, *next; struct remoteconf *rmconf; for (p = LIST_FIRST(&ph1tree); p; p = next) { next = LIST_NEXT(p, chain); if (p->status >= PHASE1ST_EXPIRED) continue; if (p->rmconf == NULL) continue; rmconf = getrmconf_by_ph1(p); if (rmconf == NULL || rmconf == RMCONF_ERR_MULTIPLE) remove_ph1(p); else p->rmconf = rmconf; } return 1; } static int revalidate_ph2tree(void){ struct ph2handle *p, *next; for (p = LIST_FIRST(&ph2tree); p; p = next) { next = LIST_NEXT(p, chain); if (p->status == PHASE2ST_EXPIRED) continue; if(!revalidate_ph2(p)){ plog(LLV_DEBUG, LOCATION, NULL, "PH2 not validated, removing it\n"); remove_ph2(p); } } return 1; } int revalidate_ph12(void) { revalidate_ph1tree_rmconf(); revalidate_ph2tree(); return 1; } #ifdef ENABLE_HYBRID struct ph1handle * getph1bylogin(login) char *login; { struct ph1handle *p; LIST_FOREACH(p, &ph1tree, chain) { if (p->mode_cfg == NULL) continue; if (strncmp(p->mode_cfg->login, login, LOGINLEN) == 0) return p; } return NULL; } int purgeph1bylogin(login) char *login; { struct ph1handle *p, *next; int found = 0; for (p = LIST_FIRST(&ph1tree); p; p = next) { next = LIST_NEXT(p, chain); if (p->mode_cfg == NULL) continue; if (strncmp(p->mode_cfg->login, login, LOGINLEN) == 0) { if (p->status >= PHASE1ST_EXPIRED) continue; if (p->status >= PHASE1ST_ESTABLISHED) isakmp_info_send_d1(p); purge_remote(p); found++; } } return found; } #endif #ifdef __rtems__ #include "rtems-bsd-racoon-handler-data.h" #endif /* __rtems__ */