diff -ur freeswan-1.98b/pluto/connections.c freeswan-1.98b-notify-delete/pluto/connections.c
--- freeswan-1.98b/pluto/connections.c	Fri Jun 21 21:22:47 2002
+++ freeswan-1.98b-notify-delete/pluto/connections.c	Wed Jul 24 18:57:21 2002
@@ -674,6 +674,7 @@
 	}
 
 	/* set internal fields */
+	c->initiated = FALSE;
 	c->instance_serial = 0;
 	c->ac_next = connections;
 	connections = c;
@@ -1070,6 +1071,7 @@
 	     * This is a fudge, but not yet important.
 	     * If we are to proceed asynchronously, whackfd will be NULL_FD.
 	     */
+	    c->initiated = TRUE;
 	    ipsecdoi_initiate(whackfd, c, c->policy, 1, SOS_NOBODY);
 	    whackfd = NULL_FD;	/* protect from close */
 	}
@@ -1369,6 +1371,7 @@
 	{
 	    set_cur_connection(c);
 	    log("terminating SAs using this connection");
+	    c->initiated = FALSE;
 	    delete_states_by_connection(c);
 	    reset_cur_connection();
 	}
diff -ur freeswan-1.98b/pluto/connections.h freeswan-1.98b-notify-delete/pluto/connections.h
--- freeswan-1.98b/pluto/connections.h	Fri Jun 21 21:22:47 2002
+++ freeswan-1.98b-notify-delete/pluto/connections.h	Wed Jul 24 18:57:21 2002
@@ -118,6 +118,7 @@
     enum connection_kind kind;
     const struct iface *interface;	/* filled in iff oriented */
     enum routing_t routing;	/* level of routing in place */
+    bool initiated;
 
     so_serial_t	/* state object serial number */
 	newest_isakmp_sa,
diff -ur freeswan-1.98b/pluto/demux.c freeswan-1.98b-notify-delete/pluto/demux.c
--- freeswan-1.98b/pluto/demux.c	Sat Mar  9 21:45:38 2002
+++ freeswan-1.98b-notify-delete/pluto/demux.c	Wed Jul 24 18:38:42 2002
@@ -779,7 +779,7 @@
 static stf_status
 informational(struct msg_digest *md UNUSED)
 {
-    loglog(RC_LOG_SERIOUS, "received and ignored informational message");
+    /* loglog(RC_LOG_SERIOUS, "received and ignored informational message"); */
     return STF_IGNORE;
 }
 
@@ -1031,12 +1031,27 @@
     struct state *st = NULL;
     enum state_kind from_state = STATE_UNDEFINED;	/* state we started in */
 
+#define SEND_NOTIFICATION(t) { \
+    if (st) send_notification_from_state(st, from_state, t); \
+    else send_notification_from_md(md, t); }
+
     if (!in_struct(&md->hdr, &isakmp_hdr_desc, &md->packet_pbs, &md->message_pbs))
     {
-	/* XXX specific failures (special notification?):
+	/* Identify specific failures:
 	 * - bad ISAKMP major/minor version numbers
-	 * - size of packet vs size of message
 	 */
+	if (md->packet_pbs.roof - md->packet_pbs.cur >= (ptrdiff_t)isakmp_hdr_desc.size) {
+	    struct isakmp_hdr *hdr = (struct isakmp_hdr *)md->packet_pbs.cur;
+	    if ((hdr->isa_version >> ISA_MAJ_SHIFT) != ISAKMP_MAJOR_VERSION) {
+		SEND_NOTIFICATION(INVALID_MAJOR_VERSION);
+		return;
+	    }
+	    else if ((hdr->isa_version & ISA_MIN_MASK) != ISAKMP_MINOR_VERSION) {
+		SEND_NOTIFICATION(INVALID_MINOR_VERSION);
+		return;
+	    }
+	}
+	SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 	return;
     }
 
@@ -1044,6 +1059,7 @@
     {
 	log("size (%u) differs from size specified in ISAKMP HDR (%u)"
 	    , (unsigned) pbs_room(&md->packet_pbs), md->hdr.isa_length);
+	SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 	return;
     }
 
@@ -1059,14 +1075,14 @@
 	{
 	    log("Message ID was 0x%08lx but should be zero in Main Mode",
 		(unsigned long) md->hdr.isa_msgid);
-	    /* XXX Could send notification back */
+	    SEND_NOTIFICATION(INVALID_MESSAGE_ID);
 	    return;
 	}
 
 	if (is_zero_cookie(md->hdr.isa_icookie))
 	{
 	    log("Initiator Cookie must not be zero in Main Mode message");
-	    /* XXX Could send notification back */
+	    SEND_NOTIFICATION(INVALID_COOKIE);
 	    return;
 	}
 
@@ -1079,6 +1095,7 @@
 	    {
 		log("initial Main Mode message is invalid:"
 		    " its Encrypted Flag is on");
+		SEND_NOTIFICATION(INVALID_FLAGS);
 		return;
 	    }
 
@@ -1137,7 +1154,7 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "encrypted Informational Exchange message is invalid"
 		    " because it is for incomplete ISAKMP SA");
-		/* XXX Could send notification back */
+		SEND_NOTIFICATION(INVALID_FLAGS);
 		return;
 	    }
 
@@ -1145,7 +1162,7 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "Informational Exchange message is invalid because"
 		    " it has a Message ID of 0");
