Added tnc/tnccs-20-fail-init and tnc/tnccs-20-fail-resp scenarios
[strongswan.git] / src / libtnccs / plugins / tnccs_20 / tnccs_20_server.c
1 /*
2 * Copyright (C) 2015 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "tnccs_20_server.h"
17 #include "messages/pb_tnc_msg.h"
18 #include "messages/ietf/pb_pa_msg.h"
19 #include "messages/ietf/pb_error_msg.h"
20 #include "messages/ietf/pb_assessment_result_msg.h"
21 #include "messages/ietf/pb_access_recommendation_msg.h"
22 #include "messages/ietf/pb_remediation_parameters_msg.h"
23 #include "messages/ietf/pb_reason_string_msg.h"
24 #include "messages/ietf/pb_language_preference_msg.h"
25 #include "messages/ita/pb_mutual_capability_msg.h"
26 #include "messages/ita/pb_noskip_test_msg.h"
27 #include "messages/tcg/pb_pdp_referral_msg.h"
28 #include "state_machine/pb_tnc_state_machine.h"
29
30 #include <tncif_names.h>
31 #include <tncif_pa_subtypes.h>
32
33 #include <tnc/tnc.h>
34 #include <tnc/tnccs/tnccs_manager.h>
35 #include <tnc/imv/imv_manager.h>
36
37 #include <threading/mutex.h>
38 #include <utils/debug.h>
39 #include <collections/linked_list.h>
40 #include <pen/pen.h>
41
42 typedef struct private_tnccs_20_server_t private_tnccs_20_server_t;
43
44 /**
45 * Private data of a tnccs_20_server_t object.
46 */
47 struct private_tnccs_20_server_t {
48
49 /**
50 * Public tnccs_20_server_t interface.
51 */
52 tnccs_20_server_t public;
53
54 /**
55 * PB-TNC State Machine
56 */
57 pb_tnc_state_machine_t *state_machine;
58
59 /**
60 * Connection ID assigned to this TNCCS connection
61 */
62 TNC_ConnectionID connection_id;
63
64 /**
65 * PB-TNC messages to be sent
66 */
67 linked_list_t *messages;
68
69 /**
70 * Type of PB-TNC batch being constructed
71 */
72 pb_tnc_batch_type_t batch_type;
73
74 /**
75 * Maximum PB-TNC batch size
76 */
77 size_t max_batch_len;
78
79 /**
80 * Mutex locking the batch in construction
81 */
82 mutex_t *mutex;
83
84 /**
85 * Flag set while processing
86 */
87 bool fatal_error;
88
89 /**
90 * Flag set by IMC/IMV RequestHandshakeRetry() function
91 */
92 bool request_handshake_retry;
93
94 /**
95 * SendMessage() by IMV only allowed if flag is set
96 */
97 bool send_msg;
98
99 /**
100 * Set of IMV recommendations
101 */
102 recommendations_t *recs;
103
104 /**
105 * TNC IF-T transport protocol for EAP methods
106 */
107 bool eap_transport;
108
109 /**
110 * Mutual PB-TNC protocol enabled
111 */
112 bool mutual;
113
114 /**
115 * Mutual Capability message sent
116 */
117 bool sent_mutual_capability;
118
119 };
120
121 /**
122 * The following two functions are shared with the tnccs_20_server class
123 */
124 extern void tnccs_20_handle_ietf_error_msg(pb_tnc_msg_t *msg,
125 bool *fatal_error);
126 extern bool tnccs_20_handle_ita_mutual_capability_msg(pb_tnc_msg_t *msg);
127
128 /**
129 * If the batch type changes then delete all accumulated PB-TNC messages
130 */
131 static void change_batch_type(private_tnccs_20_server_t *this,
132 pb_tnc_batch_type_t batch_type)
133 {
134 pb_tnc_msg_t *msg;
135
136 if (batch_type != this->batch_type)
137 {
138 if (this->batch_type != PB_BATCH_NONE)
139 {
140 DBG1(DBG_TNC, "cancelling PB-TNC %N batch",
141 pb_tnc_batch_type_names, this->batch_type);
142
143 while (this->messages->remove_last(this->messages,
144 (void**)&msg) == SUCCESS)
145 {
146 msg->destroy(msg);
147 }
148 }
149 this->batch_type = batch_type;
150 }
151 }
152
153 /**
154 * Handle a single PB-TNC IETF standard message according to its type
155 */
156 static void handle_ietf_message(private_tnccs_20_server_t *this, pb_tnc_msg_t *msg)
157 {
158 pen_type_t msg_type = msg->get_type(msg);
159
160 switch (msg_type.type)
161 {
162 case PB_MSG_EXPERIMENTAL:
163 /* nothing to do */
164 break;
165 case PB_MSG_PA:
166 {
167 pb_pa_msg_t *pa_msg;
168 pen_type_t msg_subtype;
169 u_int16_t imc_id, imv_id;
170 chunk_t msg_body;
171 bool excl;
172 enum_name_t *pa_subtype_names;
173
174 pa_msg = (pb_pa_msg_t*)msg;
175 msg_subtype = pa_msg->get_subtype(pa_msg);
176 msg_body = pa_msg->get_body(pa_msg);
177 imc_id = pa_msg->get_collector_id(pa_msg);
178 imv_id = pa_msg->get_validator_id(pa_msg);
179 excl = pa_msg->get_exclusive_flag(pa_msg);
180
181 pa_subtype_names = get_pa_subtype_names(msg_subtype.vendor_id);
182 if (pa_subtype_names)
183 {
184 DBG2(DBG_TNC, "handling PB-PA message type '%N/%N' 0x%06x/0x%08x",
185 pen_names, msg_subtype.vendor_id, pa_subtype_names,
186 msg_subtype.type, msg_subtype.vendor_id, msg_subtype.type);
187 }
188 else
189 {
190 DBG2(DBG_TNC, "handling PB-PA message type '%N' 0x%06x/0x%08x",
191 pen_names, msg_subtype.vendor_id, msg_subtype.vendor_id,
192 msg_subtype.type);
193 }
194 this->send_msg = TRUE;
195 tnc->imvs->receive_message(tnc->imvs, this->connection_id,
196 excl, msg_body.ptr, msg_body.len,
197 msg_subtype.vendor_id,
198 msg_subtype.type, imc_id, imv_id);
199 this->send_msg = FALSE;
200 break;
201 }
202 case PB_MSG_ERROR:
203 tnccs_20_handle_ietf_error_msg(msg, &this->fatal_error);
204 break;
205 case PB_MSG_LANGUAGE_PREFERENCE:
206 {
207 pb_language_preference_msg_t *lang_msg;
208 chunk_t lang;
209
210 lang_msg = (pb_language_preference_msg_t*)msg;
211 lang = lang_msg->get_language_preference(lang_msg);
212 DBG2(DBG_TNC, "setting language preference to '%.*s'",
213 (int)lang.len, lang.ptr);
214 this->recs->set_preferred_language(this->recs, lang);
215 break;
216 }
217 default:
218 break;
219 }
220 }
221
222 /**
223 * Handle a single PB-TNC ITA standard message according to its type
224 */
225 static void handle_ita_message(private_tnccs_20_server_t *this, pb_tnc_msg_t *msg)
226 {
227 pen_type_t msg_type = msg->get_type(msg);
228
229 switch (msg_type.type)
230 {
231 case PB_ITA_MSG_MUTUAL_CAPABILITY:
232 this->mutual = tnccs_20_handle_ita_mutual_capability_msg(msg);
233
234 /* Respond with PB-TNC Mutual Capability message if necessary */
235 if (this->mutual && !this->sent_mutual_capability)
236 {
237 msg = pb_mutual_capability_msg_create(PB_MUTUAL_HALF_DUPLEX);
238 this->mutex->lock(this->mutex);
239 this->messages->insert_last(this->messages, msg);
240 this->mutex->unlock(this->mutex);
241 this->sent_mutual_capability = TRUE;
242 }
243 break;
244 default:
245 break;
246 }
247 }
248
249 /**
250 * Handle a single PB-TNC message according to its type
251 */
252 static void handle_message(private_tnccs_20_server_t *this, pb_tnc_msg_t *msg)
253 {
254 pen_type_t msg_type = msg->get_type(msg);
255
256 switch (msg_type.vendor_id)
257 {
258 case PEN_IETF:
259 handle_ietf_message(this, msg);
260 break;
261 case PEN_ITA:
262 handle_ita_message(this, msg);
263 break;
264 default:
265 break;
266 }
267 }
268
269 /**
270 * Build an SRETRY batch
271 */
272 static void build_retry_batch(private_tnccs_20_server_t *this)
273 {
274 if (this->batch_type == PB_BATCH_SRETRY)
275 {
276 /* retry batch has already been selected */
277 return;
278 }
279 change_batch_type(this, PB_BATCH_SRETRY);
280
281 this->recs->clear_recommendation(this->recs);
282 tnc->imvs->notify_connection_change(tnc->imvs, this->connection_id,
283 TNC_CONNECTION_STATE_HANDSHAKE);
284 }
285
286 METHOD(tnccs_20_handler_t, process, status_t,
287 private_tnccs_20_server_t *this, pb_tnc_batch_t *batch)
288 {
289 pb_tnc_batch_type_t batch_type;
290 status_t status;
291
292 batch_type = batch->get_type(batch);
293
294 DBG1(DBG_TNC, "processing PB-TNC %N batch for Connection ID %d",
295 pb_tnc_batch_type_names, batch_type, this->connection_id);
296 status = batch->process(batch, this->state_machine);
297
298 if (status != FAILED)
299 {
300 enumerator_t *enumerator;
301 pb_tnc_msg_t *msg;
302 bool empty = TRUE;
303
304 if (batch_type == PB_BATCH_CRETRY)
305 {
306 /* Send an SRETRY batch in response */
307 this->mutex->lock(this->mutex);
308 build_retry_batch(this);
309 this->mutex->unlock(this->mutex);
310 }
311
312 enumerator = batch->create_msg_enumerator(batch);
313 while (enumerator->enumerate(enumerator, &msg))
314 {
315 handle_message(this, msg);
316 empty = FALSE;
317 }
318 enumerator->destroy(enumerator);
319
320 /* received a CLOSE batch from PB-TNC client */
321 if (batch_type == PB_BATCH_CLOSE)
322 {
323 return empty ? SUCCESS : FAILED;
324 }
325
326 this->send_msg = TRUE;
327 tnc->imvs->batch_ending(tnc->imvs, this->connection_id);
328 this->send_msg = FALSE;
329 }
330
331 switch (status)
332 {
333 case FAILED:
334 this->fatal_error = TRUE;
335 status = VERIFY_ERROR;
336 break;
337 case VERIFY_ERROR:
338 break;
339 case SUCCESS:
340 default:
341 status = NEED_MORE;
342 break;
343 }
344
345 return status;
346 }
347
348 /**
349 * Build a RESULT batch if a final recommendation is available
350 */
351 static void check_and_build_recommendation(private_tnccs_20_server_t *this)
352 {
353 TNC_IMV_Action_Recommendation rec;
354 TNC_IMV_Evaluation_Result eval;
355 TNC_ConnectionState state;
356 TNC_IMVID id;
357 chunk_t reason, language;
358 enumerator_t *enumerator;
359 pb_tnc_msg_t *msg;
360 pb_access_recommendation_code_t pb_rec;
361
362 if (!this->recs->have_recommendation(this->recs, &rec, &eval))
363 {
364 tnc->imvs->solicit_recommendation(tnc->imvs, this->connection_id);
365 }
366 if (this->recs->have_recommendation(this->recs, &rec, &eval))
367 {
368 this->batch_type = PB_BATCH_RESULT;
369
370 msg = pb_assessment_result_msg_create(eval);
371 this->messages->insert_last(this->messages, msg);
372
373 /**
374 * Map IMV Action Recommendation codes to PB Access Recommendation codes
375 * and communicate Access Recommendation to IMVs
376 */
377 switch (rec)
378 {
379 case TNC_IMV_ACTION_RECOMMENDATION_ALLOW:
380 state = TNC_CONNECTION_STATE_ACCESS_ALLOWED;
381 pb_rec = PB_REC_ACCESS_ALLOWED;
382 break;
383 case TNC_IMV_ACTION_RECOMMENDATION_ISOLATE:
384 state = TNC_CONNECTION_STATE_ACCESS_ISOLATED;
385 pb_rec = PB_REC_QUARANTINED;
386 break;
387 case TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS:
388 case TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION:
389 default:
390 state = TNC_CONNECTION_STATE_ACCESS_NONE;
391 pb_rec = PB_REC_ACCESS_DENIED;
392 }
393 tnc->imvs->notify_connection_change(tnc->imvs, this->connection_id,
394 state);
395
396 msg = pb_access_recommendation_msg_create(pb_rec);
397 this->messages->insert_last(this->messages, msg);
398
399 enumerator = this->recs->create_reason_enumerator(this->recs);
400 while (enumerator->enumerate(enumerator, &id, &reason, &language))
401 {
402 msg = pb_reason_string_msg_create(reason, language);
403 this->messages->insert_last(this->messages, msg);
404 }
405 enumerator->destroy(enumerator);
406 }
407 }
408
409 METHOD(tnccs_20_handler_t, build, status_t,
410 private_tnccs_20_server_t *this, void *buf, size_t *buflen, size_t *msglen)
411 {
412 status_t status;
413 pb_tnc_state_t state;
414
415 state = this->state_machine->get_state(this->state_machine);
416
417 if (this->fatal_error && state == PB_STATE_END)
418 {
419 DBG1(DBG_TNC, "a fatal PB-TNC error occurred, terminating connection");
420 return FAILED;
421 }
422
423 /* Do not allow any asynchronous IMVs to add additional messages */
424 this->mutex->lock(this->mutex);
425
426 if (this->request_handshake_retry)
427 {
428 if (state != PB_STATE_INIT)
429 {
430 build_retry_batch(this);
431 }
432
433 /* Reset the flag for the next handshake retry request */
434 this->request_handshake_retry = FALSE;
435 }
436
437 if (state == PB_STATE_SERVER_WORKING &&
438 this->recs->have_recommendation(this->recs, NULL, NULL))
439 {
440 check_and_build_recommendation(this);
441 }
442
443 if (this->batch_type == PB_BATCH_NONE)
444 {
445 if (state == PB_STATE_SERVER_WORKING)
446 {
447 if (this->state_machine->get_empty_cdata(this->state_machine))
448 {
449 check_and_build_recommendation(this);
450 }
451 else
452 {
453 DBG2(DBG_TNC, "no recommendation available yet, "
454 "sending empty PB-TNC SDATA batch");
455 this->batch_type = PB_BATCH_SDATA;
456 }
457 }
458 }
459
460 if (this->batch_type != PB_BATCH_NONE)
461 {
462 pb_tnc_batch_t *batch;
463 pb_tnc_msg_t *msg;
464 chunk_t data;
465 int msg_count;
466 enumerator_t *enumerator;
467
468 if (this->state_machine->send_batch(this->state_machine, this->batch_type))
469 {
470 batch = pb_tnc_batch_create(TRUE, this->batch_type,
471 min(this->max_batch_len, *buflen));
472
473 enumerator = this->messages->create_enumerator(this->messages);
474 while (enumerator->enumerate(enumerator, &msg))
475 {
476 if (batch->add_msg(batch, msg))
477 {
478 this->messages->remove_at(this->messages, enumerator);
479 }
480 else
481 {
482 break;
483 }
484 }
485 enumerator->destroy(enumerator);
486
487 batch->build(batch);
488 data = batch->get_encoding(batch);
489 DBG1(DBG_TNC, "sending PB-TNC %N batch (%d bytes) for Connection ID %u",
490 pb_tnc_batch_type_names, this->batch_type, data.len,
491 this->connection_id);
492 DBG3(DBG_TNC, "%B", &data);
493
494 *buflen = data.len;
495 *msglen = 0;
496 memcpy(buf, data.ptr, *buflen);
497 batch->destroy(batch);
498
499 msg_count = this->messages->get_count(this->messages);
500 if (msg_count)
501 {
502 DBG2(DBG_TNC, "queued %d PB-TNC message%s for next %N batch",
503 msg_count, (msg_count == 1) ? "" : "s",
504 pb_tnc_batch_type_names, this->batch_type);
505 }
506 else
507 {
508 this->batch_type = PB_BATCH_NONE;
509 }
510
511 status = ALREADY_DONE;
512 }
513 else
514 {
515 change_batch_type(this, PB_BATCH_NONE);
516 status = INVALID_STATE;
517 }
518 }
519 else
520 {
521 DBG1(DBG_TNC, "no PB-TNC batch to send");
522 status = INVALID_STATE;
523 }
524 this->mutex->unlock(this->mutex);
525
526 return status;
527 }
528
529 METHOD(tnccs_20_handler_t, begin_handshake, void,
530 private_tnccs_20_server_t *this, bool mutual)
531 {
532 pb_tnc_msg_t *msg;
533 identification_t *pdp_server;
534 u_int16_t *pdp_port;
535
536 tnc->imvs->notify_connection_change(tnc->imvs, this->connection_id,
537 TNC_CONNECTION_STATE_HANDSHAKE);
538
539 /* Send a PB-TNC TCG PDP Referral message if PDP is known */
540 pdp_server = (identification_t*)lib->get(lib, "pt-tls-server");
541 pdp_port = (u_int16_t*)lib->get(lib, "pt-tls-port");
542
543 if (this->eap_transport && pdp_server && pdp_port)
544 {
545 msg = pb_pdp_referral_msg_create_from_fqdn(
546 pdp_server->get_encoding(pdp_server), *pdp_port);
547 this->mutex->lock(this->mutex);
548 this->messages->insert_last(this->messages, msg);
549 this->mutex->unlock(this->mutex);
550 }
551
552 /* Send a PB-Noskip-Test message for testing purposes */
553 if (lib->settings->get_bool(lib->settings,
554 "%s.plugins.tnccs-20.tests.pb_tnc_noskip", FALSE, lib->ns))
555 {
556 msg = pb_noskip_test_msg_create();
557 this->mutex->lock(this->mutex);
558 this->messages->insert_last(this->messages, msg);
559 this->mutex->unlock(this->mutex);
560 }
561 }
562
563 METHOD(tnccs_20_handler_t, get_send_flag, bool,
564 private_tnccs_20_server_t *this)
565 {
566 return this->send_msg;
567 }
568
569 METHOD(tnccs_20_handler_t, get_mutual, bool,
570 private_tnccs_20_server_t *this)
571 {
572 return this->mutual;
573 }
574
575 METHOD(tnccs_20_handler_t, get_state, pb_tnc_state_t,
576 private_tnccs_20_server_t *this)
577 {
578 return this->state_machine->get_state(this->state_machine);
579 }
580
581 METHOD(tnccs_20_handler_t, add_msg, void,
582 private_tnccs_20_server_t *this, pb_tnc_msg_t *msg)
583 {
584 /* adding PA message to SDATA batch only */
585 this->mutex->lock(this->mutex);
586 if (this->batch_type == PB_BATCH_NONE)
587 {
588 this->batch_type = PB_BATCH_SDATA;
589 }
590 if (this->batch_type == PB_BATCH_SDATA)
591 {
592 this->messages->insert_last(this->messages, msg);
593 }
594 else
595 {
596 msg->destroy(msg);
597 }
598 this->mutex->unlock(this->mutex);
599 }
600
601 METHOD(tnccs_20_handler_t, handle_errors, void,
602 private_tnccs_20_server_t *this, pb_tnc_batch_t *batch,
603 bool fatal_header_error)
604 {
605 pb_tnc_msg_t *msg;
606 enumerator_t *enumerator;
607
608 if (fatal_header_error || this->fatal_error)
609 {
610 this->mutex->lock(this->mutex);
611 change_batch_type(this, PB_BATCH_CLOSE);
612 this->mutex->unlock(this->mutex);
613 }
614
615 enumerator = batch->create_error_enumerator(batch);
616 while (enumerator->enumerate(enumerator, &msg))
617 {
618 this->mutex->lock(this->mutex);
619 this->messages->insert_last(this->messages, msg->get_ref(msg));
620 this->mutex->unlock(this->mutex);
621 }
622 enumerator->destroy(enumerator);
623 }
624
625 METHOD(tnccs_20_handler_t, destroy, void,
626 private_tnccs_20_server_t *this)
627 {
628 if (this->connection_id)
629 {
630 tnc->tnccs->remove_connection(tnc->tnccs, this->connection_id, TRUE);
631 }
632 this->state_machine->destroy(this->state_machine);
633 this->mutex->destroy(this->mutex);
634 this->messages->destroy_offset(this->messages,
635 offsetof(pb_tnc_msg_t, destroy));
636 free(this);
637 }
638
639 METHOD(tnccs_20_server_t, have_recommendation, bool,
640 private_tnccs_20_server_t *this, TNC_IMV_Action_Recommendation *rec,
641 TNC_IMV_Evaluation_Result *eval)
642 {
643 return this->recs->have_recommendation(this->recs, rec, eval);
644 }
645
646 /**
647 * See header
648 */
649 tnccs_20_handler_t* tnccs_20_server_create(tnccs_t *tnccs,
650 tnccs_send_message_t send_msg,
651 size_t max_batch_len,
652 size_t max_msg_len,
653 bool eap_transport)
654 {
655 private_tnccs_20_server_t *this;
656
657 INIT(this,
658 .public = {
659 .handler = {
660 .process = _process,
661 .build = _build,
662 .begin_handshake = _begin_handshake,
663 .get_send_flag = _get_send_flag,
664 .get_mutual = _get_mutual,
665 .get_state = _get_state,
666 .add_msg = _add_msg,
667 .handle_errors = _handle_errors,
668 .destroy = _destroy,
669 },
670 .have_recommendation = _have_recommendation,
671 },
672 .state_machine = pb_tnc_state_machine_create(TRUE),
673 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
674 .messages = linked_list_create(),
675 .batch_type = PB_BATCH_SDATA,
676 .max_batch_len = max_batch_len,
677 .eap_transport = eap_transport,
678 );
679
680 this->connection_id = tnc->tnccs->create_connection(tnc->tnccs,
681 TNCCS_2_0, tnccs, send_msg,
682 &this->request_handshake_retry,
683 max_msg_len, &this->recs);
684 if (!this->connection_id)
685 {
686 destroy(this);
687 return NULL;
688 }
689 tnc->imvs->notify_connection_change(tnc->imvs, this->connection_id,
690 TNC_CONNECTION_STATE_CREATE);
691
692 return &this->public.handler;
693 }