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