-		/* XXX Could send notification back */
+		SEND_NOTIFICATION(INVALID_MESSAGE_ID);
 		return;
 	    }
 
@@ -1154,7 +1171,7 @@
 		loglog(RC_LOG_SERIOUS, "Informational Exchange message is invalid because"
 		    " it has a previously used Message ID (0x%08lx)"
 		    , (unsigned long)md->hdr.isa_msgid);
-		/* XXX Could send notification back */
+		SEND_NOTIFICATION(INVALID_MESSAGE_ID);
 		return;
 	    }
 
@@ -1169,7 +1186,7 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "Informational Exchange message for"
 		    " an established ISAKMP SA must be encrypted");
-		/* XXX Could send notification back */
+		SEND_NOTIFICATION(INVALID_FLAGS);
 		return;
 	    }
 	    from_state = STATE_INFO;
@@ -1181,7 +1198,7 @@
 	{
 	    log("Quick Mode message is invalid because"
 		" it has an Initiator Cookie of 0");
-	    /* XXX Could send notification back */
+	    SEND_NOTIFICATION(INVALID_COOKIE);
 	    return;
 	}
 
@@ -1189,7 +1206,7 @@
 	{
 	    log("Quick Mode message is invalid because"
 		" it has a Responder Cookie of 0");
-	    /* XXX Could send notification back */
+	    SEND_NOTIFICATION(INVALID_COOKIE);
 	    return;
 	}
 
@@ -1197,7 +1214,7 @@
 	{
 	    log("Quick Mode message is invalid because"
 		" it has a Message ID of 0");
-	    /* XXX Could send notification back */
+	    SEND_NOTIFICATION(INVALID_MESSAGE_ID);
 	    return;
 	}
 
@@ -1227,7 +1244,7 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "Quick Mode message is unacceptable because"
 		    " it is for an incomplete ISAKMP SA");
-		/* XXX Could send notification back */
+		SEND_NOTIFICATION(PAYLOAD_MALFORMED /* XXX ? */);
 		return;
 	    }
 
@@ -1238,7 +1255,7 @@
 		    " it uses a previously used Message ID 0x%08lx"
 		    " (perhaps this is a duplicated packet)"
 		    , (unsigned long) md->hdr.isa_msgid);
-		/* XXX Could send notification INVALID_MESSAGE_ID back */
+		SEND_NOTIFICATION(INVALID_MESSAGE_ID);
 		return;
 	    }
 
@@ -1264,6 +1281,7 @@
     default:
 	log("unsupported exchange type %s in message"
 	    , enum_show(&exchange_names, md->hdr.isa_xchg));
+	SEND_NOTIFICATION(UNSUPPORTED_EXCHANGE_TYPE);
 	return;
     }
 
@@ -1359,14 +1377,14 @@
 	if (st == NULL)
 	{
 	    log("discarding encrypted message for an unknown ISAKMP SA");
-	    /* XXX Could send notification back */
+	    SEND_NOTIFICATION(PAYLOAD_MALFORMED /* XXX ? */);
 	    return;
 	}
 	if (st->st_skeyid_e.ptr == (u_char *) NULL)
 	{
 	    loglog(RC_LOG_SERIOUS, "discarding encrypted message"
 		" because we haven't yet negotiated keying materiel");
-	    /* XXX Could send notification back */
+	    SEND_NOTIFICATION(INVALID_FLAGS);
 	    return;
 	}
 
@@ -1398,7 +1416,7 @@
 	    if (pbs_left(&md->message_pbs) % e->blocksize != 0)
 	    {
 		loglog(RC_LOG_SERIOUS, "malformed message: not a multiple of encryption blocksize");
-		/* XXX Could send notification back */
+		SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 		return;
 	    }
 
@@ -1433,7 +1451,7 @@
 	if (smc->flags & SMF_INPUT_ENCRYPTED)
 	{
 	    loglog(RC_LOG_SERIOUS, "packet rejected: should have been encrypted");
-	    /* XXX Could send notification back */
+	    SEND_NOTIFICATION(INVALID_FLAGS);
 	    return;
 	}
     }
@@ -1460,6 +1478,7 @@
 	    if (pd == &md->digest[PAYLIMIT])
 	    {
 		loglog(RC_LOG_SERIOUS, "more than %d payloads in message; ignored", PAYLIMIT);
+		SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 		return;
 	    }
 
@@ -1476,6 +1495,7 @@
 		    loglog(RC_LOG_SERIOUS, "%smessage ignored because it contains an unknown or"
 			" unexpected payload type (%s) at the outermost level"
 			, excuse, enum_show(&payload_names, np));
+		    SEND_NOTIFICATION(INVALID_PAYLOAD_TYPE);
 		    return;
 		}
 	    }
@@ -1489,6 +1509,7 @@
 		    loglog(RC_LOG_SERIOUS, "%smessage ignored because it contains an"
 			" payload type (%s) unexpected in this message"
 			, excuse, enum_show(&payload_names, np));
+		    SEND_NOTIFICATION(INVALID_PAYLOAD_TYPE);
 		    return;
 		}
 		needed &= ~s;
@@ -1497,6 +1518,7 @@
 	    if (!in_struct(&pd->payload, sd, &md->message_pbs, &pd->pbs))
 	    {
 		loglog(RC_LOG_SERIOUS, "%smalformed payload in packet", excuse);
+		SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 		return;
 	    }
 
