implemented IETF Numeric Version attribute
[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
21 #include <utils/linked_list.h>
22 #include <debug.h>
23
24 typedef struct private_os_info_t private_os_info_t;
25
26 ENUM(os_fwd_status_names, OS_FWD_DISABLED, OS_FWD_UNKNOWN,
27 "disabled",
28 "enabled",
29 "unknown"
30 );
31
32 /**
33 * Private data of an os_info_t object.
34 *
35 */
36 struct private_os_info_t {
37
38 /**
39 * Public os_info_t interface.
40 */
41 os_info_t public;
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(os_info_t, get_name, chunk_t,
56 private_os_info_t *this)
57 {
58 return this->name;
59 }
60
61 METHOD(os_info_t, get_numeric_version, void,
62 private_os_info_t *this, u_int32_t *major, u_int32_t *minor)
63 {
64 u_char *pos;
65
66 if (major)
67 {
68 *major = atol(this->version.ptr);
69 }
70 pos = memchr(this->version.ptr, '.', this->version.len);
71 if (minor)
72 {
73 *minor = pos ? atol(pos + 1) : 0;
74 }
75 }
76
77 METHOD(os_info_t, get_version, chunk_t,
78 private_os_info_t *this)
79 {
80 return this->version;
81 }
82
83 METHOD(os_info_t, get_fwd_status, os_fwd_status_t,
84 private_os_info_t *this)
85 {
86 const char ip_forward[] = "/proc/sys/net/ipv4/ip_forward";
87 char buf[2];
88 FILE *file;
89
90 os_fwd_status_t fwd_status = OS_FWD_UNKNOWN;
91
92 file = fopen(ip_forward, "r");
93 if (file)
94 {
95 if (fread(buf, 1, 1, file) == 1)
96 {
97 switch (buf[0])
98 {
99 case '0':
100 fwd_status = OS_FWD_DISABLED;
101 break;
102 case '1':
103 fwd_status = OS_FWD_ENABLED;
104 break;
105 default:
106 DBG1(DBG_IMC, "\"%s\" returns invalid value ", ip_forward);
107 break;
108 }
109 }
110 else
111 {
112 DBG1(DBG_IMC, "could not read from \"%s\"", ip_forward);
113 }
114 fclose(file);
115 }
116 else
117 {
118 DBG1(DBG_IMC, "failed to open \"%s\"", ip_forward);
119 }
120
121 return fwd_status;
122 }
123
124 METHOD(os_info_t, get_uptime, time_t,
125 private_os_info_t *this)
126 {
127 const char proc_uptime[] = "/proc/uptime";
128 FILE *file;
129 time_t uptime;
130
131 file = fopen(proc_uptime, "r");
132 if (!file)
133 {
134 DBG1(DBG_IMC, "failed to open \"%s\"", proc_uptime);
135 return 0;
136 }
137 if (fscanf(file, "%u", &uptime) != 1)
138 {
139 DBG1(DBG_IMC, "failed to read file \"%s\"", proc_uptime);
140 uptime = 0;
141 }
142 fclose(file);
143
144 return uptime;
145 }
146
147 METHOD(os_info_t, create_package_enumerator, enumerator_t*,
148 private_os_info_t *this)
149 {
150 /* TODO */
151
152 return NULL;
153 }
154
155
156 METHOD(os_info_t, destroy, void,
157 private_os_info_t *this)
158 {
159 free(this->name.ptr);
160 free(this->version.ptr);
161 free(this);
162 }
163
164 #define RELEASE_LSB 0
165 #define RELEASE_DEBIAN 1
166
167 /**
168 * Determine Linux distribution version and hardware platform
169 */
170 static bool extract_platform_info(chunk_t *name, chunk_t *version)
171 {
172 FILE *file;
173 u_char buf[BUF_LEN], *pos = buf;
174 int len = BUF_LEN - 1;
175 chunk_t os_name = chunk_empty;
176 chunk_t os_version = chunk_empty;
177 struct utsname uninfo;
178 int i;
179
180 /* Linux/Unix distribution release info (from http://linuxmafia.com) */
181 const char* releases[] = {
182 "/etc/lsb-release", "/etc/debian_version",
183 "/etc/SuSE-release", "/etc/novell-release",
184 "/etc/sles-release", "/etc/redhat-release",
185 "/etc/fedora-release", "/etc/gentoo-release",
186 "/etc/slackware-version", "/etc/annvix-release",
187 "/etc/arch-release", "/etc/arklinux-release",
188 "/etc/aurox-release", "/etc/blackcat-release",
189 "/etc/cobalt-release", "/etc/conectiva-release",
190 "/etc/debian_release", "/etc/immunix-release",
191 "/etc/lfs-release", "/etc/linuxppc-release",
192 "/etc/mandrake-release", "/etc/mandriva-release",
193 "/etc/mandrakelinux-release", "/etc/mklinux-release",
194 "/etc/pld-release", "/etc/redhat_version",
195 "/etc/slackware-release", "/etc/e-smith-release",
196 "/etc/release", "/etc/sun-release",
197 "/etc/tinysofa-release", "/etc/turbolinux-release",
198 "/etc/ultrapenguin-release", "/etc/UnitedLinux-release",
199 "/etc/va-release", "/etc/yellowdog-release"
200 };
201
202 const char lsb_distrib_id[] = "DISTRIB_ID=";
203 const char lsb_distrib_release[] = "DISTRIB_RELEASE=";
204
205 for (i = 0; i < countof(releases); i++)
206 {
207 file = fopen(releases[i], "r");
208 if (!file)
209 {
210 continue;
211 }
212
213 /* read release file into buffer */
214 fseek(file, 0, SEEK_END);
215 len = min(ftell(file), len);
216 rewind(file);
217 buf[len] = '\0';
218 if (fread(buf, 1, len, file) != len)
219 {
220 DBG1(DBG_IMC, "failed to read file \"%s\"", releases[i]);
221 fclose(file);
222 return FALSE;
223 }
224 fclose(file);
225
226 DBG1(DBG_IMC, "processing \"%s\" file", releases[i]);
227
228 switch (i)
229 {
230 case RELEASE_LSB:
231 {
232 /* Determine Distribution ID */
233 pos = strstr(buf, lsb_distrib_id);
234 if (!pos)
235 {
236 DBG1(DBG_IMC, "failed to find begin of DISTRIB_ID field");
237 return FALSE;
238 }
239 pos += strlen(lsb_distrib_id);
240
241 os_name.ptr = pos;
242
243 pos = strchr(pos, '\n');
244 if (!pos)
245 {
246 DBG1(DBG_IMC, "failed to find end of DISTRIB_ID field");
247 return FALSE;
248 }
249
250 os_name.len = pos - os_name.ptr;
251
252 /* Determine Distribution Release */
253 pos = strstr(buf, lsb_distrib_release);
254 if (!pos)
255 {
256 DBG1(DBG_IMC, "failed to find begin of DISTRIB_RELEASE field");
257 return FALSE;
258 }
259 pos += strlen(lsb_distrib_release);
260
261 os_version.ptr = pos;
262
263 pos = strchr(pos, '\n');
264 if (!pos)
265 {
266 DBG1(DBG_IMC, "failed to find end of DISTRIB_RELEASE field");
267 return FALSE;
268 }
269
270 os_version.len = pos - os_version.ptr;
271
272 break;
273 }
274 case RELEASE_DEBIAN:
275 {
276 char str_debian[] = "Debian";
277
278 os_name = chunk_create(str_debian, strlen(str_debian));
279 os_version.ptr = buf;
280
281 pos = strchr(buf, '\n');
282 if (!pos)
283 {
284 DBG1(DBG_PTS, "failed to find end of release string");
285 return FALSE;
286 }
287
288 os_version.len = pos - os_version.ptr;
289
290 break;
291 }
292 default:
293 {
294 const char str_release[] = " release ";
295
296 os_name.ptr = buf;
297
298 pos = strstr(buf, str_release);
299 if (!pos)
300 {
301 DBG1(DBG_IMC, "failed to find release keyword");
302 return FALSE;
303 }
304
305 os_name.len = pos - os_name.ptr;
306 pos += strlen(str_release);
307 os_version.ptr = pos;
308
309 pos = strchr(pos, '\n');
310 if (!pos)
311 {
312 DBG1(DBG_IMC, "failed to find end of release string");
313 return FALSE;
314 }
315
316 os_version.len = pos - os_version.ptr;
317
318 break;
319 }
320 }
321 break;
322 }
323
324 if (!os_name.ptr)
325 {
326 DBG1(DBG_IMC, "no distribution release file found");
327 return FALSE;
328 }
329
330 if (uname(&uninfo) < 0)
331 {
332 DBG1(DBG_IMC, "could not retrieve machine architecture");
333 return FALSE;
334 }
335
336 /* copy OS name */
337 *name = chunk_clone(os_name);
338
339 /* copy OS version and machine architecture */
340 *version = chunk_alloc(os_version.len + 1 + strlen(uninfo.machine));
341 pos = version->ptr;
342 memcpy(pos, os_version.ptr, os_version.len);
343 pos += os_version.len;
344 *pos++ = ' ';
345 memcpy(pos, uninfo.machine, strlen(uninfo.machine));
346
347 return TRUE;
348 }
349
350 /**
351 * See header
352 */
353 os_info_t *os_info_create(void)
354 {
355 private_os_info_t *this;
356 chunk_t name, version;
357
358 /* As an opton OS name and OS version can be configured manually */
359 name.ptr = lib->settings->get_str(lib->settings,
360 "libimcv.os_info.name", NULL);
361 version.ptr = lib->settings->get_str(lib->settings,
362 "libimcv.os_info.version", NULL);
363 if (name.ptr && version.ptr)
364 {
365 name.len = strlen(name.ptr);
366 name = chunk_clone(name);
367
368 version.len = strlen(version.ptr);
369 version = chunk_clone(version);
370 }
371 else
372 {
373 if (!extract_platform_info(&name, &version))
374 {
375 return NULL;
376 }
377 }
378 DBG1(DBG_IMC, "operating system name is '%.*s'",
379 name.len, name.ptr);
380 DBG1(DBG_IMC, "operating system version is '%.*s'",
381 version.len, version.ptr);
382
383 INIT(this,
384 .public = {
385 .get_name = _get_name,
386 .get_numeric_version = _get_numeric_version,
387 .get_version = _get_version,
388 .get_fwd_status = _get_fwd_status,
389 .get_uptime = _get_uptime,
390 .create_package_enumerator = _create_package_enumerator,
391 .destroy = _destroy,
392 },
393 .name = name,
394 .version = version,
395 );
396
397 return &this->public;
398 }