hostap/src/eap_server/eap_server_tnc.c
Jouni Malinen 94d9bfd59b Rename EAP server source files to avoid duplicate names
This makes it easier to build both EAP peer and server functionality
into the same project with some toolchains.
2010-02-19 18:54:07 +02:00

548 lines
13 KiB
C

/*
* EAP server method: EAP-TNC (Trusted Network Connect)
* Copyright (c) 2007-2010, Jouni Malinen <j@w1.fi>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Alternatively, this software may be distributed under the terms of BSD
* license.
*
* See README and COPYING for more details.
*/
#include "includes.h"
#include "common.h"
#include "base64.h"
#include "eap_i.h"
#include "tncs.h"
struct eap_tnc_data {
enum { START, CONTINUE, RECOMMENDATION, FRAG_ACK, WAIT_FRAG_ACK, DONE,
FAIL } state;
enum { ALLOW, ISOLATE, NO_ACCESS, NO_RECOMMENDATION } recommendation;
struct tncs_data *tncs;
struct wpabuf *in_buf;
struct wpabuf *out_buf;
size_t out_used;
size_t fragment_size;
int was_done:1;
int was_fail:1;
};
/* EAP-TNC Flags */
#define EAP_TNC_FLAGS_LENGTH_INCLUDED 0x80
#define EAP_TNC_FLAGS_MORE_FRAGMENTS 0x40
#define EAP_TNC_FLAGS_START 0x20
#define EAP_TNC_VERSION_MASK 0x07
#define EAP_TNC_VERSION 1
static void * eap_tnc_init(struct eap_sm *sm)
{
struct eap_tnc_data *data;
data = os_zalloc(sizeof(*data));
if (data == NULL)
return NULL;
data->state = START;
data->tncs = tncs_init();
if (data->tncs == NULL) {
os_free(data);
return NULL;
}
data->fragment_size = 1300;
return data;
}
static void eap_tnc_reset(struct eap_sm *sm, void *priv)
{
struct eap_tnc_data *data = priv;
wpabuf_free(data->in_buf);
wpabuf_free(data->out_buf);
tncs_deinit(data->tncs);
os_free(data);
}
static struct wpabuf * eap_tnc_build_start(struct eap_sm *sm,
struct eap_tnc_data *data, u8 id)
{
struct wpabuf *req;
req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, 1, EAP_CODE_REQUEST,
id);
if (req == NULL) {
wpa_printf(MSG_ERROR, "EAP-TNC: Failed to allocate memory for "
"request");
data->state = FAIL;
return NULL;
}
wpabuf_put_u8(req, EAP_TNC_FLAGS_START | EAP_TNC_VERSION);
data->state = CONTINUE;
return req;
}
static struct wpabuf * eap_tnc_build(struct eap_sm *sm,
struct eap_tnc_data *data)
{
struct wpabuf *req;
u8 *rpos, *rpos1;
size_t rlen;
char *start_buf, *end_buf;
size_t start_len, end_len;
size_t imv_len;
imv_len = tncs_total_send_len(data->tncs);
start_buf = tncs_if_tnccs_start(data->tncs);
if (start_buf == NULL)
return NULL;
start_len = os_strlen(start_buf);
end_buf = tncs_if_tnccs_end();
if (end_buf == NULL) {
os_free(start_buf);
return NULL;
}
end_len = os_strlen(end_buf);
rlen = start_len + imv_len + end_len;
req = wpabuf_alloc(rlen);
if (req == NULL) {
os_free(start_buf);
os_free(end_buf);
return NULL;
}
wpabuf_put_data(req, start_buf, start_len);
os_free(start_buf);
rpos1 = wpabuf_put(req, 0);
rpos = tncs_copy_send_buf(data->tncs, rpos1);
wpabuf_put(req, rpos - rpos1);
wpabuf_put_data(req, end_buf, end_len);
os_free(end_buf);
wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-TNC: Request",
wpabuf_head(req), wpabuf_len(req));
return req;
}
static struct wpabuf * eap_tnc_build_recommendation(struct eap_sm *sm,
struct eap_tnc_data *data)
{
switch (data->recommendation) {
case ALLOW:
data->state = DONE;
break;
case ISOLATE:
data->state = FAIL;
/* TODO: support assignment to a different VLAN */
break;
case NO_ACCESS:
data->state = FAIL;
break;
case NO_RECOMMENDATION:
data->state = DONE;
break;
default:
wpa_printf(MSG_DEBUG, "EAP-TNC: Unknown recommendation");
return NULL;
}
return eap_tnc_build(sm, data);
}
static struct wpabuf * eap_tnc_build_frag_ack(u8 id, u8 code)
{
struct wpabuf *msg;
msg = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, 1, code, id);
if (msg == NULL) {
wpa_printf(MSG_ERROR, "EAP-TNC: Failed to allocate memory "
"for fragment ack");
return NULL;
}
wpabuf_put_u8(msg, EAP_TNC_VERSION); /* Flags */
wpa_printf(MSG_DEBUG, "EAP-TNC: Send fragment ack");
return msg;
}
static struct wpabuf * eap_tnc_build_msg(struct eap_tnc_data *data, u8 id)
{
struct wpabuf *req;
u8 flags;
size_t send_len, plen;
wpa_printf(MSG_DEBUG, "EAP-TNC: Generating Request");
flags = EAP_TNC_VERSION;
send_len = wpabuf_len(data->out_buf) - data->out_used;
if (1 + send_len > data->fragment_size) {
send_len = data->fragment_size - 1;
flags |= EAP_TNC_FLAGS_MORE_FRAGMENTS;
if (data->out_used == 0) {
flags |= EAP_TNC_FLAGS_LENGTH_INCLUDED;
send_len -= 4;
}
}
plen = 1 + send_len;
if (flags & EAP_TNC_FLAGS_LENGTH_INCLUDED)
plen += 4;
req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, plen,
EAP_CODE_REQUEST, id);
if (req == NULL)
return NULL;
wpabuf_put_u8(req, flags); /* Flags */
if (flags & EAP_TNC_FLAGS_LENGTH_INCLUDED)
wpabuf_put_be32(req, wpabuf_len(data->out_buf));
wpabuf_put_data(req, wpabuf_head_u8(data->out_buf) + data->out_used,
send_len);
data->out_used += send_len;
if (data->out_used == wpabuf_len(data->out_buf)) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Sending out %lu bytes "
"(message sent completely)",
(unsigned long) send_len);
wpabuf_free(data->out_buf);
data->out_buf = NULL;
data->out_used = 0;
} else {
wpa_printf(MSG_DEBUG, "EAP-TNC: Sending out %lu bytes "
"(%lu more to send)", (unsigned long) send_len,
(unsigned long) wpabuf_len(data->out_buf) -
data->out_used);
if (data->state == FAIL)
data->was_fail = 1;
else if (data->state == DONE)
data->was_done = 1;
data->state = WAIT_FRAG_ACK;
}
return req;
}
static struct wpabuf * eap_tnc_buildReq(struct eap_sm *sm, void *priv, u8 id)
{
struct eap_tnc_data *data = priv;
switch (data->state) {
case START:
tncs_init_connection(data->tncs);
return eap_tnc_build_start(sm, data, id);
case CONTINUE:
if (data->out_buf == NULL) {
data->out_buf = eap_tnc_build(sm, data);
if (data->out_buf == NULL) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Failed to "
"generate message");
return NULL;
}
data->out_used = 0;
}
return eap_tnc_build_msg(data, id);
case RECOMMENDATION:
if (data->out_buf == NULL) {
data->out_buf = eap_tnc_build_recommendation(sm, data);
if (data->out_buf == NULL) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Failed to "
"generate recommendation message");
return NULL;
}
data->out_used = 0;
}
return eap_tnc_build_msg(data, id);
case WAIT_FRAG_ACK:
return eap_tnc_build_msg(data, id);
case FRAG_ACK:
return eap_tnc_build_frag_ack(id, EAP_CODE_REQUEST);
case DONE:
case FAIL:
return NULL;
}
return NULL;
}
static Boolean eap_tnc_check(struct eap_sm *sm, void *priv,
struct wpabuf *respData)
{
struct eap_tnc_data *data = priv;
const u8 *pos;
size_t len;
pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TNC, respData,
&len);
if (pos == NULL) {
wpa_printf(MSG_INFO, "EAP-TNC: Invalid frame");
return TRUE;
}
if (len == 0 && data->state != WAIT_FRAG_ACK) {
wpa_printf(MSG_INFO, "EAP-TNC: Invalid frame (empty)");
return TRUE;
}
if (len == 0)
return FALSE; /* Fragment ACK does not include flags */
if ((*pos & EAP_TNC_VERSION_MASK) != EAP_TNC_VERSION) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Unsupported version %d",
*pos & EAP_TNC_VERSION_MASK);
return TRUE;
}
if (*pos & EAP_TNC_FLAGS_START) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Peer used Start flag");
return TRUE;
}
return FALSE;
}
static void tncs_process(struct eap_tnc_data *data, struct wpabuf *inbuf)
{
enum tncs_process_res res;
res = tncs_process_if_tnccs(data->tncs, wpabuf_head(inbuf),
wpabuf_len(inbuf));
switch (res) {
case TNCCS_RECOMMENDATION_ALLOW:
wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS allowed access");
data->state = RECOMMENDATION;
data->recommendation = ALLOW;
break;
case TNCCS_RECOMMENDATION_NO_RECOMMENDATION:
wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS has no recommendation");
data->state = RECOMMENDATION;
data->recommendation = NO_RECOMMENDATION;
break;
case TNCCS_RECOMMENDATION_ISOLATE:
wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS requested isolation");
data->state = RECOMMENDATION;
data->recommendation = ISOLATE;
break;
case TNCCS_RECOMMENDATION_NO_ACCESS:
wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS rejected access");
data->state = RECOMMENDATION;
data->recommendation = NO_ACCESS;
break;
case TNCCS_PROCESS_ERROR:
wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS processing error");
data->state = FAIL;
break;
default:
break;
}
}
static int eap_tnc_process_cont(struct eap_tnc_data *data,
const u8 *buf, size_t len)
{
/* Process continuation of a pending message */
if (len > wpabuf_tailroom(data->in_buf)) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Fragment overflow");
data->state = FAIL;
return -1;
}
wpabuf_put_data(data->in_buf, buf, len);
wpa_printf(MSG_DEBUG, "EAP-TNC: Received %lu bytes, waiting for %lu "
"bytes more", (unsigned long) len,
(unsigned long) wpabuf_tailroom(data->in_buf));
return 0;
}
static int eap_tnc_process_fragment(struct eap_tnc_data *data,
u8 flags, u32 message_length,
const u8 *buf, size_t len)
{
/* Process a fragment that is not the last one of the message */
if (data->in_buf == NULL && !(flags & EAP_TNC_FLAGS_LENGTH_INCLUDED)) {
wpa_printf(MSG_DEBUG, "EAP-TNC: No Message Length field in a "
"fragmented packet");
return -1;
}
if (data->in_buf == NULL) {
/* First fragment of the message */
data->in_buf = wpabuf_alloc(message_length);
if (data->in_buf == NULL) {
wpa_printf(MSG_DEBUG, "EAP-TNC: No memory for "
"message");
return -1;
}
wpabuf_put_data(data->in_buf, buf, len);
wpa_printf(MSG_DEBUG, "EAP-TNC: Received %lu bytes in first "
"fragment, waiting for %lu bytes more",
(unsigned long) len,
(unsigned long) wpabuf_tailroom(data->in_buf));
}
return 0;
}
static void eap_tnc_process(struct eap_sm *sm, void *priv,
struct wpabuf *respData)
{
struct eap_tnc_data *data = priv;
const u8 *pos, *end;
size_t len;
u8 flags;
u32 message_length = 0;
struct wpabuf tmpbuf;
pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TNC, respData, &len);
if (pos == NULL)
return; /* Should not happen; message already verified */
end = pos + len;
if (len == 1 && (data->state == DONE || data->state == FAIL)) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Peer acknowledged the last "
"message");
return;
}
if (len == 0) {
/* fragment ack */
flags = 0;
} else
flags = *pos++;
if (flags & EAP_TNC_FLAGS_LENGTH_INCLUDED) {
if (end - pos < 4) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Message underflow");
data->state = FAIL;
return;
}
message_length = WPA_GET_BE32(pos);
pos += 4;
if (message_length < (u32) (end - pos)) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Invalid Message "
"Length (%d; %ld remaining in this msg)",
message_length, (long) (end - pos));
data->state = FAIL;
return;
}
}
wpa_printf(MSG_DEBUG, "EAP-TNC: Received packet: Flags 0x%x "
"Message Length %u", flags, message_length);
if (data->state == WAIT_FRAG_ACK) {
if (len > 1) {
wpa_printf(MSG_DEBUG, "EAP-TNC: Unexpected payload "
"in WAIT_FRAG_ACK state");
data->state = FAIL;
return;
}
wpa_printf(MSG_DEBUG, "EAP-TNC: Fragment acknowledged");
if (data->was_fail)
data->state = FAIL;
else if (data->was_done)
data->state = DONE;
else
data->state = CONTINUE;
return;
}
if (data->in_buf && eap_tnc_process_cont(data, pos, end - pos) < 0) {
data->state = FAIL;
return;
}
if (flags & EAP_TNC_FLAGS_MORE_FRAGMENTS) {
if (eap_tnc_process_fragment(data, flags, message_length,
pos, end - pos) < 0)
data->state = FAIL;
else
data->state = FRAG_ACK;
return;
} else if (data->state == FRAG_ACK) {
wpa_printf(MSG_DEBUG, "EAP-TNC: All fragments received");
data->state = CONTINUE;
}
if (data->in_buf == NULL) {
/* Wrap unfragmented messages as wpabuf without extra copy */
wpabuf_set(&tmpbuf, pos, end - pos);
data->in_buf = &tmpbuf;
}
wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-TNC: Received payload",
wpabuf_head(data->in_buf), wpabuf_len(data->in_buf));
tncs_process(data, data->in_buf);
if (data->in_buf != &tmpbuf)
wpabuf_free(data->in_buf);
data->in_buf = NULL;
}
static Boolean eap_tnc_isDone(struct eap_sm *sm, void *priv)
{
struct eap_tnc_data *data = priv;
return data->state == DONE || data->state == FAIL;
}
static Boolean eap_tnc_isSuccess(struct eap_sm *sm, void *priv)
{
struct eap_tnc_data *data = priv;
return data->state == DONE;
}
int eap_server_tnc_register(void)
{
struct eap_method *eap;
int ret;
eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION,
EAP_VENDOR_IETF, EAP_TYPE_TNC, "TNC");
if (eap == NULL)
return -1;
eap->init = eap_tnc_init;
eap->reset = eap_tnc_reset;
eap->buildReq = eap_tnc_buildReq;
eap->check = eap_tnc_check;
eap->process = eap_tnc_process;
eap->isDone = eap_tnc_isDone;
eap->isSuccess = eap_tnc_isSuccess;
ret = eap_server_method_register(eap);
if (ret)
eap_server_method_free(eap);
return ret;
}