libimcv: Added inactive field to device database table
[strongswan.git] / src / sw-collector / sw_collector_history.c
1 /*
2 * Copyright (C) 2017 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 #define _GNU_SOURCE
17 #include <stdio.h>
18 #include <time.h>
19
20 #include "sw_collector_history.h"
21 #include "sw_collector_dpkg.h"
22
23 #include <swima/swima_event.h>
24 #include <swid_gen/swid_gen_info.h>
25
26 typedef struct private_sw_collector_history_t private_sw_collector_history_t;
27
28 /**
29 * Private data of an sw_collector_history_t object.
30 */
31 struct private_sw_collector_history_t {
32
33 /**
34 * Public members of sw_collector_history_state_t
35 */
36 sw_collector_history_t public;
37
38 /**
39 * Software Event Source Number
40 */
41 uint8_t source;
42
43 /**
44 * Reference to OS info object
45 */
46 swid_gen_info_t *info;
47
48 /**
49 * Reference to collector database
50 */
51 sw_collector_db_t *db;
52
53 };
54
55 /**
56 * Define auxiliary package_t list item object
57 */
58 typedef struct package_t package_t;
59
60 struct package_t {
61 char *package;
62 char *version;
63 char *old_version;
64 char *sw_id;
65 char *old_sw_id;
66 };
67
68 /**
69 * Create package_t list item object
70 */
71 static package_t* create_package(swid_gen_info_t *info, chunk_t package,
72 chunk_t version, chunk_t old_version)
73 {
74 package_t *this;
75
76 INIT(this,
77 .package = strndup(package.ptr, package.len),
78 .version = strndup(version.ptr, version.len),
79 .old_version = strndup(old_version.ptr, old_version.len),
80 )
81
82 this->sw_id = info->create_sw_id(info, this->package, this->version);
83 if (old_version.len)
84 {
85 this->old_sw_id = info->create_sw_id(info, this->package,
86 this->old_version);
87 }
88
89 return this;
90 }
91
92 /**
93 * Free package_t list item object
94 */
95 static void free_package(package_t *this)
96 {
97 if (this)
98 {
99 free(this->package);
100 free(this->version);
101 free(this->old_version);
102 free(this->sw_id);
103 free(this->old_sw_id);
104 free(this);
105 }
106 }
107
108 /**
109 * Extract and parse a single package item
110 */
111 static package_t* extract_package(chunk_t item, swid_gen_info_t *info,
112 sw_collector_history_op_t op)
113 {
114 chunk_t package, package_stripped, version, old_version;
115 package_t *p;
116
117 /* extract package name */
118 if (!extract_token(&package, ' ', &item))
119 {
120 fprintf(stderr, "version not found.\n");
121 return NULL;
122 }
123 item = chunk_skip(item, 1);
124
125 /* strip architecture suffix if present */
126 if (extract_token(&package_stripped, ':', &package))
127 {
128 package = package_stripped;
129 }
130
131 /* extract versions */
132 version = old_version = chunk_empty;
133
134 if (item.len > 0)
135 {
136 if (extract_token(&version, ',', &item))
137 {
138 eat_whitespace(&item);
139 if (!match("automatic", &item))
140 {
141 old_version = version;
142 version = item;
143 }
144 }
145 else
146 {
147 version = item;
148 }
149 }
150 p = create_package(info, package, version, old_version);
151
152 /* generate log entry */
153 if (op == SW_OP_UPGRADE)
154 {
155 DBG2(DBG_IMC, " %s (%s, %s)", p->package, p->old_version, p->version);
156 DBG2(DBG_IMC, " +%s", p->sw_id);
157 DBG2(DBG_IMC, " -%s", p->old_sw_id);
158 }
159 else
160 {
161 DBG2(DBG_IMC, " %s (%s)", p->package, p->version);
162 DBG2(DBG_IMC, " %s%s", (op == SW_OP_INSTALL) ? "+" : "-", p->sw_id);
163 }
164
165 return p;
166 }
167
168 METHOD(sw_collector_history_t, extract_timestamp, bool,
169 private_sw_collector_history_t *this, chunk_t args, char *buf)
170 {
171 struct tm loc, utc;
172 chunk_t t1, t2;
173 time_t t;
174
175 /* Break down local time with format t1 = yyyy-mm-dd and t2 = hh:mm:ss */
176 if (!eat_whitespace(&args) || !extract_token(&t1, ' ', &args) ||
177 !eat_whitespace(&args) || t1.len != 10 || args.len != 8)
178 {
179 DBG1(DBG_IMC, "unable to parse start-date");
180 return FALSE;
181 }
182 t2 = args;
183
184 if (sscanf(t1.ptr, "%4d-%2d-%2d",
185 &loc.tm_year, &loc.tm_mon, &loc.tm_mday) != 3)
186 {
187 DBG1(DBG_IMC, "unable to parse date format yyyy-mm-dd");
188 return FALSE;
189 }
190 loc.tm_year -= 1900;
191 loc.tm_mon -= 1;
192 loc.tm_isdst = -1;
193
194 if (sscanf(t2.ptr, "%2d:%2d:%2d",
195 &loc.tm_hour, &loc.tm_min, &loc.tm_sec) != 3)
196 {
197 DBG1(DBG_IMC, "unable to parse time format hh:mm:ss");
198 return FALSE;
199 }
200
201 /* Convert from local time to UTC */
202 t = mktime(&loc);
203 gmtime_r(&t, &utc);
204 utc.tm_year += 1900;
205 utc.tm_mon += 1;
206
207 /* Form timestamp according to RFC 3339 (20 characters) */
208 snprintf(buf, 21, "%4d-%02d-%02dT%02d:%02d:%02dZ",
209 utc.tm_year, utc.tm_mon, utc.tm_mday,
210 utc.tm_hour, utc.tm_min, utc.tm_sec);
211
212 return TRUE;
213 }
214
215 METHOD(sw_collector_history_t, extract_packages, bool,
216 private_sw_collector_history_t *this, chunk_t args, uint32_t eid,
217 sw_collector_history_op_t op)
218 {
219 bool success = FALSE;
220 package_t *p = NULL;
221 chunk_t item;
222
223 eat_whitespace(&args);
224
225 while (extract_token(&item, ')', &args))
226 {
227 char *del_sw_id = NULL, *del_version = NULL;
228 char *nx, *px, *vx, *v1;
229 bool installed;
230 u_int sw_idx, ix;
231 uint32_t sw_id, sw_id_epoch_less = 0;
232 enumerator_t *e;
233
234 p = extract_package(item, this->info, op);
235 if (!p)
236 {
237 goto end;
238 }
239
240 /* packages without version information cannot be handled */
241 if (strlen(p->version) == 0)
242 {
243 free_package(p);
244 continue;
245 }
246
247 switch (op)
248 {
249 case SW_OP_REMOVE:
250 /* prepare subsequent deletion sw event */
251 del_sw_id = p->sw_id;
252 del_version = p->version;
253 break;
254 case SW_OP_UPGRADE:
255 /* prepare subsequent deletion sw event */
256 del_sw_id = p->old_sw_id;
257 del_version = p->old_version;
258 /* fall through to next case */
259 case SW_OP_INSTALL:
260 sw_id = this->db->get_sw_id(this->db, p->sw_id, NULL, NULL,
261 NULL, &installed);
262 if (sw_id)
263 {
264 /* sw identifier exists - update state to 'installed' */
265 if (installed)
266 {
267 /* this case should not occur */
268 DBG1(DBG_IMC, " warning: sw_id %d is already "
269 "installed", sw_id);
270 }
271 else if (!this->db->update_sw_id(this->db, sw_id, NULL,
272 NULL, TRUE))
273 {
274 goto end;
275 }
276 }
277 else
278 {
279 /* new sw identifier - create with state 'installed' */
280 sw_id = this->db->set_sw_id(this->db, p->sw_id, p->package,
281 p->version, this->source, TRUE);
282 if (!sw_id)
283 {
284 goto end;
285 }
286 }
287
288 /* add creation sw event with current eid */
289 if (!this->db->add_sw_event(this->db, eid, sw_id,
290 SWIMA_EVENT_ACTION_CREATION))
291 {
292 goto end;
293 }
294 break;
295 }
296
297 if (op != SW_OP_INSTALL)
298 {
299 sw_id = 0;
300
301 /* look for existing installed package versions */
302 e = this->db->create_sw_enumerator(this->db, SW_QUERY_INSTALLED,
303 p->package);
304 if (!e)
305 {
306 goto end;
307 }
308
309 while (e->enumerate(e, &sw_idx, &nx, &px, &vx, &ix))
310 {
311 if (streq(vx, del_version))
312 {
313 /* full match with epoch */
314 sw_id = sw_idx;
315 break;
316 }
317 v1 = strchr(vx, ':');
318 if (v1 && streq(++v1, del_version))
319 {
320 /* match with stripped epoch */
321 sw_id_epoch_less = sw_idx;
322 }
323 }
324 e->destroy(e);
325
326 if (!sw_id && sw_id_epoch_less)
327 {
328 /* no full match - fall back to epoch-less match */
329 sw_id = sw_id_epoch_less;
330 }
331 if (sw_id)
332 {
333 /* sw identifier exists - update state to 'removed' */
334 if (!this->db->update_sw_id(this->db, sw_id, NULL, NULL, FALSE))
335 {
336 goto end;
337 }
338 }
339 else
340 {
341 /* new sw identifier - create with state 'removed' */
342 sw_id = this->db->set_sw_id(this->db, del_sw_id, p->package,
343 del_version, this->source, FALSE);
344
345 /* add creation sw event with eid = 1 */
346 if (!sw_id || !this->db->add_sw_event(this->db, 1, sw_id,
347 SWIMA_EVENT_ACTION_CREATION))
348 {
349 goto end;
350 }
351 }
352
353 /* add creation sw event with current eid */
354 if (!this->db->add_sw_event(this->db, eid, sw_id,
355 SWIMA_EVENT_ACTION_DELETION))
356 {
357 goto end;
358 }
359 }
360 free_package(p);
361
362 if (args.len < 2)
363 {
364 break;
365 }
366 args = chunk_skip(args, 2);
367 }
368 p = NULL;
369 success = TRUE;
370
371 end:
372 free_package(p);
373
374 return success;
375 }
376
377 METHOD(sw_collector_history_t, merge_installed_packages, bool,
378 private_sw_collector_history_t *this)
379 {
380 uint32_t sw_id, count = 0;
381 char *package, *arch, *version, *v1, *name, *n1;
382 bool installed, success = FALSE;
383 sw_collector_dpkg_t *dpkg;
384 enumerator_t *enumerator;
385
386 DBG1(DBG_IMC, "Merging:");
387
388 dpkg = sw_collector_dpkg_create();
389 if (!dpkg)
390 {
391 return FALSE;
392 }
393
394 enumerator = dpkg->create_sw_enumerator(dpkg);
395 while (enumerator->enumerate(enumerator, &package, &arch, &version))
396 {
397 name = this->info->create_sw_id(this->info, package, version);
398 DBG3(DBG_IMC, " %s merged", name);
399
400 sw_id = this->db->get_sw_id(this->db, name, NULL, NULL, NULL,
401 &installed);
402 if (sw_id)
403 {
404 if (!installed)
405 {
406 DBG1(DBG_IMC, " warning: existing sw_id %u"
407 " is not installed", sw_id);
408
409 if (!this->db->update_sw_id(this->db, sw_id, name, version,
410 TRUE))
411 {
412 free(name);
413 goto end;
414 }
415 }
416 }
417 else
418 {
419 /* check for a Debian epoch number */
420 v1 = strchr(version, ':');
421 if (v1)
422 {
423 /* check for existing and installed epoch-less version */
424 n1 = this->info->create_sw_id(this->info, package, ++v1);
425 sw_id = this->db->get_sw_id(this->db, n1, NULL, NULL, NULL,
426 &installed);
427 free(n1);
428
429 if (sw_id && installed)
430 {
431 /* add epoch to existing version */
432 if (!this->db->update_sw_id(this->db, sw_id, name, version,
433 installed))
434 {
435 free(name);
436 goto end;
437 }
438 }
439 else
440 {
441 sw_id = 0;
442 }
443 }
444 }
445
446 if (!sw_id)
447 {
448 /* new sw identifier - create with state 'installed' */
449 sw_id = this->db->set_sw_id(this->db, name, package, version,
450 this->source, TRUE);
451
452 /* add creation sw event with eid = 1 */
453 if (!sw_id || !this->db->add_sw_event(this->db, 1, sw_id,
454 SWIMA_EVENT_ACTION_CREATION))
455 {
456 free(name);
457 goto end;
458 }
459
460 }
461 free(name);
462 count++;
463 }
464 success = TRUE;
465
466 DBG1(DBG_IMC, " merged %u installed packages, %u registered in database",
467 count, this->db->get_sw_id_count(this->db, SW_QUERY_INSTALLED));
468
469 end:
470 enumerator->destroy(enumerator);
471 dpkg->destroy(dpkg);
472
473 return success;
474 }
475
476 METHOD(sw_collector_history_t, destroy, void,
477 private_sw_collector_history_t *this)
478 {
479 this->info->destroy(this->info);
480 free(this);
481 }
482
483 /**
484 * Described in header.
485 */
486 sw_collector_history_t *sw_collector_history_create(sw_collector_db_t *db,
487 uint8_t source)
488 {
489 private_sw_collector_history_t *this;
490 swid_gen_info_t *info;
491 os_type_t os_type;
492 char *os;
493
494 info = swid_gen_info_create();
495
496 /* check if OS supports apg/dpkg history logs */
497 info->get_os(info, &os);
498 os_type = info->get_os_type(info);
499 if (os_type != OS_TYPE_DEBIAN && os_type != OS_TYPE_UBUNTU)
500 {
501 DBG1(DBG_IMC, "%.*s not supported", os);
502 info->destroy(info);
503 return NULL;
504 }
505
506 INIT(this,
507 .public = {
508 .extract_timestamp = _extract_timestamp,
509 .extract_packages = _extract_packages,
510 .merge_installed_packages = _merge_installed_packages,
511 .destroy = _destroy,
512 },
513 .source = source,
514 .info = info,
515 .db = db,
516 );
517
518 return &this->public;
519 }