Added fragmentation support for EAP-TNC
[libeap.git] / src / eap_server / eap_tnc.c
1 /*
2  * EAP server method: EAP-TNC (Trusted Network Connect)
3  * Copyright (c) 2007-2008, Jouni Malinen <j@w1.fi>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as
7  * published by the Free Software Foundation.
8  *
9  * Alternatively, this software may be distributed under the terms of BSD
10  * license.
11  *
12  * See README and COPYING for more details.
13  */
14
15 #include "includes.h"
16
17 #include "common.h"
18 #include "base64.h"
19 #include "eap_i.h"
20 #include "tncs.h"
21
22
23 struct eap_tnc_data {
24         enum { START, CONTINUE, RECOMMENDATION, FRAG_ACK, WAIT_FRAG_ACK, DONE,
25                FAIL } state;
26         enum { ALLOW, ISOLATE, NO_ACCESS, NO_RECOMMENDATION } recommendation;
27         struct tncs_data *tncs;
28         struct wpabuf *in_buf;
29         struct wpabuf *out_buf;
30         size_t out_used;
31         size_t fragment_size;
32 };
33
34
35 /* EAP-TNC Flags */
36 #define EAP_TNC_FLAGS_LENGTH_INCLUDED 0x80
37 #define EAP_TNC_FLAGS_MORE_FRAGMENTS 0x40
38 #define EAP_TNC_FLAGS_START 0x20
39 #define EAP_TNC_VERSION_MASK 0x07
40
41 #define EAP_TNC_VERSION 1
42
43
44 static void * eap_tnc_init(struct eap_sm *sm)
45 {
46         struct eap_tnc_data *data;
47
48         data = os_zalloc(sizeof(*data));
49         if (data == NULL)
50                 return NULL;
51         data->state = START;
52         data->tncs = tncs_init();
53         if (data->tncs == NULL) {
54                 os_free(data);
55                 return NULL;
56         }
57
58         data->fragment_size = 1300;
59
60         return data;
61 }
62
63
64 static void eap_tnc_reset(struct eap_sm *sm, void *priv)
65 {
66         struct eap_tnc_data *data = priv;
67         wpabuf_free(data->in_buf);
68         wpabuf_free(data->out_buf);
69         tncs_deinit(data->tncs);
70         os_free(data);
71 }
72
73
74 static struct wpabuf * eap_tnc_build_start(struct eap_sm *sm,
75                                            struct eap_tnc_data *data, u8 id)
76 {
77         struct wpabuf *req;
78
79         req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, 1, EAP_CODE_REQUEST,
80                             id);
81         if (req == NULL) {
82                 wpa_printf(MSG_ERROR, "EAP-TNC: Failed to allocate memory for "
83                            "request");
84                 data->state = FAIL;
85                 return NULL;
86         }
87
88         wpabuf_put_u8(req, EAP_TNC_FLAGS_START | EAP_TNC_VERSION);
89
90         data->state = CONTINUE;
91
92         return req;
93 }
94
95
96 static struct wpabuf * eap_tnc_build(struct eap_sm *sm,
97                                      struct eap_tnc_data *data, u8 id)
98 {
99         struct wpabuf *req;
100         u8 *rpos, *rpos1, *start;
101         size_t rlen;
102         char *start_buf, *end_buf;
103         size_t start_len, end_len;
104         size_t imv_len;
105
106         imv_len = tncs_total_send_len(data->tncs);
107
108         start_buf = tncs_if_tnccs_start(data->tncs);
109         if (start_buf == NULL)
110                 return NULL;
111         start_len = os_strlen(start_buf);
112         end_buf = tncs_if_tnccs_end();
113         if (end_buf == NULL) {
114                 os_free(start_buf);
115                 return NULL;
116         }
117         end_len = os_strlen(end_buf);
118
119         rlen = 1 + start_len + imv_len + end_len;
120         req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, rlen,
121                             EAP_CODE_REQUEST, id);
122         if (req == NULL) {
123                 os_free(start_buf);
124                 os_free(end_buf);
125                 return NULL;
126         }
127
128         start = wpabuf_put(req, 0);
129         wpabuf_put_u8(req, EAP_TNC_VERSION);
130         wpabuf_put_data(req, start_buf, start_len);
131         os_free(start_buf);
132
133         rpos1 = wpabuf_put(req, 0);
134         rpos = tncs_copy_send_buf(data->tncs, rpos1);
135         wpabuf_put(req, rpos - rpos1);
136
137         wpabuf_put_data(req, end_buf, end_len);
138         os_free(end_buf);
139
140         wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-TNC: Request", start, rlen);
141
142         return req;
143 }
144
145
146 static struct wpabuf * eap_tnc_build_recommendation(struct eap_sm *sm,
147                                                     struct eap_tnc_data *data,
148                                                     u8 id)
149 {
150         switch (data->recommendation) {
151         case ALLOW:
152                 data->state = DONE;
153                 break;
154         case ISOLATE:
155                 data->state = FAIL;
156                 /* TODO: support assignment to a different VLAN */
157                 break;
158         case NO_ACCESS:
159                 data->state = FAIL;
160                 break;
161         case NO_RECOMMENDATION:
162                 data->state = DONE;
163                 break;
164         default:
165                 wpa_printf(MSG_DEBUG, "EAP-TNC: Unknown recommendation");
166                 return NULL;
167         }
168
169         return eap_tnc_build(sm, data, id);
170 }
171
172
173 static struct wpabuf * eap_tnc_build_frag_ack(u8 id, u8 code)
174 {
175         struct wpabuf *msg;
176
177         msg = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, 0, code, id);
178         if (msg == NULL) {
179                 wpa_printf(MSG_ERROR, "EAP-TNC: Failed to allocate memory "
180                            "for fragment ack");
181                 return NULL;
182         }
183
184         wpa_printf(MSG_DEBUG, "EAP-TNC: Send fragment ack");
185
186         return msg;
187 }
188
189
190 static struct wpabuf * eap_tnc_build_msg(struct eap_tnc_data *data, u8 id)
191 {
192         struct wpabuf *req;
193         u8 flags;
194         size_t send_len, plen;
195
196         wpa_printf(MSG_DEBUG, "EAP-TNC: Generating Request");
197
198         flags = EAP_TNC_VERSION;
199         send_len = wpabuf_len(data->out_buf) - data->out_used;
200         if (1 + send_len > data->fragment_size) {
201                 send_len = data->fragment_size - 1;
202                 flags |= EAP_TNC_FLAGS_MORE_FRAGMENTS;
203                 if (data->out_used == 0) {
204                         flags |= EAP_TNC_FLAGS_LENGTH_INCLUDED;
205                         send_len -= 4;
206                 }
207         }
208
209         plen = 1 + send_len;
210         if (flags & EAP_TNC_FLAGS_LENGTH_INCLUDED)
211                 plen += 4;
212         req = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_TNC, plen,
213                             EAP_CODE_REQUEST, id);
214         if (req == NULL)
215                 return NULL;
216
217         wpabuf_put_u8(req, flags); /* Flags */
218         if (flags & EAP_TNC_FLAGS_LENGTH_INCLUDED)
219                 wpabuf_put_be32(req, wpabuf_len(data->out_buf));
220
221         wpabuf_put_data(req, wpabuf_head_u8(data->out_buf) + data->out_used,
222                         send_len);
223         data->out_used += send_len;
224
225         if (data->out_used == wpabuf_len(data->out_buf)) {
226                 wpa_printf(MSG_DEBUG, "EAP-TNC: Sending out %lu bytes "
227                            "(message sent completely)",
228                            (unsigned long) send_len);
229                 wpabuf_free(data->out_buf);
230                 data->out_buf = NULL;
231                 data->out_used = 0;
232         } else {
233                 wpa_printf(MSG_DEBUG, "EAP-TNC: Sending out %lu bytes "
234                            "(%lu more to send)", (unsigned long) send_len,
235                            (unsigned long) wpabuf_len(data->out_buf) -
236                            data->out_used);
237                 data->state = WAIT_FRAG_ACK;
238         }
239
240         return req;
241 }
242
243
244 static struct wpabuf * eap_tnc_buildReq(struct eap_sm *sm, void *priv, u8 id)
245 {
246         struct eap_tnc_data *data = priv;
247
248         switch (data->state) {
249         case START:
250                 tncs_init_connection(data->tncs);
251                 return eap_tnc_build_start(sm, data, id);
252         case CONTINUE:
253                 if (data->out_buf == NULL) {
254                         data->out_buf = eap_tnc_build(sm, data, id);
255                         if (data->out_buf == NULL) {
256                                 wpa_printf(MSG_DEBUG, "EAP-TNC: Failed to "
257                                            "generate message");
258                                 return NULL;
259                         }
260                         data->out_used = 0;
261                 }
262                 return eap_tnc_build_msg(data, id);
263         case RECOMMENDATION:
264                 if (data->out_buf == NULL) {
265                         data->out_buf = eap_tnc_build_recommendation(sm, data,
266                                                                      id);
267                         if (data->out_buf == NULL) {
268                                 wpa_printf(MSG_DEBUG, "EAP-TNC: Failed to "
269                                            "generate recommendation message");
270                                 return NULL;
271                         }
272                         data->out_used = 0;
273                 }
274                 return eap_tnc_build_msg(data, id);
275         case WAIT_FRAG_ACK:
276                 return eap_tnc_build_msg(data, id);
277         case FRAG_ACK:
278                 return eap_tnc_build_frag_ack(id, EAP_CODE_REQUEST);
279         case DONE:
280         case FAIL:
281                 return NULL;
282         }
283
284         return NULL;
285 }
286
287
288 static Boolean eap_tnc_check(struct eap_sm *sm, void *priv,
289                              struct wpabuf *respData)
290 {
291         struct eap_tnc_data *data = priv;
292         const u8 *pos;
293         size_t len;
294
295         pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TNC, respData,
296                                &len);
297         if (pos == NULL) {
298                 wpa_printf(MSG_INFO, "EAP-TNC: Invalid frame");
299                 return TRUE;
300         }
301
302         if (len == 0 && data->state != WAIT_FRAG_ACK) {
303                 wpa_printf(MSG_INFO, "EAP-TNC: Invalid frame (empty)");
304                 return TRUE;
305         }
306
307         if (len == 0)
308                 return FALSE; /* Fragment ACK does not include flags */
309
310         if ((*pos & EAP_TNC_VERSION_MASK) != EAP_TNC_VERSION) {
311                 wpa_printf(MSG_DEBUG, "EAP-TNC: Unsupported version %d",
312                            *pos & EAP_TNC_VERSION_MASK);
313                 return TRUE;
314         }
315
316         if (*pos & EAP_TNC_FLAGS_START) {
317                 wpa_printf(MSG_DEBUG, "EAP-TNC: Peer used Start flag");
318                 return TRUE;
319         }
320
321         return FALSE;
322 }
323
324
325 static void tncs_process(struct eap_tnc_data *data, struct wpabuf *inbuf)
326 {
327         enum tncs_process_res res;
328
329         res = tncs_process_if_tnccs(data->tncs, wpabuf_head(inbuf),
330                                     wpabuf_len(inbuf));
331         switch (res) {
332         case TNCCS_RECOMMENDATION_ALLOW:
333                 wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS allowed access");
334                 data->state = RECOMMENDATION;
335                 data->recommendation = ALLOW;
336                 break;
337         case TNCCS_RECOMMENDATION_NO_RECOMMENDATION:
338                 wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS has no recommendation");
339                 data->state = RECOMMENDATION;
340                 data->recommendation = NO_RECOMMENDATION;
341                 break;
342         case TNCCS_RECOMMENDATION_ISOLATE:
343                 wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS requested isolation");
344                 data->state = RECOMMENDATION;
345                 data->recommendation = ISOLATE;
346                 break;
347         case TNCCS_RECOMMENDATION_NO_ACCESS:
348                 wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS rejected access");
349                 data->state = RECOMMENDATION;
350                 data->recommendation = NO_ACCESS;
351                 break;
352         case TNCCS_PROCESS_ERROR:
353                 wpa_printf(MSG_DEBUG, "EAP-TNC: TNCS processing error");
354                 data->state = FAIL;
355                 break;
356         default:
357                 break;
358         }
359 }
360
361
362 static int eap_tnc_process_cont(struct eap_tnc_data *data,
363                                 const u8 *buf, size_t len)
364 {
365         /* Process continuation of a pending message */
366         if (len > wpabuf_tailroom(data->in_buf)) {
367                 wpa_printf(MSG_DEBUG, "EAP-TNC: Fragment overflow");
368                 data->state = FAIL;
369                 return -1;
370         }
371
372         wpabuf_put_data(data->in_buf, buf, len);
373         wpa_printf(MSG_DEBUG, "EAP-TNC: Received %lu bytes, waiting for %lu "
374                    "bytes more", (unsigned long) len,
375                    (unsigned long) wpabuf_tailroom(data->in_buf));
376
377         return 0;
378 }
379
380
381 static int eap_tnc_process_fragment(struct eap_tnc_data *data,
382                                     u8 flags, u32 message_length,
383                                     const u8 *buf, size_t len)
384 {
385         /* Process a fragment that is not the last one of the message */
386         if (data->in_buf == NULL && !(flags & EAP_TNC_FLAGS_LENGTH_INCLUDED)) {
387                 wpa_printf(MSG_DEBUG, "EAP-TNC: No Message Length field in a "
388                            "fragmented packet");
389                 return -1;
390         }
391
392         if (data->in_buf == NULL) {
393                 /* First fragment of the message */
394                 data->in_buf = wpabuf_alloc(message_length);
395                 if (data->in_buf == NULL) {
396                         wpa_printf(MSG_DEBUG, "EAP-TNC: No memory for "
397                                    "message");
398                         return -1;
399                 }
400                 wpabuf_put_data(data->in_buf, buf, len);
401                 wpa_printf(MSG_DEBUG, "EAP-TNC: Received %lu bytes in first "
402                            "fragment, waiting for %lu bytes more",
403                            (unsigned long) len,
404                            (unsigned long) wpabuf_tailroom(data->in_buf));
405         }
406
407         return 0;
408 }
409
410
411 static void eap_tnc_process(struct eap_sm *sm, void *priv,
412                             struct wpabuf *respData)
413 {
414         struct eap_tnc_data *data = priv;
415         const u8 *pos, *end;
416         size_t len;
417         u8 flags;
418         u32 message_length = 0;
419         struct wpabuf tmpbuf;
420
421         pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_TNC, respData, &len);
422         if (pos == NULL)
423                 return; /* Should not happen; message already verified */
424
425         end = pos + len;
426
427         if (len == 1 && (data->state == DONE || data->state == FAIL)) {
428                 wpa_printf(MSG_DEBUG, "EAP-TNC: Peer acknowledged the last "
429                            "message");
430                 return;
431         }
432
433         if (len == 0) {
434                 /* fragment ack */
435                 flags = 0;
436         } else
437                 flags = *pos++;
438
439         if (flags & EAP_TNC_FLAGS_LENGTH_INCLUDED) {
440                 if (end - pos < 4) {
441                         wpa_printf(MSG_DEBUG, "EAP-TNC: Message underflow");
442                         data->state = FAIL;
443                         return;
444                 }
445                 message_length = WPA_GET_BE32(pos);
446                 pos += 4;
447
448                 if (message_length < (u32) (end - pos)) {
449                         wpa_printf(MSG_DEBUG, "EAP-TNC: Invalid Message "
450                                    "Length (%d; %ld remaining in this msg)",
451                                    message_length, (long) (end - pos));
452                         data->state = FAIL;
453                         return;
454                 }
455         }
456         wpa_printf(MSG_DEBUG, "EAP-TNC: Received packet: Flags 0x%x "
457                    "Message Length %u", flags, message_length);
458
459         if (data->state == WAIT_FRAG_ACK) {
460                 if (len != 0) {
461                         wpa_printf(MSG_DEBUG, "EAP-TNC: Unexpected payload "
462                                    "in WAIT_FRAG_ACK state");
463                         data->state = FAIL;
464                         return;
465                 }
466                 wpa_printf(MSG_DEBUG, "EAP-TNC: Fragment acknowledged");
467                 data->state = CONTINUE;
468                 return;
469         }
470
471         if (data->in_buf && eap_tnc_process_cont(data, pos, end - pos) < 0) {
472                 data->state = FAIL;
473                 return;
474         }
475                 
476         if (flags & EAP_TNC_FLAGS_MORE_FRAGMENTS) {
477                 if (eap_tnc_process_fragment(data, flags, message_length,
478                                              pos, end - pos) < 0)
479                         data->state = FAIL;
480                 else
481                         data->state = FRAG_ACK;
482                 return;
483         } else if (data->state == FRAG_ACK) {
484                 wpa_printf(MSG_DEBUG, "EAP-TNC: All fragments received");
485                 data->state = CONTINUE;
486         }
487
488         if (data->in_buf == NULL) {
489                 /* Wrap unfragmented messages as wpabuf without extra copy */
490                 wpabuf_set(&tmpbuf, pos, end - pos);
491                 data->in_buf = &tmpbuf;
492         }
493
494         wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-TNC: Received payload",
495                           wpabuf_head(data->in_buf), wpabuf_len(data->in_buf));
496         tncs_process(data, data->in_buf);
497
498         if (data->in_buf != &tmpbuf)
499                 wpabuf_free(data->in_buf);
500         data->in_buf = NULL;
501 }
502
503
504 static Boolean eap_tnc_isDone(struct eap_sm *sm, void *priv)
505 {
506         struct eap_tnc_data *data = priv;
507         return data->state == DONE;
508 }
509
510
511 static Boolean eap_tnc_isSuccess(struct eap_sm *sm, void *priv)
512 {
513         struct eap_tnc_data *data = priv;
514         return data->state == DONE;
515 }
516
517
518 int eap_server_tnc_register(void)
519 {
520         struct eap_method *eap;
521         int ret;
522
523         eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION,
524                                       EAP_VENDOR_IETF, EAP_TYPE_TNC, "TNC");
525         if (eap == NULL)
526                 return -1;
527
528         eap->init = eap_tnc_init;
529         eap->reset = eap_tnc_reset;
530         eap->buildReq = eap_tnc_buildReq;
531         eap->check = eap_tnc_check;
532         eap->process = eap_tnc_process;
533         eap->isDone = eap_tnc_isDone;
534         eap->isSuccess = eap_tnc_isSuccess;
535
536         ret = eap_server_method_register(eap);
537         if (ret)
538                 eap_server_method_free(eap);
539         return ret;
540 }