refactored PB-TNC state machine in send direction
[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_language_preference_message.h"
24
25 #include <debug.h>
26 #include <daemon.h>
27 #include <threading/mutex.h>
28 #include <tnc/tncif.h>
29 #include <tnc/tncifimv_names.h>
30 #include <tnc/tnccs/tnccs.h>
31
32 typedef struct private_tnccs_20_t private_tnccs_20_t;
33
34 /**
35 * Private data of a tnccs_20_t object.
36 */
37 struct private_tnccs_20_t {
38
39 /**
40 * Public tls_t interface.
41 */
42 tls_t public;
43
44 /**
45 * TNCC if TRUE, TNCS if FALSE
46 */
47 bool is_server;
48
49 /**
50 * PB-TNC State Machine
51 */
52 pb_tnc_state_t state;
53
54 /**
55 * Connection ID assigned to this TNCCS connection
56 */
57 TNC_ConnectionID connection_id;
58
59 /**
60 * PB-TNC batch being constructed
61 */
62 pb_tnc_batch_t *batch;
63
64 /**
65 * Mutex locking the batch in construction
66 */
67 mutex_t *mutex;
68
69 /**
70 * Flag set by IMC/IMV RequestHandshakeRetry() function
71 */
72 bool request_handshake_retry;
73
74 /**
75 * Set of IMV recommendations (TNC Server only)
76 */
77 recommendations_t *recs;
78 };
79
80 METHOD(tnccs_t, send_message, void,
81 private_tnccs_20_t* this, TNC_IMCID imc_id, TNC_IMVID imv_id,
82 TNC_BufferReference msg,
83 TNC_UInt32 msg_len,
84 TNC_MessageType msg_type)
85 {
86 TNC_MessageSubtype msg_sub_type;
87 TNC_VendorID msg_vendor_id;
88 pb_tnc_message_t *pb_tnc_msg;
89 pb_tnc_batch_type_t batch_type;
90
91 msg_sub_type = msg_type & TNC_SUBTYPE_ANY;
92 msg_vendor_id = (msg_type >> 8) & TNC_VENDORID_ANY;
93
94 pb_tnc_msg = pb_pa_message_create(msg_vendor_id, msg_sub_type, imc_id, imv_id,
95 chunk_create(msg, msg_len));
96
97 /* adding PA message to SDATA or CDATA batch only */
98 batch_type = this->is_server ? PB_BATCH_SDATA : PB_BATCH_CDATA;
99 this->mutex->lock(this->mutex);
100 if (!this->batch)
101 {
102 this->batch = pb_tnc_batch_create(this->is_server, batch_type);
103 }
104 if (this->batch->get_type(this->batch) == batch_type)
105 {
106 this->batch->add_message(this->batch, pb_tnc_msg);
107 }
108 else
109 {
110 pb_tnc_msg->destroy(pb_tnc_msg);
111 }
112 this->mutex->unlock(this->mutex);
113 }
114
115 METHOD(tls_t, process, status_t,
116 private_tnccs_20_t *this, void *buf, size_t buflen)
117 {
118 chunk_t data;
119 pb_tnc_batch_t *batch;
120 pb_tnc_message_t *msg;
121 pb_tnc_state_t old_state;
122 enumerator_t *enumerator;
123 status_t status;
124
125 if (this->is_server && !this->connection_id)
126 {
127 this->connection_id = charon->tnccs->create_connection(charon->tnccs,
128 (tnccs_t*)this, _send_message,
129 &this->request_handshake_retry, &this->recs);
130 if (!this->connection_id)
131 {
132 return FAILED;
133 }
134 charon->imvs->notify_connection_change(charon->imvs,
135 this->connection_id, TNC_CONNECTION_STATE_CREATE);
136 }
137 data = chunk_create(buf, buflen);
138 DBG1(DBG_TNC, "received TNCCS Batch (%u bytes) for Connection ID %u",
139 data.len, this->connection_id);
140 DBG3(DBG_TNC, "%B", &data);
141 batch = pb_tnc_batch_create_from_data(this->is_server, data);
142
143 old_state = this->state;
144 status = batch->process(batch, &this->state);
145 if (this->state != old_state)
146 {
147 DBG2(DBG_TNC, "PB-TNC state transition from '%N' to '%N'",
148 pb_tnc_state_names, old_state, pb_tnc_state_names, this->state);
149 }
150 switch (status)
151 {
152 case SUCCESS:
153 default:
154 break;
155 case FAILED:
156 if (this->batch)
157 {
158 DBG1(DBG_TNC, "cancelling PB-TNC %N Batch",
159 pb_tnc_batch_type_names, this->batch->get_type(this->batch));
160 this->batch->destroy(this->batch);
161 }
162 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CLOSE);
163 /* fall through */
164 case VERIFY_ERROR:
165 enumerator = batch->create_error_enumerator(batch);
166 while (enumerator->enumerate(enumerator, &msg))
167 {
168 this->batch->add_message(this->batch, msg->get_ref(msg));
169 }
170 enumerator->destroy(enumerator);
171 }
172 batch->destroy(batch);
173
174 if (this->is_server)
175 {
176 charon->imvs->batch_ending(charon->imvs, this->connection_id);
177 }
178 else
179 {
180 charon->imcs->batch_ending(charon->imcs, this->connection_id);
181 }
182 return NEED_MORE;
183 }
184
185 /*
186 * Implements the PB-TNC state machine in send direction
187 */
188 static bool state_transition_upon_send(pb_tnc_state_t *state,
189 pb_tnc_batch_type_t batch_type)
190 {
191 switch (*state)
192 {
193 case PB_STATE_INIT:
194 if (batch_type == PB_BATCH_CDATA)
195 {
196 *state = PB_STATE_SERVER_WORKING;
197 break;
198 }
199 if (batch_type == PB_BATCH_SDATA)
200 {
201 *state = PB_STATE_CLIENT_WORKING;
202 break;
203 }
204 if (batch_type == PB_BATCH_CLOSE)
205 {
206 *state = PB_STATE_END;
207 break;
208 }
209 return FALSE;
210 case PB_STATE_SERVER_WORKING:
211 if (batch_type == PB_BATCH_SDATA)
212 {
213 *state = PB_STATE_CLIENT_WORKING;
214 break;
215 }
216 if (batch_type == PB_BATCH_RESULT)
217 {
218 *state = PB_STATE_DECIDED;
219 break;
220 }
221 if (batch_type == PB_BATCH_CRETRY ||
222 batch_type == PB_BATCH_SRETRY)
223 {
224 break;
225 }
226 if (batch_type == PB_BATCH_CLOSE)
227 {
228 *state = PB_STATE_END;
229 break;
230 }
231 return FALSE;
232 case PB_STATE_CLIENT_WORKING:
233 if (batch_type == PB_BATCH_CDATA)
234 {
235 *state = PB_STATE_SERVER_WORKING;
236 break;
237 }
238 if (batch_type == PB_BATCH_CRETRY)
239 {
240 break;
241 }
242 if (batch_type == PB_BATCH_CLOSE)
243 {
244 *state = PB_STATE_END;
245 break;
246 }
247 return FALSE;
248 case PB_STATE_DECIDED:
249 if (batch_type == PB_BATCH_CRETRY ||
250 batch_type == PB_BATCH_SRETRY)
251 {
252 *state = PB_STATE_SERVER_WORKING;
253 break;
254 }
255 if (batch_type == PB_BATCH_CLOSE)
256 {
257 *state = PB_STATE_END;
258 break;
259 }
260 return FALSE;
261 case PB_STATE_END:
262 if (batch_type == PB_BATCH_CLOSE)
263 {
264 break;
265 }
266 return FALSE;
267 }
268 return TRUE;
269 }
270
271 METHOD(tls_t, build, status_t,
272 private_tnccs_20_t *this, void *buf, size_t *buflen, size_t *msglen)
273 {
274 if (!this->is_server && !this->connection_id)
275 {
276 pb_tnc_message_t *msg;
277 char *pref_lang;
278
279 this->connection_id = charon->tnccs->create_connection(charon->tnccs,
280 (tnccs_t*)this, _send_message,
281 &this->request_handshake_retry, NULL);
282 if (!this->connection_id)
283 {
284 return FAILED;
285 }
286
287 /* Create PB-TNC Language Preference Message */
288 pref_lang = charon->imcs->get_preferred_language(charon->imcs);
289 msg = pb_language_preference_message_create(chunk_create(pref_lang,
290 strlen(pref_lang)));
291 this->mutex->lock(this->mutex);
292 this->batch = pb_tnc_batch_create(this->is_server, PB_BATCH_CDATA);
293 this->batch->add_message(this->batch, msg);
294 this->mutex->unlock(this->mutex);
295
296 charon->imcs->notify_connection_change(charon->imcs,
297 this->connection_id, TNC_CONNECTION_STATE_CREATE);
298 charon->imcs->notify_connection_change(charon->imcs,
299 this->connection_id, TNC_CONNECTION_STATE_HANDSHAKE);
300 charon->imcs->begin_handshake(charon->imcs, this->connection_id);
301 }
302
303 if (this->batch)
304 {
305 pb_tnc_batch_type_t batch_type;
306 pb_tnc_state_t old_state;
307 status_t status;
308 chunk_t data;
309
310 batch_type = this->batch->get_type(this->batch);
311 old_state = this->state;
312 this->mutex->lock(this->mutex);
313
314 if (state_transition_upon_send(&this->state, batch_type))
315 {
316 this->batch->build(this->batch);
317 data = this->batch->get_encoding(this->batch);
318 DBG1(DBG_TNC, "sending PB-TNC %N Batch (%d bytes) for Connection ID %u",
319 pb_tnc_batch_type_names, batch_type, data.len,
320 this->connection_id);
321 DBG3(DBG_TNC, "%B", &data);
322
323 *msglen = data.len;
324 *buflen = data.len;
325 memcpy(buf, data.ptr, data.len);
326 status = ALREADY_DONE;
327 }
328 else
329 {
330 DBG1(DBG_TNC, "cancelling unexpected PB-TNC Batch Type: %N",
331 pb_tnc_batch_type_names, batch_type);
332 status = INVALID_STATE;
333 }
334
335 this->batch->destroy(this->batch);
336 this->batch = NULL;
337 this->mutex->unlock(this->mutex);
338
339 if (this->state != old_state)
340 {
341 DBG2(DBG_TNC, "PB-TNC state transition from '%N' to '%N'",
342 pb_tnc_state_names, old_state, pb_tnc_state_names, this->state);
343 }
344 return status;
345 }
346 else
347 {
348 DBG1(DBG_TNC, "no TNCCS Batch to send");
349 return INVALID_STATE;
350 }
351 }
352
353 METHOD(tls_t, is_server, bool,
354 private_tnccs_20_t *this)
355 {
356 return this->is_server;
357 }
358
359 METHOD(tls_t, get_purpose, tls_purpose_t,
360 private_tnccs_20_t *this)
361 {
362 return TLS_PURPOSE_EAP_TNC;
363 }
364
365 METHOD(tls_t, is_complete, bool,
366 private_tnccs_20_t *this)
367 {
368 TNC_IMV_Action_Recommendation rec;
369 TNC_IMV_Evaluation_Result eval;
370
371 if (this->recs && this->recs->have_recommendation(this->recs, &rec, &eval))
372 {
373 DBG2(DBG_TNC, "Final recommendation '%N' and evaluation '%N'",
374 action_recommendation_names, rec, evaluation_result_names, eval);
375
376 return charon->imvs->enforce_recommendation(charon->imvs, rec);
377 }
378 else
379 {
380 return FALSE;
381 }
382 }
383
384 METHOD(tls_t, get_eap_msk, chunk_t,
385 private_tnccs_20_t *this)
386 {
387 return chunk_empty;
388 }
389
390 METHOD(tls_t, destroy, void,
391 private_tnccs_20_t *this)
392 {
393 charon->tnccs->remove_connection(charon->tnccs, this->connection_id);
394 this->mutex->destroy(this->mutex);
395 DESTROY_IF(this->batch);
396 free(this);
397 }
398
399 /**
400 * See header
401 */
402 tls_t *tnccs_20_create(bool is_server)
403 {
404 private_tnccs_20_t *this;
405
406 INIT(this,
407 .public = {
408 .process = _process,
409 .build = _build,
410 .is_server = _is_server,
411 .get_purpose = _get_purpose,
412 .is_complete = _is_complete,
413 .get_eap_msk = _get_eap_msk,
414 .destroy = _destroy,
415 },
416 .is_server = is_server,
417 .state = PB_STATE_INIT,
418 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
419 );
420
421 return &this->public;
422 }