show PB-PA message type/subtype
[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 "batch/pb_tnc_batch.h"
19 #include "messages/pb_tnc_msg.h"
20 #include "messages/pb_pa_msg.h"
21 #include "messages/pb_error_msg.h"
22 #include "messages/pb_assessment_result_msg.h"
23 #include "messages/pb_access_recommendation_msg.h"
24 #include "messages/pb_remediation_parameters_msg.h"
25 #include "messages/pb_reason_string_msg.h"
26 #include "messages/pb_language_preference_msg.h"
27 #include "state_machine/pb_tnc_state_machine.h"
28
29 #include <debug.h>
30 #include <daemon.h>
31 #include <threading/mutex.h>
32 #include <tnc/tnccs/tnccs.h>
33 #include <pen/pen.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_machine_t *state_machine;
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 while processing
74 */
75 bool fatal_error;
76
77 /**
78 * Flag set by IMC/IMV RequestHandshakeRetry() function
79 */
80 bool request_handshake_retry;
81
82 /**
83 * SendMessage() by IMC/IMV only allowed if flag is set
84 */
85 bool send_msg;
86
87 /**
88 * Set of IMV recommendations (TNC Server only)
89 */
90 recommendations_t *recs;
91 };
92
93 METHOD(tnccs_t, send_msg, TNC_Result,
94 private_tnccs_20_t* this, TNC_IMCID imc_id, TNC_IMVID imv_id,
95 TNC_BufferReference msg,
96 TNC_UInt32 msg_len,
97 TNC_MessageType msg_type)
98 {
99 TNC_MessageSubtype msg_sub_type;
100 TNC_VendorID msg_vendor_id;
101 pb_tnc_msg_t *pb_tnc_msg;
102 pb_tnc_batch_type_t batch_type;
103
104 if (!this->send_msg)
105 {
106 DBG1(DBG_TNC, "%s %u not allowed to call SendMessage()",
107 this->is_server ? "IMV" : "IMC",
108 this->is_server ? imv_id : imc_id);
109 return TNC_RESULT_ILLEGAL_OPERATION;
110 }
111
112 msg_sub_type = msg_type & TNC_SUBTYPE_ANY;
113 msg_vendor_id = (msg_type >> 8) & TNC_VENDORID_ANY;
114
115 pb_tnc_msg = pb_pa_msg_create(msg_vendor_id, msg_sub_type, imc_id, imv_id,
116 chunk_create(msg, msg_len));
117
118 if (msg_vendor_id == PEN_IETF)
119 {
120 DBG2(DBG_TNC, "creating PB-PA message type '%N/%N' 0x%06x/0x%02x",
121 pen_names, msg_vendor_id, pa_tnc_subtype_names, msg_sub_type,
122 msg_vendor_id, msg_sub_type);
123 }
124 else
125 {
126 DBG2(DBG_TNC, "creating PB-PA message type '%N' 0x%06x/0x%02x",
127 pen_names, msg_vendor_id, msg_vendor_id, msg_sub_type);
128 }
129
130 /* adding PA message to SDATA or CDATA batch only */
131 batch_type = this->is_server ? PB_BATCH_SDATA : PB_BATCH_CDATA;
132 this->mutex->lock(this->mutex);
133 if (!this->batch)
134 {
135 this->batch = pb_tnc_batch_create(this->is_server, batch_type);
136 }
137 if (this->batch->get_type(this->batch) == batch_type)
138 {
139 this->batch->add_msg(this->batch, pb_tnc_msg);
140 }
141 else
142 {
143 pb_tnc_msg->destroy(pb_tnc_msg);
144 }
145 this->mutex->unlock(this->mutex);
146 return TNC_RESULT_SUCCESS;
147 }
148
149 /**
150 * Handle a single PB-TNC message according to its type
151 */
152 static void handle_message(private_tnccs_20_t *this, pb_tnc_msg_t *msg)
153 {
154 switch (msg->get_type(msg))
155 {
156 case PB_MSG_EXPERIMENTAL:
157 /* nothing to do */
158 break;
159 case PB_MSG_PA:
160 {
161 pb_pa_msg_t *pa_msg;
162 TNC_MessageType msg_type;
163 u_int32_t vendor_id, subtype;
164 chunk_t msg_body;
165
166 pa_msg = (pb_pa_msg_t*)msg;
167 vendor_id = pa_msg->get_vendor_id(pa_msg, &subtype);
168 msg_type = (vendor_id << 8) | (subtype & 0xff);
169 msg_body = pa_msg->get_body(pa_msg);
170
171 if (vendor_id == PEN_IETF)
172 {
173 DBG2(DBG_TNC, "handling PB-PA message type '%N/%N' 0x%06x/0x%02x",
174 pen_names, vendor_id, pa_tnc_subtype_names, subtype,
175 vendor_id, subtype);
176 }
177 else
178 {
179 DBG2(DBG_TNC, "handling PB-PA message type '%N' 0x%06x/0x%02x",
180 pen_names, vendor_id, vendor_id, subtype);
181 }
182
183 this->send_msg = TRUE;
184 if (this->is_server)
185 {
186 charon->imvs->receive_message(charon->imvs,
187 this->connection_id, msg_body.ptr, msg_body.len, msg_type);
188 }
189 else
190 {
191 charon->imcs->receive_message(charon->imcs,
192 this->connection_id, msg_body.ptr, msg_body.len,msg_type);
193 }
194 this->send_msg = FALSE;
195 break;
196 }
197 case PB_MSG_ASSESSMENT_RESULT:
198 {
199 pb_assessment_result_msg_t *assess_msg;
200 u_int32_t result;
201
202 assess_msg = (pb_assessment_result_msg_t*)msg;
203 result = assess_msg->get_assessment_result(assess_msg);
204 DBG1(DBG_TNC, "PB-TNC assessment result is '%N'",
205 TNC_IMV_Evaluation_Result_names, result);
206 break;
207 }
208 case PB_MSG_ACCESS_RECOMMENDATION:
209 {
210 pb_access_recommendation_msg_t *rec_msg;
211 pb_access_recommendation_code_t rec;
212 TNC_ConnectionState state = TNC_CONNECTION_STATE_ACCESS_NONE;
213
214 rec_msg = (pb_access_recommendation_msg_t*)msg;
215 rec = rec_msg->get_access_recommendation(rec_msg);
216 DBG1(DBG_TNC, "PB-TNC access recommendation is '%N'",
217 pb_access_recommendation_code_names, rec);
218 switch (rec)
219 {
220 case PB_REC_ACCESS_ALLOWED:
221 state = TNC_CONNECTION_STATE_ACCESS_ALLOWED;
222 break;
223 case PB_REC_ACCESS_DENIED:
224 state = TNC_CONNECTION_STATE_ACCESS_NONE;
225 break;
226 case PB_REC_QUARANTINED:
227 state = TNC_CONNECTION_STATE_ACCESS_ISOLATED;
228 }
229 charon->imcs->notify_connection_change(charon->imcs,
230 this->connection_id, state);
231 break;
232 }
233 case PB_MSG_REMEDIATION_PARAMETERS:
234 {
235 /* TODO : Remediation parameters message processing */
236 break;
237 }
238 case PB_MSG_ERROR:
239 {
240 pb_error_msg_t *err_msg;
241 bool fatal;
242 u_int32_t vendor_id;
243 u_int16_t error_code;
244
245 err_msg = (pb_error_msg_t*)msg;
246 fatal = err_msg->get_fatal_flag(err_msg);
247 vendor_id = err_msg->get_vendor_id(err_msg);
248 error_code = err_msg->get_error_code(err_msg);
249
250 if (fatal)
251 {
252 this->fatal_error = TRUE;
253 }
254
255 if (vendor_id == PEN_IETF)
256 {
257 switch (error_code)
258 {
259 case PB_ERROR_INVALID_PARAMETER:
260 case PB_ERROR_UNSUPPORTED_MANDATORY_MSG:
261 DBG1(DBG_TNC, "received %s PB-TNC error '%N' "
262 "(offset %u bytes)",
263 fatal ? "fatal" : "non-fatal",
264 pb_tnc_error_code_names, error_code,
265 err_msg->get_offset(err_msg));
266 break;
267 case PB_ERROR_VERSION_NOT_SUPPORTED:
268 DBG1(DBG_TNC, "received %s PB-TNC error '%N' "
269 "caused by bad version 0x%02x",
270 fatal ? "fatal" : "non-fatal",
271 pb_tnc_error_code_names, error_code,
272 err_msg->get_bad_version(err_msg));
273 break;
274 case PB_ERROR_UNEXPECTED_BATCH_TYPE:
275 case PB_ERROR_LOCAL_ERROR:
276 default:
277 DBG1(DBG_TNC, "received %s PB-TNC error '%N'",
278 fatal ? "fatal" : "non-fatal",
279 pb_tnc_error_code_names, error_code);
280 break;
281 }
282 }
283 else
284 {
285 DBG1(DBG_TNC, "received %s PB-TNC error (%u) "
286 "with Vendor ID 0x%06x",
287 fatal ? "fatal" : "non-fatal",
288 error_code, vendor_id);
289 }
290 break;
291 }
292 case PB_MSG_LANGUAGE_PREFERENCE:
293 {
294 pb_language_preference_msg_t *lang_msg;
295 chunk_t lang;
296
297 lang_msg = (pb_language_preference_msg_t*)msg;
298 lang = lang_msg->get_language_preference(lang_msg);
299
300 DBG2(DBG_TNC, "setting language preference to '%.*s'",
301 lang.len, lang.ptr);
302 this->recs->set_preferred_language(this->recs, lang);
303 break;
304 }
305 case PB_MSG_REASON_STRING:
306 {
307 pb_reason_string_msg_t *reason_msg;
308 chunk_t reason_string, language_code;
309
310 reason_msg = (pb_reason_string_msg_t*)msg;
311 reason_string = reason_msg->get_reason_string(reason_msg);
312 language_code = reason_msg->get_language_code(reason_msg);
313 DBG2(DBG_TNC, "reason string is '%.*s", reason_string.len,
314 reason_string.ptr);
315 DBG2(DBG_TNC, "language code is '%.*s", language_code.len,
316 language_code.ptr);
317 break;
318 }
319 default:
320 break;
321 }
322 }
323
324 /**
325 * Build a CRETRY or SRETRY batch
326 */
327 static void build_retry_batch(private_tnccs_20_t *this)
328 {
329 pb_tnc_batch_type_t batch_retry_type;
330
331 batch_retry_type = this->is_server ? PB_BATCH_SRETRY : PB_BATCH_CRETRY;
332 if (this->batch)
333 {
334 if (this->batch->get_type(this->batch) == batch_retry_type)
335 {
336 /* retry batch has already been created */
337 return;
338 }
339 DBG1(DBG_TNC, "cancelling PB-TNC %N batch",
340 pb_tnc_batch_type_names, this->batch->get_type(this->batch));
341 this->batch->destroy(this->batch);
342 }
343 this->batch = pb_tnc_batch_create(this->is_server, batch_retry_type);
344 }
345
346 METHOD(tls_t, process, status_t,
347 private_tnccs_20_t *this, void *buf, size_t buflen)
348 {
349 chunk_t data;
350 pb_tnc_batch_t *batch;
351 pb_tnc_msg_t *msg;
352 enumerator_t *enumerator;
353 status_t status;
354
355 if (this->is_server && !this->connection_id)
356 {
357 this->connection_id = charon->tnccs->create_connection(charon->tnccs,
358 (tnccs_t*)this, _send_msg,
359 &this->request_handshake_retry, &this->recs);
360 if (!this->connection_id)
361 {
362 return FAILED;
363 }
364 charon->imvs->notify_connection_change(charon->imvs,
365 this->connection_id, TNC_CONNECTION_STATE_CREATE);
366 charon->imvs->notify_connection_change(charon->imvs,
367 this->connection_id, TNC_CONNECTION_STATE_HANDSHAKE);
368 }
369
370 data = chunk_create(buf, buflen);
371 DBG1(DBG_TNC, "received TNCCS batch (%u bytes) for Connection ID %u",
372 data.len, this->connection_id);
373 DBG3(DBG_TNC, "%B", &data);
374 batch = pb_tnc_batch_create_from_data(this->is_server, data);
375 status = batch->process(batch, this->state_machine);
376
377 if (status != FAILED)
378 {
379 enumerator_t *enumerator;
380 pb_tnc_msg_t *msg;
381 pb_tnc_batch_type_t batch_type;
382 bool empty = TRUE;
383
384 batch_type = batch->get_type(batch);
385
386 if (batch_type == PB_BATCH_CRETRY)
387 {
388 /* Send an SRETRY batch in response */
389 this->mutex->lock(this->mutex);
390 build_retry_batch(this);
391 this->mutex->unlock(this->mutex);
392 }
393 else if (batch_type == PB_BATCH_SRETRY)
394 {
395 /* Restart the measurements */
396 charon->imcs->notify_connection_change(charon->imcs,
397 this->connection_id, TNC_CONNECTION_STATE_HANDSHAKE);
398 this->send_msg = TRUE;
399 charon->imcs->begin_handshake(charon->imcs, this->connection_id);
400 this->send_msg = FALSE;
401 }
402
403 enumerator = batch->create_msg_enumerator(batch);
404 while (enumerator->enumerate(enumerator, &msg))
405 {
406 handle_message(this, msg);
407 empty = FALSE;
408 }
409 enumerator->destroy(enumerator);
410
411 /* received an empty CLOSE batch from PB-TNC client */
412 if (this->is_server && batch_type == PB_BATCH_CLOSE && empty)
413 {
414 batch->destroy(batch);
415 if (this->fatal_error)
416 {
417 DBG1(DBG_TNC, "a fatal PB-TNC error occurred, "
418 "terminating connection");
419 return FAILED;
420 }
421 else
422 {
423 return SUCCESS;
424 }
425 }
426
427 this->send_msg = TRUE;
428 if (this->is_server)
429 {
430 charon->imvs->batch_ending(charon->imvs, this->connection_id);
431 }
432 else
433 {
434 charon->imcs->batch_ending(charon->imcs, this->connection_id);
435 }
436 this->send_msg = FALSE;
437 }
438
439 switch (status)
440 {
441 case FAILED:
442 this->fatal_error = TRUE;
443 this->mutex->lock(this->mutex);
444 if (this->batch)
445 {
446 DBG1(DBG_TNC, "cancelling PB-TNC %N batch",
447 pb_tnc_batch_type_names, this->batch->get_type(this->batch));
448 this->batch->destroy(this->batch);
449 }
450 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CLOSE);
451 this->mutex->unlock(this->mutex);
452 /* fall through to add error messages to outbound batch */
453 case VERIFY_ERROR:
454 enumerator = batch->create_error_enumerator(batch);
455 while (enumerator->enumerate(enumerator, &msg))
456 {
457 this->mutex->lock(this->mutex);
458 this->batch->add_msg(this->batch, msg->get_ref(msg));
459 this->mutex->unlock(this->mutex);
460 }
461 enumerator->destroy(enumerator);
462 break;
463 case SUCCESS:
464 default:
465 break;
466 }
467 batch->destroy(batch);
468
469 return NEED_MORE;
470 }
471
472 /**
473 * Build a RESULT batch if a final recommendation is available
474 */
475 static void check_and_build_recommendation(private_tnccs_20_t *this)
476 {
477 TNC_IMV_Action_Recommendation rec;
478 TNC_IMV_Evaluation_Result eval;
479 TNC_IMVID id;
480 chunk_t reason, language;
481 enumerator_t *enumerator;
482 pb_tnc_msg_t *msg;
483 pb_access_recommendation_code_t pb_rec;
484
485 if (!this->recs->have_recommendation(this->recs, &rec, &eval))
486 {
487 charon->imvs->solicit_recommendation(charon->imvs, this->connection_id);
488 }
489 if (this->recs->have_recommendation(this->recs, &rec, &eval))
490 {
491 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_RESULT);
492
493 msg = pb_assessment_result_msg_create(eval);
494 this->batch->add_msg(this->batch, msg);
495
496 /**
497 * Map IMV Action Recommendation codes to PB Access Recommendation codes
498 */
499 switch (rec)
500 {
501 case TNC_IMV_ACTION_RECOMMENDATION_ALLOW:
502 pb_rec = PB_REC_ACCESS_ALLOWED;
503 break;
504 case TNC_IMV_ACTION_RECOMMENDATION_ISOLATE:
505 pb_rec = PB_REC_QUARANTINED;
506 break;
507 case TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS:
508 case TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION:
509 default:
510 pb_rec = PB_REC_ACCESS_DENIED;
511 }
512 msg = pb_access_recommendation_msg_create(pb_rec);
513 this->batch->add_msg(this->batch, msg);
514
515 enumerator = this->recs->create_reason_enumerator(this->recs);
516 while (enumerator->enumerate(enumerator, &id, &reason, &language))
517 {
518 msg = pb_reason_string_msg_create(reason, language);
519 this->batch->add_msg(this->batch, msg);
520 }
521 enumerator->destroy(enumerator);
522 }
523 }
524
525 METHOD(tls_t, build, status_t,
526 private_tnccs_20_t *this, void *buf, size_t *buflen, size_t *msglen)
527 {
528 status_t status;
529 pb_tnc_state_t state;
530
531 /* Initialize the connection */
532 if (!this->is_server && !this->connection_id)
533 {
534 pb_tnc_msg_t *msg;
535 char *pref_lang;
536
537 this->connection_id = charon->tnccs->create_connection(charon->tnccs,
538 (tnccs_t*)this, _send_msg,
539 &this->request_handshake_retry, NULL);
540 if (!this->connection_id)
541 {
542 return FAILED;
543 }
544
545 /* Create PB-TNC Language Preference message */
546 pref_lang = charon->imcs->get_preferred_language(charon->imcs);
547 msg = pb_language_preference_msg_create(chunk_create(pref_lang,
548 strlen(pref_lang)));
549 this->mutex->lock(this->mutex);
550 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CDATA);
551 this->batch->add_msg(this->batch, msg);
552 this->mutex->unlock(this->mutex);
553
554 charon->imcs->notify_connection_change(charon->imcs,
555 this->connection_id, TNC_CONNECTION_STATE_CREATE);
556 charon->imcs->notify_connection_change(charon->imcs,
557 this->connection_id, TNC_CONNECTION_STATE_HANDSHAKE);
558 this->send_msg = TRUE;
559 charon->imcs->begin_handshake(charon->imcs, this->connection_id);
560 this->send_msg = FALSE;
561 }
562
563 state = this->state_machine->get_state(this->state_machine);
564
565 if (this->is_server && this->fatal_error && state == PB_STATE_END)
566 {
567 DBG1(DBG_TNC, "a fatal PB-TNC error occurred, terminating connection");
568 return FAILED;
569 }
570
571 /* Do not allow any asynchronous IMCs or IMVs to add additional messages */
572 this->mutex->lock(this->mutex);
573
574 if (this->request_handshake_retry)
575 {
576 if (state != PB_STATE_INIT)
577 {
578 build_retry_batch(this);
579 }
580
581 /* Reset the flag for the next handshake retry request */
582 this->request_handshake_retry = FALSE;
583 }
584
585 if (!this->batch)
586 {
587 if (this->is_server)
588 {
589 if (state == PB_STATE_SERVER_WORKING)
590 {
591 check_and_build_recommendation(this);
592 }
593 }
594 else
595 {
596 /**
597 * if the DECIDED state has been reached and no CRETRY is under way
598 * or if a CLOSE batch with error messages has been received,
599 * a PB-TNC client replies with an empty CLOSE batch.
600 */
601 if (state == PB_STATE_DECIDED || state == PB_STATE_END)
602 {
603 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CLOSE);
604 }
605 }
606 }
607
608 if (this->batch)
609 {
610 pb_tnc_batch_type_t batch_type;
611 chunk_t data;
612
613 batch_type = this->batch->get_type(this->batch);
614
615 if (this->state_machine->send_batch(this->state_machine, batch_type))
616 {
617 this->batch->build(this->batch);
618 data = this->batch->get_encoding(this->batch);
619 DBG1(DBG_TNC, "sending PB-TNC %N batch (%d bytes) for Connection ID %u",
620 pb_tnc_batch_type_names, batch_type, data.len,
621 this->connection_id);
622 DBG3(DBG_TNC, "%B", &data);
623 *msglen = data.len;
624
625 if (data.len > *buflen)
626 {
627 DBG1(DBG_TNC, "fragmentation of PB-TNC batch not supported yet");
628 }
629 else
630 {
631 *buflen = data.len;
632 }
633 memcpy(buf, data.ptr, *buflen);
634 status = ALREADY_DONE;
635 }
636 else
637 {
638 DBG1(DBG_TNC, "cancelling unexpected PB-TNC batch type: %N",
639 pb_tnc_batch_type_names, batch_type);
640 status = INVALID_STATE;
641 }
642
643 this->batch->destroy(this->batch);
644 this->batch = NULL;
645 }
646 else
647 {
648 DBG1(DBG_TNC, "no PB-TNC batch to send");
649 status = INVALID_STATE;
650 }
651 this->mutex->unlock(this->mutex);
652
653 return status;
654 }
655
656 METHOD(tls_t, is_server, bool,
657 private_tnccs_20_t *this)
658 {
659 return this->is_server;
660 }
661
662 METHOD(tls_t, get_purpose, tls_purpose_t,
663 private_tnccs_20_t *this)
664 {
665 return TLS_PURPOSE_EAP_TNC;
666 }
667
668 METHOD(tls_t, is_complete, bool,
669 private_tnccs_20_t *this)
670 {
671 TNC_IMV_Action_Recommendation rec;
672 TNC_IMV_Evaluation_Result eval;
673
674 if (this->recs && this->recs->have_recommendation(this->recs, &rec, &eval))
675 {
676 return charon->imvs->enforce_recommendation(charon->imvs, rec, eval);
677 }
678 else
679 {
680 return FALSE;
681 }
682 }
683
684 METHOD(tls_t, get_eap_msk, chunk_t,
685 private_tnccs_20_t *this)
686 {
687 return chunk_empty;
688 }
689
690 METHOD(tls_t, destroy, void,
691 private_tnccs_20_t *this)
692 {
693 charon->tnccs->remove_connection(charon->tnccs, this->connection_id,
694 this->is_server);
695 this->state_machine->destroy(this->state_machine);
696 this->mutex->destroy(this->mutex);
697 DESTROY_IF(this->batch);
698 free(this);
699 }
700
701 /**
702 * See header
703 */
704 tls_t *tnccs_20_create(bool is_server)
705 {
706 private_tnccs_20_t *this;
707
708 INIT(this,
709 .public = {
710 .process = _process,
711 .build = _build,
712 .is_server = _is_server,
713 .get_purpose = _get_purpose,
714 .is_complete = _is_complete,
715 .get_eap_msk = _get_eap_msk,
716 .destroy = _destroy,
717 },
718 .is_server = is_server,
719 .state_machine = pb_tnc_state_machine_create(is_server),
720 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
721 );
722
723 return &this->public;
724 }