예제 #1
0
/* Generate and match a NAT-D hash against the NAT-D payload (pl.) data.  */
static int
nat_t_match_nat_d_payload(struct message *msg, struct sockaddr *sa)
{
	struct payload *p;
	u_int8_t *hbuf;
	size_t	 hbuflen;
	int	 found = 0;

	/*
	 * If there are no NAT-D payloads in the message, return "found"
	 * as this will avoid NAT-T (see nat_t_exchange_check_nat_d()).
	 */
	if ((p = payload_first(msg, ISAKMP_PAYLOAD_NAT_D_DRAFT)) == NULL &&
	    (p = payload_first(msg, ISAKMP_PAYLOAD_NAT_D)) == NULL)
		return 1;

	hbuf = nat_t_generate_nat_d_hash(msg, sa, &hbuflen);
	if (!hbuf)
		return 0;

	for (; p; p = TAILQ_NEXT(p, link)) {
		if (GET_ISAKMP_GEN_LENGTH (p->p) !=
		    hbuflen + ISAKMP_NAT_D_DATA_OFF)
			continue;

		if (memcmp(p->p + ISAKMP_NAT_D_DATA_OFF, hbuf, hbuflen) == 0) {
			found++;
			break;
		}
	}
	free(hbuf);
	return found;
}
예제 #2
0
/*
 * Accept a set of transforms offered by the initiator and chose one we can
 * handle.
 */