@@ -1536,6 +1558,7 @@
 	    loglog(RC_LOG_SERIOUS, "message for %s is missing payloads %s"
 		, enum_show(&state_names, from_state)
 		, bitnamesof(payload_name, needed));
+	    SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 	    return;
 	}
     }
@@ -1551,6 +1574,7 @@
 	&& md->hdr.isa_np != ISAKMP_NEXT_SA)
 	{
 	    loglog(RC_LOG_SERIOUS, "malformed Phase 1 message: does not start with an SA payload");
+	    SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 	    return;
 	}
     }
@@ -1574,6 +1598,7 @@
 	if (md->hdr.isa_np != ISAKMP_NEXT_HASH)
 	{
 	    loglog(RC_LOG_SERIOUS, "malformed Quick Mode message: does not start with a HASH payload");
+	    SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 	    return;
 	}
 
@@ -1587,6 +1612,7 @@
 		if (p != &md->digest[i])
 		{
 		    loglog(RC_LOG_SERIOUS, "malformed Quick Mode message: SA payload is in wrong position");
+		    SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 		    return;
 		}
 	    }
@@ -1607,12 +1633,14 @@
 		    loglog(RC_LOG_SERIOUS, "malformed Quick Mode message:"
 			" if any ID payload is present,"
 			" there must be exactly two");
+		    SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 		    return;
 		}
 		if (id+1 != id->next)
 		{
 		    loglog(RC_LOG_SERIOUS, "malformed Quick Mode message:"
 			" the ID payloads are not adjacent");
+		    SEND_NOTIFICATION(PAYLOAD_MALFORMED);
 		    return;
 		}
 	    }
@@ -1635,7 +1663,7 @@
 
 	for (p = md->chain[ISAKMP_NEXT_D]; p != NULL; p = p->next)
 	{
-	    loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload");
+	    accept_delete(st, md, p);
 	    DBG_cond_dump(DBG_PARSING, "del:", p->pbs.cur, pbs_left(&p->pbs));
 	}
 
@@ -1882,13 +1910,14 @@
 	    result = STF_FAIL;
 	    /* FALL THROUGH ... */
 	case STF_FAIL:
-	    /* XXX Could send notification back
-	     * As it is, we act as if this message never happened:
+	    /* As it is, we act as if this message never happened:
 	     * whatever retrying was in place, remains in place.
 	     */
 	    whack_log(RC_NOTIFICATION + md->note
 		, "%s: %s", enum_name(&state_names, st->st_state)
 		, enum_name(&ipsec_notification_names, md->note));
