accumulate package counts over multiple attributes
[strongswan.git] / src / libimcv / plugins / imv_os / imv_os.c
1 /*
2 * Copyright (C) 2012 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 "imv_os_state.h"
17 #include "imv_os_database.h"
18
19 #include <imv/imv_agent.h>
20 #include <imv/imv_msg.h>
21 #include <ietf/ietf_attr.h>
22 #include <ietf/ietf_attr_attr_request.h>
23 #include <ietf/ietf_attr_default_pwd_enabled.h>
24 #include <ietf/ietf_attr_fwd_enabled.h>
25 #include <ietf/ietf_attr_installed_packages.h>
26 #include <ietf/ietf_attr_numeric_version.h>
27 #include <ietf/ietf_attr_op_status.h>
28 #include <ietf/ietf_attr_pa_tnc_error.h>
29 #include <ietf/ietf_attr_product_info.h>
30 #include <ietf/ietf_attr_remediation_instr.h>
31 #include <ietf/ietf_attr_string_version.h>
32 #include <ita/ita_attr.h>
33 #include <ita/ita_attr_get_settings.h>
34 #include <ita/ita_attr_settings.h>
35 #include <ita/ita_attr_angel.h>
36
37 #include <tncif_names.h>
38 #include <tncif_pa_subtypes.h>
39
40 #include <pen/pen.h>
41 #include <collections/linked_list.h>
42 #include <utils/debug.h>
43 #include <utils/lexparser.h>
44
45 /* IMV definitions */
46
47 static const char imv_name[] = "OS";
48
49 static pen_type_t msg_types[] = {
50 { PEN_IETF, PA_SUBTYPE_IETF_OPERATING_SYSTEM }
51 };
52
53 static imv_agent_t *imv_os;
54
55 /**
56 * IMV OS database
57 */
58 static imv_os_database_t *os_db;
59
60 /*
61 * see section 3.8.1 of TCG TNC IF-IMV Specification 1.3
62 */
63 TNC_Result TNC_IMV_Initialize(TNC_IMVID imv_id,
64 TNC_Version min_version,
65 TNC_Version max_version,
66 TNC_Version *actual_version)
67 {
68 char *uri;
69
70 if (imv_os)
71 {
72 DBG1(DBG_IMV, "IMV \"%s\" has already been initialized", imv_name);
73 return TNC_RESULT_ALREADY_INITIALIZED;
74 }
75 imv_os = imv_agent_create(imv_name, msg_types, countof(msg_types),
76 imv_id, actual_version);
77 if (!imv_os)
78 {
79 return TNC_RESULT_FATAL;
80 }
81 if (min_version > TNC_IFIMV_VERSION_1 || max_version < TNC_IFIMV_VERSION_1)
82 {
83 DBG1(DBG_IMV, "no common IF-IMV version");
84 return TNC_RESULT_NO_COMMON_VERSION;
85 }
86
87 /* attach OS database */
88 uri = lib->settings->get_str(lib->settings,
89 "libimcv.plugins.imv-os.database", NULL);
90 if (uri)
91 {
92 os_db = imv_os_database_create(uri);
93 }
94
95 return TNC_RESULT_SUCCESS;
96 }
97
98 /**
99 * see section 3.8.2 of TCG TNC IF-IMV Specification 1.3
100 */
101 TNC_Result TNC_IMV_NotifyConnectionChange(TNC_IMVID imv_id,
102 TNC_ConnectionID connection_id,
103 TNC_ConnectionState new_state)
104 {
105 imv_state_t *state;
106
107 if (!imv_os)
108 {
109 DBG1(DBG_IMV, "IMV \"%s\" has not been initialized", imv_name);
110 return TNC_RESULT_NOT_INITIALIZED;
111 }
112 switch (new_state)
113 {
114 case TNC_CONNECTION_STATE_CREATE:
115 state = imv_os_state_create(connection_id);
116 return imv_os->create_state(imv_os, state);
117 case TNC_CONNECTION_STATE_DELETE:
118 return imv_os->delete_state(imv_os, connection_id);
119 default:
120 return imv_os->change_state(imv_os, connection_id,
121 new_state, NULL);
122 }
123 }
124
125 /**
126 * print multi-line values to debug output
127 */
128 static void dbg_imv_multi_line(chunk_t value)
129 {
130 chunk_t line;
131
132 while (extract_token(&line, '\n', &value))
133 {
134 DBG2(DBG_IMV, " %.*s", line.len, line.ptr);
135 }
136 if (value.len)
137 {
138 DBG2(DBG_IMV, " %.*s", value.len, value.ptr);
139 }
140 }
141
142 static TNC_Result receive_message(imv_state_t *state, imv_msg_t *in_msg)
143 {
144 imv_msg_t *out_msg;
145 imv_os_state_t *os_state;
146 enumerator_t *enumerator;
147 pa_tnc_attr_t *attr;
148 pen_type_t type;
149 TNC_Result result;
150 chunk_t os_name = chunk_empty;
151 chunk_t os_version = chunk_empty;
152 bool fatal_error = FALSE, assessment = FALSE;
153 int count, count_bad, count_ok;
154
155 os_state = (imv_os_state_t*)state;
156
157 /* parse received PA-TNC message and handle local and remote errors */
158 result = in_msg->receive(in_msg, &fatal_error);
159 if (result != TNC_RESULT_SUCCESS)
160 {
161 return result;
162 }
163
164 out_msg = imv_msg_create_as_reply(in_msg);
165
166 /* analyze PA-TNC attributes */
167 enumerator = in_msg->create_attribute_enumerator(in_msg);
168 while (enumerator->enumerate(enumerator, &attr))
169 {
170 type = attr->get_type(attr);
171
172 if (type.vendor_id == PEN_IETF)
173 {
174 switch (type.type)
175 {
176 case IETF_ATTR_PRODUCT_INFORMATION:
177 {
178 ietf_attr_product_info_t *attr_cast;
179 pen_t vendor_id;
180
181 attr_cast = (ietf_attr_product_info_t*)attr;
182 os_name = attr_cast->get_info(attr_cast, &vendor_id, NULL);
183 if (vendor_id != PEN_IETF)
184 {
185 DBG1(DBG_IMV, "operating system name is '%.*s' "
186 "from vendor %N", os_name.len, os_name.ptr,
187 pen_names, vendor_id);
188 }
189 else
190 {
191 DBG1(DBG_IMV, "operating system name is '%.*s'",
192 os_name.len, os_name.ptr);
193 }
194 break;
195 }
196 case IETF_ATTR_STRING_VERSION:
197 {
198 ietf_attr_string_version_t *attr_cast;
199
200 attr_cast = (ietf_attr_string_version_t*)attr;
201 os_version = attr_cast->get_version(attr_cast, NULL, NULL);
202 if (os_version.len)
203 {
204 DBG1(DBG_IMV, "operating system version is '%.*s'",
205 os_version.len, os_version.ptr);
206 }
207 break;
208 }
209 case IETF_ATTR_NUMERIC_VERSION:
210 {
211 ietf_attr_numeric_version_t *attr_cast;
212 u_int32_t major, minor;
213
214 attr_cast = (ietf_attr_numeric_version_t*)attr;
215 attr_cast->get_version(attr_cast, &major, &minor);
216 DBG1(DBG_IMV, "operating system numeric version is %d.%d",
217 major, minor);
218 break;
219 }
220 case IETF_ATTR_OPERATIONAL_STATUS:
221 {
222 ietf_attr_op_status_t *attr_cast;
223 op_status_t op_status;
224 op_result_t op_result;
225 time_t last_boot;
226
227 attr_cast = (ietf_attr_op_status_t*)attr;
228 op_status = attr_cast->get_status(attr_cast);
229 op_result = attr_cast->get_result(attr_cast);
230 last_boot = attr_cast->get_last_use(attr_cast);
231 DBG1(DBG_IMV, "operational status: %N, result: %N",
232 op_status_names, op_status, op_result_names, op_result);
233 DBG1(DBG_IMV, "last boot: %T", &last_boot, TRUE);
234 break;
235 }
236 case IETF_ATTR_FORWARDING_ENABLED:
237 {
238 ietf_attr_fwd_enabled_t *attr_cast;
239 os_fwd_status_t fwd_status;
240
241 attr_cast = (ietf_attr_fwd_enabled_t*)attr;
242 fwd_status = attr_cast->get_status(attr_cast);
243 DBG1(DBG_IMV, "IPv4 forwarding status: %N",
244 os_fwd_status_names, fwd_status);
245 break;
246 }
247 case IETF_ATTR_FACTORY_DEFAULT_PWD_ENABLED:
248 {
249 ietf_attr_default_pwd_enabled_t *attr_cast;
250 bool default_pwd_status;
251
252 attr_cast = (ietf_attr_default_pwd_enabled_t*)attr;
253 default_pwd_status = attr_cast->get_status(attr_cast);
254 DBG1(DBG_IMV, "factory default password: %sabled",
255 default_pwd_status ? "en":"dis");
256 break;
257 }
258 case IETF_ATTR_INSTALLED_PACKAGES:
259 {
260 ietf_attr_installed_packages_t *attr_cast;
261 enumerator_t *e;
262 status_t status;
263
264 /* Received at least one Installed Packages attribute */
265 os_state->set_package_request(os_state, FALSE);
266
267 if (!os_db)
268 {
269 break;
270 }
271 attr_cast = (ietf_attr_installed_packages_t*)attr;
272
273 e = attr_cast->create_enumerator(attr_cast);
274 status = os_db->check_packages(os_db, os_state, e);
275 e->destroy(e);
276
277 if (status == FAILED)
278 {
279 state->set_recommendation(state,
280 TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION,
281 TNC_IMV_EVALUATION_RESULT_ERROR);
282 assessment = TRUE;
283 }
284 break;
285 }
286 default:
287 break;
288 }
289 }
290 else if (type.vendor_id == PEN_ITA)
291 {
292 switch (type.type)
293 {
294 case ITA_ATTR_SETTINGS:
295 {
296 ita_attr_settings_t *attr_cast;
297 enumerator_t *e;
298 char *name;
299 chunk_t value;
300
301 attr_cast = (ita_attr_settings_t*)attr;
302 e = attr_cast->create_enumerator(attr_cast);
303 while (e->enumerate(e, &name, &value))
304 {
305 DBG1(DBG_IMV, "setting '%s'", name);
306 dbg_imv_multi_line(value);
307 }
308 e->destroy(e);
309 break;
310 }
311 case ITA_ATTR_START_ANGEL:
312 os_state->set_angel_count(os_state, TRUE);
313 break;
314 case ITA_ATTR_STOP_ANGEL:
315 os_state->set_angel_count(os_state, FALSE);
316 break;
317 default:
318 break;
319 }
320 }
321 }
322 enumerator->destroy(enumerator);
323
324 if (os_name.len && os_version.len)
325 {
326 os_type_t os_type;
327 char *product_info;
328 char *uri = "http://remediation.strongswan.org/fix-it/";
329 char *string = "use a Linux operating system instead of Windows 1.2.3";
330 char *lang_code = "en";
331
332 os_type = os_type_from_name(os_name);
333 os_state->set_info(os_state,os_type, os_name, os_version);
334 product_info = os_state->get_info(os_state, NULL, NULL, NULL);
335
336 if (streq(product_info, "Windows 1.2.3"))
337 {
338 DBG1(DBG_IMV, "OS '%s' is not supported", product_info);
339
340 attr = ietf_attr_remediation_instr_create_from_string(
341 chunk_create(string, strlen(string)),
342 chunk_create(lang_code, strlen(lang_code)));
343 out_msg->add_attribute(out_msg, attr);
344 attr = ietf_attr_remediation_instr_create_from_uri(
345 chunk_create(uri, strlen(uri)));
346 out_msg->add_attribute(out_msg, attr);
347
348 state->set_recommendation(state,
349 TNC_IMV_ACTION_RECOMMENDATION_ISOLATE,
350 TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MINOR);
351 assessment = TRUE;
352 }
353 else
354 {
355 ita_attr_get_settings_t *attr_cast;
356
357 DBG1(DBG_IMV, "requesting installed packages for '%s'",
358 product_info);
359 os_state->set_package_request(os_state, TRUE);
360 attr = ietf_attr_attr_request_create(PEN_IETF,
361 IETF_ATTR_INSTALLED_PACKAGES);
362 out_msg->add_attribute(out_msg, attr);
363
364 /* requesting Android or Linux settings */
365 attr = ita_attr_get_settings_create();
366 attr_cast = (ita_attr_get_settings_t*)attr;
367
368 if (os_type == OS_TYPE_ANDROID)
369 {
370 attr_cast->add(attr_cast, "android_id");
371 attr_cast->add(attr_cast, "install_non_market_apps");
372 }
373 else
374 {
375 attr_cast->add(attr_cast, "/proc/sys/kernel/random/boot_id");
376 attr_cast->add(attr_cast, "/proc/sys/kernel/tainted");
377 }
378 out_msg->add_attribute(out_msg, attr);
379 }
380 }
381
382 if (fatal_error)
383 {
384 state->set_recommendation(state,
385 TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION,
386 TNC_IMV_EVALUATION_RESULT_ERROR);
387 assessment = TRUE;
388 }
389
390 /* If all Installed Packages attributes were received, go to assessment */
391 if (!assessment &&
392 !os_state->get_package_request(os_state) &&
393 !os_state->get_angel_count(os_state))
394 {
395 os_state->get_count(os_state, &count, &count_bad, &count_ok);
396 DBG1(DBG_IMV, "processed %d packages: %d bad, %d ok, %d not found",
397 count, count_bad, count_ok, count - count_bad - count_ok);
398
399 if (count_bad)
400 {
401 state->set_recommendation(state,
402 TNC_IMV_ACTION_RECOMMENDATION_ISOLATE,
403 TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MINOR);
404 }
405 else
406 {
407 state->set_recommendation(state,
408 TNC_IMV_ACTION_RECOMMENDATION_ALLOW,
409 TNC_IMV_EVALUATION_RESULT_COMPLIANT);
410 }
411 assessment = TRUE;
412 }
413
414 if (assessment)
415 {
416 result = out_msg->send_assessment(out_msg);
417 out_msg->destroy(out_msg);
418 if (result != TNC_RESULT_SUCCESS)
419 {
420 return result;
421 }
422 return imv_os->provide_recommendation(imv_os, state);
423 }
424
425 /* send PA-TNC message with excl flag set */
426 result = out_msg->send(out_msg, TRUE);
427 out_msg->destroy(out_msg);
428
429 return result;
430 }
431
432 /**
433 * see section 3.8.4 of TCG TNC IF-IMV Specification 1.3
434 */
435 TNC_Result TNC_IMV_ReceiveMessage(TNC_IMVID imv_id,
436 TNC_ConnectionID connection_id,
437 TNC_BufferReference msg,
438 TNC_UInt32 msg_len,
439 TNC_MessageType msg_type)
440 {
441 imv_state_t *state;
442 imv_msg_t *in_msg;
443 TNC_Result result;
444
445 if (!imv_os)
446 {
447 DBG1(DBG_IMV, "IMV \"%s\" has not been initialized", imv_name);
448 return TNC_RESULT_NOT_INITIALIZED;
449 }
450 if (!imv_os->get_state(imv_os, connection_id, &state))
451 {
452 return TNC_RESULT_FATAL;
453 }
454 in_msg = imv_msg_create_from_data(imv_os, state, connection_id, msg_type,
455 chunk_create(msg, msg_len));
456 result = receive_message(state, in_msg);
457 in_msg->destroy(in_msg);
458
459 return result;
460 }
461
462 /**
463 * see section 3.8.6 of TCG TNC IF-IMV Specification 1.3
464 */
465 TNC_Result TNC_IMV_ReceiveMessageLong(TNC_IMVID imv_id,
466 TNC_ConnectionID connection_id,
467 TNC_UInt32 msg_flags,
468 TNC_BufferReference msg,
469 TNC_UInt32 msg_len,
470 TNC_VendorID msg_vid,
471 TNC_MessageSubtype msg_subtype,
472 TNC_UInt32 src_imc_id,
473 TNC_UInt32 dst_imv_id)
474 {
475 imv_state_t *state;
476 imv_msg_t *in_msg;
477 TNC_Result result;
478
479 if (!imv_os)
480 {
481 DBG1(DBG_IMV, "IMV \"%s\" has not been initialized", imv_name);
482 return TNC_RESULT_NOT_INITIALIZED;
483 }
484 if (!imv_os->get_state(imv_os, connection_id, &state))
485 {
486 return TNC_RESULT_FATAL;
487 }
488 in_msg = imv_msg_create_from_long_data(imv_os, state, connection_id,
489 src_imc_id, dst_imv_id, msg_vid, msg_subtype,
490 chunk_create(msg, msg_len));
491 result =receive_message(state, in_msg);
492 in_msg->destroy(in_msg);
493
494 return result;
495 }
496
497 /**
498 * see section 3.8.7 of TCG TNC IF-IMV Specification 1.3
499 */
500 TNC_Result TNC_IMV_SolicitRecommendation(TNC_IMVID imv_id,
501 TNC_ConnectionID connection_id)
502 {
503 imv_state_t *state;
504
505 if (!imv_os)
506 {
507 DBG1(DBG_IMV, "IMV \"%s\" has not been initialized", imv_name);
508 return TNC_RESULT_NOT_INITIALIZED;
509 }
510 if (!imv_os->get_state(imv_os, connection_id, &state))
511 {
512 return TNC_RESULT_FATAL;
513 }
514 return imv_os->provide_recommendation(imv_os, state);
515 }
516
517 /**
518 * see section 3.8.8 of TCG TNC IF-IMV Specification 1.3
519 */
520 TNC_Result TNC_IMV_BatchEnding(TNC_IMVID imv_id,
521 TNC_ConnectionID connection_id)
522 {
523 imv_state_t *state;
524 imv_os_state_t *os_state;
525 TNC_Result result = TNC_RESULT_SUCCESS;
526
527 if (!imv_os)
528 {
529 DBG1(DBG_IMV, "IMV \"%s\" has not been initialized", imv_name);
530 return TNC_RESULT_NOT_INITIALIZED;
531 }
532 if (!imv_os->get_state(imv_os, connection_id, &state))
533 {
534 return TNC_RESULT_FATAL;
535 }
536 os_state = (imv_os_state_t*)state;
537
538 if (os_state->get_info(os_state, NULL, NULL, NULL) == NULL)
539 {
540 imv_msg_t *out_msg;
541 pa_tnc_attr_t *attr;
542 ietf_attr_attr_request_t *attr_cast;
543
544 out_msg = imv_msg_create(imv_os, state, connection_id, imv_id,
545 TNC_IMCID_ANY, msg_types[0]);
546 attr = ietf_attr_attr_request_create(PEN_IETF,
547 IETF_ATTR_PRODUCT_INFORMATION);
548 attr_cast = (ietf_attr_attr_request_t*)attr;
549 attr_cast->add(attr_cast, PEN_IETF, IETF_ATTR_STRING_VERSION);
550 attr_cast->add(attr_cast, PEN_IETF, IETF_ATTR_NUMERIC_VERSION);
551 attr_cast->add(attr_cast, PEN_IETF, IETF_ATTR_OPERATIONAL_STATUS);
552 attr_cast->add(attr_cast, PEN_IETF, IETF_ATTR_FORWARDING_ENABLED);
553 attr_cast->add(attr_cast, PEN_IETF, IETF_ATTR_FACTORY_DEFAULT_PWD_ENABLED);
554 out_msg->add_attribute(out_msg, attr);
555
556 /* send PA-TNC message with excl flag not set */
557 result = out_msg->send(out_msg, FALSE);
558 out_msg->destroy(out_msg);
559 }
560
561 return result;
562 }
563
564 /**
565 * see section 3.8.9 of TCG TNC IF-IMV Specification 1.3
566 */
567 TNC_Result TNC_IMV_Terminate(TNC_IMVID imv_id)
568 {
569 if (!imv_os)
570 {
571 DBG1(DBG_IMV, "IMV \"%s\" has not been initialized", imv_name);
572 return TNC_RESULT_NOT_INITIALIZED;
573 }
574 DESTROY_IF(os_db);
575
576 imv_os->destroy(imv_os);
577 imv_os = NULL;
578
579 return TNC_RESULT_SUCCESS;
580 }
581
582 /**
583 * see section 4.2.8.1 of TCG TNC IF-IMV Specification 1.3
584 */
585 TNC_Result TNC_IMV_ProvideBindFunction(TNC_IMVID imv_id,
586 TNC_TNCS_BindFunctionPointer bind_function)
587 {
588 if (!imv_os)
589 {
590 DBG1(DBG_IMV, "IMV \"%s\" has not been initialized", imv_name);
591 return TNC_RESULT_NOT_INITIALIZED;
592 }
593 return imv_os->bind_functions(imv_os, bind_function);
594 }