int
ike_phase_1_responder_recv_SA(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct sa      *sa = TAILQ_FIRST(&exchange->sa_list);
	struct ipsec_sa *isa = sa->data;
	struct payload *sa_p = payload_first(msg, ISAKMP_PAYLOAD_SA);
	struct payload *prop = payload_first(msg, ISAKMP_PAYLOAD_PROPOSAL);
	struct ipsec_exch *ie = exchange->data;

	/* Mark the SA as handled.  */
	sa_p->flags |= PL_MARK;

	/* IKE requires that only one SA with only one proposal exists.  */
	if (TAILQ_NEXT(sa_p, link) || TAILQ_NEXT(prop, link)) {
		log_print("ike_phase_1_responder_recv_SA: "
		    "multiple SA or proposal payloads in phase 1");
		/* XXX Is there a better notification type?  */
		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
		return -1;
	}
	/* Chose a transform from the SA.  */
	if (message_negotiate_sa(msg, ike_phase_1_validate_prop) ||
	    !TAILQ_FIRST(&sa->protos))
		return -1;

	/* XXX Move into message_negotiate_sa?  */
	ipsec_decode_transform(msg, sa, TAILQ_FIRST(&sa->protos),
	    TAILQ_FIRST(&sa->protos)->chosen->p);

	ie->group = group_get(isa->group_desc);

	/*
	 * Check that the mandatory attributes: encryption, hash,
	 * authentication method and Diffie-Hellman group description, has
	 * been supplied.
	 */
	if (!exchange->crypto || !ie->hash || !ie->ike_auth || !ie->group) {
		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
		return -1;
	}
	/* Save the body for later hash computation.  */
	ie->sa_i_b_len = GET_ISAKMP_GEN_LENGTH(sa_p->p) - ISAKMP_GEN_SZ;
	ie->sa_i_b = malloc(ie->sa_i_b_len);
	if (!ie->sa_i_b) {
		/* XXX How to notify peer?  */
		log_error("ike_phase_1_responder_recv_SA: malloc (%lu) failed",
		    (unsigned long)ie->sa_i_b_len);
		return -1;
	}
	memcpy(ie->sa_i_b, sa_p->p + ISAKMP_GEN_SZ, ie->sa_i_b_len);
	return 0;
}
예제 #3
0
static int
isakmp_responder(struct message *msg)
{
	struct payload *p;
	u_int16_t	type;

	switch (msg->exchange->type) {
	case ISAKMP_EXCH_INFO:
		for (p = payload_first(msg, ISAKMP_PAYLOAD_NOTIFY); p;
		    p = TAILQ_NEXT(p, link)) {
			type = GET_ISAKMP_NOTIFY_MSG_TYPE(p->p);

			LOG_DBG((LOG_EXCHANGE, 10, "isakmp_responder: "
			    "got NOTIFY of type %s",
			    constant_name(isakmp_notify_cst,
			    type)));

			switch (type) {
			case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE:
			case ISAKMP_NOTIFY_STATUS_DPD_R_U_THERE_ACK:
				dpd_handle_notify(msg, p);
				break;

			default:
				p->flags |= PL_MARK;
				break;
			}
		}

		for (p = payload_first(msg, ISAKMP_PAYLOAD_DELETE); p;
		    p = TAILQ_NEXT(p, link)) {
			LOG_DBG((LOG_EXCHANGE, 10,
			    "isakmp_responder: got DELETE, ignoring"));
			p->flags |= PL_MARK;
		}
		return 0;

	case ISAKMP_EXCH_TRANSACTION:
		/* return 0 isakmp_cfg_responder (msg); */

	default:
		/* XXX So far we don't accept any proposals.  */
		if (payload_first(msg, ISAKMP_PAYLOAD_SA)) {
			message_drop(msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN,
			    0, 1, 0);
			return -1;
		}
	}
	return 0;
}
예제 #4
0
int
cfg_verify_hash(struct message *msg)
{
	struct payload *hashp = payload_first(msg, ISAKMP_PAYLOAD_HASH);
	struct ipsec_sa *isa = msg->isakmp_sa->data;
	struct prf     *prf;
	u_int8_t       *hash, *comp_hash;
	size_t          hash_len;

	if (!hashp) {
		log_print("cfg_verify_hash: phase 2 message missing HASH");
		message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION,
		    0, 1, 0);
		return -1;
	}
	hash = hashp->p;
	hash_len = GET_ISAKMP_GEN_LENGTH(hash);
	comp_hash = malloc(hash_len - ISAKMP_GEN_SZ);
	if (!comp_hash) {
		log_error("cfg_verify_hash: malloc (%lu) failed",
		    (unsigned long)hash_len - ISAKMP_GEN_SZ);
		return -1;
	}
	/* Verify hash.  */
	prf = prf_alloc(isa->prf_type, isa->hash, isa->skeyid_a,
	    isa->skeyid_len);
	if (!prf) {
		free(comp_hash);
		return -1;
	}
	prf->Init(prf->prfctx);
	prf->Update(prf->prfctx, msg->exchange->message_id,
	    ISAKMP_HDR_MESSAGE_ID_LEN);
	prf->Update(prf->prfctx, hash + hash_len,
	    msg->iov[0].iov_len - ISAKMP_HDR_SZ - hash_len);
	prf->Final(comp_hash, prf->prfctx);
	prf_free(prf);

	if (memcmp(hash + ISAKMP_GEN_SZ, comp_hash, hash_len - ISAKMP_GEN_SZ)
	    != 0) {
		message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION,
		    0, 1, 0);
		free(comp_hash);
		return -1;
	}
	free(comp_hash);

	/* Mark the HASH as handled.  */
	hashp->flags |= PL_MARK;

	/* Mark message authenticated. */
	msg->flags |= MSG_AUTHENTICATED;

	return 0;
}
예제 #5
0
/* Figure out what transform the responder chose.  */
int
ike_phase_1_initiator_recv_SA(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct sa      *sa = TAILQ_FIRST(&exchange->sa_list);
	struct ipsec_exch *ie = exchange->data;
	struct ipsec_sa *isa = sa->data;
	struct payload *sa_p = payload_first(msg, ISAKMP_PAYLOAD_SA);
	struct payload *prop = payload_first(msg, ISAKMP_PAYLOAD_PROPOSAL);
	struct payload *xf = payload_first(msg, ISAKMP_PAYLOAD_TRANSFORM);

	/*
	 * IKE requires that only one SA with only one proposal exists and
	 * since we are getting an answer on our transform offer, only one
	 * transform.
	 */
	if (TAILQ_NEXT(sa_p, link) || TAILQ_NEXT(prop, link) ||
	    TAILQ_NEXT(xf, link)) {
		log_print("ike_phase_1_initiator_recv_SA: "
		    "multiple SA, proposal or transform payloads in phase 1");
		/* XXX Is there a better notification type?  */
		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
		return -1;
	}
	/* Check that the chosen transform matches an offer.  */
	if (message_negotiate_sa(msg, ike_phase_1_validate_prop) ||
	    !TAILQ_FIRST(&sa->protos))
		return -1;

	ipsec_decode_transform(msg, sa, TAILQ_FIRST(&sa->protos), xf->p);

	/* XXX I don't like exchange-specific stuff in here.  */
	if (exchange->type != ISAKMP_EXCH_AGGRESSIVE)
		ie->group = group_get(isa->group_desc);

	/* Mark the SA as handled.  */
	sa_p->flags |= PL_MARK;

	return 0;
}
예제 #6
0
/*
 * As "the server", this starts REQ/REPLY (initiated by the client).
 * As "the client", this starts SET/ACK (initiated by the server).
 */
