df3fe850460500915026f201424cc0d06fcdf4f3
[strongswan.git] / src / libcharon / plugins / tnccs_20 / tnccs_20.c
1 /*
2 * Copyright (C) 2010 Sansar Choinyanbuu
3 * Copyright (C) 2010 Andreas Steffen
4 * HSR Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #include "tnccs_20.h"
18 #include "batch/pb_tnc_batch.h"
19 #include "messages/pb_tnc_msg.h"
20 #include "messages/pb_pa_msg.h"
21 #include "messages/pb_error_msg.h"
22 #include "messages/pb_assessment_result_msg.h"
23 #include "messages/pb_access_recommendation_msg.h"
24 #include "messages/pb_remediation_parameters_msg.h"
25 #include "messages/pb_reason_string_msg.h"
26 #include "messages/pb_language_preference_msg.h"
27 #include "state_machine/pb_tnc_state_machine.h"
28
29 #include <debug.h>
30 #include <daemon.h>
31 #include <threading/mutex.h>
32 #include <tnc/tnccs/tnccs.h>
33 #include <pen/pen.h>
34
35 typedef struct private_tnccs_20_t private_tnccs_20_t;
36
37 /**
38 * Private data of a tnccs_20_t object.
39 */
40 struct private_tnccs_20_t {
41
42 /**
43 * Public tls_t interface.
44 */
45 tls_t public;
46
47 /**
48 * TNCC if TRUE, TNCS if FALSE
49 */
50 bool is_server;
51
52 /**
53 * PB-TNC State Machine
54 */
55 pb_tnc_state_machine_t *state_machine;
56
57 /**
58 * Connection ID assigned to this TNCCS connection
59 */
60 TNC_ConnectionID connection_id;
61
62 /**
63 * PB-TNC batch being constructed
64 */
65 pb_tnc_batch_t *batch;
66
67 /**
68 * Mutex locking the batch in construction
69 */
70 mutex_t *mutex;
71
72 /**
73 * Flag set while processing
74 */
75 bool fatal_error;
76
77 /**
78 * Flag set by IMC/IMV RequestHandshakeRetry() function
79 */
80 bool request_handshake_retry;
81
82 /**
83 * SendMessage() by IMC/IMV only allowed if flag is set
84 */
85 bool send_msg;
86
87 /**
88 * Set of IMV recommendations (TNC Server only)
89 */
90 recommendations_t *recs;
91 };
92
93 METHOD(tnccs_t, send_msg, TNC_Result,
94 private_tnccs_20_t* this, TNC_IMCID imc_id, TNC_IMVID imv_id,
95 TNC_BufferReference msg,
96 TNC_UInt32 msg_len,
97 TNC_MessageType msg_type)
98 {
99 TNC_MessageSubtype msg_sub_type;
100 TNC_VendorID msg_vendor_id;
101 pb_tnc_msg_t *pb_tnc_msg;
102 pb_tnc_batch_type_t batch_type;
103
104 if (!this->send_msg)
105 {
106 DBG1(DBG_TNC, "%s %u not allowed to call SendMessage()",
107 this->is_server ? "IMV" : "IMC",
108 this->is_server ? imv_id : imc_id);
109 return TNC_RESULT_ILLEGAL_OPERATION;
110 }
111
112 msg_sub_type = msg_type & TNC_SUBTYPE_ANY;
113 msg_vendor_id = (msg_type >> 8) & TNC_VENDORID_ANY;
114
115 pb_tnc_msg = pb_pa_msg_create(msg_vendor_id, msg_sub_type, imc_id, imv_id,
116 chunk_create(msg, msg_len));
117 DBG2(DBG_TNC, "creating PB-PA message type 0x%06x(%N)/0x%02x",
118 msg_vendor_id, pen_names, msg_vendor_id, msg_sub_type);
119
120 /* adding PA message to SDATA or CDATA batch only */
121 batch_type = this->is_server ? PB_BATCH_SDATA : PB_BATCH_CDATA;
122 this->mutex->lock(this->mutex);
123 if (!this->batch)
124 {
125 this->batch = pb_tnc_batch_create(this->is_server, batch_type);
126 }
127 if (this->batch->get_type(this->batch) == batch_type)
128 {
129 this->batch->add_msg(this->batch, pb_tnc_msg);
130 }
131 else
132 {
133 pb_tnc_msg->destroy(pb_tnc_msg);
134 }
135 this->mutex->unlock(this->mutex);
136 return TNC_RESULT_SUCCESS;
137 }
138
139 /**
140 * Handle a single PB-TNC message according to its type
141 */
142 static void handle_message(private_tnccs_20_t *this, pb_tnc_msg_t *msg)
143 {
144 switch (msg->get_type(msg))
145 {
146 case PB_MSG_EXPERIMENTAL:
147 /* nothing to do */
148 break;
149 case PB_MSG_PA:
150 {
151 pb_pa_msg_t *pa_msg;
152 TNC_MessageType msg_type;
153 u_int32_t vendor_id, subtype;
154 chunk_t msg_body;
155
156 pa_msg = (pb_pa_msg_t*)msg;
157 vendor_id = pa_msg->get_vendor_id(pa_msg, &subtype);
158 msg_type = (vendor_id << 8) | (subtype & 0xff);
159 msg_body = pa_msg->get_body(pa_msg);
160
161 DBG2(DBG_TNC, "handling PB-PA message type 0x%06x(%N)/0x%02x",
162 vendor_id, pen_names, vendor_id, subtype);
163
164 this->send_msg = TRUE;
165 if (this->is_server)
166 {
167 charon->imvs->receive_message(charon->imvs,
168 this->connection_id, msg_body.ptr, msg_body.len, msg_type);
169 }
170 else
171 {
172 charon->imcs->receive_message(charon->imcs,
173 this->connection_id, msg_body.ptr, msg_body.len,msg_type);
174 }
175 this->send_msg = FALSE;
176 break;
177 }
178 case PB_MSG_ASSESSMENT_RESULT:
179 {
180 pb_assessment_result_msg_t *assess_msg;
181 u_int32_t result;
182
183 assess_msg = (pb_assessment_result_msg_t*)msg;
184 result = assess_msg->get_assessment_result(assess_msg);
185 DBG1(DBG_TNC, "PB-TNC assessment result is '%N'",
186 TNC_IMV_Evaluation_Result_names, result);
187 break;
188 }
189 case PB_MSG_ACCESS_RECOMMENDATION:
190 {
191 pb_access_recommendation_msg_t *rec_msg;
192 pb_access_recommendation_code_t rec;
193 TNC_ConnectionState state = TNC_CONNECTION_STATE_ACCESS_NONE;
194
195 rec_msg = (pb_access_recommendation_msg_t*)msg;
196 rec = rec_msg->get_access_recommendation(rec_msg);
197 DBG1(DBG_TNC, "PB-TNC access recommendation is '%N'",
198 pb_access_recommendation_code_names, rec);
199 switch (rec)
200 {
201 case PB_REC_ACCESS_ALLOWED:
202 state = TNC_CONNECTION_STATE_ACCESS_ALLOWED;
203 break;
204 case PB_REC_ACCESS_DENIED:
205 state = TNC_CONNECTION_STATE_ACCESS_NONE;
206 break;
207 case PB_REC_QUARANTINED:
208 state = TNC_CONNECTION_STATE_ACCESS_ISOLATED;
209 }
210 charon->imcs->notify_connection_change(charon->imcs,
211 this->connection_id, state);
212 break;
213 }
214 case PB_MSG_REMEDIATION_PARAMETERS:
215 {
216 /* TODO : Remediation parameters message processing */
217 break;
218 }
219 case PB_MSG_ERROR:
220 {
221 pb_error_msg_t *err_msg;
222 bool fatal;
223 u_int32_t vendor_id;
224 u_int16_t error_code;
225
226 err_msg = (pb_error_msg_t*)msg;
227 fatal = err_msg->get_fatal_flag(err_msg);
228 vendor_id = err_msg->get_vendor_id(err_msg);
229 error_code = err_msg->get_error_code(err_msg);
230
231 if (fatal)
232 {
233 this->fatal_error = TRUE;
234 }
235
236 if (vendor_id == PEN_IETF)
237 {
238 switch (error_code)
239 {
240 case PB_ERROR_INVALID_PARAMETER:
241 case PB_ERROR_UNSUPPORTED_MANDATORY_MSG:
242 DBG1(DBG_TNC, "received %s PB-TNC error '%N' "
243 "(offset %u bytes)",
244 fatal ? "fatal" : "non-fatal",
245 pb_tnc_error_code_names, error_code,
246 err_msg->get_offset(err_msg));
247 break;
248 case PB_ERROR_VERSION_NOT_SUPPORTED:
249 DBG1(DBG_TNC, "received %s PB-TNC error '%N' "
250 "caused by bad version 0x%02x",
251 fatal ? "fatal" : "non-fatal",
252 pb_tnc_error_code_names, error_code,
253 err_msg->get_bad_version(err_msg));
254 break;
255 case PB_ERROR_UNEXPECTED_BATCH_TYPE:
256 case PB_ERROR_LOCAL_ERROR:
257 default:
258 DBG1(DBG_TNC, "received %s PB-TNC error '%N'",
259 fatal ? "fatal" : "non-fatal",
260 pb_tnc_error_code_names, error_code);
261 break;
262 }
263 }
264 else
265 {
266 DBG1(DBG_TNC, "received %s PB-TNC error (%u) "
267 "with Vendor ID 0x%06x",
268 fatal ? "fatal" : "non-fatal",
269 error_code, vendor_id);
270 }
271 break;
272 }
273 case PB_MSG_LANGUAGE_PREFERENCE:
274 {
275 pb_language_preference_msg_t *lang_msg;
276 chunk_t lang;
277
278 lang_msg = (pb_language_preference_msg_t*)msg;
279 lang = lang_msg->get_language_preference(lang_msg);
280
281 DBG2(DBG_TNC, "setting language preference to '%.*s'",
282 lang.len, lang.ptr);
283 this->recs->set_preferred_language(this->recs, lang);
284 break;
285 }
286 case PB_MSG_REASON_STRING:
287 {
288 pb_reason_string_msg_t *reason_msg;
289 chunk_t reason_string, language_code;
290
291 reason_msg = (pb_reason_string_msg_t*)msg;
292 reason_string = reason_msg->get_reason_string(reason_msg);
293 language_code = reason_msg->get_language_code(reason_msg);
294 DBG2(DBG_TNC, "reason string is '%.*s", reason_string.len,
295 reason_string.ptr);
296 DBG2(DBG_TNC, "language code is '%.*s", language_code.len,
297 language_code.ptr);
298 break;
299 }
300 default:
301 break;
302 }
303 }
304
305 /**
306 * Build a CRETRY or SRETRY batch
307 */
308 static void build_retry_batch(private_tnccs_20_t *this)
309 {
310 pb_tnc_batch_type_t batch_retry_type;
311
312 batch_retry_type = this->is_server ? PB_BATCH_SRETRY : PB_BATCH_CRETRY;
313 if (this->batch)
314 {
315 if (this->batch->get_type(this->batch) == batch_retry_type)
316 {
317 /* retry batch has already been created */
318 return;
319 }
320 DBG1(DBG_TNC, "cancelling PB-TNC %N batch",
321 pb_tnc_batch_type_names, this->batch->get_type(this->batch));
322 this->batch->destroy(this->batch);
323 }
324 this->batch = pb_tnc_batch_create(this->is_server, batch_retry_type);
325 }
326
327 METHOD(tls_t, process, status_t,
328 private_tnccs_20_t *this, void *buf, size_t buflen)
329 {
330 chunk_t data;
331 pb_tnc_batch_t *batch;
332 pb_tnc_msg_t *msg;
333 enumerator_t *enumerator;
334 status_t status;
335
336 if (this->is_server && !this->connection_id)
337 {
338 this->connection_id = charon->tnccs->create_connection(charon->tnccs,
339 (tnccs_t*)this, _send_msg,
340 &this->request_handshake_retry, &this->recs);
341 if (!this->connection_id)
342 {
343 return FAILED;
344 }
345 charon->imvs->notify_connection_change(charon->imvs,
346 this->connection_id, TNC_CONNECTION_STATE_CREATE);
347 charon->imvs->notify_connection_change(charon->imvs,
348 this->connection_id, TNC_CONNECTION_STATE_HANDSHAKE);
349 }
350
351 data = chunk_create(buf, buflen);
352 DBG1(DBG_TNC, "received TNCCS batch (%u bytes) for Connection ID %u",
353 data.len, this->connection_id);
354 DBG3(DBG_TNC, "%B", &data);
355 batch = pb_tnc_batch_create_from_data(this->is_server, data);
356 status = batch->process(batch, this->state_machine);
357
358 if (status != FAILED)
359 {
360 enumerator_t *enumerator;
361 pb_tnc_msg_t *msg;
362 pb_tnc_batch_type_t batch_type;
363 bool empty = TRUE;
364
365 batch_type = batch->get_type(batch);
366
367 if (batch_type == PB_BATCH_CRETRY)
368 {
369 /* Send an SRETRY batch in response */
370 this->mutex->lock(this->mutex);
371 build_retry_batch(this);
372 this->mutex->unlock(this->mutex);
373 }
374 else if (batch_type == PB_BATCH_SRETRY)
375 {
376 /* Restart the measurements */
377 charon->imcs->notify_connection_change(charon->imcs,
378 this->connection_id, TNC_CONNECTION_STATE_HANDSHAKE);
379 this->send_msg = TRUE;
380 charon->imcs->begin_handshake(charon->imcs, this->connection_id);
381 this->send_msg = FALSE;
382 }
383
384 enumerator = batch->create_msg_enumerator(batch);
385 while (enumerator->enumerate(enumerator, &msg))
386 {
387 handle_message(this, msg);
388 empty = FALSE;
389 }
390 enumerator->destroy(enumerator);
391
392 /* received an empty CLOSE batch from PB-TNC client */
393 if (this->is_server && batch_type == PB_BATCH_CLOSE && empty)
394 {
395 batch->destroy(batch);
396 if (this->fatal_error)
397 {
398 DBG1(DBG_TNC, "a fatal PB-TNC error occurred, "
399 "terminating connection");
400 return FAILED;
401 }
402 else
403 {
404 return SUCCESS;
405 }
406 }
407
408 this->send_msg = TRUE;
409 if (this->is_server)
410 {
411 charon->imvs->batch_ending(charon->imvs, this->connection_id);
412 }
413 else
414 {
415 charon->imcs->batch_ending(charon->imcs, this->connection_id);
416 }
417 this->send_msg = FALSE;
418 }
419
420 switch (status)
421 {
422 case FAILED:
423 this->fatal_error = TRUE;
424 this->mutex->lock(this->mutex);
425 if (this->batch)
426 {
427 DBG1(DBG_TNC, "cancelling PB-TNC %N batch",
428 pb_tnc_batch_type_names, this->batch->get_type(this->batch));
429 this->batch->destroy(this->batch);
430 }
431 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CLOSE);
432 this->mutex->unlock(this->mutex);
433 /* fall through to add error messages to outbound batch */
434 case VERIFY_ERROR:
435 enumerator = batch->create_error_enumerator(batch);
436 while (enumerator->enumerate(enumerator, &msg))
437 {
438 this->mutex->lock(this->mutex);
439 this->batch->add_msg(this->batch, msg->get_ref(msg));
440 this->mutex->unlock(this->mutex);
441 }
442 enumerator->destroy(enumerator);
443 break;
444 case SUCCESS:
445 default:
446 break;
447 }
448 batch->destroy(batch);
449
450 return NEED_MORE;
451 }
452
453 /**
454 * Build a RESULT batch if a final recommendation is available
455 */
456 static void check_and_build_recommendation(private_tnccs_20_t *this)
457 {
458 TNC_IMV_Action_Recommendation rec;
459 TNC_IMV_Evaluation_Result eval;
460 TNC_IMVID id;
461 chunk_t reason, language;
462 enumerator_t *enumerator;
463 pb_tnc_msg_t *msg;
464
465 if (!this->recs->have_recommendation(this->recs, &rec, &eval))
466 {
467 charon->imvs->solicit_recommendation(charon->imvs, this->connection_id);
468 }
469 if (this->recs->have_recommendation(this->recs, &rec, &eval))
470 {
471 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_RESULT);
472
473 msg = pb_assessment_result_msg_create(eval);
474 this->batch->add_msg(this->batch, msg);
475
476 /**
477 * IMV Action Recommendation and PB Access Recommendation codes
478 * are shifted by one.
479 */
480 msg = pb_access_recommendation_msg_create(rec + 1);
481 this->batch->add_msg(this->batch, msg);
482
483 enumerator = this->recs->create_reason_enumerator(this->recs);
484 while (enumerator->enumerate(enumerator, &id, &reason, &language))
485 {
486 msg = pb_reason_string_msg_create(reason, language);
487 this->batch->add_msg(this->batch, msg);
488 }
489 enumerator->destroy(enumerator);
490 }
491 }
492
493 METHOD(tls_t, build, status_t,
494 private_tnccs_20_t *this, void *buf, size_t *buflen, size_t *msglen)
495 {
496 status_t status;
497 pb_tnc_state_t state;
498
499 /* Initialize the connection */
500 if (!this->is_server && !this->connection_id)
501 {
502 pb_tnc_msg_t *msg;
503 char *pref_lang;
504
505 this->connection_id = charon->tnccs->create_connection(charon->tnccs,
506 (tnccs_t*)this, _send_msg,
507 &this->request_handshake_retry, NULL);
508 if (!this->connection_id)
509 {
510 return FAILED;
511 }
512
513 /* Create PB-TNC Language Preference message */
514 pref_lang = charon->imcs->get_preferred_language(charon->imcs);
515 msg = pb_language_preference_msg_create(chunk_create(pref_lang,
516 strlen(pref_lang)));
517 this->mutex->lock(this->mutex);
518 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CDATA);
519 this->batch->add_msg(this->batch, msg);
520 this->mutex->unlock(this->mutex);
521
522 charon->imcs->notify_connection_change(charon->imcs,
523 this->connection_id, TNC_CONNECTION_STATE_CREATE);
524 charon->imcs->notify_connection_change(charon->imcs,
525 this->connection_id, TNC_CONNECTION_STATE_HANDSHAKE);
526 this->send_msg = TRUE;
527 charon->imcs->begin_handshake(charon->imcs, this->connection_id);
528 this->send_msg = FALSE;
529 }
530
531 state = this->state_machine->get_state(this->state_machine);
532
533 if (this->is_server && this->fatal_error && state == PB_STATE_END)
534 {
535 DBG1(DBG_TNC, "a fatal PB-TNC error occurred, terminating connection");
536 return FAILED;
537 }
538
539 /* Do not allow any asynchronous IMCs or IMVs to add additional messages */
540 this->mutex->lock(this->mutex);
541
542 if (this->request_handshake_retry)
543 {
544 if (state != PB_STATE_INIT)
545 {
546 build_retry_batch(this);
547 }
548
549 /* Reset the flag for the next handshake retry request */
550 this->request_handshake_retry = FALSE;
551 }
552
553 if (!this->batch)
554 {
555 if (this->is_server)
556 {
557 if (state == PB_STATE_SERVER_WORKING)
558 {
559 check_and_build_recommendation(this);
560 }
561 }
562 else
563 {
564 /**
565 * if the DECIDED state has been reached and no CRETRY is under way
566 * or if a CLOSE batch with error messages has been received,
567 * a PB-TNC client replies with an empty CLOSE batch.
568 */
569 if (state == PB_STATE_DECIDED || state == PB_STATE_END)
570 {
571 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CLOSE);
572 }
573 }
574 }
575
576 if (this->batch)
577 {
578 pb_tnc_batch_type_t batch_type;
579 chunk_t data;
580
581 batch_type = this->batch->get_type(this->batch);
582
583 if (this->state_machine->send_batch(this->state_machine, batch_type))
584 {
585 this->batch->build(this->batch);
586 data = this->batch->get_encoding(this->batch);
587 DBG1(DBG_TNC, "sending PB-TNC %N batch (%d bytes) for Connection ID %u",
588 pb_tnc_batch_type_names, batch_type, data.len,
589 this->connection_id);
590 DBG3(DBG_TNC, "%B", &data);
591 *msglen = data.len;
592
593 if (data.len > *buflen)
594 {
595 DBG1(DBG_TNC, "fragmentation of PB-TNC batch not supported yet");
596 }
597 else
598 {
599 *buflen = data.len;
600 }
601 memcpy(buf, data.ptr, *buflen);
602 status = ALREADY_DONE;
603 }
604 else
605 {
606 DBG1(DBG_TNC, "cancelling unexpected PB-TNC batch type: %N",
607 pb_tnc_batch_type_names, batch_type);
608 status = INVALID_STATE;
609 }
610
611 this->batch->destroy(this->batch);
612 this->batch = NULL;
613 }
614 else
615 {
616 DBG1(DBG_TNC, "no PB-TNC batch to send");
617 status = INVALID_STATE;
618 }
619 this->mutex->unlock(this->mutex);
620
621 return status;
622 }
623
624 METHOD(tls_t, is_server, bool,
625 private_tnccs_20_t *this)
626 {
627 return this->is_server;
628 }
629
630 METHOD(tls_t, get_purpose, tls_purpose_t,
631 private_tnccs_20_t *this)
632 {
633 return TLS_PURPOSE_EAP_TNC;
634 }
635
636 METHOD(tls_t, is_complete, bool,
637 private_tnccs_20_t *this)
638 {
639 TNC_IMV_Action_Recommendation rec;
640 TNC_IMV_Evaluation_Result eval;
641
642 if (this->recs && this->recs->have_recommendation(this->recs, &rec, &eval))
643 {
644 return charon->imvs->enforce_recommendation(charon->imvs, rec, eval);
645 }
646 else
647 {
648 return FALSE;
649 }
650 }
651
652 METHOD(tls_t, get_eap_msk, chunk_t,
653 private_tnccs_20_t *this)
654 {
655 return chunk_empty;
656 }
657
658 METHOD(tls_t, destroy, void,
659 private_tnccs_20_t *this)
660 {
661 charon->tnccs->remove_connection(charon->tnccs, this->connection_id,
662 this->is_server);
663 this->state_machine->destroy(this->state_machine);
664 this->mutex->destroy(this->mutex);
665 DESTROY_IF(this->batch);
666 free(this);
667 }
668
669 /**
670 * See header
671 */
672 tls_t *tnccs_20_create(bool is_server)
673 {
674 private_tnccs_20_t *this;
675
676 INIT(this,
677 .public = {
678 .process = _process,
679 .build = _build,
680 .is_server = _is_server,
681 .get_purpose = _get_purpose,
682 .is_complete = _is_complete,
683 .get_eap_msk = _get_eap_msk,
684 .destroy = _destroy,
685 },
686 .is_server = is_server,
687 .state_machine = pb_tnc_state_machine_create(is_server),
688 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
689 );
690
691 return &this->public;
692 }