strongswan/src/libtnccs/plugins/tnccs_20/batch/pb_tnc_batch.c

627 lines
16 KiB
C

/*
* Copyright (C) 2010 Sansar Choinyanbuu
* Copyright (C) 2010-2015 Andreas Steffen
* 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.
*/
#include "pb_tnc_batch.h"
#include "messages/ietf/pb_error_msg.h"
#include "messages/ietf/pb_pa_msg.h"
#include "state_machine/pb_tnc_state_machine.h"
#include <tnc/tnccs/tnccs.h>
#include <collections/linked_list.h>
#include <bio/bio_writer.h>
#include <bio/bio_reader.h>
#include <pen/pen.h>
#include <utils/debug.h>
ENUM(pb_tnc_batch_type_names, PB_BATCH_CDATA, PB_BATCH_CLOSE,
"CDATA",
"SDATA",
"RESULT",
"CRETRY",
"SRETRY",
"CLOSE"
);
typedef struct private_pb_tnc_batch_t private_pb_tnc_batch_t;
/**
* PB-Batch Header (see section 4.1 of RFC 5793)
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Version |D| Reserved | B-Type|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Batch Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
#define PB_TNC_BATCH_FLAG_NONE 0x00
#define PB_TNC_BATCH_FLAG_D (1<<7)
/**
* PB-TNC Message (see section 4.2 of RFC 5793)
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Flags | PB-TNC Vendor ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | PB-TNC Message Type |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | PB-TNC Message Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | PB-TNC Message Value (Variable Length) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
#define PB_TNC_FLAG_NONE 0x00
#define PB_TNC_FLAG_NOSKIP (1<<7)
#define PB_TNC_RESERVED_MSG_TYPE 0xffffffff
/**
* Private data of a pb_tnc_batch_t object.
*
*/
struct private_pb_tnc_batch_t {
/**
* Public pb_pa_msg_t interface.
*/
pb_tnc_batch_t public;
/**
* from TNC server if TRUE, from TNC client if FALSE
*/
bool is_server;
/**
* PB-TNC Batch type
*/
pb_tnc_batch_type_t type;
/**
* Current PB-TNC Batch size
*/
size_t batch_len;
/**
* Maximum PB-TNC Batch size
*/
size_t max_batch_len;
/**
* linked list of PB-TNC messages
*/
linked_list_t *messages;
/**
* linked list of PB-TNC error messages
*/
linked_list_t *errors;
/**
* Encoded message
*/
chunk_t encoding;
/**
* Offset into encoding (used for error reporting)
*/
u_int32_t offset;
};
METHOD(pb_tnc_batch_t, get_type, pb_tnc_batch_type_t,
private_pb_tnc_batch_t *this)
{
return this->type;
}
METHOD(pb_tnc_batch_t, get_encoding, chunk_t,
private_pb_tnc_batch_t *this)
{
return this->encoding;
}
METHOD(pb_tnc_batch_t, add_msg, bool,
private_pb_tnc_batch_t *this, pb_tnc_msg_t* msg)
{
enum_name_t *msg_type_names;
chunk_t msg_value;
pen_type_t msg_type;
size_t msg_len;
msg->build(msg);
msg_value = msg->get_encoding(msg);
msg_len = PB_TNC_MSG_HEADER_SIZE + msg_value.len;
if (this->batch_len + msg_len > this->max_batch_len)
{
/* message just does not fit into this batch */
return FALSE;
}
this->batch_len += msg_len;
msg_type = msg->get_type(msg);
switch (msg_type.vendor_id)
{
default:
case PEN_IETF:
msg_type_names = pb_tnc_msg_type_names;
break;
case PEN_TCG:
msg_type_names = pb_tnc_tcg_msg_type_names;
break;
case PEN_ITA:
msg_type_names = pb_tnc_ita_msg_type_names;
break;
}
DBG2(DBG_TNC, "adding %N/%N message", pen_names, msg_type.vendor_id,
msg_type_names, msg_type.type);
this->messages->insert_last(this->messages, msg);
return TRUE;
}
METHOD(pb_tnc_batch_t, build, void,
private_pb_tnc_batch_t *this)
{
u_int8_t version;
u_int32_t msg_len;
chunk_t msg_value;
enumerator_t *enumerator;
pen_type_t msg_type;
pb_tnc_msg_t *msg;
pb_tnc_msg_info_t *msg_infos;
bio_writer_t *writer;
/* Set wrong PB-TNC version for testing purposes to force a PB-TNC error */
version = lib->settings->get_int(lib->settings,
"%s.plugins.tnccs-20.tests.pb_tnc_version",
PB_TNC_VERSION, lib->ns);
/* build PB-TNC batch header */
writer = bio_writer_create(this->batch_len);
writer->write_uint8 (writer, version);
writer->write_uint8 (writer, this->is_server ?
PB_TNC_BATCH_FLAG_D : PB_TNC_BATCH_FLAG_NONE);
writer->write_uint16(writer, this->type);
writer->write_uint32(writer, this->batch_len);
/* build PB-TNC messages */
enumerator = this->messages->create_enumerator(this->messages);
while (enumerator->enumerate(enumerator, &msg))
{
u_int8_t flags = PB_TNC_FLAG_NONE;
/* build PB-TNC message */
msg_value = msg->get_encoding(msg);
msg_len = PB_TNC_MSG_HEADER_SIZE + msg_value.len;
msg_type = msg->get_type(msg);
switch (msg_type.vendor_id)
{
default:
case PEN_IETF:
msg_infos = pb_tnc_msg_infos;
break;
case PEN_TCG:
msg_infos = pb_tnc_tcg_msg_infos;
break;
case PEN_ITA:
msg_infos = pb_tnc_ita_msg_infos;
break;
}
if (msg_infos[msg_type.type].has_noskip_flag)
{
flags |= PB_TNC_FLAG_NOSKIP;
}
writer->write_uint8 (writer, flags);
writer->write_uint24(writer, msg_type.vendor_id);
writer->write_uint32(writer, msg_type.type);
writer->write_uint32(writer, msg_len);
writer->write_data (writer, msg_value);
}
enumerator->destroy(enumerator);
this->encoding = writer->extract_buf(writer);
writer->destroy(writer);
}
METHOD(pb_tnc_batch_t, process_header, status_t,
private_pb_tnc_batch_t *this, bool directionality, bool is_server,
bool *from_server)
{
bio_reader_t *reader;
pb_tnc_msg_t *msg;
pb_error_msg_t *err_msg;
u_int8_t version, flags, reserved, type;
u_int32_t batch_len;
if (this->encoding.len < PB_TNC_BATCH_HEADER_SIZE)
{
DBG1(DBG_TNC, "%u bytes insufficient to parse PB-TNC batch header",
this->encoding.len);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, 0);
goto fatal;
}
reader = bio_reader_create(this->encoding);
reader->read_uint8 (reader, &version);
reader->read_uint8 (reader, &flags);
reader->read_uint8 (reader, &reserved);
reader->read_uint8 (reader, &type);
reader->read_uint32(reader, &batch_len);
reader->destroy(reader);
/* Version */
if (version != PB_TNC_VERSION)
{
DBG1(DBG_TNC, "unsupported TNCCS batch version 0x%02x", version);
msg = pb_error_msg_create(TRUE, PEN_IETF,
PB_ERROR_VERSION_NOT_SUPPORTED);
err_msg = (pb_error_msg_t*)msg;
err_msg->set_bad_version(err_msg, version);
goto fatal;
}
/* Directionality */
*from_server = (flags & PB_TNC_BATCH_FLAG_D) != PB_TNC_BATCH_FLAG_NONE;
if (directionality & (*from_server == is_server))
{
DBG1(DBG_TNC, "wrong Directionality: batch is from a PB %s",
is_server ? "server" : "client");
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, 1);
goto fatal;
}
/* Batch Type */
this->type = type & 0x0F;
if (this->type > PB_BATCH_ROOF)
{
DBG1(DBG_TNC, "unknown PB-TNC batch type: %d", this->type);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, 3);
goto fatal;
}
/* Batch Length */
if (this->encoding.len != batch_len)
{
DBG1(DBG_TNC, "%u bytes of data is not equal to batch length of %u bytes",
this->encoding.len, batch_len);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, 4);
goto fatal;
}
this->offset = PB_TNC_BATCH_HEADER_SIZE;
return SUCCESS;
fatal:
this->errors->insert_last(this->errors, msg);
return FAILED;
}
static status_t process_tnc_msg(private_pb_tnc_batch_t *this)
{
bio_reader_t *reader;
pb_tnc_msg_t *pb_tnc_msg, *msg;
pb_tnc_msg_info_t *msg_infos;
u_int8_t flags;
u_int32_t vendor_id, msg_type, msg_len, offset;
chunk_t data, msg_value;
bool noskip_flag;
enum_name_t *msg_type_names;
pen_type_t msg_pen_type;
status_t status;
data = chunk_skip(this->encoding, this->offset);
if (data.len < PB_TNC_MSG_HEADER_SIZE)
{
DBG1(DBG_TNC, "%u bytes insufficient to parse PB-TNC message header",
data.len);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, this->offset);
goto fatal;
}
reader = bio_reader_create(data);
reader->read_uint8 (reader, &flags);
reader->read_uint24(reader, &vendor_id);
reader->read_uint32(reader, &msg_type);
reader->read_uint32(reader, &msg_len);
reader->destroy(reader);
noskip_flag = (flags & PB_TNC_FLAG_NOSKIP) != PB_TNC_FLAG_NONE;
if (msg_len > data.len)
{
DBG1(DBG_TNC, "%u bytes insufficient to parse PB-TNC message", data.len);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, this->offset + 8);
goto fatal;
}
if (vendor_id == PEN_RESERVED)
{
DBG1(DBG_TNC, "Vendor ID 0x%06x is reserved", PEN_RESERVED);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, this->offset + 1);
goto fatal;
}
if (msg_type == PB_TNC_RESERVED_MSG_TYPE)
{
DBG1(DBG_TNC, "PB-TNC message Type 0x%08x is reserved",
PB_TNC_RESERVED_MSG_TYPE);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, this->offset + 4);
goto fatal;
}
if (vendor_id == PEN_IETF && msg_type <= PB_MSG_ROOF)
{
if (msg_type == PB_MSG_EXPERIMENTAL && noskip_flag)
{
DBG1(DBG_TNC, "reject IETF/PB-Experimental message with "
"NOSKIP flag set");
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_UNSUPPORTED_MANDATORY_MSG, this->offset);
goto fatal;
}
msg_type_names = pb_tnc_msg_type_names;
msg_infos = pb_tnc_msg_infos;
}
else if (vendor_id == PEN_TCG && msg_type <= PB_TCG_MSG_ROOF &&
msg_type > PB_TCG_MSG_RESERVED)
{
msg_type_names = pb_tnc_tcg_msg_type_names;
msg_infos = pb_tnc_tcg_msg_infos;
}
else if (vendor_id == PEN_ITA && msg_type <= PB_ITA_MSG_ROOF &&
msg_type > PB_ITA_MSG_NOSKIP_TEST)
{
msg_type_names = pb_tnc_ita_msg_type_names;
msg_infos = pb_tnc_ita_msg_infos;
}
else
{
if (msg_len < PB_TNC_MSG_HEADER_SIZE)
{
DBG1(DBG_TNC, "%u bytes too small for PB-TNC message length",
msg_len);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, this->offset + 8);
goto fatal;
}
if (noskip_flag)
{
DBG1(DBG_TNC, "reject PB-TNC message (0x%06x/0x%08x)",
vendor_id, msg_type);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_UNSUPPORTED_MANDATORY_MSG, this->offset);
goto fatal;
}
else
{
DBG1(DBG_TNC, "ignore PB-TNC message (0x%06x/0x%08x)",
vendor_id, msg_type);
this->offset += msg_len;
return SUCCESS;
}
}
if (msg_infos[msg_type].has_noskip_flag != TRUE_OR_FALSE &&
msg_infos[msg_type].has_noskip_flag != noskip_flag)
{
DBG1(DBG_TNC, "%N/%N message must%s have NOSKIP flag set",
pen_names, vendor_id, msg_type_names, msg_type,
msg_infos[msg_type].has_noskip_flag ? "" : " not");
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, this->offset);
goto fatal;
}
if (msg_len < msg_infos[msg_type].min_size ||
(msg_infos[msg_type].exact_size &&
msg_len != msg_infos[msg_type].min_size))
{
DBG1(DBG_TNC, "%N/%N message length must be %s %u bytes but is %u bytes",
pen_names, vendor_id, msg_type_names, msg_type,
msg_infos[msg_type].exact_size ? "exactly" : "at least",
msg_infos[msg_type].min_size, msg_len);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, this->offset);
goto fatal;
}
if (msg_infos[msg_type].in_result_batch && this->type != PB_BATCH_RESULT)
{
if (this->is_server)
{
DBG1(DBG_TNC,"reject %N/%N message received from a PB-TNC client",
pen_names, vendor_id, msg_type_names, msg_type);
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, this->offset);
goto fatal;
}
else
{
DBG1(DBG_TNC,"ignore %N/%N message not received within RESULT batch",
pen_names, vendor_id, msg_type_names, msg_type);
this->offset += msg_len;
return SUCCESS;
}
}
DBG2(DBG_TNC, "processing %N/%N message (%u bytes)", pen_names, vendor_id,
msg_type_names, msg_type, msg_len);
data.len = msg_len;
msg_value = chunk_skip(data, PB_TNC_MSG_HEADER_SIZE);
msg_pen_type = pen_type_create(vendor_id, msg_type);
pb_tnc_msg = pb_tnc_msg_create_from_data(msg_pen_type, msg_value);
status = pb_tnc_msg->process(pb_tnc_msg, &offset);
if (status == FAILED || status == VERIFY_ERROR)
{
msg = pb_error_msg_create_with_offset(TRUE, PEN_IETF,
PB_ERROR_INVALID_PARAMETER, this->offset + offset);
this->errors->insert_last(this->errors, msg);
}
if (status == FAILED)
{
pb_tnc_msg->destroy(pb_tnc_msg);
return FAILED;
}
this->messages->insert_last(this->messages, pb_tnc_msg);
this->offset += msg_len;
return status;
fatal:
this->errors->insert_last(this->errors, msg);
return FAILED;
}
METHOD(pb_tnc_batch_t, process, status_t,
private_pb_tnc_batch_t *this, pb_tnc_state_machine_t *state_machine)
{
pb_tnc_msg_t *msg;
status_t status = SUCCESS;
if (!state_machine->receive_batch(state_machine, this->type))
{
DBG1(DBG_TNC, "unexpected PB-TNC batch type: %N",
pb_tnc_batch_type_names, this->type);
msg = pb_error_msg_create(TRUE, PEN_IETF,
PB_ERROR_UNEXPECTED_BATCH_TYPE);
this->errors->insert_last(this->errors, msg);
return FAILED;
}
/* Register an empty CDATA batch with the state machine */
if (this->type == PB_BATCH_CDATA)
{
state_machine->set_empty_cdata(state_machine,
this->offset == this->encoding.len);
}
while (this->offset < this->encoding.len)
{
switch (process_tnc_msg(this))
{
case FAILED:
return FAILED;
case VERIFY_ERROR:
status = VERIFY_ERROR;
break;
case SUCCESS:
default:
break;
}
}
return status;
}
METHOD(pb_tnc_batch_t, create_msg_enumerator, enumerator_t*,
private_pb_tnc_batch_t *this)
{
return this->messages->create_enumerator(this->messages);
}
METHOD(pb_tnc_batch_t, create_error_enumerator, enumerator_t*,
private_pb_tnc_batch_t *this)
{
return this->errors->create_enumerator(this->errors);
}
METHOD(pb_tnc_batch_t, destroy, void,
private_pb_tnc_batch_t *this)
{
this->messages->destroy_offset(this->messages,
offsetof(pb_tnc_msg_t, destroy));
this->errors->destroy_offset(this->errors,
offsetof(pb_tnc_msg_t, destroy));
free(this->encoding.ptr);
free(this);
}
/**
* See header
*/
pb_tnc_batch_t* pb_tnc_batch_create(bool is_server, pb_tnc_batch_type_t type,
size_t max_batch_len)
{
private_pb_tnc_batch_t *this;
INIT(this,
.public = {
.get_type = _get_type,
.get_encoding = _get_encoding,
.add_msg = _add_msg,
.build = _build,
.process = _process,
.create_msg_enumerator = _create_msg_enumerator,
.create_error_enumerator = _create_error_enumerator,
.destroy = _destroy,
},
.is_server = is_server,
.type = type,
.max_batch_len = max_batch_len,
.batch_len = PB_TNC_BATCH_HEADER_SIZE,
.messages = linked_list_create(),
.errors = linked_list_create(),
);
DBG2(DBG_TNC, "creating PB-TNC %N batch", pb_tnc_batch_type_names, type);
return &this->public;
}
/**
* See header
*/
pb_tnc_batch_t* pb_tnc_batch_create_from_data(chunk_t data)
{
private_pb_tnc_batch_t *this;
INIT(this,
.public = {
.get_type = _get_type,
.get_encoding = _get_encoding,
.add_msg = _add_msg,
.build = _build,
.process_header = _process_header,
.process = _process,
.create_msg_enumerator = _create_msg_enumerator,
.create_error_enumerator = _create_error_enumerator,
.destroy = _destroy,
},
.messages = linked_list_create(),
.errors = linked_list_create(),
.encoding = chunk_clone(data),
);
return &this->public;
}