static int
cfg_responder_recv_ATTR(struct message *msg)
{
	struct payload *attrp = payload_first(msg, ISAKMP_PAYLOAD_ATTRIBUTE);
	struct ipsec_exch *ie = msg->exchange->data;
	struct sa      *isakmp_sa = msg->isakmp_sa;
	struct isakmp_cfg_attr *attr;
	struct sockaddr *sa;
	char           *addr;

	if (msg->exchange->phase == 2)
		if (cfg_verify_hash(msg))
			return -1;

	ie->cfg_id = GET_ISAKMP_ATTRIBUTE_ID(attrp->p);
	ie->cfg_type = attrp->p[ISAKMP_ATTRIBUTE_TYPE_OFF];

	switch (ie->cfg_type) {
	case ISAKMP_CFG_REQUEST:
	case ISAKMP_CFG_SET:
		break;

	default:
		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
		log_print("cfg_responder_recv_ATTR: "
		    "unexpected configuration message type %d", ie->cfg_type);
		return -1;
	}

	attribute_map(attrp->p + ISAKMP_ATTRIBUTE_ATTRS_OFF,
	    GET_ISAKMP_GEN_LENGTH(attrp->p) - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
	    cfg_decode_attribute, ie);

	switch (ie->cfg_type) {
	case ISAKMP_CFG_REQUEST:
		/* We're done.  */
		break;

	case ISAKMP_CFG_SET: {
			/* SET/ACK -- Client side (SET from server) */
			const char     *uk_addr = "<unknown>";

			msg->transport->vtbl->get_dst(isakmp_sa->transport,
			    &sa);
			if (sockaddr2text(sa, &addr, 0) < 0)
				addr = (char *) uk_addr;

			for (attr = LIST_FIRST(&ie->attrs); attr;
			    attr = LIST_NEXT(attr, link))
				LOG_DBG((LOG_NEGOTIATION, 50,
				    "cfg_responder_recv_ATTR: "
				    "server %s asks us to SET attribute %s",
				    addr, constant_name(isakmp_cfg_attr_cst,
					attr->type)));

			/*
			 * XXX Here's the place to add code to walk through
			 * XXX each attribute and send them along to dhclient
			 * XXX or whatever. Each attribute that we act upon
			 * XXX (such as setting a netmask), should be marked
			 * XXX like this for us to send the proper ACK
			 * XXX response: attr->attr_used++;
			 */

			if (addr != uk_addr)
				free(addr);
		}
		break;

	default:
		break;
	}

	attrp->flags |= PL_MARK;
	return 0;
}
예제 #7
0
/*
 * As "the server", this ends SET/ACK.
 * As "the client", this ends REQ/REPLY.
 */
