2 * Copyright (C) 2010 Sansar Choinyanbuu
3 * Copyright (C) 2010-2012 Andreas Steffen
4 * HSR Hochschule fuer Technik Rapperswil
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>.
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
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"
29 #include <tncif_names.h>
30 #include <tncif_pa_subtypes.h>
33 #include <tnc/tnccs/tnccs_manager.h>
34 #include <tnc/imc/imc_manager.h>
35 #include <tnc/imv/imv_manager.h>
37 #include <utils/debug.h>
39 #include <threading/mutex.h>
40 #include <collections/linked_list.h>
43 typedef struct private_tnccs_20_t private_tnccs_20_t
;
46 * Private data of a tnccs_20_t object.
48 struct private_tnccs_20_t
{
51 * Public tls_t interface.
56 * TNCC if TRUE, TNCS if FALSE
61 * PB-TNC State Machine
63 pb_tnc_state_machine_t
*state_machine
;
66 * Connection ID assigned to this TNCCS connection
68 TNC_ConnectionID connection_id
;
71 * PB-TNC messages to be sent
73 linked_list_t
*messages
;
76 * Type of PB-TNC batch being constructed
78 pb_tnc_batch_type_t batch_type
;
81 * Maximum PB-TNC batch size
86 * Maximum PA-TNC message size
91 * Mutex locking the batch in construction
96 * Flag set while processing
101 * Flag set by IMC/IMV RequestHandshakeRetry() function
103 bool request_handshake_retry
;
106 * SendMessage() by IMC/IMV only allowed if flag is set
111 * Set of IMV recommendations (TNC Server only)
113 recommendations_t
*recs
;
118 * If the batch type changes then delete all accumulated PB-TNC messages
120 void change_batch_type(private_tnccs_20_t
*this, pb_tnc_batch_type_t batch_type
)
124 if (batch_type
!= this->batch_type
)
126 if (this->batch_type
!= PB_BATCH_NONE
)
128 DBG1(DBG_TNC
, "cancelling PB-TNC %N batch",
129 pb_tnc_batch_type_names
, this->batch_type
);
131 while (this->messages
->remove_last(this->messages
,
132 (void**)&msg
) == SUCCESS
)
137 this->batch_type
= batch_type
;
141 METHOD(tnccs_t
, send_msg
, TNC_Result
,
142 private_tnccs_20_t
* this, TNC_IMCID imc_id
, TNC_IMVID imv_id
,
143 TNC_UInt32 msg_flags
,
144 TNC_BufferReference msg
,
146 TNC_VendorID msg_vid
,
147 TNC_MessageSubtype msg_subtype
)
149 pb_tnc_msg_t
*pb_tnc_msg
;
150 pb_tnc_batch_type_t batch_type
;
151 enum_name_t
*pa_subtype_names
;
156 DBG1(DBG_TNC
, "%s %u not allowed to call SendMessage()",
157 this->is_server ?
"IMV" : "IMC",
158 this->is_server ? imv_id
: imc_id
);
159 return TNC_RESULT_ILLEGAL_OPERATION
;
161 excl
= (msg_flags
& TNC_MESSAGE_FLAGS_EXCLUSIVE
) != 0;
163 pb_tnc_msg
= pb_pa_msg_create(msg_vid
, msg_subtype
, imc_id
, imv_id
,
164 excl
, chunk_create(msg
, msg_len
));
166 pa_subtype_names
= get_pa_subtype_names(msg_vid
);
167 if (pa_subtype_names
)
169 DBG2(DBG_TNC
, "creating PB-PA message type '%N/%N' 0x%06x/0x%08x",
170 pen_names
, msg_vid
, pa_subtype_names
, msg_subtype
,
171 msg_vid
, msg_subtype
);
175 DBG2(DBG_TNC
, "creating PB-PA message type '%N' 0x%06x/0x%08x",
176 pen_names
, msg_vid
, msg_vid
, msg_subtype
);
179 /* adding PA message to SDATA or CDATA batch only */
180 batch_type
= this->is_server ? PB_BATCH_SDATA
: PB_BATCH_CDATA
;
181 this->mutex
->lock(this->mutex
);
182 if (this->batch_type
== PB_BATCH_NONE
)
184 this->batch_type
= batch_type
;
186 if (this->batch_type
== batch_type
)
188 this->messages
->insert_last(this->messages
, pb_tnc_msg
);
192 pb_tnc_msg
->destroy(pb_tnc_msg
);
194 this->mutex
->unlock(this->mutex
);
195 return TNC_RESULT_SUCCESS
;
199 * Handle a single PB-TNC message according to its type
201 static void handle_message(private_tnccs_20_t
*this, pb_tnc_msg_t
*msg
)
203 switch (msg
->get_type(msg
))
205 case PB_MSG_EXPERIMENTAL
:
211 pen_type_t msg_subtype
;
212 u_int16_t imc_id
, imv_id
;
215 enum_name_t
*pa_subtype_names
;
217 pa_msg
= (pb_pa_msg_t
*)msg
;
218 msg_subtype
= pa_msg
->get_subtype(pa_msg
);
219 msg_body
= pa_msg
->get_body(pa_msg
);
220 imc_id
= pa_msg
->get_collector_id(pa_msg
);
221 imv_id
= pa_msg
->get_validator_id(pa_msg
);
222 excl
= pa_msg
->get_exclusive_flag(pa_msg
);
224 pa_subtype_names
= get_pa_subtype_names(msg_subtype
.vendor_id
);
225 if (pa_subtype_names
)
227 DBG2(DBG_TNC
, "handling PB-PA message type '%N/%N' 0x%06x/0x%08x",
228 pen_names
, msg_subtype
.vendor_id
, pa_subtype_names
,
229 msg_subtype
.type
, msg_subtype
.vendor_id
, msg_subtype
.type
);
233 DBG2(DBG_TNC
, "handling PB-PA message type '%N' 0x%06x/0x%08x",
234 pen_names
, msg_subtype
.vendor_id
, msg_subtype
.vendor_id
,
238 this->send_msg
= TRUE
;
241 tnc
->imvs
->receive_message(tnc
->imvs
, this->connection_id
,
242 excl
, msg_body
.ptr
, msg_body
.len
,
243 msg_subtype
.vendor_id
,
244 msg_subtype
.type
, imc_id
, imv_id
);
248 tnc
->imcs
->receive_message(tnc
->imcs
, this->connection_id
,
249 excl
, msg_body
.ptr
, msg_body
.len
,
250 msg_subtype
.vendor_id
,
251 msg_subtype
.type
, imv_id
, imc_id
);
253 this->send_msg
= FALSE
;
256 case PB_MSG_ASSESSMENT_RESULT
:
258 pb_assessment_result_msg_t
*assess_msg
;
261 assess_msg
= (pb_assessment_result_msg_t
*)msg
;
262 result
= assess_msg
->get_assessment_result(assess_msg
);
263 DBG1(DBG_TNC
, "PB-TNC assessment result is '%N'",
264 TNC_IMV_Evaluation_Result_names
, result
);
267 case PB_MSG_ACCESS_RECOMMENDATION
:
269 pb_access_recommendation_msg_t
*rec_msg
;
270 pb_access_recommendation_code_t rec
;
271 TNC_ConnectionState state
= TNC_CONNECTION_STATE_ACCESS_NONE
;
273 rec_msg
= (pb_access_recommendation_msg_t
*)msg
;
274 rec
= rec_msg
->get_access_recommendation(rec_msg
);
275 DBG1(DBG_TNC
, "PB-TNC access recommendation is '%N'",
276 pb_access_recommendation_code_names
, rec
);
279 case PB_REC_ACCESS_ALLOWED
:
280 state
= TNC_CONNECTION_STATE_ACCESS_ALLOWED
;
282 case PB_REC_ACCESS_DENIED
:
283 state
= TNC_CONNECTION_STATE_ACCESS_NONE
;
285 case PB_REC_QUARANTINED
:
286 state
= TNC_CONNECTION_STATE_ACCESS_ISOLATED
;
288 tnc
->imcs
->notify_connection_change(tnc
->imcs
, this->connection_id
,
292 case PB_MSG_REMEDIATION_PARAMETERS
:
294 /* TODO : Remediation parameters message processing */
299 pb_error_msg_t
*err_msg
;
302 u_int16_t error_code
;
304 err_msg
= (pb_error_msg_t
*)msg
;
305 fatal
= err_msg
->get_fatal_flag(err_msg
);
306 vendor_id
= err_msg
->get_vendor_id(err_msg
);
307 error_code
= err_msg
->get_error_code(err_msg
);
311 this->fatal_error
= TRUE
;
314 if (vendor_id
== PEN_IETF
)
318 case PB_ERROR_INVALID_PARAMETER
:
319 case PB_ERROR_UNSUPPORTED_MANDATORY_MSG
:
320 DBG1(DBG_TNC
, "received %s PB-TNC error '%N' "
322 fatal ?
"fatal" : "non-fatal",
323 pb_tnc_error_code_names
, error_code
,
324 err_msg
->get_offset(err_msg
));
326 case PB_ERROR_VERSION_NOT_SUPPORTED
:
327 DBG1(DBG_TNC
, "received %s PB-TNC error '%N' "
328 "caused by bad version 0x%02x",
329 fatal ?
"fatal" : "non-fatal",
330 pb_tnc_error_code_names
, error_code
,
331 err_msg
->get_bad_version(err_msg
));
333 case PB_ERROR_UNEXPECTED_BATCH_TYPE
:
334 case PB_ERROR_LOCAL_ERROR
:
336 DBG1(DBG_TNC
, "received %s PB-TNC error '%N'",
337 fatal ?
"fatal" : "non-fatal",
338 pb_tnc_error_code_names
, error_code
);
344 DBG1(DBG_TNC
, "received %s PB-TNC error (%u) "
345 "with Vendor ID 0x%06x",
346 fatal ?
"fatal" : "non-fatal",
347 error_code
, vendor_id
);
351 case PB_MSG_LANGUAGE_PREFERENCE
:
353 pb_language_preference_msg_t
*lang_msg
;
356 lang_msg
= (pb_language_preference_msg_t
*)msg
;
357 lang
= lang_msg
->get_language_preference(lang_msg
);
359 DBG2(DBG_TNC
, "setting language preference to '%.*s'",
360 (int)lang
.len
, lang
.ptr
);
361 this->recs
->set_preferred_language(this->recs
, lang
);
364 case PB_MSG_REASON_STRING
:
366 pb_reason_string_msg_t
*reason_msg
;
367 chunk_t reason_string
, language_code
;
369 reason_msg
= (pb_reason_string_msg_t
*)msg
;
370 reason_string
= reason_msg
->get_reason_string(reason_msg
);
371 language_code
= reason_msg
->get_language_code(reason_msg
);
372 DBG1(DBG_TNC
, "reason string is '%.*s' [%.*s]",
373 (int)reason_string
.len
, reason_string
.ptr
,
374 (int)language_code
.len
, language_code
.ptr
);
383 * Build a CRETRY or SRETRY batch
385 static void build_retry_batch(private_tnccs_20_t
*this)
387 pb_tnc_batch_type_t batch_retry_type
;
389 batch_retry_type
= this->is_server ? PB_BATCH_SRETRY
: PB_BATCH_CRETRY
;
390 if (this->batch_type
== batch_retry_type
)
392 /* retry batch has already been selected */
396 change_batch_type(this, batch_retry_type
);
400 this->recs
->clear_recommendation(this->recs
);
401 tnc
->imvs
->notify_connection_change(tnc
->imvs
, this->connection_id
,
402 TNC_CONNECTION_STATE_HANDSHAKE
);
406 METHOD(tls_t
, process
, status_t
,
407 private_tnccs_20_t
*this, void *buf
, size_t buflen
)
410 pb_tnc_batch_t
*batch
;
412 enumerator_t
*enumerator
;
415 if (this->is_server
&& !this->connection_id
)
417 this->connection_id
= tnc
->tnccs
->create_connection(tnc
->tnccs
,
418 TNCCS_2_0
, (tnccs_t
*)this, _send_msg
,
419 &this->request_handshake_retry
,
420 this->max_msg_len
, &this->recs
);
421 if (!this->connection_id
)
425 tnc
->imvs
->notify_connection_change(tnc
->imvs
, this->connection_id
,
426 TNC_CONNECTION_STATE_CREATE
);
427 tnc
->imvs
->notify_connection_change(tnc
->imvs
, this->connection_id
,
428 TNC_CONNECTION_STATE_HANDSHAKE
);
431 data
= chunk_create(buf
, buflen
);
432 DBG1(DBG_TNC
, "received TNCCS batch (%u bytes) for Connection ID %u",
433 data
.len
, this->connection_id
);
434 DBG3(DBG_TNC
, "%B", &data
);
435 batch
= pb_tnc_batch_create_from_data(this->is_server
, data
);
436 status
= batch
->process(batch
, this->state_machine
);
438 if (status
!= FAILED
)
440 enumerator_t
*enumerator
;
442 pb_tnc_batch_type_t batch_type
;
445 batch_type
= batch
->get_type(batch
);
447 if (batch_type
== PB_BATCH_CRETRY
)
449 /* Send an SRETRY batch in response */
450 this->mutex
->lock(this->mutex
);
451 build_retry_batch(this);
452 this->mutex
->unlock(this->mutex
);
454 else if (batch_type
== PB_BATCH_SRETRY
)
456 /* Restart the measurements */
457 tnc
->imcs
->notify_connection_change(tnc
->imcs
,
458 this->connection_id
, TNC_CONNECTION_STATE_HANDSHAKE
);
459 this->send_msg
= TRUE
;
460 tnc
->imcs
->begin_handshake(tnc
->imcs
, this->connection_id
);
461 this->send_msg
= FALSE
;
464 enumerator
= batch
->create_msg_enumerator(batch
);
465 while (enumerator
->enumerate(enumerator
, &msg
))
467 handle_message(this, msg
);
470 enumerator
->destroy(enumerator
);
472 /* received an empty CLOSE batch from PB-TNC client */
473 if (this->is_server
&& batch_type
== PB_BATCH_CLOSE
&& empty
)
475 batch
->destroy(batch
);
476 if (this->fatal_error
)
478 DBG1(DBG_TNC
, "a fatal PB-TNC error occurred, "
479 "terminating connection");
488 this->send_msg
= TRUE
;
491 tnc
->imvs
->batch_ending(tnc
->imvs
, this->connection_id
);
495 tnc
->imcs
->batch_ending(tnc
->imcs
, this->connection_id
);
497 this->send_msg
= FALSE
;
503 this->fatal_error
= TRUE
;
504 this->mutex
->lock(this->mutex
);
505 change_batch_type(this, PB_BATCH_CLOSE
);
506 this->mutex
->unlock(this->mutex
);
507 /* fall through to add error messages to outbound batch */
509 enumerator
= batch
->create_error_enumerator(batch
);
510 while (enumerator
->enumerate(enumerator
, &msg
))
512 this->mutex
->lock(this->mutex
);
513 this->messages
->insert_last(this->messages
, msg
->get_ref(msg
));
514 this->mutex
->unlock(this->mutex
);
516 enumerator
->destroy(enumerator
);
522 batch
->destroy(batch
);
528 * Build a RESULT batch if a final recommendation is available
530 static void check_and_build_recommendation(private_tnccs_20_t
*this)
532 TNC_IMV_Action_Recommendation rec
;
533 TNC_IMV_Evaluation_Result eval
;
535 chunk_t reason
, language
;
536 enumerator_t
*enumerator
;
538 pb_access_recommendation_code_t pb_rec
;
540 if (!this->recs
->have_recommendation(this->recs
, &rec
, &eval
))
542 tnc
->imvs
->solicit_recommendation(tnc
->imvs
, this->connection_id
);
544 if (this->recs
->have_recommendation(this->recs
, &rec
, &eval
))
546 this->batch_type
= PB_BATCH_RESULT
;
548 msg
= pb_assessment_result_msg_create(eval
);
549 this->messages
->insert_last(this->messages
, msg
);
552 * Map IMV Action Recommendation codes to PB Access Recommendation codes
556 case TNC_IMV_ACTION_RECOMMENDATION_ALLOW
:
557 pb_rec
= PB_REC_ACCESS_ALLOWED
;
559 case TNC_IMV_ACTION_RECOMMENDATION_ISOLATE
:
560 pb_rec
= PB_REC_QUARANTINED
;
562 case TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS
:
563 case TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION
:
565 pb_rec
= PB_REC_ACCESS_DENIED
;
567 msg
= pb_access_recommendation_msg_create(pb_rec
);
568 this->messages
->insert_last(this->messages
, msg
);
570 enumerator
= this->recs
->create_reason_enumerator(this->recs
);
571 while (enumerator
->enumerate(enumerator
, &id
, &reason
, &language
))
573 msg
= pb_reason_string_msg_create(reason
, language
);
574 this->messages
->insert_last(this->messages
, msg
);
576 enumerator
->destroy(enumerator
);
580 METHOD(tls_t
, build
, status_t
,
581 private_tnccs_20_t
*this, void *buf
, size_t *buflen
, size_t *msglen
)
584 pb_tnc_state_t state
;
586 /* Initialize the connection */
587 if (!this->is_server
&& !this->connection_id
)
592 this->connection_id
= tnc
->tnccs
->create_connection(tnc
->tnccs
,
593 TNCCS_2_0
, (tnccs_t
*)this, _send_msg
,
594 &this->request_handshake_retry
,
595 this->max_msg_len
, NULL
);
596 if (!this->connection_id
)
601 /* Create PB-TNC Language Preference message */
602 pref_lang
= tnc
->imcs
->get_preferred_language(tnc
->imcs
);
603 msg
= pb_language_preference_msg_create(chunk_create(pref_lang
,
605 this->mutex
->lock(this->mutex
);
606 this->batch_type
= PB_BATCH_CDATA
;
607 this->messages
->insert_last(this->messages
, msg
);
608 this->mutex
->unlock(this->mutex
);
610 tnc
->imcs
->notify_connection_change(tnc
->imcs
, this->connection_id
,
611 TNC_CONNECTION_STATE_CREATE
);
612 tnc
->imcs
->notify_connection_change(tnc
->imcs
, this->connection_id
,
613 TNC_CONNECTION_STATE_HANDSHAKE
);
614 this->send_msg
= TRUE
;
615 tnc
->imcs
->begin_handshake(tnc
->imcs
, this->connection_id
);
616 this->send_msg
= FALSE
;
619 state
= this->state_machine
->get_state(this->state_machine
);
621 if (this->fatal_error
&& state
== PB_STATE_END
)
623 DBG1(DBG_TNC
, "a fatal PB-TNC error occurred, terminating connection");
627 /* Do not allow any asynchronous IMCs or IMVs to add additional messages */
628 this->mutex
->lock(this->mutex
);
630 if (this->request_handshake_retry
)
632 if (state
!= PB_STATE_INIT
)
634 build_retry_batch(this);
637 /* Reset the flag for the next handshake retry request */
638 this->request_handshake_retry
= FALSE
;
641 if (this->is_server
&& state
== PB_STATE_SERVER_WORKING
&&
642 this->recs
->have_recommendation(this->recs
, NULL
, NULL
))
644 check_and_build_recommendation(this);
647 if (this->batch_type
== PB_BATCH_NONE
)
649 if (this->is_server
&& state
== PB_STATE_SERVER_WORKING
)
651 if (this->state_machine
->get_empty_cdata(this->state_machine
))
653 check_and_build_recommendation(this);
657 DBG2(DBG_TNC
, "no recommendation available yet, "
658 "sending empty PB-TNC SDATA batch");
659 this->batch_type
= PB_BATCH_SDATA
;
665 * In the DECIDED state and if no CRETRY is under way,
666 * a PB-TNC client replies with an empty CLOSE batch.
668 if (state
== PB_STATE_DECIDED
)
670 this->batch_type
= PB_BATCH_CLOSE
;
675 if (this->batch_type
!= PB_BATCH_NONE
)
677 pb_tnc_batch_t
*batch
;
681 enumerator_t
*enumerator
;
683 if (this->state_machine
->send_batch(this->state_machine
, this->batch_type
))
685 batch
= pb_tnc_batch_create(this->is_server
, this->batch_type
,
686 min(this->max_batch_len
, *buflen
));
688 enumerator
= this->messages
->create_enumerator(this->messages
);
689 while (enumerator
->enumerate(enumerator
, &msg
))
691 if (batch
->add_msg(batch
, msg
))
693 this->messages
->remove_at(this->messages
, enumerator
);
700 enumerator
->destroy(enumerator
);
703 data
= batch
->get_encoding(batch
);
704 DBG1(DBG_TNC
, "sending PB-TNC %N batch (%d bytes) for Connection ID %u",
705 pb_tnc_batch_type_names
, this->batch_type
, data
.len
,
706 this->connection_id
);
707 DBG3(DBG_TNC
, "%B", &data
);
711 memcpy(buf
, data
.ptr
, *buflen
);
712 batch
->destroy(batch
);
714 msg_count
= this->messages
->get_count(this->messages
);
717 DBG2(DBG_TNC
, "queued %d PB-TNC message%s for next %N batch",
718 msg_count
, (msg_count
== 1) ?
"" : "s",
719 pb_tnc_batch_type_names
, this->batch_type
);
723 this->batch_type
= PB_BATCH_NONE
;
726 status
= ALREADY_DONE
;
730 change_batch_type(this, PB_BATCH_NONE
);
731 status
= INVALID_STATE
;
736 DBG1(DBG_TNC
, "no PB-TNC batch to send");
737 status
= INVALID_STATE
;
739 this->mutex
->unlock(this->mutex
);
744 METHOD(tls_t
, is_server
, bool,
745 private_tnccs_20_t
*this)
747 return this->is_server
;
750 METHOD(tls_t
, get_purpose
, tls_purpose_t
,
751 private_tnccs_20_t
*this)
753 return TLS_PURPOSE_EAP_TNC
;
756 METHOD(tls_t
, is_complete
, bool,
757 private_tnccs_20_t
*this)
759 TNC_IMV_Action_Recommendation rec
;
760 TNC_IMV_Evaluation_Result eval
;
762 if (this->recs
&& this->recs
->have_recommendation(this->recs
, &rec
, &eval
))
764 return tnc
->imvs
->enforce_recommendation(tnc
->imvs
, rec
, eval
);
772 METHOD(tls_t
, get_eap_msk
, chunk_t
,
773 private_tnccs_20_t
*this)
778 METHOD(tls_t
, destroy
, void,
779 private_tnccs_20_t
*this)
781 tnc
->tnccs
->remove_connection(tnc
->tnccs
, this->connection_id
,
783 this->state_machine
->destroy(this->state_machine
);
784 this->mutex
->destroy(this->mutex
);
785 this->messages
->destroy_offset(this->messages
,
786 offsetof(pb_tnc_msg_t
, destroy
));
793 tls_t
*tnccs_20_create(bool is_server
)
795 private_tnccs_20_t
*this;
801 .is_server
= _is_server
,
802 .get_purpose
= _get_purpose
,
803 .is_complete
= _is_complete
,
804 .get_eap_msk
= _get_eap_msk
,
807 .is_server
= is_server
,
808 .state_machine
= pb_tnc_state_machine_create(is_server
),
809 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
810 .messages
= linked_list_create(),
811 .max_batch_len
= lib
->settings
->get_int(lib
->settings
,
812 "%s.plugins.tnccs-20.max_batch_size", 65522,
814 .max_msg_len
= lib
->settings
->get_int(lib
->settings
,
815 "%s.plugins.tnccs-20.max_message_size", 65490,
819 return &this->public;