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