static int
cfg_initiator_recv_ATTR(struct message *msg)
{
	struct payload *attrp = payload_first(msg, ISAKMP_PAYLOAD_ATTRIBUTE);
	struct ipsec_exch *ie = msg->exchange->data;
	struct sa      *isakmp_sa = msg->isakmp_sa;
	struct isakmp_cfg_attr *attr;
	struct sockaddr *sa;
	const char     *uk_addr = "<unknown>";
	char           *addr;

	if (msg->exchange->phase == 2)
		if (cfg_verify_hash(msg))
			return -1;

	/* Sanity.  */
	if (ie->cfg_id != GET_ISAKMP_ATTRIBUTE_ID(attrp->p)) {
		log_print("cfg_initiator_recv_ATTR: "
		    "cfg packet ID does not match!");
		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
		return -1;
	}
	switch (attrp->p[ISAKMP_ATTRIBUTE_TYPE_OFF]) {
	case ISAKMP_CFG_ACK:
		if (ie->cfg_type != ISAKMP_CFG_SET) {
			log_print("cfg_initiator_recv_ATTR: "
			    "bad packet type ACK");
			message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED,
			    0, 1, 0);
			return -1;
		}
		break;
	case ISAKMP_CFG_REPLY:
		if (ie->cfg_type != ISAKMP_CFG_REQUEST) {
			log_print("cfg_initiator_recv_ATTR: "
			    "bad packet type REPLY");
			message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED,
			    0, 1, 0);
			return -1;
		}
		break;

	default:
		log_print("cfg_initiator_recv_ATTR: unexpected configuration "
		    "message type %d", attrp->p[ISAKMP_ATTRIBUTE_TYPE_OFF]);
		message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
		return -1;
	}

	attribute_map(attrp->p + ISAKMP_ATTRIBUTE_ATTRS_OFF,
	    GET_ISAKMP_GEN_LENGTH(attrp->p) - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
	    cfg_decode_attribute, ie);

	switch (ie->cfg_type) {
	case ISAKMP_CFG_ACK: {
			/* SET/ACK -- Server side (ACK from client) */
			msg->transport->vtbl->get_src(isakmp_sa->transport,
			    &sa);
			if (sockaddr2text(sa, &addr, 0) < 0)
				addr = (char *) uk_addr;

			for (attr = LIST_FIRST(&ie->attrs); attr;
			    attr = LIST_NEXT(attr, link))
				LOG_DBG((LOG_NEGOTIATION, 50,
				    "cfg_initiator_recv_ATTR: "
				    "client %s ACKs attribute %s", addr,
				    constant_name(isakmp_cfg_attr_cst,
					attr->type)));

			if (addr != uk_addr)
				free(addr);
		}
		break;

	case ISAKMP_CFG_REPLY: {
			/*
			 * REQ/REPLY: effect attributes we've gotten
			 * responses on.
			 */
			msg->transport->vtbl->get_src(isakmp_sa->transport,
			    &sa);
			if (sockaddr2text(sa, &addr, 0) < 0)
				addr = (char *) uk_addr;

			for (attr = LIST_FIRST(&ie->attrs); attr;
			    attr = LIST_NEXT(attr, link))
				LOG_DBG((LOG_NEGOTIATION, 50,
				    "cfg_initiator_recv_ATTR: "
				    "server %s replied with attribute %s",
				    addr, constant_name(isakmp_cfg_attr_cst,
					attr->type)));

			if (addr != uk_addr)
				free(addr);
		}
		break;

	default:
		break;
	}

	attrp->flags |= PL_MARK;
	return 0;
}
예제 #8
0
/* Receive ID.  */
int
ike_phase_1_recv_ID(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct payload *payload;
	char            header[80], *rs = 0, *rid = 0, *p;
	int             initiator = exchange->initiator;
	u_int8_t      **id, id_type;
	size_t         *id_len;
	ssize_t         sz;
	struct sockaddr *sa;
	sa_family_t	af = 0;

	payload = payload_first(msg, ISAKMP_PAYLOAD_ID);

	if (exchange->name)
		rs = conf_get_str(exchange->name, "Remote-ID");

	if (rs) {
		sz = ipsec_id_size(rs, &id_type);
		if (sz == -1) {
			log_print("ike_phase_1_recv_ID: could not handle "
			    "specified Remote-ID [%s]", rs);
			return -1;
		}
		rid = malloc(sz);
		if (!rid) {
			log_error("ike_phase_1_recv_ID: malloc (%lu) failed",
			    (unsigned long)sz);
			return -1;
		}
		switch (id_type) {
		case IPSEC_ID_IPV4_ADDR:
			af = AF_INET;
			break;
		case IPSEC_ID_IPV6_ADDR:
			af = AF_INET6;
			break;
		}
		switch (id_type) {
		case IPSEC_ID_IPV4_ADDR:
		case IPSEC_ID_IPV6_ADDR:
			p = conf_get_str(rs, "Address");
			if (!p) {
				log_print("ike_phase_1_recv_ID: failed to get "
				    "Address in Remote-ID section [%s]", rs);
				free(rid);
				return -1;
			}
			if (text2sockaddr(p, 0, &sa, af, 0) == -1) {
				log_print("ike_phase_1_recv_ID: "
				    "failed to parse address %s", p);
				free(rid);
				return -1;
			}
			if ((id_type == IPSEC_ID_IPV4_ADDR &&
			    sa->sa_family != AF_INET) ||
			    (id_type == IPSEC_ID_IPV6_ADDR &&
			    sa->sa_family != AF_INET6)) {
				log_print("ike_phase_1_recv_ID: "
				    "address %s not of expected family", p);
				free(rid);
				free(sa);
				return -1;
			}
			memcpy(rid, sockaddr_addrdata(sa),
			    sockaddr_addrlen(sa));
			free(sa);
			break;

		case IPSEC_ID_FQDN:
		case IPSEC_ID_USER_FQDN:
		case IPSEC_ID_KEY_ID:
			p = conf_get_str(rs, "Name");
			if (!p) {
				log_print("ike_phase_1_recv_ID: failed to "
				    "get Name in Remote-ID section [%s]", rs);
				free(rid);
				return -1;
			}
			memcpy(rid, p, sz);
			break;

		default:
			log_print("ike_phase_1_recv_ID: "
			    "unsupported ID type %d", id_type);
			free(rid);
			return -1;
		}

		/* Compare expected/desired and received remote ID */
		if (bcmp(rid, payload->p + ISAKMP_ID_DATA_OFF, sz)) {
			free(rid);
			log_print("ike_phase_1_recv_ID: "
			    "received remote ID other than expected %s", p);
			return -1;
		}
		free(rid);
	}
	/* Choose the right fields to fill in */
	id = initiator ? &exchange->id_r : &exchange->id_i;
	id_len = initiator ? &exchange->id_r_len : &exchange->id_i_len;

	*id_len = GET_ISAKMP_GEN_LENGTH(payload->p) - ISAKMP_GEN_SZ;
	*id = malloc(*id_len);
	if (!*id) {
		log_error("ike_phase_1_recv_ID: malloc (%lu) failed",
		    (unsigned long)*id_len);
		return -1;
	}
	memcpy(*id, payload->p + ISAKMP_GEN_SZ, *id_len);
	snprintf(header, sizeof header, "ike_phase_1_recv_ID: %s",
	    constant_name(ipsec_id_cst, GET_ISAKMP_ID_TYPE(payload->p)));
	LOG_DBG_BUF((LOG_NEGOTIATION, 40, header,
	    payload->p + ISAKMP_ID_DATA_OFF,
	    *id_len + ISAKMP_GEN_SZ - ISAKMP_ID_DATA_OFF));
	payload->flags |= PL_MARK;
	return 0;
}
예제 #9
0
/* Offer a set of transforms to the responder in the MSG message.  */
int
ike_phase_1_initiator_send_SA(struct message *msg)
{
	struct exchange *exchange = msg->exchange;
	struct ipsec_exch *ie = exchange->data;
	u_int8_t       *proposal = 0, *sa_buf = 0, *saved_nextp, *attr;
	u_int8_t      **transform = 0;
	size_t          transforms_len = 0, proposal_len, sa_len;
	size_t         *transform_len = 0;
	struct conf_list *conf, *life_conf;
	struct conf_list_node *xf, *life;
	int             value, update_nextp;
	size_t          i;
	struct payload *p;
	struct proto   *proto;
	struct proto_attr *pa;
	int             group_desc = -1, new_group_desc;

	/* Get the list of transforms.  */
	conf = conf_get_list(exchange->policy, "Transforms");
	if (!conf)
		return -1;

	transform = calloc(conf->cnt, sizeof *transform);
	if (!transform) {
		log_error("ike_phase_1_initiator_send_SA: calloc (%lu, %lu) "
		    "failed", (u_long)conf->cnt, (u_long)sizeof *transform);
		goto bail_out;
	}
	transform_len = calloc(conf->cnt, sizeof *transform_len);
	if (!transform_len) {
		log_error("ike_phase_1_initiator_send_SA: calloc (%lu, %lu) "
		    "failed", (u_long)conf->cnt,
		    (u_long)sizeof *transform_len);
		goto bail_out;
	}
	for (xf = TAILQ_FIRST(&conf->fields), i = 0; i < conf->cnt;
	    i++, xf = TAILQ_NEXT(xf, link)) {
		/* XXX The sizing needs to be dynamic.  */
		transform[i] = malloc(ISAKMP_TRANSFORM_SA_ATTRS_OFF +
		    16 * ISAKMP_ATTR_VALUE_OFF);
		if (!transform[i]) {
			log_error("ike_phase_1_initiator_send_SA: malloc (%d) "
			    "failed", ISAKMP_TRANSFORM_SA_ATTRS_OFF +
			    16 * ISAKMP_ATTR_VALUE_OFF);
			goto bail_out;
		}
		SET_ISAKMP_TRANSFORM_NO(transform[i], i);
		SET_ISAKMP_TRANSFORM_ID(transform[i], IPSEC_TRANSFORM_KEY_IKE);
		SET_ISAKMP_TRANSFORM_RESERVED(transform[i], 0);

		attr = transform[i] + ISAKMP_TRANSFORM_SA_ATTRS_OFF;

		if (attribute_set_constant(xf->field, "ENCRYPTION_ALGORITHM",
		    ike_encrypt_cst, IKE_ATTR_ENCRYPTION_ALGORITHM, &attr))
			goto bail_out;

		if (attribute_set_constant(xf->field, "HASH_ALGORITHM",
		    ike_hash_cst, IKE_ATTR_HASH_ALGORITHM, &attr))
			goto bail_out;

		if (attribute_set_constant(xf->field, "AUTHENTICATION_METHOD",
		    ike_auth_cst, IKE_ATTR_AUTHENTICATION_METHOD, &attr))
			goto bail_out;

		if (attribute_set_constant(xf->field, "GROUP_DESCRIPTION",
		    ike_group_desc_cst, IKE_ATTR_GROUP_DESCRIPTION, &attr)) {
			/*
			 * If no group description exists, try looking for
			 * a user-defined one.
			 */
			if (attribute_set_constant(xf->field, "GROUP_TYPE",
			    ike_group_cst, IKE_ATTR_GROUP_TYPE, &attr))
				goto bail_out;

#if 0
			if (attribute_set_bignum(xf->field, "GROUP_PRIME",
			    IKE_ATTR_GROUP_PRIME, &attr))
				goto bail_out;

			if (attribute_set_bignum(xf->field,
			    "GROUP_GENERATOR_2", IKE_ATTR_GROUP_GENERATOR_2,
			    &attr))
				goto bail_out;

			if (attribute_set_bignum(xf->field,
			    "GROUP_GENERATOR_2", IKE_ATTR_GROUP_GENERATOR_2,
			    &attr))
				goto bail_out;

			if (attribute_set_bignum(xf->field, "GROUP_CURVE_A",
			    IKE_ATTR_GROUP_CURVE_A, &attr))
				goto bail_out;

			if (attribute_set_bignum(xf->field, "GROUP_CURVE_B",
			    IKE_ATTR_GROUP_CURVE_B, &attr))
				goto bail_out;
#endif
		}
		/*
		 * Life durations are special, we should be able to specify
		 * several, one per type.
		 */
		life_conf = conf_get_list(xf->field, "Life");
		if (life_conf) {
			for (life = TAILQ_FIRST(&life_conf->fields); life;
			    life = TAILQ_NEXT(life, link)) {
				attribute_set_constant(life->field,
				    "LIFE_TYPE", ike_duration_cst,
				    IKE_ATTR_LIFE_TYPE, &attr);

				/*
				 * XXX Deals with 16 and 32 bit lifetimes
				 * only
				 */
				value = conf_get_num(life->field,
				    "LIFE_DURATION", 0);
				if (value) {
					if (value <= 0xffff)
						attr = attribute_set_basic(
						    attr,
						    IKE_ATTR_LIFE_DURATION,
						    value);
					else {
						value = htonl(value);
						attr = attribute_set_var(attr,
						    IKE_ATTR_LIFE_DURATION,
						    (u_int8_t *)&value,
						    sizeof value);
					}
				}
			}
			conf_free_list(life_conf);
		}
		attribute_set_constant(xf->field, "PRF", ike_prf_cst,
		    IKE_ATTR_PRF, &attr);

		value = conf_get_num(xf->field, "KEY_LENGTH", 0);
		if (value)
			attr = attribute_set_basic(attr, IKE_ATTR_KEY_LENGTH,
			    value);

		value = conf_get_num(xf->field, "FIELD_SIZE", 0);
		if (value)
			attr = attribute_set_basic(attr, IKE_ATTR_FIELD_SIZE,
			    value);

		value = conf_get_num(xf->field, "GROUP_ORDER", 0);
		if (value)
			attr = attribute_set_basic(attr, IKE_ATTR_GROUP_ORDER,
			    value);

		/* Record the real transform size.  */
		transforms_len += transform_len[i] = attr - transform[i];

		/* XXX I don't like exchange-specific stuff in here.  */
		if (exchange->type == ISAKMP_EXCH_AGGRESSIVE) {
			/*
			 * Make sure that if a group description is specified,
			 * it is specified for all transforms equally.
			 */
			attr = (u_int8_t *)conf_get_str(xf->field,
			    "GROUP_DESCRIPTION");
			new_group_desc =
			    attr ? constant_value(ike_group_desc_cst,
				(char *)attr) : 0;
			if (group_desc == -1)
				group_desc = new_group_desc;
			else if (group_desc != new_group_desc) {
				log_print("ike_phase_1_initiator_send_SA: "
				    "differing group descriptions in a "
				    "proposal");
				goto bail_out;
			}
		}
		/*
		 * We need to check that we actually support our
		 * configuration.
		 */
		if (attribute_map(transform[i] + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
		    transform_len[i] - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
		    exchange->doi->is_attribute_incompatible, msg)) {
			log_print("ike_phase_1_initiator_send_SA: "
			    "section [%s] has unsupported attribute(s)",
			    xf->field);
			goto bail_out;
		}
	}

	/* XXX I don't like exchange-specific stuff in here.  */
	if (exchange->type == ISAKMP_EXCH_AGGRESSIVE)
		ie->group = group_get(group_desc);

	proposal_len = ISAKMP_PROP_SPI_OFF;
	proposal = malloc(proposal_len);
	if (!proposal) {
		log_error("ike_phase_1_initiator_send_SA: malloc (%lu) failed",
		    (unsigned long)proposal_len);
		goto bail_out;
	}
	SET_ISAKMP_PROP_NO(proposal, 1);
	SET_ISAKMP_PROP_PROTO(proposal, ISAKMP_PROTO_ISAKMP);
	SET_ISAKMP_PROP_SPI_SZ(proposal, 0);
	SET_ISAKMP_PROP_NTRANSFORMS(proposal, conf->cnt);

	/* XXX I would like to see this factored out.  */
	proto = calloc(1, sizeof *proto);
	if (!proto) {
		log_error("ike_phase_1_initiator_send_SA: "
		    "calloc (1, %lu) failed", (unsigned long)sizeof *proto);
		goto bail_out;
	}
	proto->no = 1;
	proto->proto = ISAKMP_PROTO_ISAKMP;
	proto->sa = TAILQ_FIRST(&exchange->sa_list);
	proto->xf_cnt = conf->cnt;
	TAILQ_INIT(&proto->xfs);
	for (i = 0; i < proto->xf_cnt; i++) {
		pa = (struct proto_attr *)calloc(1, sizeof *pa);
		if (!pa)
			goto bail_out;
		pa->len = transform_len[i];
		pa->attrs = (u_int8_t *)malloc(pa->len);
		if (!pa->attrs) {
			free(pa);
			goto bail_out;
		}
		memcpy(pa->attrs, transform[i], pa->len);
		TAILQ_INSERT_TAIL(&proto->xfs, pa, next);
	}
	TAILQ_INSERT_TAIL(&TAILQ_FIRST(&exchange->sa_list)->protos, proto,
	    link);

	sa_len = ISAKMP_SA_SIT_OFF + IPSEC_SIT_SIT_LEN;
	sa_buf = malloc(sa_len);
	if (!sa_buf) {
		log_error("ike_phase_1_initiator_send_SA: malloc (%lu) failed",
		    (unsigned long)sa_len);
		goto bail_out;
	}
	SET_ISAKMP_SA_DOI(sa_buf, IPSEC_DOI_IPSEC);
	SET_IPSEC_SIT_SIT(sa_buf + ISAKMP_SA_SIT_OFF, IPSEC_SIT_IDENTITY_ONLY);

	/*
	 * Add the payloads.  As this is a SA, we need to recompute the
	 * lengths of the payloads containing others.
	 */
	if (message_add_payload(msg, ISAKMP_PAYLOAD_SA, sa_buf, sa_len, 1))
		goto bail_out;
	SET_ISAKMP_GEN_LENGTH(sa_buf,
	    sa_len + proposal_len + transforms_len);
	sa_buf = 0;

	saved_nextp = msg->nextp;
	if (message_add_payload(msg, ISAKMP_PAYLOAD_PROPOSAL, proposal,
	    proposal_len, 0))
		goto bail_out;
	SET_ISAKMP_GEN_LENGTH(proposal, proposal_len + transforms_len);
	proposal = 0;

	update_nextp = 0;
	for (i = 0; i < conf->cnt; i++) {
		if (message_add_payload(msg, ISAKMP_PAYLOAD_TRANSFORM,
		    transform[i], transform_len[i], update_nextp))
			goto bail_out;
		update_nextp = 1;
		transform[i] = 0;
	}
	msg->nextp = saved_nextp;

	/* Save SA payload body in ie->sa_i_b, length ie->sa_i_b_len.  */
	ie->sa_i_b_len = sa_len + proposal_len + transforms_len -
	    ISAKMP_GEN_SZ;
	ie->sa_i_b = malloc(ie->sa_i_b_len);
	if (!ie->sa_i_b) {
		log_error("ike_phase_1_initiator_send_SA: malloc (%lu) failed",
		    (unsigned long)ie->sa_i_b_len);
		goto bail_out;
	}
	memcpy(ie->sa_i_b,
	    payload_first(msg, ISAKMP_PAYLOAD_SA)->p + ISAKMP_GEN_SZ,
	    sa_len - ISAKMP_GEN_SZ);
	memcpy(ie->sa_i_b + sa_len - ISAKMP_GEN_SZ,
	    payload_first(msg, ISAKMP_PAYLOAD_PROPOSAL)->p, proposal_len);
	transforms_len = 0;
	for (i = 0, p = TAILQ_FIRST(&msg->payload[ISAKMP_PAYLOAD_TRANSFORM]);
	    i < conf->cnt; i++, p = TAILQ_NEXT(p, link)) {
		memcpy(ie->sa_i_b + sa_len + proposal_len + transforms_len -
		    ISAKMP_GEN_SZ, p->p, transform_len[i]);
		transforms_len += transform_len[i];
	}

	/* Advertise OpenBSD isakmpd. */
	if (add_vendor_openbsd(msg))
		goto bail_out;

	/* Advertise NAT-T capability.  */
	if (nat_t_add_vendor_payloads(msg))
		goto bail_out;

	/* Advertise DPD capability.  */
	if (dpd_add_vendor_payload(msg))
		goto bail_out;

	conf_free_list(conf);
	free(transform);
	free(transform_len);
	return 0;

bail_out:
	free(sa_buf);
	free(proposal);
	if (transform) {
		for (i = 0; i < conf->cnt; i++)
			if (transform[i])
				free(transform[i]);
		free(transform);
	}
	free(transform_len);
	conf_free_list(conf);
	return -1;
}