#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include "attribute.h"
#include "cert.h"
#include "constants.h"
#include "crypto.h"
#include "doi.h"
#include "dpd.h"
#include "exchange.h"
#include "field.h"
#include "hash.h"
#include "ipsec.h"
#include "ipsec_num.h"
#include "isakmp.h"
#include "log.h"
#include "message.h"
#include "nat_traversal.h"
#include "prf.h"
#include "sa.h"
#include "timer.h"
#include "transport.h"
#include "util.h"
#include "vendor.h"
#include "virtual.h"
typedef fd_set set;
#define ISSET FD_ISSET
#define SET FD_SET
#define ZERO FD_ZERO
static int message_check_duplicate(struct message *);
static int message_encrypt(struct message *);
static int message_index_payload(struct message *, struct payload *,
u_int8_t ,u_int8_t *);
static int message_parse_transform(struct message *, struct payload *,
u_int8_t, u_int8_t *);
static struct field *message_get_field(u_int8_t);
static int message_validate_payload(struct message *, struct payload *,
u_int8_t);
static u_int16_t message_payload_sz(u_int8_t);
static int message_validate_attribute(struct message *, struct payload *);
static int message_validate_cert(struct message *, struct payload *);
static int message_validate_cert_req(struct message *, struct payload *);
static int message_validate_delete(struct message *, struct payload *);
static int message_validate_hash(struct message *, struct payload *);
static int message_validate_id(struct message *, struct payload *);
static int message_validate_key_exch(struct message *, struct payload *);
static int message_validate_nat_d(struct message *, struct payload *);
static int message_validate_nat_oa(struct message *, struct payload *);
static int message_validate_nonce(struct message *, struct payload *);
static int message_validate_notify(struct message *, struct payload *);
static int message_validate_proposal(struct message *, struct payload *);
static int message_validate_sa(struct message *, struct payload *);
static int message_validate_sig(struct message *, struct payload *);
static int message_validate_transform(struct message *, struct payload *);
static int message_validate_vendor(struct message *, struct payload *);
static void message_packet_log(struct message *);
static u_int8_t *last_sa = 0;
static u_int32_t last_prop_no;
static u_int8_t *last_prop = 0;
static u_int32_t last_xf_no;
struct message *
message_alloc(struct transport *t, u_int8_t *buf, size_t sz)
{
struct message *msg;
int i;
msg = calloc(1, sizeof *msg);
if (!msg)
return 0;
msg->iov = calloc(1, sizeof *msg->iov);
if (!msg->iov) {
message_free(msg);
return 0;
}
msg->iov[0].iov_len = sz;
msg->iov[0].iov_base = malloc(sz);
if (!msg->iov[0].iov_base) {
message_free(msg);
return 0;
}
msg->iovlen = 1;
if (buf)
memcpy(msg->iov[0].iov_base, buf, sz);
msg->nextp = (u_int8_t *)msg->iov[0].iov_base +
ISAKMP_HDR_NEXT_PAYLOAD_OFF;
msg->transport = t;
transport_reference(t);
msg->payload = calloc(ISAKMP_PAYLOAD_MAX, sizeof *msg->payload);
if (!msg->payload) {
message_free(msg);
return 0;
}
for (i = 0; i < ISAKMP_PAYLOAD_MAX; i++)
TAILQ_INIT(&msg->payload[i]);
TAILQ_INIT(&msg->post_send);
LOG_DBG((LOG_MESSAGE, 90, "message_alloc: allocated %p", msg));
return msg;
}
struct message *
message_alloc_reply(struct message *msg)
{
struct message *reply;
reply = message_alloc(msg->transport, 0, ISAKMP_HDR_SZ);
reply->exchange = msg->exchange;
reply->isakmp_sa = msg->isakmp_sa;
reply->flags = msg->flags;
if (msg->isakmp_sa)
sa_reference(msg->isakmp_sa);
return reply;
}
void
message_free(struct message *msg)
{
u_int32_t i;
struct payload *payload;
struct post_send *node;
LOG_DBG((LOG_MESSAGE, 20, "message_free: freeing %p", msg));
if (!msg)
return;
if (msg->iov) {
if (msg->orig && msg->orig != (u_int8_t *)msg->iov[0].iov_base)
free(msg->orig);
for (i = 0; i < msg->iovlen; i++)
free(msg->iov[i].iov_base);
free(msg->iov);
}
if (msg->retrans)
timer_remove_event(msg->retrans);
if (msg->payload) {
for (i = 0; i < ISAKMP_PAYLOAD_MAX; i++)
while ((payload = TAILQ_FIRST(&msg->payload[i]))) {
TAILQ_REMOVE(&msg->payload[i], payload, link);
free(payload);
}
free(msg->payload);
}
while ((node = TAILQ_FIRST(&msg->post_send)))
TAILQ_REMOVE(&msg->post_send, node, link);
if (msg->transport) {
if (msg->flags & MSG_IN_TRANSIT)
TAILQ_REMOVE(msg->transport->vtbl->get_queue(msg),
msg, link);
transport_release(msg->transport);
}
if (msg->isakmp_sa)
sa_release(msg->isakmp_sa);
free(msg);
}
static int
message_parse_payloads(struct message *msg, struct payload *p, u_int8_t next,
u_int8_t *buf, set *accepted_payloads, int (*func)(struct message *,
struct payload *, u_int8_t, u_int8_t *))
{
u_int8_t payload;
u_int16_t len;
int sz = 0;
do {
LOG_DBG((LOG_MESSAGE, 50,
"message_parse_payloads: offset %ld payload %s",
(long)(buf - (u_int8_t *) msg->iov[0].iov_base),
constant_name(isakmp_payload_cst, next)));
if (buf + ISAKMP_GEN_SZ > (u_int8_t *)msg->iov[0].iov_base +
msg->iov[0].iov_len) {
log_print("message_parse_payloads: short message");
message_drop(msg,
ISAKMP_NOTIFY_UNEQUAL_PAYLOAD_LENGTHS, 0, 1, 1);
return -1;
}
payload = next;
next = GET_ISAKMP_GEN_NEXT_PAYLOAD(buf);
if (next >= ISAKMP_PAYLOAD_RESERVED_MIN &&
next <= ISAKMP_PAYLOAD_RESERVED_MAX) {
log_print("message_parse_payloads: invalid next "
"payload type %s in payload of type %d",
constant_name(isakmp_payload_cst, next), payload);
message_drop(msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE,
0, 1, 1);
return -1;
}
if (GET_ISAKMP_GEN_RESERVED(buf) != 0) {
log_print("message_parse_payloads: reserved field "
"non-zero: %x", GET_ISAKMP_GEN_RESERVED(buf));
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED,
0, 1, 1);
return -1;
}
len = GET_ISAKMP_GEN_LENGTH(buf);
if (message_payload_sz(payload) == 0) {
log_print("message_parse_payloads: unknown minimum "
"payload size for payload type %s",
constant_name(isakmp_payload_cst, payload));
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED,
0, 1, 1);
return -1;
}
if (len < message_payload_sz(payload)) {
log_print("message_parse_payloads: payload too "
"short: %u", len);
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED,
0, 1, 1);
return -1;
}
if (buf + len > (u_int8_t *)msg->iov[0].iov_base +
msg->iov[0].iov_len) {
log_print("message_parse_payloads: payload too "
"long: %u", len);
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED,
0, 1, 1);
return -1;
}
if (next >= ISAKMP_PAYLOAD_PRIVATE_MIN &&
next != ISAKMP_PAYLOAD_NAT_D_DRAFT &&
next != ISAKMP_PAYLOAD_NAT_OA_DRAFT) {
LOG_DBG((LOG_MESSAGE, 30, "message_parse_payloads: "
"private next payload type %s in payload of "
"type %d ignored",
constant_name(isakmp_payload_cst, next), payload));
goto next_payload;
}
if (!ISSET(payload, accepted_payloads)) {
log_print("message_parse_payloads: payload type %s "
"unexpected", constant_name(isakmp_payload_cst,
payload));
message_drop(msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE,
0, 1, 1);
return -1;
}
if (func(msg, p, payload, buf))
return -1;
next_payload:
buf += len;
sz += len;
} while (next != ISAKMP_PAYLOAD_NONE);
return sz;
}
static int
message_parse_proposal(struct message *msg, struct payload *p,
u_int8_t payload, u_int8_t *buf)
{
set payload_set;
if (message_index_payload(msg, p, payload, buf) == -1)
return -1;
ZERO(&payload_set);
SET(ISAKMP_PAYLOAD_TRANSFORM, &payload_set);
if (message_parse_payloads(msg,
TAILQ_LAST(&msg->payload[ISAKMP_PAYLOAD_PROPOSAL], payload_head),
ISAKMP_PAYLOAD_TRANSFORM, buf + ISAKMP_PROP_SPI_OFF +
GET_ISAKMP_PROP_SPI_SZ(buf), &payload_set, message_parse_transform)
== -1)
return -1;
return 0;
}
static int
message_parse_transform(struct message *msg, struct payload *p,
u_int8_t payload, u_int8_t *buf)
{
if (message_index_payload(msg, p, payload, buf) == -1)
return -1;
LOG_DBG((LOG_MESSAGE, 50, "Transform %d's attributes",
GET_ISAKMP_TRANSFORM_NO(buf)));
attribute_map(buf + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
GET_ISAKMP_GEN_LENGTH(buf) - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
msg->exchange->doi->debug_attribute, msg);
return 0;
}
static struct field *
message_get_field(u_int8_t payload)
{
switch (payload) {
case ISAKMP_PAYLOAD_SA:
return isakmp_sa_fld;
case ISAKMP_PAYLOAD_PROPOSAL:
return isakmp_prop_fld;
case ISAKMP_PAYLOAD_TRANSFORM:
return isakmp_transform_fld;
case ISAKMP_PAYLOAD_KEY_EXCH:
return isakmp_ke_fld;
case ISAKMP_PAYLOAD_ID:
return isakmp_id_fld;
case ISAKMP_PAYLOAD_CERT:
return isakmp_cert_fld;
case ISAKMP_PAYLOAD_CERT_REQ:
return isakmp_certreq_fld;
case ISAKMP_PAYLOAD_HASH:
return isakmp_hash_fld;
case ISAKMP_PAYLOAD_SIG:
return isakmp_sig_fld;
case ISAKMP_PAYLOAD_NONCE:
return isakmp_nonce_fld;
case ISAKMP_PAYLOAD_NOTIFY:
return isakmp_notify_fld;
case ISAKMP_PAYLOAD_DELETE:
return isakmp_delete_fld;
case ISAKMP_PAYLOAD_VENDOR:
return isakmp_vendor_fld;
case ISAKMP_PAYLOAD_ATTRIBUTE:
return isakmp_attribute_fld;
case ISAKMP_PAYLOAD_NAT_D:
case ISAKMP_PAYLOAD_NAT_D_DRAFT:
return isakmp_nat_d_fld;
case ISAKMP_PAYLOAD_NAT_OA:
case ISAKMP_PAYLOAD_NAT_OA_DRAFT:
return isakmp_nat_oa_fld;
case ISAKMP_PAYLOAD_SAK:
case ISAKMP_PAYLOAD_SAT:
case ISAKMP_PAYLOAD_KD:
case ISAKMP_PAYLOAD_SEQ:
case ISAKMP_PAYLOAD_POP:
default:
break;
}
return NULL;
}
static int
message_validate_payload(struct message *m, struct payload *p, u_int8_t payload)
{
switch (payload) {
case ISAKMP_PAYLOAD_SA:
return message_validate_sa(m, p);
case ISAKMP_PAYLOAD_PROPOSAL:
return message_validate_proposal(m, p);
case ISAKMP_PAYLOAD_TRANSFORM:
return message_validate_transform(m, p);
case ISAKMP_PAYLOAD_KEY_EXCH:
return message_validate_key_exch(m, p);
case ISAKMP_PAYLOAD_ID:
return message_validate_id(m, p);
case ISAKMP_PAYLOAD_CERT:
return message_validate_cert(m, p);
case ISAKMP_PAYLOAD_CERT_REQ:
return message_validate_cert_req(m, p);
case ISAKMP_PAYLOAD_HASH:
return message_validate_hash(m, p);
case ISAKMP_PAYLOAD_SIG:
return message_validate_sig(m, p);
case ISAKMP_PAYLOAD_NONCE:
return message_validate_nonce(m, p);
case ISAKMP_PAYLOAD_NOTIFY:
return message_validate_notify(m, p);
case ISAKMP_PAYLOAD_DELETE:
return message_validate_delete(m, p);
case ISAKMP_PAYLOAD_VENDOR:
return message_validate_vendor(m, p);
case ISAKMP_PAYLOAD_ATTRIBUTE:
return message_validate_attribute(m, p);
case ISAKMP_PAYLOAD_NAT_D:
case ISAKMP_PAYLOAD_NAT_D_DRAFT:
return message_validate_nat_d(m, p);
case ISAKMP_PAYLOAD_NAT_OA:
case ISAKMP_PAYLOAD_NAT_OA_DRAFT:
return message_validate_nat_oa(m, p);
case ISAKMP_PAYLOAD_SAK:
case ISAKMP_PAYLOAD_SAT:
case ISAKMP_PAYLOAD_KD:
case ISAKMP_PAYLOAD_SEQ:
case ISAKMP_PAYLOAD_POP:
default:
break;
}
message_drop(m, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, 0, 1, 1);
return -1;
}
static u_int16_t
message_payload_sz(u_int8_t payload)
{
switch (payload) {
case ISAKMP_PAYLOAD_SA:
return ISAKMP_SA_SZ;
case ISAKMP_PAYLOAD_PROPOSAL:
return ISAKMP_PROP_SZ;
case ISAKMP_PAYLOAD_TRANSFORM:
return ISAKMP_TRANSFORM_SZ;
case ISAKMP_PAYLOAD_KEY_EXCH:
return ISAKMP_KE_SZ;
case ISAKMP_PAYLOAD_ID:
return ISAKMP_ID_SZ;
case ISAKMP_PAYLOAD_CERT:
return ISAKMP_CERT_SZ;
case ISAKMP_PAYLOAD_CERT_REQ:
return ISAKMP_CERTREQ_SZ;
case ISAKMP_PAYLOAD_HASH:
return ISAKMP_HASH_SZ;
case ISAKMP_PAYLOAD_SIG:
return ISAKMP_SIG_SZ;
case ISAKMP_PAYLOAD_NONCE:
return ISAKMP_NONCE_SZ;
case ISAKMP_PAYLOAD_NOTIFY:
return ISAKMP_NOTIFY_SZ;
case ISAKMP_PAYLOAD_DELETE:
return ISAKMP_DELETE_SZ;
case ISAKMP_PAYLOAD_VENDOR:
return ISAKMP_VENDOR_SZ;
case ISAKMP_PAYLOAD_ATTRIBUTE:
return ISAKMP_ATTRIBUTE_SZ;
case ISAKMP_PAYLOAD_NAT_D:
case ISAKMP_PAYLOAD_NAT_D_DRAFT:
return ISAKMP_NAT_D_SZ;
case ISAKMP_PAYLOAD_NAT_OA:
case ISAKMP_PAYLOAD_NAT_OA_DRAFT:
return ISAKMP_NAT_OA_SZ;
case ISAKMP_PAYLOAD_SAK:
case ISAKMP_PAYLOAD_SAT:
case ISAKMP_PAYLOAD_KD:
case ISAKMP_PAYLOAD_SEQ:
case ISAKMP_PAYLOAD_POP:
default:
return 0;
}
}
static int
message_validate_attribute(struct message *msg, struct payload *p)
{
if (!msg->exchange) {
if (zero_test((u_int8_t *) msg->iov[0].iov_base +
ISAKMP_HDR_MESSAGE_ID_OFF, ISAKMP_HDR_MESSAGE_ID_LEN))
msg->exchange = exchange_setup_p1(msg,
IPSEC_DOI_IPSEC);
else
msg->exchange = exchange_setup_p2(msg,
IPSEC_DOI_IPSEC);
if (!msg->exchange) {
log_print("message_validate_attribute: can not "
"create exchange");
message_free(msg);
return -1;
}
}
return 0;
}
static int
message_validate_cert(struct message *msg, struct payload *p)
{
if (GET_ISAKMP_CERT_ENCODING(p->p) >= ISAKMP_CERTENC_RESERVED_MIN) {
message_drop(msg, ISAKMP_NOTIFY_INVALID_CERT_ENCODING, 0, 1,
1);
return -1;
}
return 0;
}
static int
message_validate_cert_req(struct message *msg, struct payload *p)
{
struct cert_handler *cert;
size_t len =
GET_ISAKMP_GEN_LENGTH(p->p) - ISAKMP_CERTREQ_AUTHORITY_OFF;
if (GET_ISAKMP_CERTREQ_TYPE(p->p) >= ISAKMP_CERTENC_RESERVED_MIN) {
message_drop(msg, ISAKMP_NOTIFY_INVALID_CERT_ENCODING, 0, 1,
1);
return -1;
}
cert = cert_get(GET_ISAKMP_CERTREQ_TYPE(p->p));
if (!cert || (len && !cert->certreq_validate(p->p +
ISAKMP_CERTREQ_AUTHORITY_OFF, len))) {
message_drop(msg, ISAKMP_NOTIFY_CERT_TYPE_UNSUPPORTED, 0, 1,
1);
return -1;
}
return 0;
}
static int
message_validate_delete(struct message *msg, struct payload *p)
{
u_int8_t proto = GET_ISAKMP_DELETE_PROTO(p->p);
struct doi *doi;
struct sa *sa, *isakmp_sa;
struct sockaddr *dst, *dst_isa;
u_int32_t nspis = GET_ISAKMP_DELETE_NSPIS(p->p);
u_int8_t *spis = (u_int8_t *)p->p + ISAKMP_DELETE_SPI_OFF;
u_int32_t i;
char *addr;
if ((msg->flags & MSG_AUTHENTICATED) == 0) {
log_print("message_validate_delete: "
"got unauthenticated DELETE");
message_free(msg);
return -1;
}
doi = doi_lookup(GET_ISAKMP_DELETE_DOI(p->p));
if (!doi) {
log_print("message_validate_delete: DOI not supported");
message_free(msg);
return -1;
}
if (!msg->exchange) {
if (zero_test((u_int8_t *) msg->iov[0].iov_base
+ ISAKMP_HDR_MESSAGE_ID_OFF, ISAKMP_HDR_MESSAGE_ID_LEN))
msg->exchange = exchange_setup_p1(msg, doi->id);
else
msg->exchange = exchange_setup_p2(msg, doi->id);
if (!msg->exchange) {
log_print("message_validate_delete: can not create "
"exchange");
message_free(msg);
return -1;
}
}
if (msg->exchange->type != ISAKMP_EXCH_INFO) {
log_print("message_validate_delete: delete in exchange other "
"than INFO: %s", constant_name(isakmp_exch_cst,
msg->exchange->type));
message_free(msg);
return -1;
}
if (proto != ISAKMP_PROTO_ISAKMP && doi->validate_proto(proto)) {
log_print("message_validate_delete: protocol not supported");
message_free(msg);
return -1;
}
for (i = 0; i < nspis; i++) {
isakmp_sa = msg->isakmp_sa;
if (!isakmp_sa) {
log_print("message_validate_delete: invalid spi (no "
"valid ISAKMP SA found)");
message_free(msg);
return -1;
}
isakmp_sa->transport->vtbl->get_dst(isakmp_sa->transport,
&dst_isa);
msg->transport->vtbl->get_dst(msg->transport, &dst);
if (proto == ISAKMP_PROTO_ISAKMP)
sa = sa_lookup_isakmp_sa(dst, spis + i
* ISAKMP_HDR_COOKIES_LEN);
else
sa = ipsec_sa_lookup(dst, ((u_int32_t *) spis)[i],
proto);
if (!sa) {
LOG_DBG((LOG_MESSAGE, 50, "message_validate_delete: "
"invalid spi (no valid SA found)"));
message_free(msg);
return -1;
}
sa->transport->vtbl->get_dst(sa->transport, &dst);
if (dst->sa_family != dst_isa->sa_family ||
memcmp(sockaddr_addrdata(dst_isa), sockaddr_addrdata(dst),
sockaddr_addrlen(dst))) {
sockaddr2text(dst_isa, &addr, 0);
log_print("message_validate_delete: invalid spi "
"(illegal delete request from %s)", addr);
free(addr);
message_free(msg);
return -1;
}
}
return 0;
}
static int
message_validate_hash(struct message *msg, struct payload *p)
{
struct sa *isakmp_sa = msg->isakmp_sa;
struct ipsec_sa *isa;
struct hash *hash;
struct payload *hashp = payload_first(msg, ISAKMP_PAYLOAD_HASH);
struct prf *prf;
u_int8_t *rest;
u_int8_t message_id[ISAKMP_HDR_MESSAGE_ID_LEN];
size_t rest_len;
if (msg->exchange && (msg->exchange->type != ISAKMP_EXCH_INFO))
return 0;
if (isakmp_sa == NULL)
goto invalid;
isa = isakmp_sa->data;
hash = hash_get(isa->hash);
if (hash == NULL)
goto invalid;
if (!isa->skeyid_a)
goto invalid;
LOG_DBG_BUF((LOG_MISC, 90, "message_validate_hash: SKEYID_a",
isa->skeyid_a, isa->skeyid_len));
prf = prf_alloc(isa->prf_type, hash->type, isa->skeyid_a,
isa->skeyid_len);
if (!prf) {
message_free(msg);
return -1;
}
GET_ISAKMP_HDR_MESSAGE_ID(msg->iov[0].iov_base, message_id);
prf->Init(prf->prfctx);
LOG_DBG_BUF((LOG_MISC, 90, "message_validate_hash: message_id",
message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
prf->Update(prf->prfctx, message_id, ISAKMP_HDR_MESSAGE_ID_LEN);
rest = hashp->p + GET_ISAKMP_GEN_LENGTH(hashp->p);
rest_len = (GET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base) - (rest -
(u_int8_t *)msg->iov[0].iov_base));
LOG_DBG_BUF((LOG_MISC, 90,
"message_validate_hash: payloads after HASH(1)", rest, rest_len));
prf->Update(prf->prfctx, rest, rest_len);
prf->Final(hash->digest, prf->prfctx);
prf_free(prf);
if (memcmp(hashp->p + ISAKMP_HASH_DATA_OFF, hash->digest,
hash->hashsize))
goto invalid;
hashp->flags |= PL_MARK;
msg->flags |= MSG_AUTHENTICATED;
return 0;
invalid:
log_print("message_validate_hash: invalid hash information");
message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 1, 1);
return -1;
}
static int
message_validate_id(struct message *msg, struct payload *p)
{
struct exchange *exchange = msg->exchange;
size_t len = GET_ISAKMP_GEN_LENGTH(p->p);
if (!exchange) {
log_print("message_validate_id: payload out of sequence");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
if (exchange->doi &&
exchange->doi->validate_id_information(GET_ISAKMP_ID_TYPE(p->p),
p->p + ISAKMP_ID_DOI_DATA_OFF, p->p + ISAKMP_ID_DATA_OFF,
len - ISAKMP_ID_DATA_OFF, exchange)) {
message_drop(msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION, 0, 1,
1);
return -1;
}
return 0;
}
static int
message_validate_key_exch(struct message *msg, struct payload *p)
{
struct exchange *exchange = msg->exchange;
size_t len = GET_ISAKMP_GEN_LENGTH(p->p);
if (!exchange) {
log_print("message_validate_key_exch: "
"payload out of sequence");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
if (exchange->doi && exchange->doi->validate_key_information(p->p +
ISAKMP_KE_DATA_OFF, len - ISAKMP_KE_DATA_OFF)) {
message_drop(msg, ISAKMP_NOTIFY_INVALID_KEY_INFORMATION,
0, 1, 1);
return -1;
}
return 0;
}
static int
message_validate_nat_d(struct message *msg, struct payload *p)
{
struct exchange *exchange = msg->exchange;
if (!exchange) {
log_print("message_validate_nat_d: payload out of sequence");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
if (exchange->phase != 1) {
log_print("message_validate_nat_d: "
"NAT-D payload must be in phase 1");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
p->flags |= PL_MARK;
return 0;
}
static int
message_validate_nat_oa(struct message *msg, struct payload *p)
{
struct exchange *exchange = msg->exchange;
if (!exchange) {
log_print("message_validate_nat_d: payload out of sequence");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
#ifdef notyet
p->flags |= PL_MARK;
#endif
return 0;
}
static int
message_validate_nonce(struct message *msg, struct payload *p)
{
if (!msg->exchange) {
log_print("message_validate_nonce: payload out of sequence");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
return 0;
}
static int
message_validate_notify(struct message *msg, struct payload *p)
{
u_int8_t proto = GET_ISAKMP_NOTIFY_PROTO(p->p);
u_int16_t type = GET_ISAKMP_NOTIFY_MSG_TYPE(p->p);
struct doi *doi;
doi = doi_lookup(GET_ISAKMP_NOTIFY_DOI(p->p));
if (!doi) {
log_print("message_validate_notify: DOI not supported");
message_free(msg);
return -1;
}
if (!msg->exchange) {
if (zero_test((u_int8_t *) msg->iov[0].iov_base +
ISAKMP_HDR_MESSAGE_ID_OFF, ISAKMP_HDR_MESSAGE_ID_LEN))
msg->exchange = exchange_setup_p1(msg, doi->id);
else
msg->exchange = exchange_setup_p2(msg, doi->id);
if (!msg->exchange) {
log_print("message_validate_notify: can not create "
"exchange");
message_free(msg);
return -1;
}
}
if (proto != ISAKMP_PROTO_ISAKMP && doi->validate_proto(proto)) {
log_print("message_validate_notify: protocol not supported");
message_free(msg);
return -1;
}
if (proto == ISAKMP_PROTO_ISAKMP &&
GET_ISAKMP_NOTIFY_SPI_SZ(p->p) == ISAKMP_HDR_COOKIES_LEN &&
msg->isakmp_sa &&
memcmp(p->p + ISAKMP_NOTIFY_SPI_OFF, msg->isakmp_sa->cookies,
ISAKMP_HDR_COOKIES_LEN) != 0) {
log_print("message_validate_notify: bad cookies");
message_drop(msg, ISAKMP_NOTIFY_INVALID_SPI, 0, 1, 1);
return -1;
}
if (type < ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE ||
(type >= ISAKMP_NOTIFY_RESERVED_MIN &&
type < ISAKMP_NOTIFY_PRIVATE_MIN) ||
(type >= ISAKMP_NOTIFY_STATUS_RESERVED1_MIN &&
type <= ISAKMP_NOTIFY_STATUS_RESERVED1_MAX) ||
(type >= ISAKMP_NOTIFY_STATUS_DOI_MIN &&
type <= ISAKMP_NOTIFY_STATUS_DOI_MAX &&
doi->validate_notification(type)) ||
type >= ISAKMP_NOTIFY_STATUS_RESERVED2_MIN) {
log_print("message_validate_notify: "
"message type not supported");
message_free(msg);
return -1;
}
return 0;
}
static int
message_validate_proposal(struct message *msg, struct payload *p)
{
u_int8_t proto = GET_ISAKMP_PROP_PROTO(p->p);
u_int8_t *sa = p->context->p;
if (!msg->exchange) {
log_print("message_validate_proposal: "
"payload out of sequence");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
if (proto != ISAKMP_PROTO_ISAKMP &&
msg->exchange->doi->validate_proto(proto)) {
message_drop(msg, ISAKMP_NOTIFY_INVALID_PROTOCOL_ID, 0, 1, 1);
return -1;
}
if (sa != last_sa)
last_sa = sa;
else if (GET_ISAKMP_PROP_NO(p->p) < last_prop_no) {
message_drop(msg, ISAKMP_NOTIFY_BAD_PROPOSAL_SYNTAX, 0, 1, 1);
return -1;
}
last_prop_no = GET_ISAKMP_PROP_NO(p->p);
return 0;
}
static int
message_validate_sa(struct message *msg, struct payload *p)
{
set payload_set;
size_t len;
u_int32_t doi_id;
struct exchange *exchange = msg->exchange;
u_int8_t *pkt = msg->iov[0].iov_base;
doi_id = GET_ISAKMP_SA_DOI(p->p);
if (!doi_lookup(doi_id)) {
log_print("message_validate_sa: DOI not supported");
message_drop(msg, ISAKMP_NOTIFY_DOI_NOT_SUPPORTED, 0, 1, 1);
return -1;
}
if (!exchange) {
if (zero_test(pkt + ISAKMP_HDR_RCOOKIE_OFF,
ISAKMP_HDR_RCOOKIE_LEN))
exchange = exchange_setup_p1(msg, doi_id);
else if (msg->isakmp_sa->flags & SA_FLAG_READY)
exchange = exchange_setup_p2(msg, doi_id);
else {
message_free(msg);
return -1;
}
if (!exchange) {
message_free(msg);
return -1;
}
}
msg->exchange = exchange;
if (exchange->initiator) {
} else if (sa_create(exchange, msg->transport)) {
message_free(msg);
return -1;
}
if (exchange->phase == 1) {
msg->isakmp_sa = TAILQ_FIRST(&exchange->sa_list);
if (msg->isakmp_sa)
sa_reference(msg->isakmp_sa);
}
if (exchange->doi->validate_situation(p->p + ISAKMP_SA_SIT_OFF, &len,
GET_ISAKMP_GEN_LENGTH(p->p) - ISAKMP_SA_SIT_OFF)) {
log_print("message_validate_sa: situation not supported");
message_drop(msg, ISAKMP_NOTIFY_SITUATION_NOT_SUPPORTED,
0, 1, 1);
return -1;
}
last_sa = last_prop = 0;
last_prop_no = last_xf_no = 0;
ZERO(&payload_set);
SET(ISAKMP_PAYLOAD_PROPOSAL, &payload_set);
if (message_parse_payloads(msg, p, ISAKMP_PAYLOAD_PROPOSAL,
p->p + ISAKMP_SA_SIT_OFF + len, &payload_set,
message_parse_proposal) == -1)
return -1;
return 0;
}
static int
message_validate_sig(struct message *msg, struct payload *p)
{
if (!msg->exchange) {
log_print("message_validate_sig: payload out of sequence");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
return 0;
}
static int
message_validate_transform(struct message *msg, struct payload *p)
{
u_int8_t proto = GET_ISAKMP_PROP_PROTO(p->context->p);
u_int8_t *prop = p->context->p;
if (!msg->exchange) {
log_print("message_validate_transform: "
"payload out of sequence");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
if (msg->exchange->doi
->validate_transform_id(proto, GET_ISAKMP_TRANSFORM_ID(p->p))) {
message_drop(msg, ISAKMP_NOTIFY_INVALID_TRANSFORM_ID, 0, 1, 1);
return -1;
}
if (!zero_test(p->p + ISAKMP_TRANSFORM_RESERVED_OFF,
ISAKMP_TRANSFORM_RESERVED_LEN)) {
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
if (prop != last_prop)
last_prop = prop;
else if (GET_ISAKMP_TRANSFORM_NO(p->p) <= last_xf_no) {
message_drop(msg, ISAKMP_NOTIFY_BAD_PROPOSAL_SYNTAX, 0, 1, 1);
return -1;
}
last_xf_no = GET_ISAKMP_TRANSFORM_NO(p->p);
if (attribute_map(p->p + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
GET_ISAKMP_GEN_LENGTH(p->p) - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
msg->exchange->doi->validate_attribute, msg)) {
message_drop(msg, ISAKMP_NOTIFY_ATTRIBUTES_NOT_SUPPORTED,
0, 1, 1);
return -1;
}
return 0;
}
static int
message_validate_vendor(struct message *msg, struct payload *p)
{
if (!msg->exchange) {
log_print("message_validate_vendor: payload out of sequence");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
return -1;
}
if (msg->exchange->phase != 1) {
message_drop(msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, 0, 1, 1);
return -1;
}
check_vendor_openbsd(msg, p);
dpd_check_vendor_payload(msg, p);
nat_t_check_vendor_payload(msg, p);
if (!(p->flags & PL_MARK))
LOG_DBG((LOG_MESSAGE, 40, "message_validate_vendor: "
"vendor ID seen"));
return 0;
}
static int
message_index_payload(struct message *msg, struct payload *p, u_int8_t payload,
u_int8_t *buf)
{
struct payload *payload_node;
payload_node = malloc(sizeof *payload_node);
if (!payload_node) {
message_free(msg);
return -1;
}
payload_node->p = buf;
payload_node->context = p;
payload_node->flags = 0;
TAILQ_INSERT_TAIL(&msg->payload[payload], payload_node, link);
return 0;
}
static int
message_sort_payloads(struct message *msg, u_int8_t next)
{
set payload_set;
int i, sz;
ZERO(&payload_set);
for (i = ISAKMP_PAYLOAD_SA; i < ISAKMP_PAYLOAD_MAX; i++)
if (i != ISAKMP_PAYLOAD_PROPOSAL && i !=
ISAKMP_PAYLOAD_TRANSFORM)
SET(i, &payload_set);
sz = message_parse_payloads(msg, 0, next,
(u_int8_t *)msg->iov[0].iov_base + ISAKMP_HDR_SZ, &payload_set,
message_index_payload);
if (sz == -1)
return -1;
msg->iov[0].iov_len = ISAKMP_HDR_SZ + sz;
SET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base, ISAKMP_HDR_SZ + sz);
return 0;
}
static int
message_validate_payloads(struct message *msg)
{
int i;
struct payload *p;
struct field *f;
for (i = ISAKMP_PAYLOAD_SA; i < ISAKMP_PAYLOAD_MAX; i++)
TAILQ_FOREACH(p, &msg->payload[i], link) {
LOG_DBG((LOG_MESSAGE, 60, "message_validate_payloads: "
"payload %s at %p of message %p",
constant_name(isakmp_payload_cst, i), p->p, msg));
if ((f = message_get_field(i)) != NULL)
field_dump_payload(f, p->p);
if (message_validate_payload(msg, p, i))
return -1;
}
return 0;
}
int
message_recv(struct message *msg)
{
u_int8_t *buf = msg->iov[0].iov_base;
size_t sz = msg->iov[0].iov_len;
u_int8_t exch_type;
int setup_isakmp_sa, msgid_is_zero;
u_int8_t flags;
struct keystate *ks = 0;
struct proto tmp_proto;
struct sa tmp_sa;
struct transport *t;
if (sz < ISAKMP_HDR_SZ || sz != GET_ISAKMP_HDR_LENGTH(buf)) {
log_print("message_recv: bad message length");
message_drop(msg, 0, 0, 1, 1);
return -1;
}
message_dump_raw("message_recv", msg, LOG_MESSAGE);
setup_isakmp_sa = zero_test(buf + ISAKMP_HDR_RCOOKIE_OFF,
ISAKMP_HDR_RCOOKIE_LEN);
if (setup_isakmp_sa) {
if (exchange_lookup_from_icookie(buf + ISAKMP_HDR_ICOOKIE_OFF) ||
sa_lookup_from_icookie(buf + ISAKMP_HDR_ICOOKIE_OFF)) {
LOG_DBG((LOG_MESSAGE, 90,
"message_recv: dropping setup for existing SA"));
message_free(msg);
return -1;
}
} else {
msg->isakmp_sa = sa_lookup_by_header(buf, 0);
if (msg->isakmp_sa)
sa_reference(msg->isakmp_sa);
if (!msg->isakmp_sa) {
msg->exchange = exchange_lookup_from_icookie(buf +
ISAKMP_HDR_ICOOKIE_OFF);
if (msg->exchange && msg->exchange->phase == 1 &&
zero_test(msg->exchange->cookies +
ISAKMP_HDR_RCOOKIE_OFF, ISAKMP_HDR_RCOOKIE_LEN))
exchange_upgrade_p1(msg);
else {
log_print("message_recv: invalid cookie(s) "
"%08x%08x %08x%08x",
decode_32(buf + ISAKMP_HDR_ICOOKIE_OFF),
decode_32(buf + ISAKMP_HDR_ICOOKIE_OFF + 4),
decode_32(buf + ISAKMP_HDR_RCOOKIE_OFF),
decode_32(buf + ISAKMP_HDR_RCOOKIE_OFF + 4));
tmp_proto.sa = &tmp_sa;
tmp_sa.doi = doi_lookup(ISAKMP_DOI_ISAKMP);
tmp_proto.proto = ISAKMP_PROTO_ISAKMP;
tmp_proto.spi_sz[1] = ISAKMP_HDR_COOKIES_LEN;
tmp_proto.spi[1] =
buf + ISAKMP_HDR_COOKIES_OFF;
message_drop(msg, ISAKMP_NOTIFY_INVALID_COOKIE,
&tmp_proto, 1, 1);
return -1;
}
#if 0
msg->isakmp_sa = sa_lookup_from_icookie(buf +
ISAKMP_HDR_ICOOKIE_OFF);
if (msg->isakmp_sa)
sa_isakmp_upgrade(msg);
#endif
}
msg->exchange = exchange_lookup(buf, 1);
}
if (message_check_duplicate(msg))
return -1;
if (GET_ISAKMP_HDR_NEXT_PAYLOAD(buf) >= ISAKMP_PAYLOAD_RESERVED_MIN) {
log_print("message_recv: invalid payload type %d in ISAKMP "
"header (check passphrases, if applicable and in Phase 1)",
GET_ISAKMP_HDR_NEXT_PAYLOAD(buf));
message_drop(msg, ISAKMP_NOTIFY_INVALID_PAYLOAD_TYPE, 0, 1, 1);
return -1;
}
if (ISAKMP_VERSION_MAJOR(GET_ISAKMP_HDR_VERSION(buf)) != 1) {
log_print("message_recv: invalid version major %d",
ISAKMP_VERSION_MAJOR(GET_ISAKMP_HDR_VERSION(buf)));
message_drop(msg, ISAKMP_NOTIFY_INVALID_MAJOR_VERSION, 0, 1,
1);
return -1;
}
if (ISAKMP_VERSION_MINOR(GET_ISAKMP_HDR_VERSION(buf)) != 0) {
log_print("message_recv: invalid version minor %d",
ISAKMP_VERSION_MINOR(GET_ISAKMP_HDR_VERSION(buf)));
message_drop(msg, ISAKMP_NOTIFY_INVALID_MINOR_VERSION, 0, 1,
1);
return -1;
}
exch_type = GET_ISAKMP_HDR_EXCH_TYPE(buf);
if (exch_type == ISAKMP_EXCH_NONE ||
(exch_type >= ISAKMP_EXCH_FUTURE_MIN &&
exch_type <= ISAKMP_EXCH_FUTURE_MAX) ||
(setup_isakmp_sa && exch_type >= ISAKMP_EXCH_DOI_MIN)) {
log_print("message_recv: invalid exchange type %s",
constant_name(isakmp_exch_cst, exch_type));
message_drop(msg, ISAKMP_NOTIFY_INVALID_EXCHANGE_TYPE, 0, 1,
1);
return -1;
}
flags = GET_ISAKMP_HDR_FLAGS(buf);
if (flags & ~(ISAKMP_FLAGS_ENC | ISAKMP_FLAGS_COMMIT |
ISAKMP_FLAGS_AUTH_ONLY)) {
log_print("message_recv: invalid flags 0x%x",
GET_ISAKMP_HDR_FLAGS(buf));
message_drop(msg, ISAKMP_NOTIFY_INVALID_FLAGS, 0, 1, 1);
return -1;
}
msgid_is_zero = zero_test(buf + ISAKMP_HDR_MESSAGE_ID_OFF,
ISAKMP_HDR_MESSAGE_ID_LEN);
if (setup_isakmp_sa && !msgid_is_zero) {
log_print("message_recv: invalid message id");
message_drop(msg, ISAKMP_NOTIFY_INVALID_MESSAGE_ID, 0, 1, 1);
return -1;
}
if (!setup_isakmp_sa && msgid_is_zero) {
msg->exchange = exchange_lookup(buf, 0);
if (!msg->exchange) {
log_print("message_recv: phase 1 message after "
"ISAKMP SA is ready");
message_free(msg);
return -1;
} else if (msg->exchange->last_sent) {
LOG_DBG((LOG_MESSAGE, 80, "message_recv: resending "
"last message from phase 1"));
message_send(msg->exchange->last_sent);
}
}
if (flags & ISAKMP_FLAGS_ENC) {
if (!msg->isakmp_sa) {
LOG_DBG((LOG_MISC, 10, "message_recv: no isakmp_sa "
"for encrypted message"));
message_free(msg);
return -1;
}
ks = msg->isakmp_sa->doi->get_keystate(msg);
if (!ks) {
message_free(msg);
return -1;
}
msg->orig = malloc(sz);
if (!msg->orig) {
message_free(msg);
free(ks);
return -1;
}
memcpy(msg->orig, buf, sz);
crypto_decrypt(ks, buf + ISAKMP_HDR_SZ, sz - ISAKMP_HDR_SZ);
} else
msg->orig = buf;
msg->orig_sz = sz;
message_packet_log(msg);
if (GET_ISAKMP_HDR_NEXT_PAYLOAD(buf) != ISAKMP_PAYLOAD_NONE &&
message_sort_payloads(msg, GET_ISAKMP_HDR_NEXT_PAYLOAD(buf))) {
free(ks);
return -1;
}
if (message_validate_payloads(msg)) {
free(ks);
return -1;
}
if (!msg->exchange) {
log_print("message_recv: no exchange");
message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 1);
free(ks);
return -1;
}
if (msg->isakmp_sa && msg->exchange->phase == 1) {
t = msg->isakmp_sa->transport;
msg->isakmp_sa->transport = msg->transport;
transport_reference(msg->transport);
transport_release(t);
}
if (exch_type >= ISAKMP_EXCH_DOI_MIN &&
msg->exchange->doi->validate_exchange(exch_type)) {
log_print("message_recv: invalid DOI exchange type %d",
exch_type);
message_drop(msg, ISAKMP_NOTIFY_INVALID_EXCHANGE_TYPE, 0, 1,
1);
free(ks);
return -1;
}
if (ks) {
if (!msg->exchange->keystate) {
msg->exchange->keystate = ks;
msg->exchange->crypto = ks->xf;
} else
free(ks);
}
if (flags & ISAKMP_FLAGS_ENC)
msg->exchange->flags |= EXCHANGE_FLAG_ENCRYPT;
if ((msg->exchange->flags & EXCHANGE_FLAG_COMMITTED) == 0 &&
(flags & ISAKMP_FLAGS_COMMIT))
msg->exchange->flags |= EXCHANGE_FLAG_HE_COMMITTED;
if ((flags & ISAKMP_FLAGS_ENC) == 0 &&
(msg->exchange->phase == 2 ||
(msg->exchange->keystate &&
msg->exchange->type != ISAKMP_EXCH_AGGRESSIVE))) {
log_print("message_recv: cleartext phase %d message",
msg->exchange->phase);
message_drop(msg, ISAKMP_NOTIFY_INVALID_FLAGS, 0, 1, 1);
return -1;
}
exchange_run(msg);
return 0;
}
void
message_send_expire(struct message *msg)
{
msg->retrans = 0;
message_send(msg);
}
void
message_send(struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct message *m;
struct msg_head *q;
if (msg->retrans) {
timer_remove_event(msg->retrans);
msg->retrans = 0;
}
message_packet_log(msg);
if ((msg->flags & MSG_ENCRYPTED) == 0 &&
exchange->flags & EXCHANGE_FLAG_ENCRYPT) {
if (!exchange->keystate) {
exchange->keystate = exchange->doi->get_keystate(msg);
if (!exchange->keystate)
return;
exchange->crypto = exchange->keystate->xf;
exchange->flags |= EXCHANGE_FLAG_ENCRYPT;
}
if (message_encrypt(msg)) {
return;
}
}
if (exchange->flags & EXCHANGE_FLAG_COMMITTED)
SET_ISAKMP_HDR_FLAGS(msg->iov[0].iov_base,
GET_ISAKMP_HDR_FLAGS(msg->iov[0].iov_base)
| ISAKMP_FLAGS_COMMIT);
message_dump_raw("message_send", msg, LOG_MESSAGE);
msg->flags |= MSG_IN_TRANSIT;
exchange->in_transit = msg;
q = msg->transport->vtbl->get_queue(msg);
for (m = TAILQ_FIRST(q); m; m = TAILQ_NEXT(m, link))
if (m == msg) {
LOG_DBG((LOG_MESSAGE, 60,
"message_send: msg %p already on sendq %p", m, q));
return;
}
TAILQ_INSERT_TAIL(q, msg, link);
}
void
message_setup_header(struct message *msg, u_int8_t exchange, u_int8_t flags,
u_int8_t *msg_id)
{
u_int8_t *buf = msg->iov[0].iov_base;
SET_ISAKMP_HDR_ICOOKIE(buf, msg->exchange->cookies);
SET_ISAKMP_HDR_RCOOKIE(buf, msg->exchange->cookies +
ISAKMP_HDR_ICOOKIE_LEN);
SET_ISAKMP_HDR_NEXT_PAYLOAD(buf, ISAKMP_PAYLOAD_NONE);
SET_ISAKMP_HDR_VERSION(buf, ISAKMP_VERSION_MAKE(1, 0));
SET_ISAKMP_HDR_EXCH_TYPE(buf, exchange);
SET_ISAKMP_HDR_FLAGS(buf, flags);
SET_ISAKMP_HDR_MESSAGE_ID(buf, msg_id);
SET_ISAKMP_HDR_LENGTH(buf, msg->iov[0].iov_len);
}
int
message_add_payload(struct message *msg, u_int8_t payload, u_int8_t *buf,
size_t sz, int link)
{
struct iovec *new_iov;
struct payload *payload_node;
payload_node = calloc(1, sizeof *payload_node);
if (!payload_node) {
log_error("message_add_payload: calloc (1, %lu) failed",
(unsigned long)sizeof *payload_node);
return -1;
}
new_iov = reallocarray(msg->iov, msg->iovlen + 1,
sizeof *msg->iov);
if (!new_iov) {
log_error("message_add_payload: realloc (%p, %lu) failed",
msg->iov, (msg->iovlen + 1) *
(unsigned long)sizeof *msg->iov);
free(payload_node);
return -1;
}
msg->iov = new_iov;
new_iov[msg->iovlen].iov_base = buf;
new_iov[msg->iovlen].iov_len = sz;
msg->iovlen++;
if (link)
*msg->nextp = payload;
msg->nextp = buf + ISAKMP_GEN_NEXT_PAYLOAD_OFF;
*msg->nextp = ISAKMP_PAYLOAD_NONE;
SET_ISAKMP_GEN_RESERVED(buf, 0);
SET_ISAKMP_GEN_LENGTH(buf, sz);
SET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base,
GET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base) + sz);
payload_node->p = buf;
TAILQ_INSERT_TAIL(&msg->payload[payload], payload_node, link);
return 0;
}
struct info_args {
char discr;
u_int32_t doi;
u_int8_t proto;
u_int16_t spi_sz;
union {
struct {
u_int16_t msg_type;
u_int8_t *spi;
} n;
struct {
u_int16_t nspis;
u_int8_t *spis;
} d;
struct {
u_int16_t msg_type;
u_int8_t *spi;
u_int32_t seq;
} dpd;
} u;
};
void
message_send_notification(struct message *msg, struct sa *isakmp_sa,
u_int16_t notify, struct proto *proto, int incoming)
{
struct info_args args;
struct sa *doi_sa = proto ? proto->sa : isakmp_sa;
args.discr = 'N';
args.doi = doi_sa ? doi_sa->doi->id : ISAKMP_DOI_ISAKMP;
args.proto = proto ? proto->proto : ISAKMP_PROTO_ISAKMP;
args.spi_sz = proto ? proto->spi_sz[incoming] : 0;
args.u.n.msg_type = notify;
args.u.n.spi = proto ? proto->spi[incoming] : 0;
if (isakmp_sa && (isakmp_sa->flags & SA_FLAG_READY))
exchange_establish_p2(isakmp_sa, ISAKMP_EXCH_INFO, 0, &args,
0, 0);
else
exchange_establish_p1(msg->transport, ISAKMP_EXCH_INFO,
msg->exchange ? msg->exchange->doi->id : ISAKMP_DOI_ISAKMP,
0, &args, 0, 0, 0);
}
void
message_send_delete(struct sa *sa)
{
struct info_args args;
struct proto *proto;
struct sa *isakmp_sa;
struct sockaddr *dst;
if (!sa->transport)
return;
sa->transport->vtbl->get_dst(sa->transport, &dst);
isakmp_sa = sa_isakmp_lookup_by_peer(dst, SA_LEN(dst));
if (!isakmp_sa) {
return;
}
args.discr = 'D';
args.doi = sa->doi->id;
args.u.d.nspis = 1;
for (proto = TAILQ_FIRST(&sa->protos); proto;
proto = TAILQ_NEXT(proto, link)) {
switch (proto->proto) {
case ISAKMP_PROTO_ISAKMP:
args.spi_sz = ISAKMP_HDR_COOKIES_LEN;
args.u.d.spis = sa->cookies;
break;
case IPSEC_PROTO_IPSEC_AH:
case IPSEC_PROTO_IPSEC_ESP:
case IPSEC_PROTO_IPCOMP:
args.spi_sz = proto->spi_sz[1];
args.u.d.spis = proto->spi[1];
break;
default:
log_print("message_send_delete: cannot delete unknown "
"protocol %d", proto->proto);
continue;
}
args.proto = proto->proto;
exchange_establish_p2(isakmp_sa, ISAKMP_EXCH_INFO, 0, &args,
0, 0);
}
}
void
message_send_dpd_notify(struct sa* isakmp_sa, u_int16_t notify, u_int32_t seq)
{
struct info_args args;
args.discr = 'P';
args.doi = IPSEC_DOI_IPSEC;
args.proto = ISAKMP_PROTO_ISAKMP;
args.spi_sz = ISAKMP_HDR_COOKIES_LEN;
args.u.dpd.msg_type = notify;
args.u.dpd.spi = isakmp_sa->cookies;
args.u.dpd.seq = htonl(seq);
exchange_establish_p2(isakmp_sa, ISAKMP_EXCH_INFO, 0, &args, 0, 0);
}
int
message_send_info(struct message *msg)
{
u_int8_t *buf;
size_t sz = 0;
struct info_args *args = msg->extra;
u_int8_t payload;
if (msg->exchange->doi->informational_pre_hook)
if (msg->exchange->doi->informational_pre_hook(msg))
return -1;
switch (args->discr) {
case 'P':
sz = sizeof args->u.dpd.seq;
case 'N':
sz += ISAKMP_NOTIFY_SPI_OFF + args->spi_sz;
break;
case 'D':
default:
sz = ISAKMP_DELETE_SPI_OFF + args->u.d.nspis * args->spi_sz;
break;
}
buf = calloc(1, sz);
if (!buf) {
log_error("message_send_info: calloc (1, %lu) failed",
(unsigned long)sz);
message_free(msg);
return -1;
}
switch (args->discr) {
case 'P':
memcpy(buf + ISAKMP_NOTIFY_SPI_OFF + args->spi_sz,
&args->u.dpd.seq, sizeof args->u.dpd.seq);
case 'N':
payload = ISAKMP_PAYLOAD_NOTIFY;
SET_ISAKMP_NOTIFY_DOI(buf, args->doi);
SET_ISAKMP_NOTIFY_PROTO(buf, args->proto);
SET_ISAKMP_NOTIFY_SPI_SZ(buf, args->spi_sz);
SET_ISAKMP_NOTIFY_MSG_TYPE(buf, args->u.n.msg_type);
memcpy(buf + ISAKMP_NOTIFY_SPI_OFF, args->u.n.spi,
args->spi_sz);
break;
case 'D':
default:
payload = ISAKMP_PAYLOAD_DELETE;
SET_ISAKMP_DELETE_DOI(buf, args->doi);
SET_ISAKMP_DELETE_PROTO(buf, args->proto);
SET_ISAKMP_DELETE_SPI_SZ(buf, args->spi_sz);
SET_ISAKMP_DELETE_NSPIS(buf, args->u.d.nspis);
memcpy(buf + ISAKMP_DELETE_SPI_OFF, args->u.d.spis,
args->u.d.nspis * args->spi_sz);
msg->flags |= MSG_PRIORITIZED;
break;
}
if (message_add_payload(msg, payload, buf, sz, 1)) {
free(buf);
message_free(msg);
return -1;
}
if (msg->exchange->doi->informational_post_hook)
if (msg->exchange->doi->informational_post_hook(msg)) {
message_free(msg);
return -1;
}
return 0;
}
void
message_drop(struct message *msg, int notify, struct proto *proto,
int incoming, int clean)
{
struct transport *t = msg->transport;
struct sockaddr *dst;
char *address;
short port = 0;
t->vtbl->get_dst(t, &dst);
if (sockaddr2text(dst, &address, 0)) {
log_error("message_drop: sockaddr2text () failed");
address = 0;
}
switch (dst->sa_family) {
case AF_INET:
port = ((struct sockaddr_in *)dst)->sin_port;
break;
case AF_INET6:
port = ((struct sockaddr_in6 *)dst)->sin6_port;
break;
default:
log_print("message_drop: unknown protocol family %d",
dst->sa_family);
}
log_print("dropped message from %s port %d due to notification type "
"%s", address ? address : "<unknown>", htons(port),
constant_name(isakmp_notify_cst, notify));
free(address);
if (notify)
message_send_notification(msg, msg->isakmp_sa, notify, proto,
incoming);
if (clean)
message_free(msg);
}
void
message_dump_raw(char *header, struct message *msg, int class)
{
u_int32_t i, j, k = 0;
char buf[80], *p = buf;
LOG_DBG((class, 70, "%s: message %p", header, msg));
field_dump_payload(isakmp_hdr_fld, msg->iov[0].iov_base);
for (i = 0; i < msg->iovlen; i++)
for (j = 0; j < msg->iov[i].iov_len; j++) {
snprintf(p, sizeof buf - (int) (p - buf), "%02x",
((u_int8_t *) msg->iov[i].iov_base)[j]);
p += strlen(p);
if (++k % 32 == 0) {
*p = '\0';
LOG_DBG((class, 70, "%s: %s", header, buf));
p = buf;
} else if (k % 4 == 0)
*p++ = ' ';
}
*p = '\0';
if (p != buf)
LOG_DBG((class, 70, "%s: %s", header, buf));
}
static void
message_packet_log(struct message *msg)
{
struct sockaddr *src, *dst;
struct transport *t = msg->transport;
if (msg->xmits > 0)
return;
if (msg->exchange && msg->exchange->flags & EXCHANGE_FLAG_NAT_T_ENABLE)
t = ((struct virtual_transport *)msg->transport)->encap;
if (msg->exchange &&
msg->exchange->initiator ^ (msg->exchange->step % 2)) {
t->vtbl->get_src(t, &src);
t->vtbl->get_dst(t, &dst);
} else {
t->vtbl->get_src(t, &dst);
t->vtbl->get_dst(t, &src);
}
log_packet_iov(src, dst, msg->iov, msg->iovlen);
}
static int
message_encrypt(struct message *msg)
{
struct exchange *exchange = msg->exchange;
size_t i, sz = 0;
u_int8_t *buf;
if (msg->iovlen == 1)
return 0;
for (i = 1; i < msg->iovlen; i++)
sz += msg->iov[i].iov_len;
sz = ((sz + exchange->crypto->blocksize - 1) /
exchange->crypto->blocksize) * exchange->crypto->blocksize;
buf = realloc(msg->iov[1].iov_base, sz);
if (!buf) {
log_error("message_encrypt: realloc (%p, %lu) failed",
msg->iov[1].iov_base, (unsigned long) sz);
return -1;
}
msg->iov[1].iov_base = buf;
for (i = 2; i < msg->iovlen; i++) {
memcpy(buf + msg->iov[1].iov_len, msg->iov[i].iov_base,
msg->iov[i].iov_len);
msg->iov[1].iov_len += msg->iov[i].iov_len;
free(msg->iov[i].iov_base);
}
memset(buf + msg->iov[1].iov_len, '\0', sz - msg->iov[1].iov_len);
msg->iov[1].iov_len = sz;
msg->iovlen = 2;
SET_ISAKMP_HDR_FLAGS(msg->iov[0].iov_base,
GET_ISAKMP_HDR_FLAGS(msg->iov[0].iov_base) | ISAKMP_FLAGS_ENC);
SET_ISAKMP_HDR_LENGTH(msg->iov[0].iov_base, ISAKMP_HDR_SZ + sz);
crypto_encrypt(exchange->keystate, buf, msg->iov[1].iov_len);
msg->flags |= MSG_ENCRYPTED;
crypto_update_iv(exchange->keystate);
return 0;
}
static int
message_check_duplicate(struct message *msg)
{
struct exchange *exchange = msg->exchange;
size_t sz = msg->iov[0].iov_len;
u_int8_t *pkt = msg->iov[0].iov_base;
if (!exchange)
return 0;
LOG_DBG((LOG_MESSAGE, 90, "message_check_duplicate: last_received %p",
exchange->last_received));
if (exchange->last_received) {
LOG_DBG_BUF((LOG_MESSAGE, 95,
"message_check_duplicate: last_received",
exchange->last_received->orig,
exchange->last_received->orig_sz));
if (sz == exchange->last_received->orig_sz &&
memcmp(pkt, exchange->last_received->orig, sz) == 0) {
LOG_DBG((LOG_MESSAGE, 80,
"message_check_duplicate: dropping dup"));
if (exchange->last_sent && (exchange->last_sent->flags
& MSG_LAST))
message_send(exchange->last_sent);
message_free(msg);
return -1;
}
}
if (exchange->last_sent) {
if (exchange->last_sent == exchange->in_transit) {
struct message *m = exchange->in_transit;
TAILQ_REMOVE(m->transport->vtbl->get_queue(m), m,
link);
exchange->in_transit = 0;
}
message_free(exchange->last_sent);
exchange->last_sent = 0;
}
return 0;
}
static __inline struct payload *
step_transform(struct payload *tp, struct payload **propp,
struct payload **sap)
{
tp = TAILQ_NEXT(tp, link);
if (tp) {
*propp = tp->context;
*sap = (*propp)->context;
}
return tp;
}
int
message_negotiate_sa(struct message *msg, int (*validate)(struct exchange *,
struct sa *, struct sa *))
{
struct payload *tp, *propp, *sap, *next_tp = 0, *next_propp, *next_sap;
struct payload *saved_tp = 0, *saved_propp = 0, *saved_sap = 0;
struct sa *sa;
struct proto *proto;
int suite_ok_so_far = 0;
struct exchange *exchange = msg->exchange;
sa = TAILQ_FIRST(&exchange->sa_list);
for (tp = payload_first(msg, ISAKMP_PAYLOAD_TRANSFORM); tp;
tp = next_tp) {
propp = tp->context;
sap = propp->context;
sap->flags |= PL_MARK;
next_tp = step_transform(tp, &next_propp, &next_sap);
if (!attribute_map(tp->p + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
GET_ISAKMP_GEN_LENGTH(tp->p) -
ISAKMP_TRANSFORM_SA_ATTRS_OFF,
exchange->doi->is_attribute_incompatible, msg)) {
LOG_DBG((LOG_NEGOTIATION, 30, "message_negotiate_sa: "
"transform %d proto %d proposal %d ok",
GET_ISAKMP_TRANSFORM_NO(tp->p),
GET_ISAKMP_PROP_PROTO(propp->p),
GET_ISAKMP_PROP_NO(propp->p)));
if (sa_add_transform(sa, tp, exchange->initiator,
&proto))
goto cleanup;
suite_ok_so_far = 1;
saved_tp = next_tp;
saved_propp = next_propp;
saved_sap = next_sap;
while ((next_tp = step_transform(tp, &next_propp,
&next_sap)) && next_propp == propp)
tp = next_tp;
}
retry_transform:
if (next_tp && propp != next_propp && sap == next_sap &&
(GET_ISAKMP_PROP_NO(propp->p) ==
GET_ISAKMP_PROP_NO(next_propp->p))) {
if (!suite_ok_so_far) {
LOG_DBG((LOG_NEGOTIATION, 30,
"message_negotiate_sa: proto %d proposal "
"%d failed",
GET_ISAKMP_PROP_PROTO(propp->p),
GET_ISAKMP_PROP_NO(propp->p)));
while ((proto = TAILQ_FIRST(&sa->protos)))
proto_free(proto);
while ((next_tp = step_transform(tp,
&next_propp, &next_sap)) &&
(GET_ISAKMP_PROP_NO(next_propp->p) ==
GET_ISAKMP_PROP_NO(propp->p)) &&
next_sap == sap)
tp = next_tp;
}
suite_ok_so_far = 0;
}
if (!next_tp ||
(propp != next_propp && (GET_ISAKMP_PROP_NO(propp->p) !=
GET_ISAKMP_PROP_NO(next_propp->p))) ||
sap != next_sap) {
if (suite_ok_so_far) {
if (!validate || validate(exchange, sa,
msg->isakmp_sa)) {
LOG_DBG((LOG_NEGOTIATION, 30,
"message_negotiate_sa: proposal "
"%d succeeded",
GET_ISAKMP_PROP_NO(propp->p)));
while ((next_tp = step_transform(tp,
&next_propp, &next_sap)) &&
next_sap == sap)
tp = next_tp;
} else {
LOG_DBG((LOG_NEGOTIATION, 30,
"message_negotiate_sa: proposal "
"%d failed",
GET_ISAKMP_PROP_NO(propp->p)));
next_tp = saved_tp;
next_propp = saved_propp;
next_sap = saved_sap;
suite_ok_so_far = 0;
while ((proto =
TAILQ_FIRST(&sa->protos)))
proto_free(proto);
goto retry_transform;
}
}
}
if (!next_tp || sap != next_sap) {
if (!suite_ok_so_far) {
log_print("message_negotiate_sa: no "
"compatible proposal found");
message_drop(msg,
ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0);
}
sa = TAILQ_NEXT(sa, next);
}
}
return 0;
cleanup:
while ((proto = TAILQ_FIRST(&sa->protos)))
proto_free(proto);
return -1;
}
int
message_add_sa_payload(struct message *msg)
{
struct exchange *exchange = msg->exchange;
u_int8_t *sa_buf, *saved_nextp_sa, *saved_nextp_prop;
size_t sa_len, extra_sa_len;
int i, nprotos = 0;
struct proto *proto;
u_int8_t **transforms = 0, **proposals = 0;
size_t *transform_lens = 0, *proposal_lens = 0;
struct sa *sa;
struct doi *doi = exchange->doi;
u_int8_t *spi = 0;
size_t spi_sz;
for (sa = TAILQ_FIRST(&exchange->sa_list); sa;
sa = TAILQ_NEXT(sa, next)) {
sa_len = ISAKMP_SA_SIT_OFF + doi->situation_size();
extra_sa_len = 0;
sa_buf = malloc(sa_len);
if (!sa_buf) {
log_error("message_add_sa_payload: "
"malloc (%lu) failed", (unsigned long)sa_len);
goto cleanup;
}
SET_ISAKMP_SA_DOI(sa_buf, doi->id);
doi->setup_situation(sa_buf);
nprotos = 0;
for (proto = TAILQ_FIRST(&sa->protos); proto;
proto = TAILQ_NEXT(proto, link))
nprotos++;
transforms = calloc(nprotos, sizeof *transforms);
if (!transforms) {
log_error("message_add_sa_payload: calloc (%d, %lu) "
"failed", nprotos,
(unsigned long)sizeof *transforms);
goto cleanup;
}
transform_lens = calloc(nprotos, sizeof *transform_lens);
if (!transform_lens) {
log_error("message_add_sa_payload: calloc (%d, %lu) "
"failed", nprotos,
(unsigned long) sizeof *transform_lens);
goto cleanup;
}
proposals = calloc(nprotos, sizeof *proposals);
if (!proposals) {
log_error("message_add_sa_payload: calloc (%d, %lu) "
"failed", nprotos,
(unsigned long)sizeof *proposals);
goto cleanup;
}
proposal_lens = calloc(nprotos, sizeof *proposal_lens);
if (!proposal_lens) {
log_error("message_add_sa_payload: calloc (%d, %lu) "
"failed", nprotos,
(unsigned long)sizeof *proposal_lens);
goto cleanup;
}
for (proto = TAILQ_FIRST(&sa->protos), i = 0; proto;
proto = TAILQ_NEXT(proto, link), i++) {
transform_lens[i] =
GET_ISAKMP_GEN_LENGTH(proto->chosen->p);
transforms[i] = malloc(transform_lens[i]);
if (!transforms[i]) {
log_error("message_add_sa_payload: malloc "
"(%lu) failed",
(unsigned long)transform_lens[i]);
goto cleanup;
}
if (doi->get_spi) {
spi = doi->get_spi(&spi_sz,
GET_ISAKMP_PROP_PROTO(proto->chosen->context->p),
msg);
if (spi_sz && !spi)
goto cleanup;
proto->spi[1] = spi;
proto->spi_sz[1] = spi_sz;
} else
spi_sz = 0;
proposal_lens[i] = ISAKMP_PROP_SPI_OFF + spi_sz;
proposals[i] = malloc(proposal_lens[i]);
if (!proposals[i]) {
log_error("message_add_sa_payload: malloc "
"(%lu) failed",
(unsigned long)proposal_lens[i]);
goto cleanup;
}
memcpy(transforms[i], proto->chosen->p,
transform_lens[i]);
memcpy(proposals[i], proto->chosen->context->p,
ISAKMP_PROP_SPI_OFF);
SET_ISAKMP_PROP_NTRANSFORMS(proposals[i], 1);
SET_ISAKMP_PROP_SPI_SZ(proposals[i], spi_sz);
if (spi_sz)
memcpy(proposals[i] + ISAKMP_PROP_SPI_OFF, spi,
spi_sz);
extra_sa_len += proposal_lens[i] + transform_lens[i];
}
if (message_add_payload(msg, ISAKMP_PAYLOAD_SA, sa_buf,
sa_len, 1))
goto cleanup;
SET_ISAKMP_GEN_LENGTH(sa_buf, sa_len + extra_sa_len);
sa_buf = 0;
saved_nextp_sa = msg->nextp;
for (proto = TAILQ_FIRST(&sa->protos), i = 0; proto;
proto = TAILQ_NEXT(proto, link), i++) {
if (message_add_payload(msg, ISAKMP_PAYLOAD_PROPOSAL,
proposals[i], proposal_lens[i], i > 0))
goto cleanup;
SET_ISAKMP_GEN_LENGTH(proposals[i], proposal_lens[i] +
transform_lens[i]);
proposals[i] = 0;
saved_nextp_prop = msg->nextp;
if (message_add_payload(msg, ISAKMP_PAYLOAD_TRANSFORM,
transforms[i], transform_lens[i], 0))
goto cleanup;
msg->nextp = saved_nextp_prop;
transforms[i] = 0;
}
msg->nextp = saved_nextp_sa;
free(transforms);
free(transform_lens);
free(proposals);
free(proposal_lens);
}
return 0;
cleanup:
free(sa_buf);
for (i = 0; i < nprotos; i++) {
free(transforms[i]);
free(proposals[i]);
}
free(transforms);
free(transform_lens);
free(proposals);
free(proposal_lens);
return -1;
}
u_int8_t *
message_copy(struct message *msg, size_t offset, size_t *szp)
{
int skip = 0;
size_t i, sz = 0;
ssize_t start = -1;
u_int8_t *buf, *p;
for (i = 1; i < msg->iovlen; i++) {
sz += msg->iov[i].iov_len;
if (sz <= offset)
skip = i;
else if (start < 0)
start = offset - (sz - msg->iov[i].iov_len);
}
*szp = sz - offset;
buf = malloc(*szp);
if (!buf)
return 0;
p = buf;
for (i = skip + 1; i < msg->iovlen; i++) {
memcpy(p, (u_int8_t *) msg->iov[i].iov_base + start,
msg->iov[i].iov_len - start);
p += msg->iov[i].iov_len - start;
start = 0;
}
return buf;
}
int
message_register_post_send(struct message *msg,
void (*post_send)(struct message *))
{
struct post_send *node;
node = malloc(sizeof *node);
if (!node)
return -1;
node->func = post_send;
TAILQ_INSERT_TAIL(&msg->post_send, node, link);
return 0;
}
void
message_post_send(struct message *msg)
{
struct post_send *node;
while ((node = TAILQ_FIRST(&msg->post_send)) != 0) {
TAILQ_REMOVE(&msg->post_send, node, link);
node->func(msg);
free(node);
}
}
struct payload *
payload_first(struct message *msg, u_int8_t payload)
{
return TAILQ_FIRST(&msg->payload[payload]);
}