+
+	    SEND_NOTIFICATION(md->note);
 
 	    DBG(DBG_CONTROL,
 		DBG_log("state transition function for %s failed: %s"
diff -ur freeswan-1.98b/pluto/ipsec_doi.c freeswan-1.98b-notify-delete/pluto/ipsec_doi.c
--- freeswan-1.98b/pluto/ipsec_doi.c	Fri Jun 21 21:22:47 2002
+++ freeswan-1.98b-notify-delete/pluto/ipsec_doi.c	Wed Jul 24 19:22:09 2002
@@ -182,7 +182,6 @@
     {
 	loglog(RC_LOG_SERIOUS, "KE has %u byte DH public value; %u required"
 	    , (unsigned) pbs_left(pbs), (unsigned) gr->bytes);
-	/* XXX Could send notification back */
 #ifdef DODGE_DH_MISSING_ZERO_BUG
 	if (pbs_left(pbs) > gr->bytes)
 #endif
@@ -244,76 +243,185 @@
  * Send a notification to the peer. We could make a decision on
  * whether to send the notification, based on the type and the
  * destination, if we care to.
- * XXX It doesn't handle DELETE notifications (which are also
- * XXX informational exchanges).
- * XXX Not modified to support ip_address and related (IPv4+IPv6) functions.
- */
-#if 0 /* not currently used */
-//static void
-//send_notification(int sock,
-//    u_int16_t type,
-//    u_char *spi,
-//    u_char spilen,
-//    u_char protoid,
-//    u_char *icookie,
-//    u_char *rcookie,
-//    msgid_t /*network order*/ msgid,
-//    struct sockaddr sa)
-//{
-//    u_char buffer[sizeof(struct isakmp_hdr) +
-//		 sizeof(struct isakmp_notification)];
-//    struct isakmp_hdr *isa = (struct isakmp_hdr *) buffer;
-//    struct isakmp_notification *isan = (struct isakmp_notification *)
-//				       (buffer + sizeof(struct isakmp_hdr));
-//
-//    memset(buffer, '\0', sizeof(struct isakmp_hdr) +
-//	  sizeof(struct isakmp_notification));
-//
-//    if (icookie != (u_char *) NULL)
-//	memcpy(isa->isa_icookie, icookie, COOKIE_SIZE);
-//
-//    if (rcookie != (u_char *) NULL)
-//	memcpy(isa->isa_rcookie, rcookie, COOKIE_SIZE);
-//
-//    /* Standard header */
-//    isa->isa_np = ISAKMP_NEXT_N;
-//    isa->isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION;
-//    isa->isa_xchg = ISAKMP_XCHG_INFO;
-//    isa->isa_msgid = msgid;
-//    isa->isa_length = htonl(sizeof(struct isakmp_hdr) +
-//			    sizeof(struct isakmp_notification) +
-//			    spilen);
-//
-//    /* Notification header */
-//    isan->isan_type = htons(type);
-//    isan->isan_doi = htonl(ISAKMP_DOI_IPSEC);
-//    isan->isan_length = htons(sizeof(struct isakmp_notification) + spilen);
-//    isan->isan_spisize = spilen;
-//    memcpy((u_char *)isan + sizeof(struct isakmp_notification), spi, spilen);
-//    isan->isan_protoid = protoid;
-//
-//    DBG(DBG_CONTROL, DBG_log("sending INFO type %s to %s",
-//	enum_show(&notification_names, type),
-//	show_sa(&sa)));
-//
-//    if (sendto(sock, buffer, ntohl(isa->isa_length), 0, &sa,
-//	       sizeof(sa)) != ntohl(isa->isa_length))
-//	log_errno((e, "sendto() failed in send_notification() to %s",
-//	    show_sa(&sa)));
-//    else
-//    {
-//	DBG(DBG_CONTROL, DBG_log("transmitted %d bytes", ntohl(isa->isa_length)));
-//    }
-//}
-#endif /* not currently used */
+ * It doesn't handle DELETE notifications (see send_ipsec_delete)
+ */
+static void
+send_notification(struct state *sndst, u_int16_t type, struct state *encst,
+    msgid_t msgid, u_char *icookie, u_char *rcookie,
+    u_char *spi, size_t spisize, u_char protoid)
+{
+    u_char buffer[1024];
+    pb_stream pbs, r_hdr_pbs;
+    u_char *r_hashval, *r_hash_start;
+
+    passert((sndst) && (sndst->st_connection));
+
+    log("sending %snotification %s to %s:%u",
+	encst ? "encrypted " : "",
+	enum_name(&ipsec_notification_names, type),
+	ip_str(&sndst->st_connection->that.host_addr),
+	(unsigned)sndst->st_connection->that.host_port);
+	
+    memset(buffer, 0, sizeof(buffer));
+    init_pbs(&pbs, buffer, sizeof(buffer), "notification msg");
+
+    /* HDR* */
+    {
+	struct isakmp_hdr hdr;
+
+	hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION;
+	hdr.isa_np = encst ? ISAKMP_NEXT_HASH : ISAKMP_NEXT_N;
+	hdr.isa_xchg = ISAKMP_XCHG_INFO;
+	hdr.isa_msgid = msgid;
+	hdr.isa_flags = encst ? ISAKMP_FLAG_ENCRYPTION : 0;
+	if (icookie)
+	    memcpy(hdr.isa_icookie, icookie, COOKIE_SIZE);
+	if (rcookie)
+	    memcpy(hdr.isa_rcookie, rcookie, COOKIE_SIZE);
+	if (!out_struct(&hdr, &isakmp_hdr_desc, &pbs, &r_hdr_pbs))
+	    impossible();
+    }
+
+    /* HASH -- value to be filled later */
+    if (encst)
+    {
+	pb_stream hash_pbs;
+	if (!out_generic(ISAKMP_NEXT_N, &isakmp_hash_desc, &r_hdr_pbs,
+	    &hash_pbs))
+	    impossible();
+	r_hashval = hash_pbs.cur;  /* remember where to plant value */
+	if (!out_zero(encst->st_oakley.hasher->hash_digest_size, &hash_pbs,
+	    "HASH(1)"))
+	    impossible();
+	close_output_pbs(&hash_pbs);
+	r_hash_start = r_hdr_pbs.cur; /* hash from after HASH(1) */
+    }
+
+    /* Notification Payload */
+    {
+	pb_stream not_pbs;
+	struct isakmp_notification isan;
+
+	isan.isan_doi = ISAKMP_DOI_IPSEC;
+	isan.isan_np = ISAKMP_NEXT_NONE;
+	isan.isan_type = type;
+	isan.isan_spisize = spisize;
+	isan.isan_protoid = protoid;
+
+	if (!out_struct(&isan, &isakmp_notification_desc, &r_hdr_pbs, &not_pbs)
+	    || !out_raw(spi, spisize, &not_pbs, "spi"))
+	    impossible();
+	close_output_pbs(&not_pbs);
+    }
+
+    /* calculate hash value and patch into Hash Payload */
+    if (encst)
+    {
+	struct hmac_ctx ctx;
+	hmac_init_chunk(&ctx, encst->st_oakley.hasher, encst->st_skeyid_a);
+	hmac_update(&ctx, (u_char *) &msgid, sizeof(msgid_t));
+	hmac_update(&ctx, r_hash_start, r_hdr_pbs.cur-r_hash_start);
+	hmac_final(r_hashval, &ctx);
+
+	DBG(DBG_CRYPT,
+	    DBG_log("HASH(1) computed:");
+	    DBG_dump("", r_hashval, ctx.hmac_digest_size);
+	)
+    }
+
+    /* Encrypt message (preserve st_iv) */
+    if (encst)
+    {
+	u_char old_iv[MAX_DIGEST_LEN];
+	if (encst->st_iv_len > MAX_DIGEST_LEN)
+	    impossible();
+	memcpy(old_iv, encst->st_iv, encst->st_iv_len);
+	init_phase2_iv(encst, &msgid);
+	if (!encrypt_message(&r_hdr_pbs, encst))
+	    impossible();
+	memcpy(encst->st_iv, old_iv, encst->st_iv_len);
+    }
+    else
+    {
+	close_output_pbs(&r_hdr_pbs);
+    }
+
+    /* Send packet (preserve st_tpacket) */
+    {
+	chunk_t saved_tpacket = sndst->st_tpacket;
+
+	setchunk(sndst->st_tpacket, pbs.start, pbs_offset(&pbs));
+	send_packet(sndst, "notification packet");
+	sndst->st_tpacket = saved_tpacket;
+    }
+}
+
+void
+send_notification_from_state(struct state *st, enum state_kind state,
+    u_int16_t type)
+{
+    struct state *p1st;
+
+    passert(st);
+
+    if (state == STATE_UNDEFINED)
+	state = st->st_state;
+
+    if (IS_QUICK(state)) {
+	p1st = find_phase1_state(st->st_connection, TRUE);
+	if ((p1st == NULL) || (!IS_ISAKMP_SA_ESTABLISHED(p1st->st_state))) {
+	    loglog(RC_LOG_SERIOUS,
+		"no Phase1 state for Quick mode notification");
+	    return;
+	}
+	send_notification(st, type, p1st, generate_msgid(p1st),
+	    st->st_icookie, st->st_rcookie, NULL, 0, PROTO_ISAKMP);
+    }
+    else if (IS_ISAKMP_SA_ESTABLISHED(state)) {
+	send_notification(st, type, st, generate_msgid(st),
+	    st->st_icookie, st->st_rcookie, NULL, 0, PROTO_ISAKMP);
+    }
+    else {
+	/* no ISAKMP SA established - don't encrypt notification */
+	send_notification(st, type, NULL, 0,
+	    st->st_icookie, st->st_rcookie, NULL, 0, PROTO_ISAKMP);
+    }
+}
+
+void
+send_notification_from_md(struct msg_digest *md, u_int16_t type)
+{
+    /**
+     * Create a dummy state to be able to use send_packet in
+     * send_notification
+     *
+     * we need to set:
+     *   st_connection->that.host_addr
+     *   st_connection->that.host_port
+     *   st_connection->interface
+     */
+    struct state st;
+    struct connection cnx;
+
+    passert(md);
+
+    memset(&st, 0, sizeof(st));
+    memset(&cnx, 0, sizeof(cnx));
+    st.st_connection = &cnx;
+    cnx.that.host_addr = md->sender;
+    cnx.that.host_port = md->sender_port;
+    cnx.interface = md->iface;
 
-/* Send a Delete Notification to announce deletion of inbound IPSEC SAs.
+    send_notification(&st, type, NULL, 0,
+	md->hdr.isa_icookie, md->hdr.isa_rcookie, NULL, 0, PROTO_ISAKMP);
+}
+
+/* Send a Delete Notification to announce deletion of inbound IPSEC/ISAKMP SAs.
  * Ignores states that don't have any.
- * Delete Notifications cannot announce deletion of outbound IPSEC SAs.
- * We don't bother announcing deletion of ISAKMP SAs at this point.
+ * Delete Notifications cannot announce deletion of outbound IPSEC/ISAKMP SAs.
  */
 void
-send_ipsec_delete(struct state *p2st)
+send_delete(struct state *st)
 {
     pb_stream reply_pbs;
     pb_stream r_hdr_pbs;
@@ -325,48 +433,42 @@
     u_char
 	*r_hashval,	/* where in reply to jam hash value */
 	*r_hash_start;	/* start of what is to be hashed */
+    bool isakmp_sa = FALSE;
 
-    if (!IS_IPSEC_SA_ESTABLISHED(p2st->st_state))
-	return;	/* nothing to do */
+    if (IS_IPSEC_SA_ESTABLISHED(st->st_state)) {
+	p1st = find_phase1_state(st->st_connection, TRUE);
+	if (p1st == NULL)
+	{
+	    DBG(DBG_CONTROL, DBG_log("no Phase 1 state for Delete"));
+	    return;
+	}
 
-    p1st = find_phase1_state(p2st->st_connection, TRUE);
-    if (p1st == NULL)
-    {
-	DBG(DBG_CONTROL, DBG_log("no Phase 1 state for Delete"));
-	return;
+	if (st->st_ah.present)
+	{
+	    ns->spi = st->st_ah.attrs.spi;
+	    ns->dst = st->st_connection->this.host_addr;
+	    ns->proto = PROTO_IPSEC_AH;
+	    ns++;
+	}
+	if (st->st_esp.present)
+	{
+	    ns->spi = st->st_esp.attrs.spi;
+	    ns->dst = st->st_connection->this.host_addr;
+	    ns->proto = PROTO_IPSEC_ESP;
+	    ns++;
+	}
+	
+	passert(ns != said);	/* there must be some SAs to delete */
+    }
+    else if (IS_ISAKMP_SA_ESTABLISHED(st->st_state)) {
+	p1st = st;
+	isakmp_sa = TRUE;
     }
-
-    msgid = generate_msgid(p1st);
-
-    if (p2st->st_ah.present)
-    {
-	ns->spi = p2st->st_ah.attrs.spi;
-	ns->dst = p2st->st_connection->this.host_addr;
-	ns->proto = PROTO_IPSEC_AH;
-	ns++;
-    }
-    if (p2st->st_esp.present)
-    {
-	ns->spi = p2st->st_esp.attrs.spi;
-	ns->dst = p2st->st_connection->this.host_addr;
-	ns->proto = PROTO_IPSEC_ESP;
-	ns++;
-    }
-    /* I doubt that it makes sense to delete an IPCOMP with a well-known CPI.
-     * Maybe it never makes sense to delete a CPI.
-     */
-#if 0
-    if (p2st->st_ipcomp.present)
-    {
-	ns->spi = p2st->st_ipcomp.attrs.spi;
-	ns->dst = p2st->st_connection->this.host_addr;
-	ns->proto = PROTO_IPCOMP;
-	ns++;
+    else {
+	return; /* nothing to do */
     }
-#endif
-    /* IPIP isn't a real SA, so we don't mention it */
 
-    passert(ns != said);	/* there must be some SAs to delete */
+    msgid = generate_msgid(p1st);
 
     memset(buffer, '\0', sizeof(buffer));
     init_pbs(&reply_pbs, buffer, sizeof(buffer), "delete msg");
@@ -400,7 +502,26 @@
     }
 
     /* Delete Payloads */
-    while (ns != said) {
+    if (isakmp_sa) {
+	pb_stream del_pbs;
+	struct isakmp_delete isad;
+	u_char isakmp_spi[2*COOKIE_SIZE];
+
+	isad.isad_doi = ISAKMP_DOI_IPSEC;
+	isad.isad_np = ISAKMP_NEXT_NONE;
+	isad.isad_spisize = (2 * COOKIE_SIZE);
+	isad.isad_protoid = PROTO_ISAKMP;
+	isad.isad_nospi = 1;
+
+	memcpy(isakmp_spi, st->st_icookie, COOKIE_SIZE);
+	memcpy(isakmp_spi+COOKIE_SIZE, st->st_rcookie, COOKIE_SIZE);
+
+	if (!out_struct(&isad, &isakmp_delete_desc, &r_hdr_pbs, &del_pbs)
+	|| !out_raw(&isakmp_spi, (2*COOKIE_SIZE), &del_pbs, "delete payload"))
+	    impossible();
+	close_output_pbs(&del_pbs);
+    }
+    else while (ns != said) {
 	pb_stream del_pbs;
 	struct isakmp_delete isad;
 
@@ -409,8 +530,8 @@
 	isad.isad_np = ns == said? ISAKMP_NEXT_NONE : ISAKMP_NEXT_D;
 	isad.isad_spisize = sizeof(ipsec_spi_t);
 	isad.isad_protoid = ns->proto;
-
 	isad.isad_nospi = 1;
+
 	if (!out_struct(&isad, &isakmp_delete_desc, &r_hdr_pbs, &del_pbs)
 	|| !out_raw(&ns->spi, sizeof(ipsec_spi_t), &del_pbs, "delete payload"))
 	    impossible();
@@ -457,6 +578,118 @@
     }
 }
 
+void
+accept_delete(struct state *st, struct msg_digest *md, struct payload_digest *p)
+{
+    struct isakmp_delete *d = &(p->payload.delete);
+    size_t sizespi = 0;
+    u_char *spi;
+    struct state *dst = NULL;
+    int i;
+
+    if ((!st) && (!(md->hdr.isa_flags & ISAKMP_FLAG_ENCRYPTION))) {
+	loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: not encrypted");
+	return;
+    }
+
+    if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state)) {
+	/* can't happen (if msg is encrypt), but just to be sure */
+	loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: "
+	"ISAKMP SA not established");
+	return;
+    }
+
+    if (d->isad_nospi == 0) {
+	loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: no SPI");
+	return;
+    }
+
+    if (pbs_left(&p->pbs) != ((unsigned)d->isad_spisize * d->isad_nospi)) {
+	loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: invalid size");
+	return;
+    }
+
+    switch (d->isad_protoid) {
+	case PROTO_ISAKMP:
+	    sizespi = (2*COOKIE_SIZE);
+	    break;
+	case PROTO_IPSEC_AH:
+	case PROTO_IPSEC_ESP:
+	    sizespi = sizeof(ipsec_spi_t);
+	    break;
+	default:
+	    loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: "
+		"unknown Protocol ID (%s)",
+		enum_show(&protocol_names, d->isad_protoid));
+	    return;
+	    break;
+    }
+
+    if (d->isad_spisize != sizespi) {
+	loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: "
+	    "bad size (%d) for Protocol (%s)",
+	    d->isad_spisize,
+	    enum_show(&protocol_names, d->isad_protoid));
+	return;
+    }
+
+    for (i=0; i<d->isad_nospi; i++) {
+	spi = p->pbs.cur + (i * sizespi);
+	if (d->isad_protoid == PROTO_ISAKMP) {
+	    /**
+	     * ISAKMP
+	     */
+	    dst = find_phase1_state_to_delete(st, spi /*iCookie*/,
+		spi+COOKIE_SIZE /*rCookie*/);
+	    if (dst) {
+		loglog(RC_LOG_SERIOUS, "received Delete SA payload: "
+		    "deleting ISAKMP State #%lu", dst->st_serialno);
+		delete_state(dst);
+	    }
+	    else {
+		loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: "
+		    "ISAKMP SA not found");
+	    }
+	}
+	else {
+	    /**
+	     * IPSEC (ESP/AH)
+	     */
+	    ipsec_spi_t ipsec_spi = *((ipsec_spi_t *)spi);
+
+	    dst = find_phase2_state_to_delete(st, d->isad_protoid, ipsec_spi);
+	    if (dst) {
+		struct connection *rc = dst->st_connection;
+		if ((rc) && (rc->newest_ipsec_sa == dst->st_serialno) &&
+		    (rc->initiated)) {
+		    /*
+		     * Last IPSec SA for a permanent connection that we
+		     * have initiated. Replace it in a few seconds.
+		     *
+		     * Usefull if the other peer is rebooting
+		     */
+		    loglog(RC_LOG_SERIOUS, "received Delete SA payload: "
+			"replace IPSEC State #%lu in %d seconds",
+			dst->st_serialno, EVENT_RETRANSMIT_DELAY_0);
+		    dst->st_margin = EVENT_RETRANSMIT_DELAY_0;
+		    delete_event(dst);
+		    event_schedule(EVENT_SA_REPLACE,
+			EVENT_RETRANSMIT_DELAY_0, dst);
+		}
+		else {
+		    loglog(RC_LOG_SERIOUS, "received Delete SA payload: "
+			"deleting IPSEC State #%lu", dst->st_serialno);
+		    delete_state(dst);
+		}
+	    }
+	    else {
+		loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: "
+		    "IPSEC SA not found");
+	    }
+	}
+    }
+}
+
 /* The whole message must be a multiple of 4 octets.
  * I'm not sure where this is spelled out, but look at
  * rfc2408 3.6 Transform Payload.
@@ -1282,7 +1515,6 @@
 	if (s.best_ugh[0] == '9')
 	{
 	    loglog(RC_LOG_SERIOUS, "%s", s.best_ugh + 1);
-	    /* XXX Could send notification back */
 	    return STF_FAIL + INVALID_HASH_INFORMATION;
 	}
 	else
