📄 state.c
字号:
/* routines for state objects * Copyright (C) 1997 Angelos D. Keromytis. * Copyright (C) 1998-2001 D. Hugh Redelmeier. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * RCSID $Id: state.c,v 1.151.4.5 2006/03/20 13:40:23 paul Exp $ */#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <fcntl.h>#include <sys/queue.h>#include <openswan.h>#include "constants.h"#include "defs.h"#include "id.h"#include "x509.h"#include "pgp.h"#include "certs.h"#include "smartcard.h"#ifdef XAUTH_USEPAM#include <security/pam_appl.h>#endif#include "connections.h" /* needs id.h */#include "state.h"#include "kernel.h" /* needs connections.h */#include "log.h"#include "packet.h" /* so we can calculate sizeof(struct isakmp_hdr) */#include "keys.h" /* for free_public_key */#include "rnd.h"#include "timer.h"#include "whack.h"#include "demux.h" /* needs packet.h */#include "pending.h"#include "ipsec_doi.h" /* needs demux.h and state.h */#include "sha1.h"#include "md5.h"#include "crypto.h" /* requires sha1.h and md5.h *//* * Global variables: had to go somewhere, might as well be this file. */u_int16_t pluto_port = IKE_UDP_PORT; /* Pluto's port *//* * This file has the functions that handle the * state hash table and the Message ID list. *//* Message-IDs * * A Message ID is contained in each IKE message header. * For Phase 1 exchanges (Main and Aggressive), it will be zero. * For other exchanges, which must be under the protection of an * ISAKMP SA, the Message ID must be unique within that ISAKMP SA. * Effectively, this labels the message as belonging to a particular * exchange. * BTW, we feel this uniqueness allows rekeying to be somewhat simpler * than specified by draft-jenkins-ipsec-rekeying-06.txt. * * A MessageID is a 32 bit unsigned number. We represent the value * internally in network order -- they are just blobs to us. * They are unsigned numbers to make hashing and comparing easy. * * The following mechanism is used to allocate message IDs. This * requires that we keep track of which numbers have already been used * so that we don't allocate one in use. */struct msgid_list{ msgid_t msgid; /* network order */ struct msgid_list *next;};boolreserve_msgid(struct state *isakmp_sa, msgid_t msgid){ struct msgid_list *p; passert(msgid != MAINMODE_MSGID); passert(IS_ISAKMP_ENCRYPTED(isakmp_sa->st_state)); for (p = isakmp_sa->st_used_msgids; p != NULL; p = p->next) if (p->msgid == msgid) return FALSE; p = alloc_thing(struct msgid_list, "msgid"); p->msgid = msgid; p->next = isakmp_sa->st_used_msgids; isakmp_sa->st_used_msgids = p; return TRUE;}msgid_tgenerate_msgid(struct state *isakmp_sa){ int timeout = 100; /* only try so hard for unique msgid */ msgid_t msgid; passert(IS_ISAKMP_ENCRYPTED(isakmp_sa->st_state)); for (;;) { get_rnd_bytes((void *) &msgid, sizeof(msgid)); if (msgid != 0 && reserve_msgid(isakmp_sa, msgid)) break; if (--timeout == 0) { openswan_log("gave up looking for unique msgid; using 0x%08lx" , (unsigned long) msgid); break; } } return msgid;}/* state table functions */#define STATE_TABLE_SIZE 32static struct state *statetable[STATE_TABLE_SIZE];static struct state **state_hash(const u_char *icookie, const u_char *rcookie, const ip_address *peer){ u_int i = 0, j; const unsigned char *byte_ptr; size_t length = addrbytesptr(peer, &byte_ptr); DBG(DBG_RAW | DBG_CONTROL, DBG_dump("ICOOKIE:", icookie, COOKIE_SIZE); DBG_dump("RCOOKIE:", rcookie, COOKIE_SIZE); DBG_dump("peer:", byte_ptr, length)); /* XXX the following hash is pretty pathetic */ for (j = 0; j < COOKIE_SIZE; j++) i = i * 407 + icookie[j] + rcookie[j]; for (j = 0; j < length; j++) i = i * 613 + byte_ptr[j]; i = i % STATE_TABLE_SIZE; DBG(DBG_CONTROL, DBG_log("state hash entry %d", i)); return &statetable[i];}/* Get a state object. * Caller must schedule an event for this object so that it doesn't leak. * Caller must insert_state(). */struct state *new_state(void){ static const struct state blank_state; /* initialized all to zero & NULL */ static so_serial_t next_so = SOS_FIRST; struct state *st; st = clone_thing(blank_state, "struct state in new_state()"); st->st_serialno = next_so++; passert(next_so > SOS_FIRST); /* overflow can't happen! */#ifdef XAUTH passert(st->st_oakley.xauth == 0); passert(st->st_xauth_username == NULL);#endif st->st_whack_sock = NULL_FD; anyaddr(AF_INET, &st->hidden_variables.st_nat_oa); anyaddr(AF_INET, &st->hidden_variables.st_natd); DBG(DBG_CONTROL, DBG_log("creating state object #%lu at %p", st->st_serialno, (void *) st)); return st;}/* * Initialize the state table (and mask*). */voidinit_states(void){ int i; for (i = 0; i < STATE_TABLE_SIZE; i++) statetable[i] = (struct state *) NULL;}/* Find the state object with this serial number. * This allows state object references that don't turn into dangerous * dangling pointers: reference a state by its serial number. * Returns NULL if there is no such state. * If this turns out to be a significant CPU hog, it could be * improved to use a hash table rather than sequential seartch. */struct state *state_with_serialno(so_serial_t sn){ if (sn >= SOS_FIRST) { struct state *st; int i; for (i = 0; i < STATE_TABLE_SIZE; i++) for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) if (st->st_serialno == sn) return st; } return NULL;}/* Insert a state object in the hash table. The object is inserted * at the begining of list. * Needs cookies, connection, and msgid. */voidinsert_state(struct state *st){ struct state **p = state_hash(st->st_icookie, st->st_rcookie , &st->st_connection->spd.that.host_addr); passert(st->st_hashchain_prev == NULL && st->st_hashchain_next == NULL); if (*p != NULL) { passert((*p)->st_hashchain_prev == NULL); (*p)->st_hashchain_prev = st; } st->st_hashchain_next = *p; *p = st; /* Ensure that somebody is in charge of killing this state: * if no event is scheduled for it, schedule one to discard the state. * If nothing goes wrong, this event will be replaced by * a more appropriate one. */ if (st->st_event == NULL) event_schedule(EVENT_SO_DISCARD, 0, st);}/* unlink a state object from the hash table, but don't free it */voidunhash_state(struct state *st){ /* unlink from forward chain */ struct state **p = st->st_hashchain_prev == NULL ? state_hash(st->st_icookie, st->st_rcookie , &st->st_connection->spd.that.host_addr) : &st->st_hashchain_prev->st_hashchain_next; /* unlink from forward chain */ passert(*p == st); *p = st->st_hashchain_next; /* unlink from backward chain */ if (st->st_hashchain_next != NULL) { passert(st->st_hashchain_next->st_hashchain_prev == st); st->st_hashchain_next->st_hashchain_prev = st->st_hashchain_prev; } st->st_hashchain_next = st->st_hashchain_prev = NULL;}/* Free the Whack socket file descriptor. * This has the side effect of telling Whack that we're done. */voidrelease_whack(struct state *st){ close_any(st->st_whack_sock);}/* delete a state object */voiddelete_state(struct state *st){ struct connection *const c = st->st_connection; struct state *old_cur_state = cur_state == st? NULL : cur_state; DBG(DBG_CONTROL, DBG_log("deleting state #%lu", st->st_serialno)); set_cur_state(st); /* If DPD is enabled on this state object, clear any pending events */ if(st->st_dpd_event != NULL) delete_dpd_event(st); /* if there is a suspended state transition, disconnect us */ if (st != NULL && st->st_suspended_md != NULL) { passert(st->st_suspended_md->st == st); st->st_suspended_md->st = NULL; } /* tell the other side of any IPSEC SAs that are going down */ if (IS_IPSEC_SA_ESTABLISHED(st->st_state) || IS_ISAKMP_SA_ESTABLISHED(st->st_state)) send_delete(st); delete_event(st); /* delete any pending timer event */ /* Ditch anything pending on ISAKMP SA being established. * Note: this must be done before the unhash_state to prevent * flush_pending_by_state inadvertently and prematurely * deleting our connection. */ flush_pending_by_state(st); /* if there is anything in the cryptographic queue, then remove this * state from it. */ delete_cryptographic_continuation(st); /* effectively, this deletes any ISAKMP SA that this state represents */ unhash_state(st); /* tell kernel to delete any IPSEC SA * ??? we ought to tell peer to delete IPSEC SAs */ if (IS_IPSEC_SA_ESTABLISHED(st->st_state)) delete_ipsec_sa(st, FALSE); else if (IS_ONLY_INBOUND_IPSEC_SA_ESTABLISHED(st->st_state)) delete_ipsec_sa(st, TRUE); if (c->newest_ipsec_sa == st->st_serialno) c->newest_ipsec_sa = SOS_NOBODY; if (c->newest_isakmp_sa == st->st_serialno) c->newest_isakmp_sa = SOS_NOBODY; st->st_connection = NULL; /* we might be about to free it */ cur_state = old_cur_state; /* without st_connection, st isn't complete */ connection_discard(c); release_whack(st); /* from here on we are just freeing RAM */ { struct msgid_list *p = st->st_used_msgids; while (p != NULL) { struct msgid_list *q = p; p = p->next; pfree(q); } } unreference_key(&st->st_peer_pubkey); if (st->st_sec_in_use) { mpz_clear(&(st->st_sec)); pfreeany(st->st_sec_chunk.ptr); } pfreeany(st->st_tpacket.ptr); pfreeany(st->st_rpacket.ptr); pfreeany(st->st_p1isa.ptr); pfreeany(st->st_gi.ptr); pfreeany(st->st_gr.ptr); pfreeany(st->st_shared.ptr); pfreeany(st->st_ni.ptr); pfreeany(st->st_nr.ptr); pfreeany(st->st_skeyid.ptr); pfreeany(st->st_skeyid_d.ptr); pfreeany(st->st_skeyid_a.ptr); pfreeany(st->st_skeyid_e.ptr); pfreeany(st->st_enc_key.ptr); pfreeany(st->st_ah.our_keymat); pfreeany(st->st_ah.peer_keymat); pfreeany(st->st_esp.our_keymat); pfreeany(st->st_esp.peer_keymat); pfreeany(st->st_xauth_username); pfree(st);}/* * Is a connection in use by some state? */boolstates_use_connection(struct connection *c){ /* are there any states still using it? */ struct state *st = NULL; int i; for (i = 0; st == NULL && i < STATE_TABLE_SIZE; i++) for (st = statetable[i]; st != NULL; st = st->st_hashchain_next) if (st->st_connection == c) return TRUE; return FALSE;}/* * delete all states that were created for a given connection. * if relations == TRUE, then also delete states that share * the same phase 1 SA. */voiddelete_states_by_connection(struct connection *c, bool relations){ int pass; /* this kludge avoids an n^2 algorithm */ enum connection_kind ck = c->kind; struct spd_route *sr; /* save this connection's isakmp SA, since it will get set to later SOS_NOBODY */ so_serial_t parent_sa = c->newest_isakmp_sa; if (ck == CK_INSTANCE) c->kind = CK_GOING_AWAY; /* We take two passes so that we delete any ISAKMP SAs last. * This allows Delete Notifications to be sent. * ?? We could probably double the performance by caching any * ISAKMP SA states found in the first pass, avoiding a second. */ for (pass = 0; pass != 2; pass++) { int i; /* For each hash chain... */ for (i = 0; i < STATE_TABLE_SIZE; i++) { struct state *st; /* For each state in the hash chain... */ for (st = statetable[i]; st != NULL; ) { struct state *this = st; st = st->st_hashchain_next; /* before this is deleted */ if ((this->st_connection == c || (relations && parent_sa != SOS_NOBODY && this->st_clonedfrom == parent_sa)) && (pass == 1 || !IS_ISAKMP_SA_ESTABLISHED(this->st_state))) { struct state *old_cur_state = cur_state == this? NULL : cur_state;#ifdef DEBUG lset_t old_cur_debugging = cur_debugging;#endif set_cur_state(this); openswan_log("deleting state (%s)" , enum_show(&state_names, this->st_state)); if(this->st_event != NULL) delete_event(this); delete_state(this); cur_state = old_cur_state;#ifdef DEBUG set_debugging(old_cur_debugging);#endif } } } } /* Seems to dump here because 1 of the states is NULL. Removing the Assert makes things work. We should fix this eventually. passert(c->newest_ipsec_sa == SOS_NOBODY && c->newest_isakmp_sa == SOS_NOBODY); */ sr = &c->spd; while (sr != NULL) { passert(sr->eroute_owner == SOS_NOBODY); passert(sr->routing != RT_ROUTED_TUNNEL); sr = sr->next; } if (ck == CK_INSTANCE) { c->kind = ck; delete_connection(c, relations); }}/* Walk through the state table, and delete each state whose phase 1 (IKE) * peer is among those given. */voiddelete_states_by_peer(ip_address *peer){ char peerstr[ADDRTOT_BUF]; int i, ph1; addrtot(peer, 0, peerstr, sizeof(peerstr)); whack_log(RC_COMMENT, "restarting peer %s\n", peerstr); /* first restart the phase1s */ for(ph1=0; ph1 < 2; ph1++) { /* For each hash chain... */ for (i = 0; i < STATE_TABLE_SIZE; i++) { struct state *st; /* For each state in the hash chain... */ for (st = statetable[i]; st != NULL; ) { struct state *this = st; struct connection *c = this->st_connection; char ra[ADDRTOT_BUF]; st = st->st_hashchain_next; /* before this is deleted */ addrtot(&st->st_remoteaddr, 0, ra, sizeof(ra)); DBG_log("comparing %s to %s\n", ra, peerstr); if(sameaddr(&st->st_remoteaddr, peer)) { if(ph1==0 && IS_PHASE1(st->st_state)) { whack_log(RC_COMMENT , "peer %s for connection %s crashed, replacing" , peerstr , c->name); ipsecdoi_replace(st, 1); } else { delete_event(st); event_schedule(EVENT_SA_REPLACE, 0, st); } } } } }}/* Duplicate a Phase 1 state object, to create a Phase 2 object. * Caller must schedule an event for this object so that it doesn't leak. * Caller must insert_state(). */struct state *duplicate_state(struct state *st){ struct state *nst; DBG(DBG_CONTROL, DBG_log("duplicating state object #%lu", st->st_serialno)); /* record use of the Phase 1 state */ st->st_outbound_count++; st->st_outbound_time = now(); nst = new_state(); memcpy(nst->st_icookie, st->st_icookie, COOKIE_SIZE); memcpy(nst->st_rcookie, st->st_rcookie, COOKIE_SIZE); nst->st_connection = st->st_connection; nst->st_doi = st->st_doi; nst->st_situation = st->st_situation; nst->quirks = st->quirks; nst->hidden_variables = st->hidden_variables; if(st->st_xauth_username) { nst->st_xauth_username = clone_str(st->st_xauth_username , "xauth username");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -