1189251Ssam/* 2189251Ssam * EAP peer method: EAP-TNC (Trusted Network Connect) 3189251Ssam * Copyright (c) 2007, Jouni Malinen <j@w1.fi> 4189251Ssam * 5252726Srpaulo * This software may be distributed under the terms of the BSD license. 6252726Srpaulo * See README for more details. 7189251Ssam */ 8189251Ssam 9189251Ssam#include "includes.h" 10189251Ssam 11189251Ssam#include "common.h" 12189251Ssam#include "eap_i.h" 13189251Ssam#include "tncc.h" 14189251Ssam 15189251Ssam 16189251Ssamstruct eap_tnc_data { 17189251Ssam enum { WAIT_START, PROC_MSG, WAIT_FRAG_ACK, DONE, FAIL } state; 18189251Ssam struct tncc_data *tncc; 19189251Ssam struct wpabuf *in_buf; 20189251Ssam struct wpabuf *out_buf; 21189251Ssam size_t out_used; 22189251Ssam size_t fragment_size; 23189251Ssam}; 24189251Ssam 25189251Ssam 26189251Ssam/* EAP-TNC Flags */ 27189251Ssam#define EAP_TNC_FLAGS_LENGTH_INCLUDED 0x80 28189251Ssam#define EAP_TNC_FLAGS_MORE_FRAGMENTS 0x40 29189251Ssam#define EAP_TNC_FLAGS_START 0x20 30189251Ssam#define EAP_TNC_VERSION_MASK 0x07 31189251Ssam 32189251Ssam#define EAP_TNC_VERSION 1 33189251Ssam 34189251Ssam 35189251Ssamstatic void * eap_tnc_init(struct eap_sm *sm) 36189251Ssam{ 37189251Ssam struct eap_tnc_data *data; 38189251Ssam 39189251Ssam data = os_zalloc(sizeof(*data)); 40189251Ssam if (data == NULL) 41189251Ssam return NULL; 42189251Ssam data->state = WAIT_START; 43189251Ssam data->fragment_size = 1300; 44189251Ssam data->tncc = tncc_init(); 45189251Ssam if (data->tncc == NULL) { 46189251Ssam os_free(data); 47189251Ssam return NULL; 48189251Ssam } 49189251Ssam 50189251Ssam return data; 51189251Ssam} 52189251Ssam 53189251Ssam 54189251Ssamstatic void eap_tnc_deinit(struct eap_sm *sm, void *priv) 55189251Ssam{ 56189251Ssam struct eap_tnc_data *data = priv; 57189251Ssam 58189251Ssam wpabuf_free(data->in_buf); 59189251Ssam wpabuf_free(data->out_buf); 60189251Ssam tncc_deinit(data->tncc); 61189251Ssam os_free(data); 62189251Ssam} 63189251Ssam 64189251Ssam 65189251Ssamstatic struct wpabuf * eap_tnc_build_frag_ack(u8 id, u8 code) 66189251Ssam{ 67189251Ssam struct wpabuf *msg; 68189251Ssam 69214734Srpaulo msg = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, 1, code, id); 70189251Ssam if (msg == NULL) { 71189251Ssam wpa_printf(MSG_ERROR, "EAP-TNC: Failed to allocate memory " 72189251Ssam "for fragment ack"); 73189251Ssam return NULL; 74189251Ssam } 75214734Srpaulo wpabuf_put_u8(msg, EAP_TNC_VERSION); /* Flags */ 76189251Ssam 77189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Send fragment ack"); 78189251Ssam 79189251Ssam return msg; 80189251Ssam} 81189251Ssam 82189251Ssam 83189251Ssamstatic struct wpabuf * eap_tnc_build_msg(struct eap_tnc_data *data, 84189251Ssam struct eap_method_ret *ret, u8 id) 85189251Ssam{ 86189251Ssam struct wpabuf *resp; 87189251Ssam u8 flags; 88189251Ssam size_t send_len, plen; 89189251Ssam 90189251Ssam ret->ignore = FALSE; 91189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Generating Response"); 92189251Ssam ret->allowNotifications = TRUE; 93189251Ssam 94189251Ssam flags = EAP_TNC_VERSION; 95189251Ssam send_len = wpabuf_len(data->out_buf) - data->out_used; 96189251Ssam if (1 + send_len > data->fragment_size) { 97189251Ssam send_len = data->fragment_size - 1; 98189251Ssam flags |= EAP_TNC_FLAGS_MORE_FRAGMENTS; 99189251Ssam if (data->out_used == 0) { 100189251Ssam flags |= EAP_TNC_FLAGS_LENGTH_INCLUDED; 101189251Ssam send_len -= 4; 102189251Ssam } 103189251Ssam } 104189251Ssam 105189251Ssam plen = 1 + send_len; 106189251Ssam if (flags & EAP_TNC_FLAGS_LENGTH_INCLUDED) 107189251Ssam plen += 4; 108189251Ssam resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, plen, 109189251Ssam EAP_CODE_RESPONSE, id); 110189251Ssam if (resp == NULL) 111189251Ssam return NULL; 112189251Ssam 113189251Ssam wpabuf_put_u8(resp, flags); /* Flags */ 114189251Ssam if (flags & EAP_TNC_FLAGS_LENGTH_INCLUDED) 115189251Ssam wpabuf_put_be32(resp, wpabuf_len(data->out_buf)); 116189251Ssam 117189251Ssam wpabuf_put_data(resp, wpabuf_head_u8(data->out_buf) + data->out_used, 118189251Ssam send_len); 119189251Ssam data->out_used += send_len; 120189251Ssam 121189251Ssam ret->methodState = METHOD_MAY_CONT; 122189251Ssam ret->decision = DECISION_FAIL; 123189251Ssam 124189251Ssam if (data->out_used == wpabuf_len(data->out_buf)) { 125189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Sending out %lu bytes " 126189251Ssam "(message sent completely)", 127189251Ssam (unsigned long) send_len); 128189251Ssam wpabuf_free(data->out_buf); 129189251Ssam data->out_buf = NULL; 130189251Ssam data->out_used = 0; 131189251Ssam } else { 132189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Sending out %lu bytes " 133189251Ssam "(%lu more to send)", (unsigned long) send_len, 134189251Ssam (unsigned long) wpabuf_len(data->out_buf) - 135189251Ssam data->out_used); 136189251Ssam data->state = WAIT_FRAG_ACK; 137189251Ssam } 138189251Ssam 139189251Ssam return resp; 140189251Ssam} 141189251Ssam 142189251Ssam 143189251Ssamstatic int eap_tnc_process_cont(struct eap_tnc_data *data, 144189251Ssam const u8 *buf, size_t len) 145189251Ssam{ 146189251Ssam /* Process continuation of a pending message */ 147189251Ssam if (len > wpabuf_tailroom(data->in_buf)) { 148189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Fragment overflow"); 149189251Ssam data->state = FAIL; 150189251Ssam return -1; 151189251Ssam } 152189251Ssam 153189251Ssam wpabuf_put_data(data->in_buf, buf, len); 154189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Received %lu bytes, waiting for " 155189251Ssam "%lu bytes more", (unsigned long) len, 156189251Ssam (unsigned long) wpabuf_tailroom(data->in_buf)); 157189251Ssam 158189251Ssam return 0; 159189251Ssam} 160189251Ssam 161189251Ssam 162189251Ssamstatic struct wpabuf * eap_tnc_process_fragment(struct eap_tnc_data *data, 163189251Ssam struct eap_method_ret *ret, 164189251Ssam u8 id, u8 flags, 165189251Ssam u32 message_length, 166189251Ssam const u8 *buf, size_t len) 167189251Ssam{ 168189251Ssam /* Process a fragment that is not the last one of the message */ 169189251Ssam if (data->in_buf == NULL && !(flags & EAP_TNC_FLAGS_LENGTH_INCLUDED)) { 170189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: No Message Length field in a " 171189251Ssam "fragmented packet"); 172189251Ssam ret->ignore = TRUE; 173189251Ssam return NULL; 174189251Ssam } 175189251Ssam 176189251Ssam if (data->in_buf == NULL) { 177189251Ssam /* First fragment of the message */ 178189251Ssam data->in_buf = wpabuf_alloc(message_length); 179189251Ssam if (data->in_buf == NULL) { 180189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: No memory for " 181189251Ssam "message"); 182189251Ssam ret->ignore = TRUE; 183189251Ssam return NULL; 184189251Ssam } 185189251Ssam wpabuf_put_data(data->in_buf, buf, len); 186189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Received %lu bytes in first " 187189251Ssam "fragment, waiting for %lu bytes more", 188189251Ssam (unsigned long) len, 189189251Ssam (unsigned long) wpabuf_tailroom(data->in_buf)); 190189251Ssam } 191189251Ssam 192189251Ssam return eap_tnc_build_frag_ack(id, EAP_CODE_RESPONSE); 193189251Ssam} 194189251Ssam 195189251Ssam 196189251Ssamstatic struct wpabuf * eap_tnc_process(struct eap_sm *sm, void *priv, 197189251Ssam struct eap_method_ret *ret, 198189251Ssam const struct wpabuf *reqData) 199189251Ssam{ 200189251Ssam struct eap_tnc_data *data = priv; 201189251Ssam struct wpabuf *resp; 202189251Ssam const u8 *pos, *end; 203189251Ssam u8 *rpos, *rpos1; 204189251Ssam size_t len, rlen; 205189251Ssam size_t imc_len; 206189251Ssam char *start_buf, *end_buf; 207189251Ssam size_t start_len, end_len; 208189251Ssam int tncs_done = 0; 209189251Ssam u8 flags, id; 210189251Ssam u32 message_length = 0; 211189251Ssam struct wpabuf tmpbuf; 212189251Ssam 213189251Ssam pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TNC, reqData, &len); 214189251Ssam if (pos == NULL) { 215189251Ssam wpa_printf(MSG_INFO, "EAP-TNC: Invalid frame (pos=%p len=%lu)", 216189251Ssam pos, (unsigned long) len); 217189251Ssam ret->ignore = TRUE; 218189251Ssam return NULL; 219189251Ssam } 220189251Ssam 221189251Ssam id = eap_get_id(reqData); 222189251Ssam 223189251Ssam end = pos + len; 224189251Ssam 225189251Ssam if (len == 0) 226189251Ssam flags = 0; /* fragment ack */ 227189251Ssam else 228189251Ssam flags = *pos++; 229189251Ssam 230189251Ssam if (len > 0 && (flags & EAP_TNC_VERSION_MASK) != EAP_TNC_VERSION) { 231189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Unsupported version %d", 232189251Ssam flags & EAP_TNC_VERSION_MASK); 233189251Ssam ret->ignore = TRUE; 234189251Ssam return NULL; 235189251Ssam } 236189251Ssam 237189251Ssam if (flags & EAP_TNC_FLAGS_LENGTH_INCLUDED) { 238189251Ssam if (end - pos < 4) { 239189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Message underflow"); 240189251Ssam ret->ignore = TRUE; 241189251Ssam return NULL; 242189251Ssam } 243189251Ssam message_length = WPA_GET_BE32(pos); 244189251Ssam pos += 4; 245189251Ssam 246281806Srpaulo if (message_length < (u32) (end - pos) || 247281806Srpaulo message_length > 75000) { 248189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Invalid Message " 249189251Ssam "Length (%d; %ld remaining in this msg)", 250189251Ssam message_length, (long) (end - pos)); 251189251Ssam ret->ignore = TRUE; 252189251Ssam return NULL; 253189251Ssam } 254189251Ssam } 255189251Ssam 256189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Received packet: Flags 0x%x " 257189251Ssam "Message Length %u", flags, message_length); 258189251Ssam 259189251Ssam if (data->state == WAIT_FRAG_ACK) { 260214734Srpaulo if (len > 1) { 261189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Unexpected payload in " 262189251Ssam "WAIT_FRAG_ACK state"); 263189251Ssam ret->ignore = TRUE; 264189251Ssam return NULL; 265189251Ssam } 266189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Fragment acknowledged"); 267189251Ssam data->state = PROC_MSG; 268189251Ssam return eap_tnc_build_msg(data, ret, id); 269189251Ssam } 270189251Ssam 271189251Ssam if (data->in_buf && eap_tnc_process_cont(data, pos, end - pos) < 0) { 272189251Ssam ret->ignore = TRUE; 273189251Ssam return NULL; 274189251Ssam } 275189251Ssam 276189251Ssam if (flags & EAP_TNC_FLAGS_MORE_FRAGMENTS) { 277189251Ssam return eap_tnc_process_fragment(data, ret, id, flags, 278189251Ssam message_length, pos, 279189251Ssam end - pos); 280189251Ssam } 281189251Ssam 282189251Ssam if (data->in_buf == NULL) { 283189251Ssam /* Wrap unfragmented messages as wpabuf without extra copy */ 284189251Ssam wpabuf_set(&tmpbuf, pos, end - pos); 285189251Ssam data->in_buf = &tmpbuf; 286189251Ssam } 287189251Ssam 288189251Ssam if (data->state == WAIT_START) { 289189251Ssam if (!(flags & EAP_TNC_FLAGS_START)) { 290189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Server did not use " 291189251Ssam "start flag in the first message"); 292189251Ssam ret->ignore = TRUE; 293209158Srpaulo goto fail; 294189251Ssam } 295189251Ssam 296189251Ssam tncc_init_connection(data->tncc); 297189251Ssam 298189251Ssam data->state = PROC_MSG; 299189251Ssam } else { 300189251Ssam enum tncc_process_res res; 301189251Ssam 302189251Ssam if (flags & EAP_TNC_FLAGS_START) { 303189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Server used start " 304189251Ssam "flag again"); 305189251Ssam ret->ignore = TRUE; 306209158Srpaulo goto fail; 307189251Ssam } 308189251Ssam 309189251Ssam res = tncc_process_if_tnccs(data->tncc, 310189251Ssam wpabuf_head(data->in_buf), 311189251Ssam wpabuf_len(data->in_buf)); 312189251Ssam switch (res) { 313189251Ssam case TNCCS_PROCESS_ERROR: 314189251Ssam ret->ignore = TRUE; 315209158Srpaulo goto fail; 316189251Ssam case TNCCS_PROCESS_OK_NO_RECOMMENDATION: 317189251Ssam case TNCCS_RECOMMENDATION_ERROR: 318189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: No " 319189251Ssam "TNCCS-Recommendation received"); 320189251Ssam break; 321189251Ssam case TNCCS_RECOMMENDATION_ALLOW: 322189251Ssam wpa_msg(sm->msg_ctx, MSG_INFO, 323189251Ssam "TNC: Recommendation = allow"); 324189251Ssam tncs_done = 1; 325189251Ssam break; 326189251Ssam case TNCCS_RECOMMENDATION_NONE: 327189251Ssam wpa_msg(sm->msg_ctx, MSG_INFO, 328189251Ssam "TNC: Recommendation = none"); 329189251Ssam tncs_done = 1; 330189251Ssam break; 331189251Ssam case TNCCS_RECOMMENDATION_ISOLATE: 332189251Ssam wpa_msg(sm->msg_ctx, MSG_INFO, 333189251Ssam "TNC: Recommendation = isolate"); 334189251Ssam tncs_done = 1; 335189251Ssam break; 336189251Ssam } 337189251Ssam } 338189251Ssam 339189251Ssam if (data->in_buf != &tmpbuf) 340189251Ssam wpabuf_free(data->in_buf); 341189251Ssam data->in_buf = NULL; 342189251Ssam 343189251Ssam ret->ignore = FALSE; 344189251Ssam ret->methodState = METHOD_MAY_CONT; 345189251Ssam ret->decision = DECISION_UNCOND_SUCC; 346189251Ssam ret->allowNotifications = TRUE; 347189251Ssam 348189251Ssam if (data->out_buf) { 349189251Ssam data->state = PROC_MSG; 350189251Ssam return eap_tnc_build_msg(data, ret, id); 351189251Ssam } 352189251Ssam 353189251Ssam if (tncs_done) { 354189251Ssam resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, 1, 355189251Ssam EAP_CODE_RESPONSE, eap_get_id(reqData)); 356189251Ssam if (resp == NULL) 357189251Ssam return NULL; 358189251Ssam 359189251Ssam wpabuf_put_u8(resp, EAP_TNC_VERSION); 360189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS done - reply with an " 361189251Ssam "empty ACK message"); 362189251Ssam return resp; 363189251Ssam } 364189251Ssam 365189251Ssam imc_len = tncc_total_send_len(data->tncc); 366189251Ssam 367189251Ssam start_buf = tncc_if_tnccs_start(data->tncc); 368189251Ssam if (start_buf == NULL) 369189251Ssam return NULL; 370189251Ssam start_len = os_strlen(start_buf); 371189251Ssam end_buf = tncc_if_tnccs_end(); 372189251Ssam if (end_buf == NULL) { 373189251Ssam os_free(start_buf); 374189251Ssam return NULL; 375189251Ssam } 376189251Ssam end_len = os_strlen(end_buf); 377189251Ssam 378189251Ssam rlen = start_len + imc_len + end_len; 379189251Ssam resp = wpabuf_alloc(rlen); 380189251Ssam if (resp == NULL) { 381189251Ssam os_free(start_buf); 382189251Ssam os_free(end_buf); 383189251Ssam return NULL; 384189251Ssam } 385189251Ssam 386189251Ssam wpabuf_put_data(resp, start_buf, start_len); 387189251Ssam os_free(start_buf); 388189251Ssam 389189251Ssam rpos1 = wpabuf_put(resp, 0); 390189251Ssam rpos = tncc_copy_send_buf(data->tncc, rpos1); 391189251Ssam wpabuf_put(resp, rpos - rpos1); 392189251Ssam 393189251Ssam wpabuf_put_data(resp, end_buf, end_len); 394189251Ssam os_free(end_buf); 395189251Ssam 396189251Ssam wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-TNC: Response", 397189251Ssam wpabuf_head(resp), wpabuf_len(resp)); 398189251Ssam 399189251Ssam data->out_buf = resp; 400189251Ssam data->state = PROC_MSG; 401189251Ssam return eap_tnc_build_msg(data, ret, id); 402209158Srpaulo 403209158Srpaulofail: 404209158Srpaulo if (data->in_buf == &tmpbuf) 405209158Srpaulo data->in_buf = NULL; 406209158Srpaulo return NULL; 407189251Ssam} 408189251Ssam 409189251Ssam 410189251Ssamint eap_peer_tnc_register(void) 411189251Ssam{ 412189251Ssam struct eap_method *eap; 413189251Ssam int ret; 414189251Ssam 415189251Ssam eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, 416189251Ssam EAP_VENDOR_IETF, EAP_TYPE_TNC, "TNC"); 417189251Ssam if (eap == NULL) 418189251Ssam return -1; 419189251Ssam 420189251Ssam eap->init = eap_tnc_init; 421189251Ssam eap->deinit = eap_tnc_deinit; 422189251Ssam eap->process = eap_tnc_process; 423189251Ssam 424189251Ssam ret = eap_peer_method_register(eap); 425189251Ssam if (ret) 426189251Ssam eap_peer_method_free(eap); 427189251Ssam return ret; 428189251Ssam} 429