@@ -1329,7 +1561,6 @@
 	{ \
 	    DBG_cond_dump(DBG_CRYPT, "received " hash_name ":", hash_pbs->cur, pbs_left(hash_pbs)); \
 	    loglog(RC_LOG_SERIOUS, "received " hash_name " does not match computed value in " msg_name); \
-	    /* XXX Could send notification back */ \
 	    return STF_FAIL + INVALID_HASH_INFORMATION; \
 	} \
     }
@@ -1733,7 +1964,6 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "improper %s identification payload: %s"
 		    , enum_show(&ident_names, peer.kind), ugh);
-		/* XXX Could send notification back */
 		return FALSE;
 	    }
 	}
@@ -1760,7 +1990,6 @@
 	break;
 
     default:
-	/* XXX Could send notification back */
 	loglog(RC_LOG_SERIOUS, "Unacceptable identity type (%s) in Phase 1 ID Payload"
 	    , enum_show(&ident_names, peer.kind));
 	return FALSE;
@@ -1865,7 +2094,6 @@
 	    /* XXX support more */
 	    loglog(RC_LOG_SERIOUS, "unsupported ID type %s"
 		, idtypename);
-	    /* XXX Could send notification back */
 	    return FALSE;
     }
 
@@ -1901,7 +2129,6 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "%s ID payload %s has wrong length in Quick I1 (%s)"
 		    , which, idtypename, ugh);
