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