strongswan/src/libcharon/plugins/ha/ha_message.c

691 lines
15 KiB
C

/*
* Copyright (C) 2008 Martin Willi
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#define _GNU_SOURCE
#include <string.h>
#include <arpa/inet.h>
#include "ha_message.h"
#include <daemon.h>
#define ALLOCATION_BLOCK 64
typedef struct private_ha_message_t private_ha_message_t;
/**
* Private data of an ha_message_t object.
*/
struct private_ha_message_t {
/**
* Public ha_message_t interface.
*/
ha_message_t public;
/**
* Allocated size of buf
*/
size_t allocated;
/**
* Buffer containing encoded data
*/
chunk_t buf;
};
ENUM(ha_message_type_names, HA_IKE_ADD, HA_IKE_IV,
"IKE_ADD",
"IKE_UPDATE",
"IKE_MID_INITIATOR",
"IKE_MID_RESPONDER",
"IKE_DELETE",
"CHILD_ADD",
"CHILD_DELETE",
"SEGMENT_DROP",
"SEGMENT_TAKE",
"STATUS",
"RESYNC",
"IKE_IV",
);
typedef struct ike_sa_id_encoding_t ike_sa_id_encoding_t;
/**
* Encoding if an ike_sa_id_t
*/
struct ike_sa_id_encoding_t {
uint8_t ike_version;
uint64_t initiator_spi;
uint64_t responder_spi;
uint8_t initiator;
} __attribute__((packed));
typedef struct identification_encoding_t identification_encoding_t;
/**
* Encoding of a identification_t
*/
struct identification_encoding_t {
uint8_t type;
uint8_t len;
char encoding[];
} __attribute__((packed));
typedef struct host_encoding_t host_encoding_t;
/**
* encoding of a host_t
*/
struct host_encoding_t {
uint16_t port;
uint8_t family;
char encoding[];
} __attribute__((packed));
typedef struct ts_encoding_t ts_encoding_t;
/**
* encoding of a traffic_selector_t
*/
struct ts_encoding_t {
uint8_t type;
uint8_t protocol;
uint16_t from_port;
uint16_t to_port;
uint8_t dynamic;
char encoding[];
} __attribute__((packed));
METHOD(ha_message_t, get_type, ha_message_type_t,
private_ha_message_t *this)
{
return this->buf.ptr[1];
}
/**
* check for space in buffer, increase if necessary
*/
static void check_buf(private_ha_message_t *this, size_t len)
{
int increased = 0;
while (this->buf.len + len > this->allocated)
{ /* double size */
this->allocated += ALLOCATION_BLOCK;
increased++;
}
if (increased)
{
this->buf.ptr = realloc(this->buf.ptr, this->allocated);
}
}
METHOD(ha_message_t, add_attribute, void,
private_ha_message_t *this, ha_message_attribute_t attribute, ...)
{
size_t len;
va_list args;
check_buf(this, sizeof(uint8_t));
this->buf.ptr[this->buf.len] = attribute;
this->buf.len += sizeof(uint8_t);
va_start(args, attribute);
switch (attribute)
{
/* ike_sa_id_t* */
case HA_IKE_ID:
case HA_IKE_REKEY_ID:
{
ike_sa_id_encoding_t *enc;
ike_sa_id_t *id;
id = va_arg(args, ike_sa_id_t*);
check_buf(this, sizeof(ike_sa_id_encoding_t));
enc = (ike_sa_id_encoding_t*)(this->buf.ptr + this->buf.len);
this->buf.len += sizeof(ike_sa_id_encoding_t);
enc->initiator = id->is_initiator(id);
enc->ike_version = id->get_ike_version(id);
enc->initiator_spi = id->get_initiator_spi(id);
enc->responder_spi = id->get_responder_spi(id);
break;
}
/* identification_t* */
case HA_LOCAL_ID:
case HA_REMOTE_ID:
case HA_REMOTE_EAP_ID:
{
identification_encoding_t *enc;
identification_t *id;
chunk_t data;
id = va_arg(args, identification_t*);
data = id->get_encoding(id);
check_buf(this, sizeof(identification_encoding_t) + data.len);
enc = (identification_encoding_t*)(this->buf.ptr + this->buf.len);
this->buf.len += sizeof(identification_encoding_t) + data.len;
enc->type = id->get_type(id);
enc->len = data.len;
memcpy(enc->encoding, data.ptr, data.len);
break;
}
/* host_t* */
case HA_LOCAL_ADDR:
case HA_REMOTE_ADDR:
case HA_LOCAL_VIP:
case HA_REMOTE_VIP:
case HA_PEER_ADDR:
{
host_encoding_t *enc;
host_t *host;
chunk_t data;
host = va_arg(args, host_t*);
data = host->get_address(host);
check_buf(this, sizeof(host_encoding_t) + data.len);
enc = (host_encoding_t*)(this->buf.ptr + this->buf.len);
this->buf.len += sizeof(host_encoding_t) + data.len;
enc->family = host->get_family(host);
enc->port = htons(host->get_port(host));
memcpy(enc->encoding, data.ptr, data.len);
break;
}
/* char* */
case HA_CONFIG_NAME:
{
char *str;
str = va_arg(args, char*);
len = strlen(str) + 1;
check_buf(this, len);
memcpy(this->buf.ptr + this->buf.len, str, len);
this->buf.len += len;
break;
}
/* uint8_t */
case HA_IKE_VERSION:
case HA_INITIATOR:
case HA_IPSEC_MODE:
case HA_IPCOMP:
{
uint8_t val;
val = va_arg(args, u_int);
check_buf(this, sizeof(val));
this->buf.ptr[this->buf.len] = val;
this->buf.len += sizeof(val);
break;
}
/* uint16_t */
case HA_ALG_DH:
case HA_ALG_PRF:
case HA_ALG_OLD_PRF:
case HA_ALG_ENCR:
case HA_ALG_ENCR_LEN:
case HA_ALG_INTEG:
case HA_INBOUND_CPI:
case HA_OUTBOUND_CPI:
case HA_SEGMENT:
case HA_ESN:
case HA_AUTH_METHOD:
{
uint16_t val;
val = va_arg(args, u_int);
check_buf(this, sizeof(val));
*(uint16_t*)(this->buf.ptr + this->buf.len) = htons(val);
this->buf.len += sizeof(val);
break;
}
/** uint32_t */
case HA_CONDITIONS:
case HA_EXTENSIONS:
case HA_INBOUND_SPI:
case HA_OUTBOUND_SPI:
case HA_MID:
{
uint32_t val;
val = va_arg(args, u_int);
check_buf(this, sizeof(val));
*(uint32_t*)(this->buf.ptr + this->buf.len) = htonl(val);
this->buf.len += sizeof(val);
break;
}
/** chunk_t */
case HA_NONCE_I:
case HA_NONCE_R:
case HA_SECRET:
case HA_LOCAL_DH:
case HA_REMOTE_DH:
case HA_PSK:
case HA_IV:
case HA_OLD_SKD:
{
chunk_t chunk;
chunk = va_arg(args, chunk_t);
check_buf(this, chunk.len + sizeof(uint16_t));
*(uint16_t*)(this->buf.ptr + this->buf.len) = htons(chunk.len);
memcpy(this->buf.ptr + this->buf.len + sizeof(uint16_t),
chunk.ptr, chunk.len);
this->buf.len += chunk.len + sizeof(uint16_t);;
break;
}
/** traffic_selector_t */
case HA_LOCAL_TS:
case HA_REMOTE_TS:
{
ts_encoding_t *enc;
traffic_selector_t *ts;
chunk_t data;
ts = va_arg(args, traffic_selector_t*);
data = chunk_cata("cc", ts->get_from_address(ts),
ts->get_to_address(ts));
check_buf(this, sizeof(ts_encoding_t) + data.len);
enc = (ts_encoding_t*)(this->buf.ptr + this->buf.len);
this->buf.len += sizeof(ts_encoding_t) + data.len;
enc->type = ts->get_type(ts);
enc->protocol = ts->get_protocol(ts);
enc->from_port = htons(ts->get_from_port(ts));
enc->to_port = htons(ts->get_to_port(ts));
enc->dynamic = ts->is_dynamic(ts);
memcpy(enc->encoding, data.ptr, data.len);
break;
}
default:
{
DBG1(DBG_CFG, "unable to encode, attribute %d unknown", attribute);
this->buf.len -= sizeof(uint8_t);
break;
}
}
va_end(args);
}
/**
* Attribute enumerator implementation
*/
typedef struct {
/** implements enumerator_t */
enumerator_t public;
/** position in message */
chunk_t buf;
/** cleanup handler of current element, if any */
void (*cleanup)(void* data);
/** data to pass to cleanup handler */
void *cleanup_data;
} attribute_enumerator_t;
METHOD(enumerator_t, attribute_enumerate, bool,
attribute_enumerator_t *this, va_list args)
{
ha_message_attribute_t attr, *attr_out;
ha_message_value_t *value;
VA_ARGS_VGET(args, attr_out, value);
if (this->cleanup)
{
this->cleanup(this->cleanup_data);
this->cleanup = NULL;
}
if (this->buf.len < 1)
{
return FALSE;
}
attr = this->buf.ptr[0];
this->buf = chunk_skip(this->buf, 1);
switch (attr)
{
/* ike_sa_id_t* */
case HA_IKE_ID:
case HA_IKE_REKEY_ID:
{
ike_sa_id_encoding_t *enc;
if (this->buf.len < sizeof(ike_sa_id_encoding_t))
{
return FALSE;
}
enc = (ike_sa_id_encoding_t*)(this->buf.ptr);
value->ike_sa_id = ike_sa_id_create(enc->ike_version,
enc->initiator_spi, enc->responder_spi,
enc->initiator);
*attr_out = attr;
this->cleanup = (void*)value->ike_sa_id->destroy;
this->cleanup_data = value->ike_sa_id;
this->buf = chunk_skip(this->buf, sizeof(ike_sa_id_encoding_t));
return TRUE;
}
/* identification_t* */
case HA_LOCAL_ID:
case HA_REMOTE_ID:
case HA_REMOTE_EAP_ID:
{
identification_encoding_t *enc;
enc = (identification_encoding_t*)(this->buf.ptr);
if (this->buf.len < sizeof(identification_encoding_t) ||
this->buf.len < sizeof(identification_encoding_t) + enc->len)
{
return FALSE;
}
value->id = identification_create_from_encoding(enc->type,
chunk_create(enc->encoding, enc->len));
*attr_out = attr;
this->cleanup = (void*)value->id->destroy;
this->cleanup_data = value->id;
this->buf = chunk_skip(this->buf,
sizeof(identification_encoding_t) + enc->len);
return TRUE;
}
/* host_t* */
case HA_LOCAL_ADDR:
case HA_REMOTE_ADDR:
case HA_LOCAL_VIP:
case HA_REMOTE_VIP:
case HA_PEER_ADDR:
{
host_encoding_t *enc;
enc = (host_encoding_t*)(this->buf.ptr);
if (this->buf.len < sizeof(host_encoding_t))
{
return FALSE;
}
value->host = host_create_from_chunk(enc->family,
chunk_create(enc->encoding,
this->buf.len - sizeof(host_encoding_t)),
ntohs(enc->port));
if (!value->host)
{
return FALSE;
}
*attr_out = attr;
this->cleanup = (void*)value->host->destroy;
this->cleanup_data = value->host;
this->buf = chunk_skip(this->buf, sizeof(host_encoding_t) +
value->host->get_address(value->host).len);
return TRUE;
}
/* char* */
case HA_CONFIG_NAME:
{
size_t len;
len = strnlen(this->buf.ptr, this->buf.len);
if (len >= this->buf.len)
{
return FALSE;
}
value->str = this->buf.ptr;
*attr_out = attr;
this->buf = chunk_skip(this->buf, len + 1);
return TRUE;
}
/* uint8_t */
case HA_IKE_VERSION:
case HA_INITIATOR:
case HA_IPSEC_MODE:
case HA_IPCOMP:
{
if (this->buf.len < sizeof(uint8_t))
{
return FALSE;
}
value->u8 = *(uint8_t*)this->buf.ptr;
*attr_out = attr;
this->buf = chunk_skip(this->buf, sizeof(uint8_t));
return TRUE;
}
/** uint16_t */
case HA_ALG_DH:
case HA_ALG_PRF:
case HA_ALG_OLD_PRF:
case HA_ALG_ENCR:
case HA_ALG_ENCR_LEN:
case HA_ALG_INTEG:
case HA_INBOUND_CPI:
case HA_OUTBOUND_CPI:
case HA_SEGMENT:
case HA_ESN:
case HA_AUTH_METHOD:
{
if (this->buf.len < sizeof(uint16_t))
{
return FALSE;
}
value->u16 = ntohs(*(uint16_t*)this->buf.ptr);
*attr_out = attr;
this->buf = chunk_skip(this->buf, sizeof(uint16_t));
return TRUE;
}
/** uint32_t */
case HA_CONDITIONS:
case HA_EXTENSIONS:
case HA_INBOUND_SPI:
case HA_OUTBOUND_SPI:
case HA_MID:
{
if (this->buf.len < sizeof(uint32_t))
{
return FALSE;
}
value->u32 = ntohl(*(uint32_t*)this->buf.ptr);
*attr_out = attr;
this->buf = chunk_skip(this->buf, sizeof(uint32_t));
return TRUE;
}
/** chunk_t */
case HA_NONCE_I:
case HA_NONCE_R:
case HA_SECRET:
case HA_LOCAL_DH:
case HA_REMOTE_DH:
case HA_PSK:
case HA_IV:
case HA_OLD_SKD:
{
size_t len;
if (this->buf.len < sizeof(uint16_t))
{
return FALSE;
}
len = ntohs(*(uint16_t*)this->buf.ptr);
this->buf = chunk_skip(this->buf, sizeof(uint16_t));
if (this->buf.len < len)
{
return FALSE;
}
value->chunk.len = len;
value->chunk.ptr = this->buf.ptr;
*attr_out = attr;
this->buf = chunk_skip(this->buf, len);
return TRUE;
}
case HA_LOCAL_TS:
case HA_REMOTE_TS:
{
ts_encoding_t *enc;
host_t *host;
int addr_len;
enc = (ts_encoding_t*)(this->buf.ptr);
if (this->buf.len < sizeof(ts_encoding_t))
{
return FALSE;
}
switch (enc->type)
{
case TS_IPV4_ADDR_RANGE:
addr_len = 4;
if (this->buf.len < sizeof(ts_encoding_t) + 2 * addr_len)
{
return FALSE;
}
break;
case TS_IPV6_ADDR_RANGE:
addr_len = 16;
if (this->buf.len < sizeof(ts_encoding_t) + 2 * addr_len)
{
return FALSE;
}
break;
default:
return FALSE;
}
if (enc->dynamic)
{
host = host_create_from_chunk(0,
chunk_create(enc->encoding, addr_len), 0);
if (!host)
{
return FALSE;
}
value->ts = traffic_selector_create_dynamic(enc->protocol,
ntohs(enc->from_port), ntohs(enc->to_port));
value->ts->set_address(value->ts, host);
host->destroy(host);
}
else
{
value->ts = traffic_selector_create_from_bytes(enc->protocol,
enc->type, chunk_create(enc->encoding, addr_len),
ntohs(enc->from_port),
chunk_create(enc->encoding + addr_len, addr_len),
ntohs(enc->to_port));
if (!value->ts)
{
return FALSE;
}
}
*attr_out = attr;
this->cleanup = (void*)value->ts->destroy;
this->cleanup_data = value->ts;
this->buf = chunk_skip(this->buf, sizeof(ts_encoding_t)
+ addr_len * 2);
return TRUE;
}
default:
{
return FALSE;
}
}
}
METHOD(enumerator_t, enum_destroy, void,
attribute_enumerator_t *this)
{
if (this->cleanup)
{
this->cleanup(this->cleanup_data);
}
free(this);
}
METHOD(ha_message_t, create_attribute_enumerator, enumerator_t*,
private_ha_message_t *this)
{
attribute_enumerator_t *e;
INIT(e,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _attribute_enumerate,
.destroy = _enum_destroy,
},
.buf = chunk_skip(this->buf, 2),
);
return &e->public;
}
METHOD(ha_message_t, get_encoding, chunk_t,
private_ha_message_t *this)
{
return this->buf;
}
METHOD(ha_message_t, destroy, void,
private_ha_message_t *this)
{
free(this->buf.ptr);
free(this);
}
static private_ha_message_t *ha_message_create_generic()
{
private_ha_message_t *this;
INIT(this,
.public = {
.get_type = _get_type,
.add_attribute = _add_attribute,
.create_attribute_enumerator = _create_attribute_enumerator,
.get_encoding = _get_encoding,
.destroy = _destroy,
},
);
return this;
}
/**
* See header
*/
ha_message_t *ha_message_create(ha_message_type_t type)
{
private_ha_message_t *this = ha_message_create_generic();
this->allocated = ALLOCATION_BLOCK;
this->buf.ptr = malloc(this->allocated);
this->buf.len = 2;
this->buf.ptr[0] = HA_MESSAGE_VERSION;
this->buf.ptr[1] = type;
return &this->public;
}
/**
* See header
*/
ha_message_t *ha_message_parse(chunk_t data)
{
private_ha_message_t *this;
if (data.len < 2)
{
DBG1(DBG_CFG, "HA message too short");
return NULL;
}
if (data.ptr[0] != HA_MESSAGE_VERSION)
{
DBG1(DBG_CFG, "HA message has version %d, expected %d",
data.ptr[0], HA_MESSAGE_VERSION);
return NULL;
}
this = ha_message_create_generic();
this->buf = chunk_clone(data);
this->allocated = this->buf.len;
return &this->public;
}