refactoring of change_state()
[strongswan.git] / src / libimcv / imv / imv_agent.c
1 /*
2 * Copyright (C) 2011 Andreas Steffen, HSR Hochschule fuer Technik Rapperswil
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 */
14
15 #include "imcv.h"
16 #include "imv_agent.h"
17
18 #include <tncif_names.h>
19
20 #include <debug.h>
21 #include <utils/linked_list.h>
22 #include <threading/rwlock.h>
23
24 typedef struct private_imv_agent_t private_imv_agent_t;
25
26 /**
27 * Private data of an imv_agent_t object.
28 */
29 struct private_imv_agent_t {
30
31 /**
32 * Public members of imv_agent_t
33 */
34 imv_agent_t public;
35
36 /**
37 * name of IMV
38 */
39 const char *name;
40
41 /**
42 * message type of IMV
43 */
44 TNC_MessageType type;
45
46 /**
47 * ID of IMV as assigned by TNCS
48 */
49 TNC_IMVID id;
50
51 /**
52 * list of TNCS connection entries
53 */
54 linked_list_t *connections;
55
56 /**
57 * rwlock to lock TNCS connection entries
58 */
59 rwlock_t *connection_lock;
60
61 /**
62 * Inform a TNCS about the set of message types the IMV is able to receive
63 *
64 * @param imv_id IMV ID assigned by TNCS
65 * @param supported_types list of supported message types
66 * @param type_count number of list elements
67 * @return TNC result code
68 */
69 TNC_Result (*report_message_types)(TNC_IMVID imv_id,
70 TNC_MessageTypeList supported_types,
71 TNC_UInt32 type_count);
72
73 /**
74 * Call when an IMV-IMC message is to be sent
75 *
76 * @param imv_id IMV ID assigned by TNCS
77 * @param connection_id network connection ID assigned by TNCS
78 * @param msg message to send
79 * @param msg_len message length in bytes
80 * @param msg_type message type
81 * @return TNC result code
82 */
83 TNC_Result (*send_message)(TNC_IMVID imv_id,
84 TNC_ConnectionID connection_id,
85 TNC_BufferReference msg,
86 TNC_UInt32 msg_len,
87 TNC_MessageType msg_type);
88
89 /**
90 * Deliver IMV Action Recommendation and IMV Evaluation Results to the TNCS
91 *
92 * @param imv_id IMV ID assigned by TNCS
93 # @param connection_id network connection ID assigned by TNCS
94 * @param rec IMV action recommendation
95 * @param eval IMV evaluation result
96 * @return TNC result code
97 */
98 TNC_Result (*provide_recommendation)(TNC_IMVID imv_id,
99 TNC_ConnectionID connection_id,
100 TNC_IMV_Action_Recommendation rec,
101 TNC_IMV_Evaluation_Result eval);
102
103 /**
104 * Get the value of an attribute associated with a connection
105 * or with the TNCS as a whole.
106 *
107 * @param imv_id IMV ID assigned by TNCS
108 * @param connection_id network connection ID assigned by TNCS
109 * @param attribute_id attribute ID
110 * @param buffer_len length of buffer in bytes
111 * @param buffer buffer
112 * @param out_value_len size in bytes of attribute stored in buffer
113 * @return TNC result code
114 */
115 TNC_Result (*get_attribute)(TNC_IMVID imv_id,
116 TNC_ConnectionID connection_id,
117 TNC_AttributeID attribute_id,
118 TNC_UInt32 buffer_len,
119 TNC_BufferReference buffer,
120 TNC_UInt32 *out_value_len);
121
122 /**
123 * Set the value of an attribute associated with a connection
124 * or with the TNCS as a whole.
125 *
126 * @param imv_id IMV ID assigned by TNCS
127 * @param connection_id network connection ID assigned by TNCS
128 * @param attribute_id attribute ID
129 * @param buffer_len length of buffer in bytes
130 * @param buffer buffer
131 * @return TNC result code
132 */
133 TNC_Result (*set_attribute)(TNC_IMVID imv_id,
134 TNC_ConnectionID connection_id,
135 TNC_AttributeID attribute_id,
136 TNC_UInt32 buffer_len,
137 TNC_BufferReference buffer);
138 };
139
140 METHOD(imv_agent_t, bind_functions, TNC_Result,
141 private_imv_agent_t *this, TNC_TNCS_BindFunctionPointer bind_function)
142 {
143 if (!bind_function)
144 {
145 DBG1(DBG_IMV, "TNC server failed to provide bind function");
146 return TNC_RESULT_INVALID_PARAMETER;
147 }
148 if (bind_function(this->id, "TNC_TNCS_ReportMessageTypes",
149 (void**)&this->report_message_types) != TNC_RESULT_SUCCESS)
150 {
151 this->report_message_types = NULL;
152 }
153 if (bind_function(this->id, "TNC_TNCS_RequestHandshakeRetry",
154 (void**)&this->public.request_handshake_retry) != TNC_RESULT_SUCCESS)
155 {
156 this->public.request_handshake_retry = NULL;
157 }
158 if (bind_function(this->id, "TNC_TNCS_SendMessage",
159 (void**)&this->send_message) != TNC_RESULT_SUCCESS)
160 {
161 this->send_message = NULL;
162 }
163 if (bind_function(this->id, "TNC_TNCS_ProvideRecommendation",
164 (void**)&this->provide_recommendation) != TNC_RESULT_SUCCESS)
165 {
166 this->provide_recommendation = NULL;
167 }
168 if (bind_function(this->id, "TNC_TNCS_GetAttribute",
169 (void**)&this->get_attribute) != TNC_RESULT_SUCCESS)
170 {
171 this->get_attribute = NULL;
172 }
173 if (bind_function(this->id, "TNC_TNCS_SetAttribute",
174 (void**)&this->set_attribute) != TNC_RESULT_SUCCESS)
175 {
176 this->set_attribute = NULL;
177 }
178 DBG2(DBG_IMV, "IMV %u \"%s\" provided with bind function",
179 this->id, this->name);
180
181 if (this->report_message_types)
182 {
183 this->report_message_types(this->id, &this->type, 1);
184 }
185 return TNC_RESULT_SUCCESS;
186 }
187
188 /**
189 * finds a connection state based on its Connection ID
190 */
191 static imv_state_t* find_connection(private_imv_agent_t *this,
192 TNC_ConnectionID id)
193 {
194 enumerator_t *enumerator;
195 imv_state_t *state, *found = NULL;
196
197 this->connection_lock->read_lock(this->connection_lock);
198 enumerator = this->connections->create_enumerator(this->connections);
199 while (enumerator->enumerate(enumerator, &state))
200 {
201 if (id == state->get_connection_id(state))
202 {
203 found = state;
204 break;
205 }
206 }
207 enumerator->destroy(enumerator);
208 this->connection_lock->unlock(this->connection_lock);
209
210 return found;
211 }
212
213 /**
214 * delete a connection state with a given Connection ID
215 */
216 static bool delete_connection(private_imv_agent_t *this, TNC_ConnectionID id)
217 {
218 enumerator_t *enumerator;
219 imv_state_t *state;
220 bool found = FALSE;
221
222 this->connection_lock->write_lock(this->connection_lock);
223 enumerator = this->connections->create_enumerator(this->connections);
224 while (enumerator->enumerate(enumerator, &state))
225 {
226 if (id == state->get_connection_id(state))
227 {
228 found = TRUE;
229 state->destroy(state);
230 this->connections->remove_at(this->connections, enumerator);
231 break;
232 }
233 }
234 enumerator->destroy(enumerator);
235 this->connection_lock->unlock(this->connection_lock);
236
237 return found;
238 }
239
240 METHOD(imv_agent_t, create_state, TNC_Result,
241 private_imv_agent_t *this, imv_state_t *state)
242 {
243 TNC_ConnectionID connection_id;
244
245 connection_id = state->get_connection_id(state);
246 if (find_connection(this, connection_id))
247 {
248 DBG1(DBG_IMV, "IMV %u \"%s\" already created a state for Connection ID %u",
249 this->id, this->name, connection_id);
250 state->destroy(state);
251 return TNC_RESULT_OTHER;
252 }
253 this->connection_lock->write_lock(this->connection_lock);
254 this->connections->insert_last(this->connections, state);
255 this->connection_lock->unlock(this->connection_lock);
256 DBG2(DBG_IMV, "IMV %u \"%s\" created a state for Connection ID %u",
257 this->id, this->name, connection_id);
258 return TNC_RESULT_SUCCESS;
259 }
260
261 METHOD(imv_agent_t, delete_state, TNC_Result,
262 private_imv_agent_t *this, TNC_ConnectionID connection_id)
263 {
264 if (!delete_connection(this, connection_id))
265 {
266 DBG1(DBG_IMV, "IMV %u \"%s\" has no state for Connection ID %u",
267 this->id, this->name, connection_id);
268 return TNC_RESULT_FATAL;
269 }
270 DBG2(DBG_IMV, "IMV %u \"%s\" deleted the state of Connection ID %u",
271 this->id, this->name, connection_id);
272 return TNC_RESULT_SUCCESS;
273 }
274
275 METHOD(imv_agent_t, change_state, TNC_Result,
276 private_imv_agent_t *this, TNC_ConnectionID connection_id,
277 TNC_ConnectionState new_state,
278 imv_state_t **state_p)
279 {
280 imv_state_t *state;
281
282 switch (new_state)
283 {
284 case TNC_CONNECTION_STATE_HANDSHAKE:
285 case TNC_CONNECTION_STATE_ACCESS_ALLOWED:
286 case TNC_CONNECTION_STATE_ACCESS_ISOLATED:
287 case TNC_CONNECTION_STATE_ACCESS_NONE:
288 state = find_connection(this, connection_id);
289 if (!state)
290 {
291 DBG1(DBG_IMV, "IMV %u \"%s\" has no state for Connection ID %u",
292 this->id, this->name, connection_id);
293 return TNC_RESULT_FATAL;
294 }
295 state->change_state(state, new_state);
296 DBG2(DBG_IMV, "IMV %u \"%s\" changed state of Connection ID %u to '%N'",
297 this->id, this->name, connection_id,
298 TNC_Connection_State_names, new_state);
299 if (state_p)
300 {
301 *state_p = state;
302 }
303 break;
304 case TNC_CONNECTION_STATE_CREATE:
305 DBG1(DBG_IMV, "state '%N' should be handled by create_state()",
306 TNC_Connection_State_names, new_state);
307 return TNC_RESULT_FATAL;
308 case TNC_CONNECTION_STATE_DELETE:
309 DBG1(DBG_IMV, "state '%N' should be handled by delete_state()",
310 TNC_Connection_State_names, new_state);
311 return TNC_RESULT_FATAL;
312 default:
313 DBG1(DBG_IMV, "IMV %u \"%s\" was notified of unknown state %u "
314 "for Connection ID %u",
315 this->id, this->name, new_state, connection_id);
316 return TNC_RESULT_INVALID_PARAMETER;
317 }
318 return TNC_RESULT_SUCCESS;
319 }
320
321 METHOD(imv_agent_t, get_state, bool,
322 private_imv_agent_t *this, TNC_ConnectionID connection_id,
323 imv_state_t **state)
324 {
325 *state = find_connection(this, connection_id);
326 if (!*state)
327 {
328 DBG1(DBG_IMV, "IMV %u \"%s\" has no state for Connection ID %u",
329 this->id, this->name, connection_id);
330 return FALSE;
331 }
332 return TRUE;
333 }
334
335 METHOD(imv_agent_t, send_message, TNC_Result,
336 private_imv_agent_t *this, TNC_ConnectionID connection_id, chunk_t msg)
337 {
338 if (!this->send_message)
339 {
340 return TNC_RESULT_FATAL;
341 }
342 return this->send_message(this->id, connection_id, msg.ptr, msg.len,
343 this->type);
344 }
345
346 METHOD(imv_agent_t, set_recommendation, TNC_Result,
347 private_imv_agent_t *this, TNC_ConnectionID connection_id,
348 TNC_IMV_Action_Recommendation rec,
349 TNC_IMV_Evaluation_Result eval)
350 {
351 imv_state_t *state;
352
353 state = find_connection(this, connection_id);
354 if (!state)
355 {
356 DBG1(DBG_IMV, "IMV %u \"%s\" has no state for Connection ID %u",
357 this->id, this->name, connection_id);
358 return TNC_RESULT_FATAL;
359 }
360
361 state->set_recommendation(state, rec, eval);
362 return this->provide_recommendation(this->id, connection_id, rec, eval);
363 }
364
365 METHOD(imv_agent_t, receive_message, TNC_Result,
366 private_imv_agent_t *this, TNC_ConnectionID connection_id, chunk_t msg,
367 TNC_MessageType msg_type, pa_tnc_msg_t **pa_tnc_msg)
368 {
369 pa_tnc_msg_t *pa_msg, *error_msg;
370 pa_tnc_attr_t *error_attr;
371 enumerator_t *enumerator;
372 TNC_Result result;
373
374 DBG2(DBG_IMV, "IMV %u \"%s\" received message type 0x%08x for Connection ID %u",
375 this->id, this->name, msg_type, connection_id);
376
377 *pa_tnc_msg = NULL;
378 pa_msg = pa_tnc_msg_create_from_data(msg);
379
380 switch (pa_msg->process(pa_msg))
381 {
382 case SUCCESS:
383 *pa_tnc_msg = pa_msg;
384 break;
385 case VERIFY_ERROR:
386 if (!this->send_message)
387 {
388 /* TNCS doen't have a SendMessage() function */
389 return TNC_RESULT_FATAL;
390 }
391
392 /* build error message */
393 error_msg = pa_tnc_msg_create();
394 enumerator = pa_msg->create_error_enumerator(pa_msg);
395 while (enumerator->enumerate(enumerator, &error_attr))
396 {
397 error_msg->add_attribute(error_msg,
398 error_attr->get_ref(error_attr));
399 }
400 enumerator->destroy(enumerator);
401 error_msg->build(error_msg);
402
403 /* send error message */
404 msg = error_msg->get_encoding(error_msg);
405 result = this->send_message(this->id, connection_id,
406 msg.ptr, msg.len, msg_type);
407
408 /* clean up */
409 error_msg->destroy(error_msg);
410 pa_msg->destroy(pa_msg);
411 return result;
412 case FAILED:
413 default:
414 pa_msg->destroy(pa_msg);
415 return set_recommendation(this, connection_id,
416 TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION,
417 TNC_IMV_EVALUATION_RESULT_ERROR);
418 }
419 return TNC_RESULT_SUCCESS;
420 }
421
422 METHOD(imv_agent_t, provide_recommendation, TNC_Result,
423 private_imv_agent_t *this, TNC_ConnectionID connection_id)
424 {
425 imv_state_t *state;
426 TNC_IMV_Action_Recommendation rec;
427 TNC_IMV_Evaluation_Result eval;
428 TNC_UInt32 lang_len;
429 char buf[BUF_LEN];
430 chunk_t pref_lang = { buf, 0 }, reason_string, reason_lang;
431
432 state = find_connection(this, connection_id);
433 if (!state)
434 {
435 DBG1(DBG_IMV, "IMV %u \"%s\" has no state for Connection ID %u",
436 this->id, this->name, connection_id);
437 return TNC_RESULT_FATAL;
438 }
439 state->get_recommendation(state, &rec, &eval);
440
441
442 /* send a reason string if action recommendation is not allow */
443 if (rec != TNC_IMV_ACTION_RECOMMENDATION_ALLOW)
444 {
445 /* check if there a preferred language has been requested */
446 if (this->get_attribute &&
447 this->get_attribute(this->id, connection_id,
448 TNC_ATTRIBUTEID_PREFERRED_LANGUAGE, BUF_LEN,
449 buf, &lang_len) == TNC_RESULT_SUCCESS &&
450 lang_len <= BUF_LEN)
451 {
452 pref_lang.len = lang_len;
453 DBG2(DBG_IMV, "preferred language is '%.*s'",
454 pref_lang.len, pref_lang.ptr);
455 }
456
457 /* find a reason string for the preferred or default language and set it */
458 if (this->set_attribute &&
459 state->get_reason_string(state, pref_lang, &reason_string,
460 &reason_lang))
461 {
462 this->set_attribute(this->id, connection_id,
463 TNC_ATTRIBUTEID_REASON_STRING,
464 reason_string.len, reason_string.ptr);
465 this->set_attribute(this->id, connection_id,
466 TNC_ATTRIBUTEID_REASON_LANGUAGE,
467 reason_lang.len, reason_lang.ptr);
468 }
469 }
470
471 return this->provide_recommendation(this->id, connection_id, rec, eval);
472 }
473
474 METHOD(imv_agent_t, destroy, void,
475 private_imv_agent_t *this)
476 {
477 DBG1(DBG_IMV, "IMV %u \"%s\" terminated", this->id, this->name);
478 this->connections->destroy_offset(this->connections,
479 offsetof(imv_state_t, destroy));
480 this->connection_lock->destroy(this->connection_lock);
481 free(this);
482
483 /* decrease the reference count or terminate */
484 libimcv_deinit();
485 }
486
487 /**
488 * Described in header.
489 */
490 imv_agent_t *imv_agent_create(const char *name,
491 pen_t vendor_id, u_int32_t subtype,
492 TNC_IMVID id, TNC_Version *actual_version)
493 {
494 private_imv_agent_t *this;
495
496 /* initialize or increase the reference count */
497 if (!libimcv_init())
498 {
499 return NULL;
500 }
501
502 INIT(this,
503 .public = {
504 .bind_functions = _bind_functions,
505 .create_state = _create_state,
506 .delete_state = _delete_state,
507 .change_state = _change_state,
508 .get_state = _get_state,
509 .send_message = _send_message,
510 .receive_message = _receive_message,
511 .set_recommendation = _set_recommendation,
512 .provide_recommendation = _provide_recommendation,
513 .destroy = _destroy,
514 },
515 .name = name,
516 .type = (vendor_id << 8) | (subtype && 0xff),
517 .id = id,
518 .connections = linked_list_create(),
519 .connection_lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
520 );
521
522 *actual_version = TNC_IFIMV_VERSION_1;
523 DBG1(DBG_IMV, "IMV %u \"%s\" initialized", this->id, this->name);
524
525 return &this->public;
526 }
527
528