2 * Copyright (C) 2010 Sansar Choinyanbuu
3 * Copyright (C) 2010 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"
31 #include <threading/mutex.h>
32 #include <tnc/tnccs/tnccs.h>
35 typedef struct private_tnccs_20_t private_tnccs_20_t
;
38 * Private data of a tnccs_20_t object.
40 struct private_tnccs_20_t
{
43 * Public tls_t interface.
48 * TNCC if TRUE, TNCS if FALSE
53 * PB-TNC State Machine
55 pb_tnc_state_machine_t
*state_machine
;
58 * Connection ID assigned to this TNCCS connection
60 TNC_ConnectionID connection_id
;
63 * PB-TNC batch being constructed
65 pb_tnc_batch_t
*batch
;
68 * Mutex locking the batch in construction
73 * Flag set while processing
78 * Flag set by IMC/IMV RequestHandshakeRetry() function
80 bool request_handshake_retry
;
83 * SendMessage() by IMC/IMV only allowed if flag is set
88 * Set of IMV recommendations (TNC Server only)
90 recommendations_t
*recs
;
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
,
97 TNC_MessageType msg_type
)
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
;
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
;
112 msg_sub_type
= msg_type
& TNC_SUBTYPE_ANY
;
113 msg_vendor_id
= (msg_type
>> 8) & TNC_VENDORID_ANY
;
115 pb_tnc_msg
= pb_pa_msg_create(msg_vendor_id
, msg_sub_type
, imc_id
, imv_id
,
116 chunk_create(msg
, msg_len
));
118 if (msg_vendor_id
== PEN_IETF
)
120 DBG2(DBG_TNC
, "creating PB-PA message type '%N/%N' 0x%06x/0x%02x",
121 pen_names
, msg_vendor_id
, pa_tnc_subtype_names
, msg_sub_type
,
122 msg_vendor_id
, msg_sub_type
);
126 DBG2(DBG_TNC
, "creating PB-PA message type '%N' 0x%06x/0x%02x",
127 pen_names
, msg_vendor_id
, msg_vendor_id
, msg_sub_type
);
130 /* adding PA message to SDATA or CDATA batch only */
131 batch_type
= this->is_server ? PB_BATCH_SDATA
: PB_BATCH_CDATA
;
132 this->mutex
->lock(this->mutex
);
135 this->batch
= pb_tnc_batch_create(this->is_server
, batch_type
);
137 if (this->batch
->get_type(this->batch
) == batch_type
)
139 this->batch
->add_msg(this->batch
, pb_tnc_msg
);
143 pb_tnc_msg
->destroy(pb_tnc_msg
);
145 this->mutex
->unlock(this->mutex
);
146 return TNC_RESULT_SUCCESS
;
150 * Handle a single PB-TNC message according to its type
152 static void handle_message(private_tnccs_20_t
*this, pb_tnc_msg_t
*msg
)
154 switch (msg
->get_type(msg
))
156 case PB_MSG_EXPERIMENTAL
:
162 TNC_MessageType msg_type
;
163 u_int32_t vendor_id
, subtype
;
166 pa_msg
= (pb_pa_msg_t
*)msg
;
167 vendor_id
= pa_msg
->get_vendor_id(pa_msg
, &subtype
);
168 msg_type
= (vendor_id
<< 8) | (subtype
& 0xff);
169 msg_body
= pa_msg
->get_body(pa_msg
);
171 if (vendor_id
== PEN_IETF
)
173 DBG2(DBG_TNC
, "handling PB-PA message type '%N/%N' 0x%06x/0x%02x",
174 pen_names
, vendor_id
, pa_tnc_subtype_names
, subtype
,
179 DBG2(DBG_TNC
, "handling PB-PA message type '%N' 0x%06x/0x%02x",
180 pen_names
, vendor_id
, vendor_id
, subtype
);
183 this->send_msg
= TRUE
;
186 charon
->imvs
->receive_message(charon
->imvs
,
187 this->connection_id
, msg_body
.ptr
, msg_body
.len
, msg_type
);
191 charon
->imcs
->receive_message(charon
->imcs
,
192 this->connection_id
, msg_body
.ptr
, msg_body
.len
,msg_type
);
194 this->send_msg
= FALSE
;
197 case PB_MSG_ASSESSMENT_RESULT
:
199 pb_assessment_result_msg_t
*assess_msg
;
202 assess_msg
= (pb_assessment_result_msg_t
*)msg
;
203 result
= assess_msg
->get_assessment_result(assess_msg
);
204 DBG1(DBG_TNC
, "PB-TNC assessment result is '%N'",
205 TNC_IMV_Evaluation_Result_names
, result
);
208 case PB_MSG_ACCESS_RECOMMENDATION
:
210 pb_access_recommendation_msg_t
*rec_msg
;
211 pb_access_recommendation_code_t rec
;
212 TNC_ConnectionState state
= TNC_CONNECTION_STATE_ACCESS_NONE
;
214 rec_msg
= (pb_access_recommendation_msg_t
*)msg
;
215 rec
= rec_msg
->get_access_recommendation(rec_msg
);
216 DBG1(DBG_TNC
, "PB-TNC access recommendation is '%N'",
217 pb_access_recommendation_code_names
, rec
);
220 case PB_REC_ACCESS_ALLOWED
:
221 state
= TNC_CONNECTION_STATE_ACCESS_ALLOWED
;
223 case PB_REC_ACCESS_DENIED
:
224 state
= TNC_CONNECTION_STATE_ACCESS_NONE
;
226 case PB_REC_QUARANTINED
:
227 state
= TNC_CONNECTION_STATE_ACCESS_ISOLATED
;
229 charon
->imcs
->notify_connection_change(charon
->imcs
,
230 this->connection_id
, state
);
233 case PB_MSG_REMEDIATION_PARAMETERS
:
235 /* TODO : Remediation parameters message processing */
240 pb_error_msg_t
*err_msg
;
243 u_int16_t error_code
;
245 err_msg
= (pb_error_msg_t
*)msg
;
246 fatal
= err_msg
->get_fatal_flag(err_msg
);
247 vendor_id
= err_msg
->get_vendor_id(err_msg
);
248 error_code
= err_msg
->get_error_code(err_msg
);
252 this->fatal_error
= TRUE
;
255 if (vendor_id
== PEN_IETF
)
259 case PB_ERROR_INVALID_PARAMETER
:
260 case PB_ERROR_UNSUPPORTED_MANDATORY_MSG
:
261 DBG1(DBG_TNC
, "received %s PB-TNC error '%N' "
263 fatal ?
"fatal" : "non-fatal",
264 pb_tnc_error_code_names
, error_code
,
265 err_msg
->get_offset(err_msg
));
267 case PB_ERROR_VERSION_NOT_SUPPORTED
:
268 DBG1(DBG_TNC
, "received %s PB-TNC error '%N' "
269 "caused by bad version 0x%02x",
270 fatal ?
"fatal" : "non-fatal",
271 pb_tnc_error_code_names
, error_code
,
272 err_msg
->get_bad_version(err_msg
));
274 case PB_ERROR_UNEXPECTED_BATCH_TYPE
:
275 case PB_ERROR_LOCAL_ERROR
:
277 DBG1(DBG_TNC
, "received %s PB-TNC error '%N'",
278 fatal ?
"fatal" : "non-fatal",
279 pb_tnc_error_code_names
, error_code
);
285 DBG1(DBG_TNC
, "received %s PB-TNC error (%u) "
286 "with Vendor ID 0x%06x",
287 fatal ?
"fatal" : "non-fatal",
288 error_code
, vendor_id
);
292 case PB_MSG_LANGUAGE_PREFERENCE
:
294 pb_language_preference_msg_t
*lang_msg
;
297 lang_msg
= (pb_language_preference_msg_t
*)msg
;
298 lang
= lang_msg
->get_language_preference(lang_msg
);
300 DBG2(DBG_TNC
, "setting language preference to '%.*s'",
302 this->recs
->set_preferred_language(this->recs
, lang
);
305 case PB_MSG_REASON_STRING
:
307 pb_reason_string_msg_t
*reason_msg
;
308 chunk_t reason_string
, language_code
;
310 reason_msg
= (pb_reason_string_msg_t
*)msg
;
311 reason_string
= reason_msg
->get_reason_string(reason_msg
);
312 language_code
= reason_msg
->get_language_code(reason_msg
);
313 DBG2(DBG_TNC
, "reason string is '%.*s", reason_string
.len
,
315 DBG2(DBG_TNC
, "language code is '%.*s", language_code
.len
,
325 * Build a CRETRY or SRETRY batch
327 static void build_retry_batch(private_tnccs_20_t
*this)
329 pb_tnc_batch_type_t batch_retry_type
;
331 batch_retry_type
= this->is_server ? PB_BATCH_SRETRY
: PB_BATCH_CRETRY
;
334 if (this->batch
->get_type(this->batch
) == batch_retry_type
)
336 /* retry batch has already been created */
339 DBG1(DBG_TNC
, "cancelling PB-TNC %N batch",
340 pb_tnc_batch_type_names
, this->batch
->get_type(this->batch
));
341 this->batch
->destroy(this->batch
);
343 this->batch
= pb_tnc_batch_create(this->is_server
, batch_retry_type
);
346 METHOD(tls_t
, process
, status_t
,
347 private_tnccs_20_t
*this, void *buf
, size_t buflen
)
350 pb_tnc_batch_t
*batch
;
352 enumerator_t
*enumerator
;
355 if (this->is_server
&& !this->connection_id
)
357 this->connection_id
= charon
->tnccs
->create_connection(charon
->tnccs
,
358 (tnccs_t
*)this, _send_msg
,
359 &this->request_handshake_retry
, &this->recs
);
360 if (!this->connection_id
)
364 charon
->imvs
->notify_connection_change(charon
->imvs
,
365 this->connection_id
, TNC_CONNECTION_STATE_CREATE
);
366 charon
->imvs
->notify_connection_change(charon
->imvs
,
367 this->connection_id
, TNC_CONNECTION_STATE_HANDSHAKE
);
370 data
= chunk_create(buf
, buflen
);
371 DBG1(DBG_TNC
, "received TNCCS batch (%u bytes) for Connection ID %u",
372 data
.len
, this->connection_id
);
373 DBG3(DBG_TNC
, "%B", &data
);
374 batch
= pb_tnc_batch_create_from_data(this->is_server
, data
);
375 status
= batch
->process(batch
, this->state_machine
);
377 if (status
!= FAILED
)
379 enumerator_t
*enumerator
;
381 pb_tnc_batch_type_t batch_type
;
384 batch_type
= batch
->get_type(batch
);
386 if (batch_type
== PB_BATCH_CRETRY
)
388 /* Send an SRETRY batch in response */
389 this->mutex
->lock(this->mutex
);
390 build_retry_batch(this);
391 this->mutex
->unlock(this->mutex
);
393 else if (batch_type
== PB_BATCH_SRETRY
)
395 /* Restart the measurements */
396 charon
->imcs
->notify_connection_change(charon
->imcs
,
397 this->connection_id
, TNC_CONNECTION_STATE_HANDSHAKE
);
398 this->send_msg
= TRUE
;
399 charon
->imcs
->begin_handshake(charon
->imcs
, this->connection_id
);
400 this->send_msg
= FALSE
;
403 enumerator
= batch
->create_msg_enumerator(batch
);
404 while (enumerator
->enumerate(enumerator
, &msg
))
406 handle_message(this, msg
);
409 enumerator
->destroy(enumerator
);
411 /* received an empty CLOSE batch from PB-TNC client */
412 if (this->is_server
&& batch_type
== PB_BATCH_CLOSE
&& empty
)
414 batch
->destroy(batch
);
415 if (this->fatal_error
)
417 DBG1(DBG_TNC
, "a fatal PB-TNC error occurred, "
418 "terminating connection");
427 this->send_msg
= TRUE
;
430 charon
->imvs
->batch_ending(charon
->imvs
, this->connection_id
);
434 charon
->imcs
->batch_ending(charon
->imcs
, this->connection_id
);
436 this->send_msg
= FALSE
;
442 this->fatal_error
= TRUE
;
443 this->mutex
->lock(this->mutex
);
446 DBG1(DBG_TNC
, "cancelling PB-TNC %N batch",
447 pb_tnc_batch_type_names
, this->batch
->get_type(this->batch
));
448 this->batch
->destroy(this->batch
);
450 this->batch
= pb_tnc_batch_create(this->is_server
, PB_BATCH_CLOSE
);
451 this->mutex
->unlock(this->mutex
);
452 /* fall through to add error messages to outbound batch */
454 enumerator
= batch
->create_error_enumerator(batch
);
455 while (enumerator
->enumerate(enumerator
, &msg
))
457 this->mutex
->lock(this->mutex
);
458 this->batch
->add_msg(this->batch
, msg
->get_ref(msg
));
459 this->mutex
->unlock(this->mutex
);
461 enumerator
->destroy(enumerator
);
467 batch
->destroy(batch
);
473 * Build a RESULT batch if a final recommendation is available
475 static void check_and_build_recommendation(private_tnccs_20_t
*this)
477 TNC_IMV_Action_Recommendation rec
;
478 TNC_IMV_Evaluation_Result eval
;
480 chunk_t reason
, language
;
481 enumerator_t
*enumerator
;
483 pb_access_recommendation_code_t pb_rec
;
485 if (!this->recs
->have_recommendation(this->recs
, &rec
, &eval
))
487 charon
->imvs
->solicit_recommendation(charon
->imvs
, this->connection_id
);
489 if (this->recs
->have_recommendation(this->recs
, &rec
, &eval
))
491 this->batch
= pb_tnc_batch_create(this->is_server
, PB_BATCH_RESULT
);
493 msg
= pb_assessment_result_msg_create(eval
);
494 this->batch
->add_msg(this->batch
, msg
);
497 * Map IMV Action Recommendation codes to PB Access Recommendation codes
501 case TNC_IMV_ACTION_RECOMMENDATION_ALLOW
:
502 pb_rec
= PB_REC_ACCESS_ALLOWED
;
504 case TNC_IMV_ACTION_RECOMMENDATION_ISOLATE
:
505 pb_rec
= PB_REC_QUARANTINED
;
507 case TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS
:
508 case TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION
:
510 pb_rec
= PB_REC_ACCESS_DENIED
;
512 msg
= pb_access_recommendation_msg_create(pb_rec
);
513 this->batch
->add_msg(this->batch
, msg
);
515 enumerator
= this->recs
->create_reason_enumerator(this->recs
);
516 while (enumerator
->enumerate(enumerator
, &id
, &reason
, &language
))
518 msg
= pb_reason_string_msg_create(reason
, language
);
519 this->batch
->add_msg(this->batch
, msg
);
521 enumerator
->destroy(enumerator
);
525 METHOD(tls_t
, build
, status_t
,
526 private_tnccs_20_t
*this, void *buf
, size_t *buflen
, size_t *msglen
)
529 pb_tnc_state_t state
;
531 /* Initialize the connection */
532 if (!this->is_server
&& !this->connection_id
)
537 this->connection_id
= charon
->tnccs
->create_connection(charon
->tnccs
,
538 (tnccs_t
*)this, _send_msg
,
539 &this->request_handshake_retry
, NULL
);
540 if (!this->connection_id
)
545 /* Create PB-TNC Language Preference message */
546 pref_lang
= charon
->imcs
->get_preferred_language(charon
->imcs
);
547 msg
= pb_language_preference_msg_create(chunk_create(pref_lang
,
549 this->mutex
->lock(this->mutex
);
550 this->batch
= pb_tnc_batch_create(this->is_server
, PB_BATCH_CDATA
);
551 this->batch
->add_msg(this->batch
, msg
);
552 this->mutex
->unlock(this->mutex
);
554 charon
->imcs
->notify_connection_change(charon
->imcs
,
555 this->connection_id
, TNC_CONNECTION_STATE_CREATE
);
556 charon
->imcs
->notify_connection_change(charon
->imcs
,
557 this->connection_id
, TNC_CONNECTION_STATE_HANDSHAKE
);
558 this->send_msg
= TRUE
;
559 charon
->imcs
->begin_handshake(charon
->imcs
, this->connection_id
);
560 this->send_msg
= FALSE
;
563 state
= this->state_machine
->get_state(this->state_machine
);
565 if (this->is_server
&& this->fatal_error
&& state
== PB_STATE_END
)
567 DBG1(DBG_TNC
, "a fatal PB-TNC error occurred, terminating connection");
571 /* Do not allow any asynchronous IMCs or IMVs to add additional messages */
572 this->mutex
->lock(this->mutex
);
574 if (this->request_handshake_retry
)
576 if (state
!= PB_STATE_INIT
)
578 build_retry_batch(this);
581 /* Reset the flag for the next handshake retry request */
582 this->request_handshake_retry
= FALSE
;
589 if (state
== PB_STATE_SERVER_WORKING
)
591 check_and_build_recommendation(this);
597 * if the DECIDED state has been reached and no CRETRY is under way
598 * or if a CLOSE batch with error messages has been received,
599 * a PB-TNC client replies with an empty CLOSE batch.
601 if (state
== PB_STATE_DECIDED
|| state
== PB_STATE_END
)
603 this->batch
= pb_tnc_batch_create(this->is_server
, PB_BATCH_CLOSE
);
610 pb_tnc_batch_type_t batch_type
;
613 batch_type
= this->batch
->get_type(this->batch
);
615 if (this->state_machine
->send_batch(this->state_machine
, batch_type
))
617 this->batch
->build(this->batch
);
618 data
= this->batch
->get_encoding(this->batch
);
619 DBG1(DBG_TNC
, "sending PB-TNC %N batch (%d bytes) for Connection ID %u",
620 pb_tnc_batch_type_names
, batch_type
, data
.len
,
621 this->connection_id
);
622 DBG3(DBG_TNC
, "%B", &data
);
625 if (data
.len
> *buflen
)
627 DBG1(DBG_TNC
, "fragmentation of PB-TNC batch not supported yet");
633 memcpy(buf
, data
.ptr
, *buflen
);
634 status
= ALREADY_DONE
;
638 DBG1(DBG_TNC
, "cancelling unexpected PB-TNC batch type: %N",
639 pb_tnc_batch_type_names
, batch_type
);
640 status
= INVALID_STATE
;
643 this->batch
->destroy(this->batch
);
648 DBG1(DBG_TNC
, "no PB-TNC batch to send");
649 status
= INVALID_STATE
;
651 this->mutex
->unlock(this->mutex
);
656 METHOD(tls_t
, is_server
, bool,
657 private_tnccs_20_t
*this)
659 return this->is_server
;
662 METHOD(tls_t
, get_purpose
, tls_purpose_t
,
663 private_tnccs_20_t
*this)
665 return TLS_PURPOSE_EAP_TNC
;
668 METHOD(tls_t
, is_complete
, bool,
669 private_tnccs_20_t
*this)
671 TNC_IMV_Action_Recommendation rec
;
672 TNC_IMV_Evaluation_Result eval
;
674 if (this->recs
&& this->recs
->have_recommendation(this->recs
, &rec
, &eval
))
676 return charon
->imvs
->enforce_recommendation(charon
->imvs
, rec
, eval
);
684 METHOD(tls_t
, get_eap_msk
, chunk_t
,
685 private_tnccs_20_t
*this)
690 METHOD(tls_t
, destroy
, void,
691 private_tnccs_20_t
*this)
693 charon
->tnccs
->remove_connection(charon
->tnccs
, this->connection_id
,
695 this->state_machine
->destroy(this->state_machine
);
696 this->mutex
->destroy(this->mutex
);
697 DESTROY_IF(this->batch
);
704 tls_t
*tnccs_20_create(bool is_server
)
706 private_tnccs_20_t
*this;
712 .is_server
= _is_server
,
713 .get_purpose
= _get_purpose
,
714 .is_complete
= _is_complete
,
715 .get_eap_msk
= _get_eap_msk
,
718 .is_server
= is_server
,
719 .state_machine
= pb_tnc_state_machine_create(is_server
),
720 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
723 return &this->public;