Indicate IMV in assessment log statement
[strongswan.git] / src / libimcv / imc / imc_os_info.c
1 /*
2 * Copyright (C) 2012-2014 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 "imc_os_info.h"
17
18 #include <sys/utsname.h>
19 #include <stdio.h>
20 #include <stdarg.h>
21
22 #include <collections/linked_list.h>
23 #include <utils/debug.h>
24
25 typedef struct private_imc_os_info_t private_imc_os_info_t;
26
27 /**
28 * Private data of an imc_os_info_t object.
29 *
30 */
31 struct private_imc_os_info_t {
32
33 /**
34 * Public imc_os_info_t interface.
35 */
36 imc_os_info_t public;
37
38 /**
39 * OS type
40 */
41 os_type_t type;
42
43 /**
44 * OS name
45 */
46 chunk_t name;
47
48 /**
49 * OS version
50 */
51 chunk_t version;
52
53 };
54
55 METHOD(imc_os_info_t, get_type, os_type_t,
56 private_imc_os_info_t *this)
57 {
58 return this->type;
59 }
60
61 METHOD(imc_os_info_t, get_name, chunk_t,
62 private_imc_os_info_t *this)
63 {
64 return this->name;
65 }
66
67 METHOD(imc_os_info_t, get_numeric_version, void,
68 private_imc_os_info_t *this, u_int32_t *major, u_int32_t *minor)
69 {
70 u_char *pos;
71
72 if (major)
73 {
74 *major = atol(this->version.ptr);
75 }
76 pos = memchr(this->version.ptr, '.', this->version.len);
77 if (minor)
78 {
79 *minor = pos ? atol(pos + 1) : 0;
80 }
81 }
82
83 METHOD(imc_os_info_t, get_version, chunk_t,
84 private_imc_os_info_t *this)
85 {
86 return this->version;
87 }
88
89 METHOD(imc_os_info_t, get_fwd_status, os_fwd_status_t,
90 private_imc_os_info_t *this)
91 {
92 const char ip_forward[] = "/proc/sys/net/ipv4/ip_forward";
93 char buf[2];
94 FILE *file;
95
96 os_fwd_status_t fwd_status = OS_FWD_UNKNOWN;
97
98 file = fopen(ip_forward, "r");
99 if (file)
100 {
101 if (fread(buf, 1, 1, file) == 1)
102 {
103 switch (buf[0])
104 {
105 case '0':
106 fwd_status = OS_FWD_DISABLED;
107 break;
108 case '1':
109 fwd_status = OS_FWD_ENABLED;
110 break;
111 default:
112 DBG1(DBG_IMC, "\"%s\" returns invalid value ", ip_forward);
113 break;
114 }
115 }
116 else
117 {
118 DBG1(DBG_IMC, "could not read from \"%s\"", ip_forward);
119 }
120 fclose(file);
121 }
122 else
123 {
124 DBG1(DBG_IMC, "failed to open \"%s\"", ip_forward);
125 }
126
127 return fwd_status;
128 }
129
130 METHOD(imc_os_info_t, get_uptime, time_t,
131 private_imc_os_info_t *this)
132 {
133 const char proc_uptime[] = "/proc/uptime";
134 FILE *file;
135 u_int uptime;
136
137 file = fopen(proc_uptime, "r");
138 if (!file)
139 {
140 DBG1(DBG_IMC, "failed to open \"%s\"", proc_uptime);
141 return 0;
142 }
143 if (fscanf(file, "%u", &uptime) != 1)
144 {
145 DBG1(DBG_IMC, "failed to read file \"%s\"", proc_uptime);
146 uptime = 0;
147 }
148 fclose(file);
149
150 return uptime;
151 }
152
153 METHOD(imc_os_info_t, get_setting, chunk_t,
154 private_imc_os_info_t *this, char *name)
155 {
156 FILE *file;
157 u_char buf[2048];
158 size_t i = 0;
159 chunk_t value;
160
161 if (!strpfx(name, "/etc/") && !strpfx(name, "/proc/") &&
162 !strpfx(name, "/sys/") && !strpfx(name, "/var/"))
163 {
164 /**
165 * In order to guarantee privacy, only settings from the
166 * /etc/, /proc/ and /sys/ directories can be retrieved
167 */
168 DBG1(DBG_IMC, "not allowed to access '%s'", name);
169
170 return chunk_empty;
171 }
172
173 file = fopen(name, "r");
174 if (!file)
175 {
176 DBG1(DBG_IMC, "failed to open '%s'", name);
177
178 return chunk_empty;
179 }
180 while (i < sizeof(buf) && fread(buf + i, 1, 1, file) == 1)
181 {
182 i++;
183 }
184 fclose(file);
185
186 value = chunk_create(buf, i);
187
188 return chunk_clone(value);
189 }
190
191 typedef struct {
192 /**
193 * implements enumerator_t
194 */
195 enumerator_t public;
196
197 /**
198 * package info pipe stream
199 */
200 FILE* file;
201
202 /**
203 * line buffer
204 */
205 u_char line[512];
206
207 } package_enumerator_t;
208
209 /**
210 * Implementation of package_enumerator.destroy.
211 */
212 static void package_enumerator_destroy(package_enumerator_t *this)
213 {
214 pclose(this->file);
215 free(this);
216 }
217
218 /**
219 * Implementation of package_enumerator.enumerate
220 */
221 static bool package_enumerator_enumerate(package_enumerator_t *this, ...)
222 {
223 chunk_t *name, *version;
224 u_char *pos;
225 va_list args;
226
227 while (TRUE)
228 {
229 if (!fgets(this->line, sizeof(this->line), this->file))
230 {
231 return FALSE;
232 }
233
234 pos = strchr(this->line, '\t');
235 if (!pos)
236 {
237 return FALSE;
238 }
239 *pos++ = '\0';
240
241 if (!streq(this->line, "install ok installed"))
242 {
243 continue;
244 }
245 va_start(args, this);
246
247 name = va_arg(args, chunk_t*);
248 name->ptr = pos;
249 pos = strchr(pos, '\t');
250 if (!pos)
251 {
252 va_end(args);
253 return FALSE;
254 }
255 name->len = pos++ - name->ptr;
256
257 version = va_arg(args, chunk_t*);
258 version->ptr = pos;
259 version->len = strlen(pos) - 1;
260
261 va_end(args);
262 return TRUE;
263 }
264 }
265
266 METHOD(imc_os_info_t, create_package_enumerator, enumerator_t*,
267 private_imc_os_info_t *this)
268 {
269 FILE *file;
270 const char command[] = "dpkg-query --show --showformat="
271 "'${Status}\t${Package}\t${Version}\n'";
272 package_enumerator_t *enumerator;
273
274 /* Only Debian and Ubuntu package enumeration is currently supported */
275 if (this->type != OS_TYPE_DEBIAN && this->type != OS_TYPE_UBUNTU)
276 {
277 return NULL;
278 }
279
280 /* Open a pipe stream for reading the output of the dpkg-query commmand */
281 file = popen(command, "r");
282 if (!file)
283 {
284 DBG1(DBG_IMC, "failed to run dpkg command");
285 return NULL;
286 }
287
288 /* Create a package enumerator instance */
289 enumerator = malloc_thing(package_enumerator_t);
290 enumerator->public.enumerate = (void*)package_enumerator_enumerate;
291 enumerator->public.destroy = (void*)package_enumerator_destroy;
292 enumerator->file = file;
293
294 return (enumerator_t*)enumerator;
295 }
296
297
298 METHOD(imc_os_info_t, destroy, void,
299 private_imc_os_info_t *this)
300 {
301 free(this->name.ptr);
302 free(this->version.ptr);
303 free(this);
304 }
305
306 #define RELEASE_LSB 0
307 #define RELEASE_DEBIAN 1
308
309 /**
310 * Determine Linux distribution version and hardware platform
311 */
312 static bool extract_platform_info(os_type_t *type, chunk_t *name,
313 chunk_t *version)
314 {
315 FILE *file;
316 u_char buf[BUF_LEN], *pos = buf;
317 int len = BUF_LEN - 1;
318 os_type_t os_type = OS_TYPE_UNKNOWN;
319 chunk_t os_name = chunk_empty;
320 chunk_t os_version = chunk_empty;
321 char *os_str;
322 struct utsname uninfo;
323 int i;
324
325 /* Linux/Unix distribution release info (from http://linuxmafia.com) */
326 const char* releases[] = {
327 "/etc/lsb-release", "/etc/debian_version",
328 "/etc/SuSE-release", "/etc/novell-release",
329 "/etc/sles-release", "/etc/redhat-release",
330 "/etc/fedora-release", "/etc/gentoo-release",
331 "/etc/slackware-version", "/etc/annvix-release",
332 "/etc/arch-release", "/etc/arklinux-release",
333 "/etc/aurox-release", "/etc/blackcat-release",
334 "/etc/cobalt-release", "/etc/conectiva-release",
335 "/etc/debian_release", "/etc/immunix-release",
336 "/etc/lfs-release", "/etc/linuxppc-release",
337 "/etc/mandrake-release", "/etc/mandriva-release",
338 "/etc/mandrakelinux-release", "/etc/mklinux-release",
339 "/etc/pld-release", "/etc/redhat_version",
340 "/etc/slackware-release", "/etc/e-smith-release",
341 "/etc/release", "/etc/sun-release",
342 "/etc/tinysofa-release", "/etc/turbolinux-release",
343 "/etc/ultrapenguin-release", "/etc/UnitedLinux-release",
344 "/etc/va-release", "/etc/yellowdog-release"
345 };
346
347 const char lsb_distrib_id[] = "DISTRIB_ID=";
348 const char lsb_distrib_release[] = "DISTRIB_RELEASE=";
349
350 for (i = 0; i < countof(releases); i++)
351 {
352 file = fopen(releases[i], "r");
353 if (!file)
354 {
355 continue;
356 }
357
358 /* read release file into buffer */
359 fseek(file, 0, SEEK_END);
360 len = min(ftell(file), len);
361 rewind(file);
362 buf[len] = '\0';
363 if (fread(buf, 1, len, file) != len)
364 {
365 DBG1(DBG_IMC, "failed to read file \"%s\"", releases[i]);
366 fclose(file);
367 return FALSE;
368 }
369 fclose(file);
370
371 DBG1(DBG_IMC, "processing \"%s\" file", releases[i]);
372
373 switch (i)
374 {
375 case RELEASE_LSB:
376 {
377 /* Determine Distribution ID */
378 pos = strstr(buf, lsb_distrib_id);
379 if (!pos)
380 {
381 DBG1(DBG_IMC, "failed to find begin of DISTRIB_ID field");
382 return FALSE;
383 }
384 pos += strlen(lsb_distrib_id);
385
386 os_name.ptr = pos;
387
388 pos = strchr(pos, '\n');
389 if (!pos)
390 {
391 DBG1(DBG_IMC, "failed to find end of DISTRIB_ID field");
392 return FALSE;
393 }
394 os_name.len = pos - os_name.ptr;
395
396 /* Determine Distribution Release */
397 pos = strstr(buf, lsb_distrib_release);
398 if (!pos)
399 {
400 DBG1(DBG_IMC, "failed to find begin of DISTRIB_RELEASE field");
401 return FALSE;
402 }
403 pos += strlen(lsb_distrib_release);
404
405 os_version.ptr = pos;
406
407 pos = strchr(pos, '\n');
408 if (!pos)
409 {
410 DBG1(DBG_IMC, "failed to find end of DISTRIB_RELEASE field");
411 return FALSE;
412 }
413 os_version.len = pos - os_version.ptr;
414
415 break;
416 }
417 case RELEASE_DEBIAN:
418 {
419 os_type = OS_TYPE_DEBIAN;
420
421 os_version.ptr = buf;
422 pos = strchr(buf, '\n');
423 if (!pos)
424 {
425 DBG1(DBG_PTS, "failed to find end of release string");
426 return FALSE;
427 }
428
429 os_version.len = pos - os_version.ptr;
430
431 break;
432 }
433 default:
434 {
435 const char str_release[] = " release ";
436
437 os_name.ptr = buf;
438
439 pos = strstr(buf, str_release);
440 if (!pos)
441 {
442 DBG1(DBG_IMC, "failed to find release keyword");
443 return FALSE;
444 }
445
446 os_name.len = pos - os_name.ptr;
447
448 pos += strlen(str_release);
449 os_version.ptr = pos;
450
451 pos = strchr(pos, '\n');
452 if (!pos)
453 {
454 DBG1(DBG_IMC, "failed to find end of release string");
455 return FALSE;
456 }
457
458 os_version.len = pos - os_version.ptr;
459
460 break;
461 }
462 }
463 break;
464 }
465
466 if (!os_version.ptr)
467 {
468 DBG1(DBG_IMC, "no distribution release file found");
469 return FALSE;
470 }
471
472 if (uname(&uninfo) < 0)
473 {
474 DBG1(DBG_IMC, "could not retrieve machine architecture");
475 return FALSE;
476 }
477
478 /* Try to find a matching OS type based on the OS name */
479 if (os_type == OS_TYPE_UNKNOWN)
480 {
481 os_type = os_type_from_name(os_name);
482 }
483
484 /* If known use the official OS name */
485 if (os_type != OS_TYPE_UNKNOWN)
486 {
487 os_str = enum_to_name(os_type_names, os_type);
488 os_name = chunk_create(os_str, strlen(os_str));
489 }
490
491 /* copy OS type */
492 *type = os_type;
493
494 /* copy OS name */
495 *name = chunk_clone(os_name);
496
497 /* copy OS version and machine architecture */
498 *version = chunk_alloc(os_version.len + 1 + strlen(uninfo.machine));
499 pos = version->ptr;
500 memcpy(pos, os_version.ptr, os_version.len);
501 pos += os_version.len;
502 *pos++ = ' ';
503 memcpy(pos, uninfo.machine, strlen(uninfo.machine));
504
505 return TRUE;
506 }
507
508 /**
509 * See header
510 */
511 imc_os_info_t *imc_os_info_create(void)
512 {
513 private_imc_os_info_t *this;
514 chunk_t name, version;
515 os_type_t type;
516
517 /* As an option OS name and OS version can be configured manually */
518 name.ptr = lib->settings->get_str(lib->settings,
519 "%s.imcv.imc_os_info.name", NULL, lib->ns);
520 version.ptr = lib->settings->get_str(lib->settings,
521 "%s.imcv.imc_os_info.version", NULL, lib->ns);
522 if (name.ptr && version.ptr)
523 {
524 name.len = strlen(name.ptr);
525 name = chunk_clone(name);
526
527 version.len = strlen(version.ptr);
528 version = chunk_clone(version);
529
530 type = os_type_from_name(name);
531 }
532 else
533 {
534 if (!extract_platform_info(&type, &name, &version))
535 {
536 return NULL;
537 }
538 }
539 DBG1(DBG_IMC, "operating system name is '%.*s'",
540 name.len, name.ptr);
541 DBG1(DBG_IMC, "operating system version is '%.*s'",
542 version.len, version.ptr);
543
544 INIT(this,
545 .public = {
546 .get_type = _get_type,
547 .get_name = _get_name,
548 .get_numeric_version = _get_numeric_version,
549 .get_version = _get_version,
550 .get_fwd_status = _get_fwd_status,
551 .get_uptime = _get_uptime,
552 .get_setting = _get_setting,
553 .create_package_enumerator = _create_package_enumerator,
554 .destroy = _destroy,
555 },
556 .type = type,
557 .name = name,
558 .version = version,
559 );
560
561 return &this->public;
562 }