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