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