moved tnc_imv plugin to libtnccs thanks to recommendation callback function
[strongswan.git] / src / libtnccs / plugins / tnccs_11 / tnccs_11.c
1 /*
2 * Copyright (C) 2010-2013 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_11.h"
17 #include "batch/tnccs_batch.h"
18 #include "messages/tnccs_msg.h"
19 #include "messages/imc_imv_msg.h"
20 #include "messages/tnccs_error_msg.h"
21 #include "messages/tnccs_preferred_language_msg.h"
22 #include "messages/tnccs_reason_strings_msg.h"
23 #include "messages/tnccs_recommendation_msg.h"
24
25 #include <tncif_names.h>
26 #include <tncif_pa_subtypes.h>
27
28 #include <tnc/tnc.h>
29 #include <tnc/imc/imc_manager.h>
30 #include <tnc/imv/imv_manager.h>
31 #include <tnc/tnccs/tnccs.h>
32 #include <tnc/tnccs/tnccs_manager.h>
33
34 #include <utils/debug.h>
35 #include <threading/mutex.h>
36
37 typedef struct private_tnccs_11_t private_tnccs_11_t;
38
39 /**
40 * Private data of a tnccs_11_t object.
41 */
42 struct private_tnccs_11_t {
43
44 /**
45 * Public tnccs_t interface.
46 */
47 tnccs_t public;
48
49 /**
50 * TNCC if TRUE, TNCS if FALSE
51 */
52 bool is_server;
53
54 /**
55 * Server identity
56 */
57 identification_t *server;
58
59 /**
60 * Client identity
61 */
62 identification_t *peer;
63
64 /**
65 * Underlying TNC IF-T transport protocol
66 */
67 tnc_ift_type_t transport;
68
69 /**
70 * Type of TNC client authentication
71 */
72 u_int32_t auth_type;
73
74 /**
75 * Connection ID assigned to this TNCCS connection
76 */
77 TNC_ConnectionID connection_id;
78
79 /**
80 * Last TNCCS batch ID
81 */
82 int batch_id;
83
84 /**
85 * TNCCS batch being constructed
86 */
87 tnccs_batch_t *batch;
88
89 /**
90 * Maximum PA-TNC message size
91 */
92 size_t max_msg_len;
93
94 /**
95 * Mutex locking the batch in construction
96 */
97 mutex_t *mutex;
98
99 /**
100 * Flag set while processing
101 */
102 bool fatal_error;
103
104 /**
105 * Flag set by TNCCS-Recommendation message
106 */
107 bool delete_state;
108
109 /**
110 * SendMessage() by IMC/IMV only allowed if flag is set
111 */
112 bool send_msg;
113
114 /**
115 * Flag set by IMC/IMV RequestHandshakeRetry() function
116 */
117 bool request_handshake_retry;
118
119 /**
120 * Set of IMV recommendations (TNC Server only)
121 */
122 recommendations_t *recs;
123
124 /**
125 * Callback function to communicate recommendation (TNC Server only)
126 */
127 tnccs_cb_t callback;
128
129 };
130
131 METHOD(tnccs_t, send_msg, TNC_Result,
132 private_tnccs_11_t* this, TNC_IMCID imc_id, TNC_IMVID imv_id,
133 TNC_UInt32 msg_flags,
134 TNC_BufferReference msg,
135 TNC_UInt32 msg_len,
136 TNC_VendorID msg_vid,
137 TNC_MessageSubtype msg_subtype)
138 {
139 tnccs_msg_t *tnccs_msg;
140 TNC_MessageType msg_type;
141 enum_name_t *pa_subtype_names;
142
143 if (!this->send_msg)
144 {
145 DBG1(DBG_TNC, "%s %u not allowed to call SendMessage()",
146 this->is_server ? "IMV" : "IMC",
147 this->is_server ? imv_id : imc_id);
148 return TNC_RESULT_ILLEGAL_OPERATION;
149 }
150 if (msg_vid > TNC_VENDORID_ANY || msg_subtype > TNC_SUBTYPE_ANY)
151 {
152 return TNC_RESULT_NO_LONG_MESSAGE_TYPES;
153 }
154 msg_type = (msg_vid << 8) | msg_subtype;
155
156 pa_subtype_names = get_pa_subtype_names(msg_vid);
157 if (pa_subtype_names)
158 {
159 DBG2(DBG_TNC, "creating IMC-IMV message type '%N/%N' 0x%06x/0x%02x",
160 pen_names, msg_vid, pa_subtype_names, msg_subtype,
161 msg_vid, msg_subtype);
162 }
163 else
164 {
165 DBG2(DBG_TNC, "creating IMC-IMV message type '%N' 0x%06x/0x%02x",
166 pen_names, msg_vid, msg_vid, msg_subtype);
167 }
168 tnccs_msg = imc_imv_msg_create(msg_type, chunk_create(msg, msg_len));
169
170 /* adding an IMC-IMV Message to TNCCS batch */
171 this->mutex->lock(this->mutex);
172 if (!this->batch)
173 {
174 this->batch = tnccs_batch_create(this->is_server, ++this->batch_id);
175 }
176 this->batch->add_msg(this->batch, tnccs_msg);
177 this->mutex->unlock(this->mutex);
178 return TNC_RESULT_SUCCESS;
179 }
180
181 /**
182 * Handle a single TNCCS message according to its type
183 */
184 static void handle_message(private_tnccs_11_t *this, tnccs_msg_t *msg)
185 {
186 switch (msg->get_type(msg))
187 {
188 case IMC_IMV_MSG:
189 {
190 imc_imv_msg_t *imc_imv_msg;
191 TNC_MessageType msg_type;
192 chunk_t msg_body;
193 u_int32_t msg_vid, msg_subtype;
194 enum_name_t *pa_subtype_names;
195
196 imc_imv_msg = (imc_imv_msg_t*)msg;
197 msg_type = imc_imv_msg->get_msg_type(imc_imv_msg);
198 msg_body = imc_imv_msg->get_msg_body(imc_imv_msg);
199 msg_vid = (msg_type >> 8) & TNC_VENDORID_ANY;
200 msg_subtype = msg_type & TNC_SUBTYPE_ANY;
201
202 pa_subtype_names = get_pa_subtype_names(msg_vid);
203 if (pa_subtype_names)
204 {
205 DBG2(DBG_TNC, "handling IMC-IMV message type '%N/%N' 0x%06x/0x%02x",
206 pen_names, msg_vid, pa_subtype_names, msg_subtype,
207 msg_vid, msg_subtype);
208 }
209 else
210 {
211 DBG2(DBG_TNC, "handling IMC-IMV message type '%N' 0x%06x/0x%02x",
212 pen_names, msg_vid, msg_vid, msg_subtype);
213 }
214
215 this->send_msg = TRUE;
216 if (this->is_server)
217 {
218 tnc->imvs->receive_message(tnc->imvs, this->connection_id,
219 FALSE, msg_body.ptr, msg_body.len,
220 msg_vid, msg_subtype, 0, TNC_IMVID_ANY);
221 }
222 else
223 {
224 tnc->imcs->receive_message(tnc->imcs, this->connection_id,
225 FALSE, msg_body.ptr, msg_body.len,
226 msg_vid, msg_subtype, 0, TNC_IMCID_ANY);
227 }
228 this->send_msg = FALSE;
229 break;
230 }
231 case TNCCS_MSG_RECOMMENDATION:
232 {
233 tnccs_recommendation_msg_t *rec_msg;
234 TNC_IMV_Action_Recommendation rec;
235 TNC_ConnectionState state = TNC_CONNECTION_STATE_ACCESS_NONE;
236
237 rec_msg = (tnccs_recommendation_msg_t*)msg;
238 rec = rec_msg->get_recommendation(rec_msg);
239 if (this->is_server)
240 {
241 DBG1(DBG_TNC, "ignoring NCCS-Recommendation message from "
242 " TNC client");
243 break;
244 }
245 DBG1(DBG_TNC, "TNC recommendation is '%N'",
246 TNC_IMV_Action_Recommendation_names, rec);
247 switch (rec)
248 {
249 case TNC_IMV_ACTION_RECOMMENDATION_ALLOW:
250 state = TNC_CONNECTION_STATE_ACCESS_ALLOWED;
251 break;
252 case TNC_IMV_ACTION_RECOMMENDATION_ISOLATE:
253 state = TNC_CONNECTION_STATE_ACCESS_ISOLATED;
254 break;
255 case TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS:
256 default:
257 state = TNC_CONNECTION_STATE_ACCESS_NONE;
258 }
259 tnc->imcs->notify_connection_change(tnc->imcs, this->connection_id,
260 state);
261 this->delete_state = TRUE;
262 break;
263 }
264 case TNCCS_MSG_ERROR:
265 {
266 tnccs_error_msg_t *err_msg;
267 tnccs_error_type_t error_type;
268 char *error_msg;
269
270 err_msg = (tnccs_error_msg_t*)msg;
271 error_msg = err_msg->get_message(err_msg, &error_type);
272 DBG1(DBG_TNC, "received '%N' TNCCS-Error: %s",
273 tnccs_error_type_names, error_type, error_msg);
274
275 /* we assume that all errors are fatal */
276 this->fatal_error = TRUE;
277 break;
278 }
279 case TNCCS_MSG_PREFERRED_LANGUAGE:
280 {
281 tnccs_preferred_language_msg_t *lang_msg;
282 char *lang;
283
284 lang_msg = (tnccs_preferred_language_msg_t*)msg;
285 lang = lang_msg->get_preferred_language(lang_msg);
286
287 DBG2(DBG_TNC, "setting preferred language to '%s'", lang);
288 this->recs->set_preferred_language(this->recs,
289 chunk_create(lang, strlen(lang)));
290 break;
291 }
292 case TNCCS_MSG_REASON_STRINGS:
293 {
294 tnccs_reason_strings_msg_t *reason_msg;
295 chunk_t reason_string, reason_lang;
296
297 reason_msg = (tnccs_reason_strings_msg_t*)msg;
298 reason_string = reason_msg->get_reason(reason_msg, &reason_lang);
299 DBG2(DBG_TNC, "reason string is '%.*s'", (int)reason_string.len,
300 reason_string.ptr);
301 DBG2(DBG_TNC, "language code is '%.*s'", (int)reason_lang.len,
302 reason_lang.ptr);
303 break;
304 }
305 default:
306 break;
307 }
308 }
309
310 METHOD(tls_t, process, status_t,
311 private_tnccs_11_t *this, void *buf, size_t buflen)
312 {
313 chunk_t data;
314 tnccs_batch_t *batch;
315 tnccs_msg_t *msg;
316 enumerator_t *enumerator;
317 status_t status;
318
319 if (this->is_server && !this->connection_id)
320 {
321 this->connection_id = tnc->tnccs->create_connection(tnc->tnccs,
322 TNCCS_1_1, (tnccs_t*)this, _send_msg,
323 &this->request_handshake_retry,
324 this->max_msg_len, &this->recs);
325 if (!this->connection_id)
326 {
327 return FAILED;
328 }
329 tnc->imvs->notify_connection_change(tnc->imvs, this->connection_id,
330 TNC_CONNECTION_STATE_CREATE);
331 tnc->imvs->notify_connection_change(tnc->imvs, this->connection_id,
332 TNC_CONNECTION_STATE_HANDSHAKE);
333 }
334
335 data = chunk_create(buf, buflen);
336 DBG1(DBG_TNC, "received TNCCS Batch (%u bytes) for Connection ID %u",
337 data.len, this->connection_id);
338 DBG3(DBG_TNC, "%.*s", (int)data.len, data.ptr);
339 batch = tnccs_batch_create_from_data(this->is_server, ++this->batch_id, data);
340 status = batch->process(batch);
341
342 if (status == FAILED)
343 {
344 this->fatal_error = TRUE;
345 this->mutex->lock(this->mutex);
346 if (this->batch)
347 {
348 DBG1(DBG_TNC, "cancelling TNCCS batch");
349 this->batch->destroy(this->batch);
350 this->batch_id--;
351 }
352 this->batch = tnccs_batch_create(this->is_server, ++this->batch_id);
353
354 /* add error messages to outbound batch */
355 enumerator = batch->create_error_enumerator(batch);
356 while (enumerator->enumerate(enumerator, &msg))
357 {
358 this->batch->add_msg(this->batch, msg->get_ref(msg));
359 }
360 enumerator->destroy(enumerator);
361 this->mutex->unlock(this->mutex);
362 }
363 else
364 {
365 enumerator = batch->create_msg_enumerator(batch);
366 while (enumerator->enumerate(enumerator, &msg))
367 {
368 handle_message(this, msg);
369 }
370 enumerator->destroy(enumerator);
371
372 /* received any TNCCS-Error messages */
373 if (this->fatal_error)
374 {
375 DBG1(DBG_TNC, "a fatal TNCCS-Error occurred, terminating connection");
376 batch->destroy(batch);
377 return FAILED;
378 }
379
380 this->send_msg = TRUE;
381 if (this->is_server)
382 {
383 tnc->imvs->batch_ending(tnc->imvs, this->connection_id);
384 }
385 else
386 {
387 tnc->imcs->batch_ending(tnc->imcs, this->connection_id);
388 }
389 this->send_msg = FALSE;
390 }
391 batch->destroy(batch);
392
393 return NEED_MORE;
394 }
395
396 /**
397 * Add a recommendation message if a final recommendation is available
398 */
399 static void check_and_build_recommendation(private_tnccs_11_t *this)
400 {
401 TNC_IMV_Action_Recommendation rec;
402 TNC_IMV_Evaluation_Result eval;
403 TNC_IMVID id;
404 chunk_t reason, language;
405 enumerator_t *enumerator;
406 tnccs_msg_t *msg;
407
408 if (!this->recs->have_recommendation(this->recs, &rec, &eval))
409 {
410 tnc->imvs->solicit_recommendation(tnc->imvs, this->connection_id);
411 }
412 if (this->recs->have_recommendation(this->recs, &rec, &eval))
413 {
414 if (!this->batch)
415 {
416 this->batch = tnccs_batch_create(this->is_server, ++this->batch_id);
417 }
418
419 msg = tnccs_recommendation_msg_create(rec);
420 this->batch->add_msg(this->batch, msg);
421
422 /* currently we just send the first Reason String */
423 enumerator = this->recs->create_reason_enumerator(this->recs);
424 if (enumerator->enumerate(enumerator, &id, &reason, &language))
425 {
426 msg = tnccs_reason_strings_msg_create(reason, language);
427 this->batch->add_msg(this->batch, msg);
428 }
429 enumerator->destroy(enumerator);
430
431 /* we have reache the final state */
432 this->delete_state = TRUE;
433 }
434 }
435
436 METHOD(tls_t, build, status_t,
437 private_tnccs_11_t *this, void *buf, size_t *buflen, size_t *msglen)
438 {
439 status_t status;
440
441 /* Initialize the connection */
442 if (!this->is_server && !this->connection_id)
443 {
444 tnccs_msg_t *msg;
445 char *pref_lang;
446
447 this->connection_id = tnc->tnccs->create_connection(tnc->tnccs,
448 TNCCS_1_1, (tnccs_t*)this, _send_msg,
449 &this->request_handshake_retry,
450 this->max_msg_len, NULL);
451 if (!this->connection_id)
452 {
453 return FAILED;
454 }
455
456 /* Create TNCCS-PreferredLanguage message */
457 pref_lang = tnc->imcs->get_preferred_language(tnc->imcs);
458 msg = tnccs_preferred_language_msg_create(pref_lang);
459 this->mutex->lock(this->mutex);
460 this->batch = tnccs_batch_create(this->is_server, ++this->batch_id);
461 this->batch->add_msg(this->batch, msg);
462 this->mutex->unlock(this->mutex);
463
464 tnc->imcs->notify_connection_change(tnc->imcs, this->connection_id,
465 TNC_CONNECTION_STATE_CREATE);
466 tnc->imcs->notify_connection_change(tnc->imcs, this->connection_id,
467 TNC_CONNECTION_STATE_HANDSHAKE);
468 this->send_msg = TRUE;
469 tnc->imcs->begin_handshake(tnc->imcs, this->connection_id);
470 this->send_msg = FALSE;
471 }
472
473 /* Do not allow any asynchronous IMCs or IMVs to add additional messages */
474 this->mutex->lock(this->mutex);
475
476 if (this->recs && !this->delete_state &&
477 (!this->batch || this->fatal_error))
478 {
479 check_and_build_recommendation(this);
480 }
481
482 if (this->batch)
483 {
484 chunk_t data;
485
486 this->batch->build(this->batch);
487 data = this->batch->get_encoding(this->batch);
488 DBG1(DBG_TNC, "sending TNCCS Batch (%d bytes) for Connection ID %u",
489 data.len, this->connection_id);
490 DBG3(DBG_TNC, "%.*s", (int)data.len, data.ptr);
491 *msglen = 0;
492
493 if (data.len > *buflen)
494 {
495 DBG1(DBG_TNC, "fragmentation of TNCCS batch not supported yet");
496 }
497 else
498 {
499 *buflen = data.len;
500 }
501 memcpy(buf, data.ptr, *buflen);
502 this->batch->destroy(this->batch);
503 this->batch = NULL;
504 status = ALREADY_DONE;
505 }
506 else
507 {
508 DBG1(DBG_TNC, "no TNCCS Batch to send");
509 status = INVALID_STATE;
510 }
511 this->mutex->unlock(this->mutex);
512
513 return status;
514 }
515
516 METHOD(tls_t, is_server, bool,
517 private_tnccs_11_t *this)
518 {
519 return this->is_server;
520 }
521
522 METHOD(tls_t, get_server_id, identification_t*,
523 private_tnccs_11_t *this)
524 {
525 return this->server;
526 }
527
528 METHOD(tls_t, get_peer_id, identification_t*,
529 private_tnccs_11_t *this)
530 {
531 return this->peer;
532 }
533
534 METHOD(tls_t, get_purpose, tls_purpose_t,
535 private_tnccs_11_t *this)
536 {
537 return TLS_PURPOSE_EAP_TNC;
538 }
539
540 METHOD(tls_t, is_complete, bool,
541 private_tnccs_11_t *this)
542 {
543 TNC_IMV_Action_Recommendation rec;
544 TNC_IMV_Evaluation_Result eval;
545
546 if (this->recs && this->recs->have_recommendation(this->recs, &rec, &eval))
547 {
548 return this->callback ? this->callback(rec, eval) : TRUE;
549 }
550 else
551 {
552 return FALSE;
553 }
554 }
555
556 METHOD(tls_t, get_eap_msk, chunk_t,
557 private_tnccs_11_t *this)
558 {
559 return chunk_empty;
560 }
561
562 METHOD(tls_t, destroy, void,
563 private_tnccs_11_t *this)
564 {
565 tnc->tnccs->remove_connection(tnc->tnccs, this->connection_id,
566 this->is_server);
567 this->server->destroy(this->server);
568 this->peer->destroy(this->peer);
569 this->mutex->destroy(this->mutex);
570 DESTROY_IF(this->batch);
571 free(this);
572 }
573
574 METHOD(tnccs_t, get_transport, tnc_ift_type_t,
575 private_tnccs_11_t *this)
576 {
577 return this->transport;
578 }
579
580 METHOD(tnccs_t, set_transport, void,
581 private_tnccs_11_t *this, tnc_ift_type_t transport)
582 {
583 this->transport = transport;
584 }
585
586 METHOD(tnccs_t, get_auth_type, u_int32_t,
587 private_tnccs_11_t *this)
588 {
589 return this->auth_type;
590 }
591
592 METHOD(tnccs_t, set_auth_type, void,
593 private_tnccs_11_t *this, u_int32_t auth_type)
594 {
595 this->auth_type = auth_type;
596 }
597
598 /**
599 * See header
600 */
601 tnccs_t* tnccs_11_create(bool is_server,
602 identification_t *server, identification_t *peer,
603 tnc_ift_type_t transport, tnccs_cb_t cb)
604 {
605 private_tnccs_11_t *this;
606
607 INIT(this,
608 .public = {
609 .tls = {
610 .process = _process,
611 .build = _build,
612 .is_server = _is_server,
613 .get_server_id = _get_server_id,
614 .get_peer_id = _get_peer_id,
615 .get_purpose = _get_purpose,
616 .is_complete = _is_complete,
617 .get_eap_msk = _get_eap_msk,
618 .destroy = _destroy,
619 },
620 .get_transport = _get_transport,
621 .set_transport = _set_transport,
622 .get_auth_type = _get_auth_type,
623 .set_auth_type = _set_auth_type,
624 },
625 .is_server = is_server,
626 .server = server->clone(server),
627 .peer = peer->clone(peer),
628 .transport = transport,
629 .callback = cb,
630 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
631 .max_msg_len = lib->settings->get_int(lib->settings,
632 "libtnccs.plugins.tnccs-11.max_message_size", 45000),
633 );
634
635 return &this->public;
636 }