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 246189251Ssam if (message_length < (u32) (end - pos)) { 247189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Invalid Message " 248189251Ssam "Length (%d; %ld remaining in this msg)", 249189251Ssam message_length, (long) (end - pos)); 250189251Ssam ret->ignore = TRUE; 251189251Ssam return NULL; 252189251Ssam } 253189251Ssam } 254189251Ssam 255189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Received packet: Flags 0x%x " 256189251Ssam "Message Length %u", flags, message_length); 257189251Ssam 258189251Ssam if (data->state == WAIT_FRAG_ACK) { 259214734Srpaulo if (len > 1) { 260189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Unexpected payload in " 261189251Ssam "WAIT_FRAG_ACK state"); 262189251Ssam ret->ignore = TRUE; 263189251Ssam return NULL; 264189251Ssam } 265189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Fragment acknowledged"); 266189251Ssam data->state = PROC_MSG; 267189251Ssam return eap_tnc_build_msg(data, ret, id); 268189251Ssam } 269189251Ssam 270189251Ssam if (data->in_buf && eap_tnc_process_cont(data, pos, end - pos) < 0) { 271189251Ssam ret->ignore = TRUE; 272189251Ssam return NULL; 273189251Ssam } 274189251Ssam 275189251Ssam if (flags & EAP_TNC_FLAGS_MORE_FRAGMENTS) { 276189251Ssam return eap_tnc_process_fragment(data, ret, id, flags, 277189251Ssam message_length, pos, 278189251Ssam end - pos); 279189251Ssam } 280189251Ssam 281189251Ssam if (data->in_buf == NULL) { 282189251Ssam /* Wrap unfragmented messages as wpabuf without extra copy */ 283189251Ssam wpabuf_set(&tmpbuf, pos, end - pos); 284189251Ssam data->in_buf = &tmpbuf; 285189251Ssam } 286189251Ssam 287189251Ssam if (data->state == WAIT_START) { 288189251Ssam if (!(flags & EAP_TNC_FLAGS_START)) { 289189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Server did not use " 290189251Ssam "start flag in the first message"); 291189251Ssam ret->ignore = TRUE; 292209158Srpaulo goto fail; 293189251Ssam } 294189251Ssam 295189251Ssam tncc_init_connection(data->tncc); 296189251Ssam 297189251Ssam data->state = PROC_MSG; 298189251Ssam } else { 299189251Ssam enum tncc_process_res res; 300189251Ssam 301189251Ssam if (flags & EAP_TNC_FLAGS_START) { 302189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: Server used start " 303189251Ssam "flag again"); 304189251Ssam ret->ignore = TRUE; 305209158Srpaulo goto fail; 306189251Ssam } 307189251Ssam 308189251Ssam res = tncc_process_if_tnccs(data->tncc, 309189251Ssam wpabuf_head(data->in_buf), 310189251Ssam wpabuf_len(data->in_buf)); 311189251Ssam switch (res) { 312189251Ssam case TNCCS_PROCESS_ERROR: 313189251Ssam ret->ignore = TRUE; 314209158Srpaulo goto fail; 315189251Ssam case TNCCS_PROCESS_OK_NO_RECOMMENDATION: 316189251Ssam case TNCCS_RECOMMENDATION_ERROR: 317189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: No " 318189251Ssam "TNCCS-Recommendation received"); 319189251Ssam break; 320189251Ssam case TNCCS_RECOMMENDATION_ALLOW: 321189251Ssam wpa_msg(sm->msg_ctx, MSG_INFO, 322189251Ssam "TNC: Recommendation = allow"); 323189251Ssam tncs_done = 1; 324189251Ssam break; 325189251Ssam case TNCCS_RECOMMENDATION_NONE: 326189251Ssam wpa_msg(sm->msg_ctx, MSG_INFO, 327189251Ssam "TNC: Recommendation = none"); 328189251Ssam tncs_done = 1; 329189251Ssam break; 330189251Ssam case TNCCS_RECOMMENDATION_ISOLATE: 331189251Ssam wpa_msg(sm->msg_ctx, MSG_INFO, 332189251Ssam "TNC: Recommendation = isolate"); 333189251Ssam tncs_done = 1; 334189251Ssam break; 335189251Ssam } 336189251Ssam } 337189251Ssam 338189251Ssam if (data->in_buf != &tmpbuf) 339189251Ssam wpabuf_free(data->in_buf); 340189251Ssam data->in_buf = NULL; 341189251Ssam 342189251Ssam ret->ignore = FALSE; 343189251Ssam ret->methodState = METHOD_MAY_CONT; 344189251Ssam ret->decision = DECISION_UNCOND_SUCC; 345189251Ssam ret->allowNotifications = TRUE; 346189251Ssam 347189251Ssam if (data->out_buf) { 348189251Ssam data->state = PROC_MSG; 349189251Ssam return eap_tnc_build_msg(data, ret, id); 350189251Ssam } 351189251Ssam 352189251Ssam if (tncs_done) { 353189251Ssam resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, 1, 354189251Ssam EAP_CODE_RESPONSE, eap_get_id(reqData)); 355189251Ssam if (resp == NULL) 356189251Ssam return NULL; 357189251Ssam 358189251Ssam wpabuf_put_u8(resp, EAP_TNC_VERSION); 359189251Ssam wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS done - reply with an " 360189251Ssam "empty ACK message"); 361189251Ssam return resp; 362189251Ssam } 363189251Ssam 364189251Ssam imc_len = tncc_total_send_len(data->tncc); 365189251Ssam 366189251Ssam start_buf = tncc_if_tnccs_start(data->tncc); 367189251Ssam if (start_buf == NULL) 368189251Ssam return NULL; 369189251Ssam start_len = os_strlen(start_buf); 370189251Ssam end_buf = tncc_if_tnccs_end(); 371189251Ssam if (end_buf == NULL) { 372189251Ssam os_free(start_buf); 373189251Ssam return NULL; 374189251Ssam } 375189251Ssam end_len = os_strlen(end_buf); 376189251Ssam 377189251Ssam rlen = start_len + imc_len + end_len; 378189251Ssam resp = wpabuf_alloc(rlen); 379189251Ssam if (resp == NULL) { 380189251Ssam os_free(start_buf); 381189251Ssam os_free(end_buf); 382189251Ssam return NULL; 383189251Ssam } 384189251Ssam 385189251Ssam wpabuf_put_data(resp, start_buf, start_len); 386189251Ssam os_free(start_buf); 387189251Ssam 388189251Ssam rpos1 = wpabuf_put(resp, 0); 389189251Ssam rpos = tncc_copy_send_buf(data->tncc, rpos1); 390189251Ssam wpabuf_put(resp, rpos - rpos1); 391189251Ssam 392189251Ssam wpabuf_put_data(resp, end_buf, end_len); 393189251Ssam os_free(end_buf); 394189251Ssam 395189251Ssam wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-TNC: Response", 396189251Ssam wpabuf_head(resp), wpabuf_len(resp)); 397189251Ssam 398189251Ssam data->out_buf = resp; 399189251Ssam data->state = PROC_MSG; 400189251Ssam return eap_tnc_build_msg(data, ret, id); 401209158Srpaulo 402209158Srpaulofail: 403209158Srpaulo if (data->in_buf == &tmpbuf) 404209158Srpaulo data->in_buf = NULL; 405209158Srpaulo return NULL; 406189251Ssam} 407189251Ssam 408189251Ssam 409189251Ssamint eap_peer_tnc_register(void) 410189251Ssam{ 411189251Ssam struct eap_method *eap; 412189251Ssam int ret; 413189251Ssam 414189251Ssam eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, 415189251Ssam EAP_VENDOR_IETF, EAP_TYPE_TNC, "TNC"); 416189251Ssam if (eap == NULL) 417189251Ssam return -1; 418189251Ssam 419189251Ssam eap->init = eap_tnc_init; 420189251Ssam eap->deinit = eap_tnc_deinit; 421189251Ssam eap->process = eap_tnc_process; 422189251Ssam 423189251Ssam ret = eap_peer_method_register(eap); 424189251Ssam if (ret) 425189251Ssam eap_peer_method_free(eap); 426189251Ssam return ret; 427189251Ssam} 428