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