-		/* XXX Could send notification back */
 		return FALSE;
 	    }
 	    happy(initsubnet(&temp_address, afi->mask_cnt, '0', net));
@@ -1925,7 +2152,6 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "%s ID payload %s wrong length in Quick I1"
 		    , which, idtypename);
-		/* XXX Could send notification back */
 		return FALSE;
 	    }
 	    ugh = initaddr(id_pbs->cur
@@ -1940,7 +2166,6 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "%s ID payload %s bad subnet in Quick I1 (%s)"
 		    , which, idtypename, ugh);
-		/* XXX Could send notification back */
 		return FALSE;
 	    }
 	    DBG(DBG_PARSING | DBG_CONTROL,
@@ -1963,7 +2188,6 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "%s ID payload %s wrong length in Quick I1"
 		    , which, idtypename);
-		/* XXX Could send notification back */
 		return FALSE;
 	    }
 	    ugh = initaddr(id_pbs->cur, afi->ia_sz, afi->af, &temp_address_from);
@@ -1974,7 +2198,6 @@
 	    {
 		loglog(RC_LOG_SERIOUS, "%s ID payload %s malformed (%s) in Quick I1"
 		    , which, idtypename, ugh);
-		/* XXX Could send notification back */
 		return FALSE;
 	    }
 
