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