Add default password determination capability to os_info
[strongswan.git] / src / libimcv / imc / imc_os_info.c
1 /*
2 * Copyright (C) 2012-2015 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_default_pwd_status, bool,
200 private_imc_os_info_t *this)
201 {
202 /* As an option the default password status can be configured manually */
203 return lib->settings->get_bool(lib->settings,
204 "%s.imcv.os_info.default_password_enabled", FALSE, lib->ns);
205 }
206
207 METHOD(imc_os_info_t, get_uptime, time_t,
208 private_imc_os_info_t *this)
209 {
210 const char proc_uptime[] = "/proc/uptime";
211 FILE *file;
212 u_int uptime;
213
214 file = fopen(proc_uptime, "r");
215 if (!file)
216 {
217 DBG1(DBG_IMC, "failed to open \"%s\"", proc_uptime);
218 return 0;
219 }
220 if (fscanf(file, "%u", &uptime) != 1)
221 {
222 DBG1(DBG_IMC, "failed to read file \"%s\"", proc_uptime);
223 uptime = 0;
224 }
225 fclose(file);
226
227 return uptime;
228 }
229
230 METHOD(imc_os_info_t, get_setting, chunk_t,
231 private_imc_os_info_t *this, char *name)
232 {
233 FILE *file;
234 u_char buf[2048];
235 size_t i = 0;
236 chunk_t value;
237
238 if (!strpfx(name, "/etc/") && !strpfx(name, "/proc/") &&
239 !strpfx(name, "/sys/") && !strpfx(name, "/var/"))
240 {
241 /**
242 * In order to guarantee privacy, only settings from the
243 * /etc/, /proc/ and /sys/ directories can be retrieved
244 */
245 DBG1(DBG_IMC, "not allowed to access '%s'", name);
246
247 return chunk_empty;
248 }
249
250 file = fopen(name, "r");
251 if (!file)
252 {
253 DBG1(DBG_IMC, "failed to open '%s'", name);
254
255 return chunk_empty;
256 }
257 while (i < sizeof(buf) && fread(buf + i, 1, 1, file) == 1)
258 {
259 i++;
260 }
261 fclose(file);
262
263 value = chunk_create(buf, i);
264
265 return chunk_clone(value);
266 }
267
268 typedef struct {
269 /**
270 * implements enumerator_t
271 */
272 enumerator_t public;
273
274 /**
275 * package info pipe stream
276 */
277 FILE* file;
278
279 /**
280 * line buffer
281 */
282 u_char line[512];
283
284 } package_enumerator_t;
285
286 /**
287 * Implementation of package_enumerator.destroy.
288 */
289 static void package_enumerator_destroy(package_enumerator_t *this)
290 {
291 pclose(this->file);
292 free(this);
293 }
294
295 /**
296 * Implementation of package_enumerator.enumerate
297 */
298 static bool package_enumerator_enumerate(package_enumerator_t *this, ...)
299 {
300 chunk_t *name, *version;
301 u_char *pos;
302 va_list args;
303
304 while (TRUE)
305 {
306 if (!fgets(this->line, sizeof(this->line), this->file))
307 {
308 return FALSE;
309 }
310
311 pos = strchr(this->line, '\t');
312 if (!pos)
313 {
314 return FALSE;
315 }
316 *pos++ = '\0';
317
318 if (!streq(this->line, "install ok installed"))
319 {
320 continue;
321 }
322 va_start(args, this);
323
324 name = va_arg(args, chunk_t*);
325 name->ptr = pos;
326 pos = strchr(pos, '\t');
327 if (!pos)
328 {
329 va_end(args);
330 return FALSE;
331 }
332 name->len = pos++ - name->ptr;
333
334 version = va_arg(args, chunk_t*);
335 version->ptr = pos;
336 version->len = strlen(pos) - 1;
337
338 va_end(args);
339 return TRUE;
340 }
341 }
342
343 METHOD(imc_os_info_t, create_package_enumerator, enumerator_t*,
344 private_imc_os_info_t *this)
345 {
346 FILE *file;
347 const char command[] = "dpkg-query --show --showformat="
348 "'${Status}\t${Package}\t${Version}\n'";
349 package_enumerator_t *enumerator;
350
351 /* Only Debian and Ubuntu package enumeration is currently supported */
352 if (this->type != OS_TYPE_DEBIAN && this->type != OS_TYPE_UBUNTU)
353 {
354 return NULL;
355 }
356
357 /* Open a pipe stream for reading the output of the dpkg-query commmand */
358 file = popen(command, "r");
359 if (!file)
360 {
361 DBG1(DBG_IMC, "failed to run dpkg command");
362 return NULL;
363 }
364
365 /* Create a package enumerator instance */
366 enumerator = malloc_thing(package_enumerator_t);
367 enumerator->public.enumerate = (void*)package_enumerator_enumerate;
368 enumerator->public.destroy = (void*)package_enumerator_destroy;
369 enumerator->file = file;
370
371 return (enumerator_t*)enumerator;
372 }
373
374 #define RELEASE_LSB 0
375 #define RELEASE_DEBIAN 1
376
377 /**
378 * Determine Linux distribution version and hardware platform
379 */
380 static bool extract_platform_info(os_type_t *type, chunk_t *name,
381 chunk_t *version)
382 {
383 FILE *file;
384 u_char buf[BUF_LEN], *pos = buf;
385 int len = BUF_LEN - 1;
386 os_type_t os_type = OS_TYPE_UNKNOWN;
387 chunk_t os_name = chunk_empty;
388 chunk_t os_version = chunk_empty;
389 char *os_str;
390 struct utsname uninfo;
391 int i;
392
393 /* Linux/Unix distribution release info (from http://linuxmafia.com) */
394 const char* releases[] = {
395 "/etc/lsb-release", "/etc/debian_version",
396 "/etc/SuSE-release", "/etc/novell-release",
397 "/etc/sles-release", "/etc/redhat-release",
398 "/etc/fedora-release", "/etc/gentoo-release",
399 "/etc/slackware-version", "/etc/annvix-release",
400 "/etc/arch-release", "/etc/arklinux-release",
401 "/etc/aurox-release", "/etc/blackcat-release",
402 "/etc/cobalt-release", "/etc/conectiva-release",
403 "/etc/debian_release", "/etc/immunix-release",
404 "/etc/lfs-release", "/etc/linuxppc-release",
405 "/etc/mandrake-release", "/etc/mandriva-release",
406 "/etc/mandrakelinux-release", "/etc/mklinux-release",
407 "/etc/pld-release", "/etc/redhat_version",
408 "/etc/slackware-release", "/etc/e-smith-release",
409 "/etc/release", "/etc/sun-release",
410 "/etc/tinysofa-release", "/etc/turbolinux-release",
411 "/etc/ultrapenguin-release", "/etc/UnitedLinux-release",
412 "/etc/va-release", "/etc/yellowdog-release"
413 };
414
415 const char lsb_distrib_id[] = "DISTRIB_ID=";
416 const char lsb_distrib_release[] = "DISTRIB_RELEASE=";
417
418 for (i = 0; i < countof(releases); i++)
419 {
420 file = fopen(releases[i], "r");
421 if (!file)
422 {
423 continue;
424 }
425
426 /* read release file into buffer */
427 fseek(file, 0, SEEK_END);
428 len = min(ftell(file), len);
429 rewind(file);
430 buf[len] = '\0';
431 if (fread(buf, 1, len, file) != len)
432 {
433 DBG1(DBG_IMC, "failed to read file \"%s\"", releases[i]);
434 fclose(file);
435 return FALSE;
436 }
437 fclose(file);
438
439 DBG1(DBG_IMC, "processing \"%s\" file", releases[i]);
440
441 switch (i)
442 {
443 case RELEASE_LSB:
444 {
445 /* Determine Distribution ID */
446 pos = strstr(buf, lsb_distrib_id);
447 if (!pos)
448 {
449 DBG1(DBG_IMC, "failed to find begin of DISTRIB_ID field");
450 return FALSE;
451 }
452 pos += strlen(lsb_distrib_id);
453
454 os_name.ptr = pos;
455
456 pos = strchr(pos, '\n');
457 if (!pos)
458 {
459 DBG1(DBG_IMC, "failed to find end of DISTRIB_ID field");
460 return FALSE;
461 }
462 os_name.len = pos - os_name.ptr;
463
464 /* Determine Distribution Release */
465 pos = strstr(buf, lsb_distrib_release);
466 if (!pos)
467 {
468 DBG1(DBG_IMC, "failed to find begin of DISTRIB_RELEASE field");
469 return FALSE;
470 }
471 pos += strlen(lsb_distrib_release);
472
473 os_version.ptr = pos;
474
475 pos = strchr(pos, '\n');
476 if (!pos)
477 {
478 DBG1(DBG_IMC, "failed to find end of DISTRIB_RELEASE field");
479 return FALSE;
480 }
481 os_version.len = pos - os_version.ptr;
482
483 break;
484 }
485 case RELEASE_DEBIAN:
486 {
487 os_type = OS_TYPE_DEBIAN;
488
489 os_version.ptr = buf;
490 pos = strchr(buf, '\n');
491 if (!pos)
492 {
493 DBG1(DBG_PTS, "failed to find end of release string");
494 return FALSE;
495 }
496
497 os_version.len = pos - os_version.ptr;
498
499 break;
500 }
501 default:
502 {
503 const char str_release[] = " release ";
504
505 os_name.ptr = buf;
506
507 pos = strstr(buf, str_release);
508 if (!pos)
509 {
510 DBG1(DBG_IMC, "failed to find release keyword");
511 return FALSE;
512 }
513
514 os_name.len = pos - os_name.ptr;
515
516 pos += strlen(str_release);
517 os_version.ptr = pos;
518
519 pos = strchr(pos, '\n');
520 if (!pos)
521 {
522 DBG1(DBG_IMC, "failed to find end of release string");
523 return FALSE;
524 }
525
526 os_version.len = pos - os_version.ptr;
527
528 break;
529 }
530 }
531 break;
532 }
533
534 if (!os_version.ptr)
535 {
536 DBG1(DBG_IMC, "no distribution release file found");
537 return FALSE;
538 }
539
540 if (uname(&uninfo) < 0)
541 {
542 DBG1(DBG_IMC, "could not retrieve machine architecture");
543 return FALSE;
544 }
545
546 /* Try to find a matching OS type based on the OS name */
547 if (os_type == OS_TYPE_UNKNOWN)
548 {
549 os_type = os_type_from_name(os_name);
550 }
551
552 /* If known use the official OS name */
553 if (os_type != OS_TYPE_UNKNOWN)
554 {
555 os_str = enum_to_name(os_type_names, os_type);
556 os_name = chunk_create(os_str, strlen(os_str));
557 }
558
559 /* copy OS type */
560 *type = os_type;
561
562 /* copy OS name */
563 *name = chunk_clone(os_name);
564
565 /* copy OS version and machine architecture */
566 *version = chunk_alloc(os_version.len + 1 + strlen(uninfo.machine));
567 pos = version->ptr;
568 memcpy(pos, os_version.ptr, os_version.len);
569 pos += os_version.len;
570 *pos++ = ' ';
571 memcpy(pos, uninfo.machine, strlen(uninfo.machine));
572
573 return TRUE;
574 }
575
576 #endif /* !WIN32 */
577
578 METHOD(imc_os_info_t, destroy, void,
579 private_imc_os_info_t *this)
580 {
581 free(this->name.ptr);
582 free(this->version.ptr);
583 free(this);
584 }
585
586 /**
587 * See header
588 */
589 imc_os_info_t *imc_os_info_create(void)
590 {
591 private_imc_os_info_t *this;
592 chunk_t name, version;
593 os_type_t type;
594
595 /* As an option OS name and OS version can be configured manually */
596 name.ptr = lib->settings->get_str(lib->settings,
597 "%s.imcv.os_info.name", NULL, lib->ns);
598 version.ptr = lib->settings->get_str(lib->settings,
599 "%s.imcv.os_info.version", NULL, lib->ns);
600 if (name.ptr && version.ptr)
601 {
602 name.len = strlen(name.ptr);
603 name = chunk_clone(name);
604
605 version.len = strlen(version.ptr);
606 version = chunk_clone(version);
607
608 type = os_type_from_name(name);
609 }
610 else
611 {
612 if (!extract_platform_info(&type, &name, &version))
613 {
614 return NULL;
615 }
616 }
617 DBG1(DBG_IMC, "operating system name is '%.*s'",
618 name.len, name.ptr);
619 DBG1(DBG_IMC, "operating system version is '%.*s'",
620 version.len, version.ptr);
621
622 INIT(this,
623 .public = {
624 .get_type = _get_type,
625 .get_name = _get_name,
626 .get_numeric_version = _get_numeric_version,
627 .get_version = _get_version,
628 .get_fwd_status = _get_fwd_status,
629 .get_default_pwd_status = _get_default_pwd_status,
630 .get_uptime = _get_uptime,
631 .get_setting = _get_setting,
632 .create_package_enumerator = _create_package_enumerator,
633 .destroy = _destroy,
634 },
635 .type = type,
636 .name = name,
637 .version = version,
638 );
639
640 return &this->public;
641 }