@@ -2587,7 +2810,6 @@
 		DBG_cond_dump(DBG_CRYPT, "received HASH:"
 		    , hash_pbs->cur, pbs_left(hash_pbs));
 		loglog(RC_LOG_SERIOUS, "received Hash Payload does not match computed value");
-		/* XXX Could send notification back */
 		r = STF_FAIL + INVALID_HASH_INFORMATION;
 	    }
 	}
diff -ur freeswan-1.98b/pluto/ipsec_doi.h freeswan-1.98b-notify-delete/pluto/ipsec_doi.h
--- freeswan-1.98b/pluto/ipsec_doi.h	Sat Mar 23 21:15:32 2002
+++ freeswan-1.98b-notify-delete/pluto/ipsec_doi.h	Wed Jul 24 18:38:42 2002
@@ -41,4 +41,11 @@
     quick_inR1_outI2,
     quick_inI2;
 
-extern void send_ipsec_delete(struct state *p2st);
+extern void send_delete(struct state *st);
+extern void accept_delete(struct state *st, struct msg_digest *md,
+    struct payload_digest *p);
+
+extern void send_notification_from_state(struct state *st,
+    enum state_kind state, u_int16_t type);
+extern void send_notification_from_md(struct msg_digest *md, u_int16_t type);
+
diff -ur freeswan-1.98b/pluto/spdb.c freeswan-1.98b-notify-delete/pluto/spdb.c
--- freeswan-1.98b/pluto/spdb.c	Tue Jun  4 03:28:33 2002
+++ freeswan-1.98b-notify-delete/pluto/spdb.c	Wed Jul 24 18:38:42 2002
@@ -679,7 +679,6 @@
     if (sa->isasa_doi != ISAKMP_DOI_IPSEC)
     {
 	loglog(RC_LOG_SERIOUS, "Unknown/unsupported DOI %s", enum_show(&doi_names, sa->isasa_doi));
-	/* XXX Could send notification back */
 	return DOI_NOT_SUPPORTED;
     }
 
