implemented handling of received PB-TNC messages
[strongswan.git] / src / libcharon / plugins / tnccs_20 / tnccs_20.c
1 /*
2 * Copyright (C) 2010 Sansar Choinyanbuu
3 * Copyright (C) 2010 Andreas Steffen
4 * HSR Hochschule fuer Technik Rapperswil
5 *
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>.
10 *
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
14 * for more details.
15 */
16
17 #include "tnccs_20.h"
18 #include "tnccs_20_types.h"
19 #include "batch/pb_tnc_batch.h"
20 #include "messages/pb_tnc_message.h"
21 #include "messages/pb_pa_message.h"
22 #include "messages/pb_error_message.h"
23 #include "messages/pb_assessment_result_message.h"
24 #include "messages/pb_access_recommendation_message.h"
25 #include "messages/pb_reason_string_message.h"
26 #include "messages/pb_language_preference_message.h"
27
28 #include <debug.h>
29 #include <daemon.h>
30 #include <threading/mutex.h>
31 #include <tnc/tncif.h>
32 #include <tnc/tncifimv_names.h>
33 #include <tnc/tnccs/tnccs.h>
34
35 typedef struct private_tnccs_20_t private_tnccs_20_t;
36
37 /**
38 * Private data of a tnccs_20_t object.
39 */
40 struct private_tnccs_20_t {
41
42 /**
43 * Public tls_t interface.
44 */
45 tls_t public;
46
47 /**
48 * TNCC if TRUE, TNCS if FALSE
49 */
50 bool is_server;
51
52 /**
53 * PB-TNC State Machine
54 */
55 pb_tnc_state_t state;
56
57 /**
58 * Connection ID assigned to this TNCCS connection
59 */
60 TNC_ConnectionID connection_id;
61
62 /**
63 * PB-TNC batch being constructed
64 */
65 pb_tnc_batch_t *batch;
66
67 /**
68 * Mutex locking the batch in construction
69 */
70 mutex_t *mutex;
71
72 /**
73 * Flag set by IMC/IMV RequestHandshakeRetry() function
74 */
75 bool request_handshake_retry;
76
77 /**
78 * Set of IMV recommendations (TNC Server only)
79 */
80 recommendations_t *recs;
81 };
82
83 METHOD(tnccs_t, send_message, void,
84 private_tnccs_20_t* this, TNC_IMCID imc_id, TNC_IMVID imv_id,
85 TNC_BufferReference msg,
86 TNC_UInt32 msg_len,
87 TNC_MessageType msg_type)
88 {
89 TNC_MessageSubtype msg_sub_type;
90 TNC_VendorID msg_vendor_id;
91 pb_tnc_message_t *pb_tnc_msg;
92 pb_tnc_batch_type_t batch_type;
93
94 msg_sub_type = msg_type & TNC_SUBTYPE_ANY;
95 msg_vendor_id = (msg_type >> 8) & TNC_VENDORID_ANY;
96
97 pb_tnc_msg = pb_pa_message_create(msg_vendor_id, msg_sub_type, imc_id, imv_id,
98 chunk_create(msg, msg_len));
99
100 /* adding PA message to SDATA or CDATA batch only */
101 batch_type = this->is_server ? PB_BATCH_SDATA : PB_BATCH_CDATA;
102 this->mutex->lock(this->mutex);
103 if (!this->batch)
104 {
105 this->batch = pb_tnc_batch_create(this->is_server, batch_type);
106 }
107 if (this->batch->get_type(this->batch) == batch_type)
108 {
109 this->batch->add_message(this->batch, pb_tnc_msg);
110 }
111 else
112 {
113 pb_tnc_msg->destroy(pb_tnc_msg);
114 }
115 this->mutex->unlock(this->mutex);
116 }
117
118 static status_t handle_messages(private_tnccs_20_t *this, pb_tnc_batch_t *batch)
119 {
120 enumerator_t *enumerator;
121 pb_tnc_message_t *msg;
122
123 enumerator = batch->create_msg_enumerator(batch);
124 while (enumerator->enumerate(enumerator, &msg))
125 {
126 switch (msg->get_type(msg))
127 {
128 case PB_MSG_EXPERIMENTAL:
129 /* for experiments */
130 break;
131 case PB_MSG_PA:
132 {
133 pb_pa_message_t *pa_msg;
134 TNC_MessageType msg_type;
135 u_int32_t vendor_id, subtype;
136 chunk_t msg_body;
137
138 pa_msg = (pb_pa_message_t*)msg;
139 vendor_id = pa_msg->get_vendor_id(pa_msg, &subtype);
140 msg_type = (vendor_id << 8) | (subtype & 0xff);
141 msg_body = pa_msg->get_body(pa_msg);
142
143 DBG2(DBG_TNC, "handling message type 0x%08x", msg_type);
144
145 if (this->is_server)
146 {
147 charon->imvs->receive_message(charon->imvs,
148 this->connection_id, msg_body.ptr, msg_body.len, msg_type);
149 }
150 else
151 {
152 charon->imcs->receive_message(charon->imcs,
153 this->connection_id, msg_body.ptr, msg_body.len,msg_type);
154 }
155 break;
156 }
157 case PB_MSG_ASSESSMENT_RESULT:
158 {
159 pb_assessment_result_message_t *assess_msg;
160 u_int32_t result;
161
162 assess_msg = (pb_assessment_result_message_t*)msg;
163 result = assess_msg->get_assessment_result(assess_msg);
164 DBG1(DBG_TNC, "assessment result is '%N'",
165 evaluation_result_names, result);
166 break;
167 }
168 case PB_MSG_ACCESS_RECOMMENDATION:
169 {
170 pb_access_recommendation_message_t *rec_msg;
171 u_int16_t rec;
172
173 rec_msg = (pb_access_recommendation_message_t*)msg;
174 rec = rec_msg->get_access_recommendation(rec_msg);
175 DBG1(DBG_TNC, "access_recommendation is '%N'",
176 action_recommendation_names, rec);
177 break;
178 }
179 case PB_MSG_REMEDIATION_PARAMETERS:
180 {
181 /* TODO : Remediation parameters message processing */
182 break;
183 }
184 case PB_MSG_ERROR:
185 {
186 pb_error_message_t *err_msg;
187 bool fatal;
188 u_int32_t vendor_id;
189 u_int16_t error_code;
190
191 err_msg = (pb_error_message_t*)msg;
192 fatal = err_msg->get_fatal_flag(err_msg);
193 vendor_id = err_msg->get_vendor_id(err_msg);
194 error_code = err_msg->get_error_code(err_msg);
195
196 if (vendor_id == IETF_VENDOR_ID)
197 {
198 DBG1(DBG_TNC, "%s PB-TNC Error: %N",
199 fatal ? "fatal" : "non-fatal",
200 pb_tnc_error_code_names, error_code);
201
202 switch (error_code)
203 {
204 case PB_ERROR_INVALID_PARAMETER:
205 case PB_ERROR_UNSUPPORTED_MANDATORY_MESSAGE:
206 DBG1(DBG_TNC, " at an offset of %u bytes",
207 err_msg->get_offset(err_msg));
208 break;
209 case PB_ERROR_VERSION_NOT_SUPPORTED:
210 DBG1(DBG_TNC, " with bad version 0x%02x",
211 err_msg->get_bad_version(err_msg));
212 break;
213 case PB_ERROR_UNEXPECTED_BATCH_TYPE:
214 case PB_ERROR_LOCAL_ERROR:
215 default:
216 break;
217 }
218 }
219 else
220 {
221 DBG1(DBG_TNC, "%s PB-TNC Error (%u) with Vendor ID 0x%06x",
222 fatal ? "fatal" : "non-fatal", error_code, vendor_id);
223 }
224 break;
225 }
226 case PB_MSG_LANGUAGE_PREFERENCE:
227 {
228 pb_language_preference_message_t *lang_msg;
229 chunk_t lang;
230
231 lang_msg = (pb_language_preference_message_t*)msg;
232 lang = lang_msg->get_language_preference(lang_msg);
233
234 DBG2(DBG_TNC, "setting language preference '%.*s'",
235 lang.len, lang.ptr);
236 this->recs->set_preferred_language(this->recs, lang);
237 break;
238 }
239 case PB_MSG_REASON_STRING:
240 {
241 pb_reason_string_message_t *reason_msg;
242 chunk_t reason_string, language_code;
243
244 reason_msg = (pb_reason_string_message_t*)msg;
245 reason_string = reason_msg->get_reason_string(reason_msg);
246 language_code = reason_msg->get_language_code(reason_msg);
247 DBG2(DBG_TNC, "reason string: '%.*s", reason_string.len,
248 reason_string.ptr);
249 DBG2(DBG_TNC, "language code: '%.*s", language_code.len,
250 language_code.ptr);
251 break;
252 }
253 default:
254 break;
255 }
256 }
257 enumerator->destroy(enumerator);
258
259 return SUCCESS;
260 }
261
262 METHOD(tls_t, process, status_t,
263 private_tnccs_20_t *this, void *buf, size_t buflen)
264 {
265 chunk_t data;
266 pb_tnc_batch_t *batch;
267 pb_tnc_message_t *msg;
268 pb_tnc_state_t old_state = this->state;
269 enumerator_t *enumerator;
270 status_t status;
271
272 if (this->is_server && !this->connection_id)
273 {
274 this->connection_id = charon->tnccs->create_connection(charon->tnccs,
275 (tnccs_t*)this, _send_message,
276 &this->request_handshake_retry, &this->recs);
277 if (!this->connection_id)
278 {
279 return FAILED;
280 }
281 charon->imvs->notify_connection_change(charon->imvs,
282 this->connection_id, TNC_CONNECTION_STATE_CREATE);
283 }
284
285 data = chunk_create(buf, buflen);
286 DBG1(DBG_TNC, "received TNCCS Batch (%u bytes) for Connection ID %u",
287 data.len, this->connection_id);
288 DBG3(DBG_TNC, "%B", &data);
289 batch = pb_tnc_batch_create_from_data(this->is_server, data);
290 status = batch->process(batch, &this->state);
291
292 if (this->state != old_state)
293 {
294 DBG2(DBG_TNC, "PB-TNC state transition from '%N' to '%N'",
295 pb_tnc_state_names, old_state, pb_tnc_state_names, this->state);
296 }
297
298 switch (status)
299 {
300 case SUCCESS:
301 handle_messages(this, batch);
302 if (this->is_server)
303 {
304 charon->imvs->batch_ending(charon->imvs, this->connection_id);
305 }
306 else
307 {
308 charon->imcs->batch_ending(charon->imcs, this->connection_id);
309 }
310 break;
311 case FAILED:
312 this->mutex->lock(this->mutex);
313 if (this->batch)
314 {
315 DBG1(DBG_TNC, "cancelling PB-TNC %N Batch",
316 pb_tnc_batch_type_names, this->batch->get_type(this->batch));
317 this->batch->destroy(this->batch);
318 }
319 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CLOSE);
320 this->mutex->unlock(this->mutex);
321 /* fall through */
322 case VERIFY_ERROR:
323 enumerator = batch->create_error_enumerator(batch);
324 while (enumerator->enumerate(enumerator, &msg))
325 {
326 this->mutex->lock(this->mutex);
327 this->batch->add_message(this->batch, msg->get_ref(msg));
328 this->mutex->unlock(this->mutex);
329 }
330 enumerator->destroy(enumerator);
331 default:
332 break;
333 }
334 batch->destroy(batch);
335
336 return NEED_MORE;
337 }
338
339 /*
340 * Implements the PB-TNC state machine in send direction
341 */
342 static bool state_transition_upon_send(pb_tnc_state_t *state,
343 pb_tnc_batch_type_t batch_type)
344 {
345 switch (*state)
346 {
347 case PB_STATE_INIT:
348 if (batch_type == PB_BATCH_CDATA)
349 {
350 *state = PB_STATE_SERVER_WORKING;
351 break;
352 }
353 if (batch_type == PB_BATCH_SDATA)
354 {
355 *state = PB_STATE_CLIENT_WORKING;
356 break;
357 }
358 if (batch_type == PB_BATCH_CLOSE)
359 {
360 *state = PB_STATE_END;
361 break;
362 }
363 return FALSE;
364 case PB_STATE_SERVER_WORKING:
365 if (batch_type == PB_BATCH_SDATA)
366 {
367 *state = PB_STATE_CLIENT_WORKING;
368 break;
369 }
370 if (batch_type == PB_BATCH_RESULT)
371 {
372 *state = PB_STATE_DECIDED;
373 break;
374 }
375 if (batch_type == PB_BATCH_CRETRY ||
376 batch_type == PB_BATCH_SRETRY)
377 {
378 break;
379 }
380 if (batch_type == PB_BATCH_CLOSE)
381 {
382 *state = PB_STATE_END;
383 break;
384 }
385 return FALSE;
386 case PB_STATE_CLIENT_WORKING:
387 if (batch_type == PB_BATCH_CDATA)
388 {
389 *state = PB_STATE_SERVER_WORKING;
390 break;
391 }
392 if (batch_type == PB_BATCH_CRETRY)
393 {
394 break;
395 }
396 if (batch_type == PB_BATCH_CLOSE)
397 {
398 *state = PB_STATE_END;
399 break;
400 }
401 return FALSE;
402 case PB_STATE_DECIDED:
403 if (batch_type == PB_BATCH_CRETRY ||
404 batch_type == PB_BATCH_SRETRY)
405 {
406 *state = PB_STATE_SERVER_WORKING;
407 break;
408 }
409 if (batch_type == PB_BATCH_CLOSE)
410 {
411 *state = PB_STATE_END;
412 break;
413 }
414 return FALSE;
415 case PB_STATE_END:
416 if (batch_type == PB_BATCH_CLOSE)
417 {
418 break;
419 }
420 return FALSE;
421 }
422 return TRUE;
423 }
424
425 METHOD(tls_t, build, status_t,
426 private_tnccs_20_t *this, void *buf, size_t *buflen, size_t *msglen)
427 {
428 if (!this->is_server && !this->connection_id)
429 {
430 pb_tnc_message_t *msg;
431 char *pref_lang;
432
433 this->connection_id = charon->tnccs->create_connection(charon->tnccs,
434 (tnccs_t*)this, _send_message,
435 &this->request_handshake_retry, NULL);
436 if (!this->connection_id)
437 {
438 return FAILED;
439 }
440
441 /* Create PB-TNC Language Preference Message */
442 pref_lang = charon->imcs->get_preferred_language(charon->imcs);
443 msg = pb_language_preference_message_create(chunk_create(pref_lang,
444 strlen(pref_lang)));
445 this->mutex->lock(this->mutex);
446 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CDATA);
447 this->batch->add_message(this->batch, msg);
448 this->mutex->unlock(this->mutex);
449
450 charon->imcs->notify_connection_change(charon->imcs,
451 this->connection_id, TNC_CONNECTION_STATE_CREATE);
452 charon->imcs->notify_connection_change(charon->imcs,
453 this->connection_id, TNC_CONNECTION_STATE_HANDSHAKE);
454 charon->imcs->begin_handshake(charon->imcs, this->connection_id);
455 }
456
457 if (this->batch)
458 {
459 pb_tnc_batch_type_t batch_type;
460 pb_tnc_state_t old_state;
461 status_t status;
462 chunk_t data;
463
464 batch_type = this->batch->get_type(this->batch);
465 old_state = this->state;
466 this->mutex->lock(this->mutex);
467
468 if (state_transition_upon_send(&this->state, batch_type))
469 {
470 this->batch->build(this->batch);
471 data = this->batch->get_encoding(this->batch);
472 DBG1(DBG_TNC, "sending PB-TNC %N Batch (%d bytes) for Connection ID %u",
473 pb_tnc_batch_type_names, batch_type, data.len,
474 this->connection_id);
475 DBG3(DBG_TNC, "%B", &data);
476
477 *msglen = data.len;
478 *buflen = data.len;
479 memcpy(buf, data.ptr, data.len);
480 status = ALREADY_DONE;
481 }
482 else
483 {
484 DBG1(DBG_TNC, "cancelling unexpected PB-TNC Batch Type: %N",
485 pb_tnc_batch_type_names, batch_type);
486 status = INVALID_STATE;
487 }
488
489 this->batch->destroy(this->batch);
490 this->batch = NULL;
491 this->mutex->unlock(this->mutex);
492
493 if (this->state != old_state)
494 {
495 DBG2(DBG_TNC, "PB-TNC state transition from '%N' to '%N'",
496 pb_tnc_state_names, old_state, pb_tnc_state_names, this->state);
497 }
498 return status;
499 }
500 else
501 {
502 DBG1(DBG_TNC, "no TNCCS Batch to send");
503 return INVALID_STATE;
504 }
505 }
506
507 METHOD(tls_t, is_server, bool,
508 private_tnccs_20_t *this)
509 {
510 return this->is_server;
511 }
512
513 METHOD(tls_t, get_purpose, tls_purpose_t,
514 private_tnccs_20_t *this)
515 {
516 return TLS_PURPOSE_EAP_TNC;
517 }
518
519 METHOD(tls_t, is_complete, bool,
520 private_tnccs_20_t *this)
521 {
522 TNC_IMV_Action_Recommendation rec;
523 TNC_IMV_Evaluation_Result eval;
524
525 if (this->recs && this->recs->have_recommendation(this->recs, &rec, &eval))
526 {
527 DBG2(DBG_TNC, "Final recommendation '%N' and evaluation '%N'",
528 action_recommendation_names, rec, evaluation_result_names, eval);
529
530 return charon->imvs->enforce_recommendation(charon->imvs, rec);
531 }
532 else
533 {
534 return FALSE;
535 }
536 }
537
538 METHOD(tls_t, get_eap_msk, chunk_t,
539 private_tnccs_20_t *this)
540 {
541 return chunk_empty;
542 }
543
544 METHOD(tls_t, destroy, void,
545 private_tnccs_20_t *this)
546 {
547 charon->tnccs->remove_connection(charon->tnccs, this->connection_id);
548 this->mutex->destroy(this->mutex);
549 DESTROY_IF(this->batch);
550 free(this);
551 }
552
553 /**
554 * See header
555 */
556 tls_t *tnccs_20_create(bool is_server)
557 {
558 private_tnccs_20_t *this;
559
560 INIT(this,
561 .public = {
562 .process = _process,
563 .build = _build,
564 .is_server = _is_server,
565 .get_purpose = _get_purpose,
566 .is_complete = _is_complete,
567 .get_eap_msk = _get_eap_msk,
568 .destroy = _destroy,
569 },
570 .is_server = is_server,
571 .state = PB_STATE_INIT,
572 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
573 );
574
575 return &this->public;
576 }