@@ -693,7 +692,6 @@
     {
 	loglog(RC_LOG_SERIOUS, "unsupported IPsec DOI situation (%s)"
 	    , bitnamesof(sit_bit_names, ipsecdoisit));
-	/* XXX Could send notification back */
 	return SITUATION_NOT_SUPPORTED;
     }
 
@@ -1485,7 +1483,6 @@
     if (sa->isasa_doi != ISAKMP_DOI_IPSEC)
     {
 	loglog(RC_LOG_SERIOUS, "Unknown or unsupported DOI %s", enum_show(&doi_names, sa->isasa_doi));
-	/* XXX Could send notification back */
 	return DOI_NOT_SUPPORTED;
     }
 
@@ -1497,7 +1494,6 @@
     {
 	loglog(RC_LOG_SERIOUS, "unsupported IPsec DOI situation (%s)"
 	    , bitnamesof(sit_bit_names, ipsecdoisit));
-	/* XXX Could send notification back */
 	return SITUATION_NOT_SUPPORTED;
     }
 
diff -ur freeswan-1.98b/pluto/state.c freeswan-1.98b-notify-delete/pluto/state.c
--- freeswan-1.98b/pluto/state.c	Tue Jun  4 03:28:33 2002
+++ freeswan-1.98b-notify-delete/pluto/state.c	Wed Jul 24 18:38:42 2002
@@ -293,8 +293,9 @@
     }
 
     /* tell the other side of any IPSEC SAs that are going down */
-    if (IS_IPSEC_SA_ESTABLISHED(st->st_state))
-	send_ipsec_delete(st);
+    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 */
 
@@ -531,6 +532,48 @@
 		&& memcmp(st->st_tpacket.ptr, packet, packet_len) == 0)
 		    return st;
 
+    return NULL;
+}
+
+struct state *
+find_phase2_state_to_delete(const struct state *p1st, u_int8_t protoid,
+    ipsec_spi_t spi)
+{
+    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 ( (IS_IPSEC_SA_ESTABLISHED(st->st_state))
+		&& (p1st->st_connection->host_pair == st->st_connection->host_pair)
+		&& (same_peer_ids(p1st->st_connection, st->st_connection, NULL)))
+		{
+		    if ((protoid == PROTO_IPSEC_ESP) && (st->st_esp.present)
+			&&(st->st_esp.attrs.spi == spi))
+			return st;
+		    else if ((protoid == PROTO_IPSEC_AH) && (st->st_ah.present)
+			&& (st->st_ah.attrs.spi == spi))
+			return st;
+		}
+    return NULL;
+}
+
+struct state *
+find_phase1_state_to_delete(const struct state *p1st,
+    const u_char *icookie, const u_char *rcookie)
+{
+    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 ( (IS_ISAKMP_SA_ESTABLISHED(st->st_state))
+		&& (memcmp(icookie, st->st_icookie, COOKIE_SIZE) == 0)
+		&& (memcmp(rcookie, st->st_rcookie, COOKIE_SIZE) == 0)
+		&& (p1st->st_connection->host_pair == st->st_connection->host_pair)
+		&& (same_peer_ids(p1st->st_connection, st->st_connection, NULL)) ) {
+		    return st;
+		}
     return NULL;
 }
 
diff -ur freeswan-1.98b/pluto/state.h freeswan-1.98b-notify-delete/pluto/state.h
--- freeswan-1.98b/pluto/state.h	Tue Jun  4 03:28:33 2002
+++ freeswan-1.98b-notify-delete/pluto/state.h	Wed Jul 24 18:38:42 2002
@@ -224,6 +224,10 @@
 	const ip_address *peer,
 	msgid_t msgid),
     *state_with_serialno(so_serial_t sn),
+    *find_phase2_state_to_delete(const struct state *p1st, u_int8_t protoid,
+	ipsec_spi_t spi),
+    *find_phase1_state_to_delete(const struct state *p1st,
+	const u_char *icookie, const u_char *rcookie),
     *find_phase1_state(const struct connection *c, bool established),
     *find_sender(size_t packet_len, u_char *packet);
 
