swid-gen: Share SWID generator between sw-collector, imc-swima and imc-swid
[strongswan.git] / src / libimcv / swima / swima_collector.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 #include "swima_collector.h"
17
18 #include <swid_gen/swid_gen.h>
19
20 #include <collections/linked_list.h>
21 #include <utils/debug.h>
22
23 #include <stdio.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <sys/stat.h>
27 #include <libgen.h>
28 #include <errno.h>
29
30 #define SOURCE_ID_GENERATOR 1
31 #define SOURCE_ID_COLLECTOR 2
32
33 #ifndef SWID_DIRECTORY
34 #define SWID_DIRECTORY NULL
35 #endif
36
37 /**
38 * Directories to be skipped by collector
39 */
40 static const char* skip_directories[] = {
41 "/usr/share/doc",
42 "/usr/share/help",
43 "/usr/share/icons",
44 "/usr/share/gnome/help"
45 };
46
47 typedef struct private_swima_collector_t private_swima_collector_t;
48
49 /**
50 * Private data of a swima_collector_t object.
51 *
52 */
53 struct private_swima_collector_t {
54
55 /**
56 * Public swima_collector_t interface.
57 */
58 swima_collector_t public;
59
60 /**
61 * Collect Software Identifiers only
62 */
63 bool sw_id_only;
64
65 /**
66 * Software Collector Database [if it exists]
67 */
68 database_t *db;
69
70 /**
71 * List of Software [Identifier] records
72 */
73 swima_inventory_t *inventory;
74
75 /**
76 * List of Software [Identifier] events
77 */
78 swima_events_t *events;
79
80 };
81
82 /**
83 * Extract Software Identifier from SWID tag
84 */
85 static status_t extract_sw_id(chunk_t swid_tag, chunk_t *sw_id)
86 {
87 char *pos, *tag, *tagid, *regid;
88 size_t len, tagid_len, regid_len;
89 status_t status = NOT_FOUND;
90
91 /* Copy at most 1023 bytes of the SWID tag and null-terminate it */
92 len = min(1023, swid_tag.len);
93 pos = tag = strndup(swid_tag.ptr, len);
94
95 tagid= strstr(pos, "tagId=\"");
96 if (tagid == NULL)
97 {
98 goto end;
99 }
100 tagid += 7;
101 len -= tagid - pos - 7;
102
103 pos = strchr(tagid, '"');
104 if (pos == NULL)
105 {
106 goto end;
107 }
108 tagid_len = pos - tagid;
109
110 regid= strstr(pos, "regid=\"");
111 if (regid == NULL)
112 {
113 goto end;
114 }
115 regid += 7;
116 len -= regid - pos - 7;
117
118 pos = strchr(regid, '"');
119 if (pos == NULL)
120 {
121 goto end;
122 }
123 regid_len = pos - regid;
124
125 *sw_id = chunk_cat("ccc", chunk_create(regid, regid_len),
126 chunk_from_chars('_','_'),
127 chunk_create(tagid, tagid_len));
128 status = SUCCESS;
129 end:
130 free(tag);
131
132 return status;
133 }
134
135 static status_t retrieve_inventory(private_swima_collector_t *this,
136 swima_inventory_t *targets)
137 {
138 char *name;
139 uint32_t record_id, source;
140 swima_record_t *sw_record;
141 chunk_t sw_id;
142 enumerator_t *e;
143
144 /* Retrieve complete software identifier inventory */
145 e = this->db->query(this->db,
146 "SELECT id, name, source FROM sw_identifiers WHERE installed = 1 "
147 "ORDER BY name ASC", DB_UINT, DB_TEXT, DB_UINT);
148 if (!e)
149 {
150 DBG1(DBG_IMC, "database query for installed sw_identifiers failed");
151 return FAILED;
152 }
153 while (e->enumerate(e, &record_id, &name, &source))
154 {
155 sw_id = chunk_from_str(name);
156 sw_record = swima_record_create(record_id, sw_id, chunk_empty);
157 sw_record->set_source_id(sw_record, source);
158 this->inventory->add(this->inventory, sw_record);
159 }
160 e->destroy(e);
161
162 return SUCCESS;
163 }
164
165 static status_t retrieve_events(private_swima_collector_t *this,
166 swima_inventory_t *targets)
167 {
168 enumerator_t *e;
169 char *name, *timestamp;
170 uint32_t record_id, source, action, eid, earliest_eid;
171 chunk_t sw_id, ev_ts;
172 swima_record_t *sw_record;
173 swima_event_t *sw_event;
174
175 earliest_eid = targets->get_eid(targets, NULL);
176
177 /* Retrieve complete software identifier inventory */
178 e = this->db->query(this->db,
179 "SELECT e.id, e.timestamp, i.id, i.name, i.source, s.action "
180 "FROM sw_events as s JOIN events AS e ON s.eid = e.id "
181 "JOIN sw_identifiers as i ON s.sw_id = i.id WHERE s.eid >= ?"
182 "ORDER BY s.eid, i.name, s.action ASC", DB_UINT, earliest_eid,
183 DB_UINT, DB_TEXT, DB_UINT, DB_TEXT, DB_UINT, DB_UINT);
184 if (!e)
185 {
186 DBG1(DBG_IMC, "database query for sw_events failed");
187 return FAILED;
188 }
189 while (e->enumerate(e, &eid, &timestamp, &record_id, &name, &source, &action))
190 {
191 sw_id = chunk_from_str(name);
192 ev_ts = chunk_from_str(timestamp);
193 sw_record = swima_record_create(record_id, sw_id, chunk_empty);
194 sw_record->set_source_id(sw_record, source);
195 sw_event = swima_event_create(eid, ev_ts, action, sw_record);
196 this->events->add(this->events, sw_event);
197 }
198 e->destroy(e);
199
200 return SUCCESS;
201 }
202
203 static status_t generate_tags(private_swima_collector_t *this,
204 swima_inventory_t *targets, bool pretty, bool full)
205 {
206 swid_gen_t *swid_gen;
207 swima_record_t *target, *sw_record;
208 enumerator_t *enumerator;
209 status_t status = SUCCESS;
210
211 swid_gen = swid_gen_create();
212
213 if (targets->get_count(targets) == 0)
214 {
215 chunk_t out, sw_id, swid_tag = chunk_empty;
216
217 DBG2(DBG_IMC, "SWID tag%s generation by package manager",
218 this->sw_id_only ? " ID" : "");
219
220 enumerator = swid_gen->create_tag_enumerator(swid_gen, this->sw_id_only,
221 full, pretty);
222 if (enumerator)
223 {
224 while (enumerator->enumerate(enumerator, &out))
225 {
226 if (this->sw_id_only)
227 {
228 sw_id = out;
229 }
230 else
231 {
232 swid_tag = out;
233 status = extract_sw_id(swid_tag, &sw_id);
234 if (status != SUCCESS)
235 {
236 DBG1(DBG_IMC, "software id could not be extracted "
237 "from tag");
238 chunk_free(&swid_tag);
239 break;
240 }
241 }
242 sw_record = swima_record_create(0, sw_id, chunk_empty);
243 sw_record->set_source_id(sw_record, SOURCE_ID_GENERATOR);
244 if (!this->sw_id_only)
245 {
246 sw_record->set_record(sw_record, swid_tag);
247 chunk_free(&swid_tag);
248 }
249 this->inventory->add(this->inventory, sw_record);
250 chunk_free(&sw_id);
251 }
252 enumerator->destroy(enumerator);
253 }
254 else
255 {
256 status = NOT_SUPPORTED;
257 }
258 }
259 else if (!this->sw_id_only)
260 {
261 DBG2(DBG_IMC, "targeted SWID tag generation");
262
263 enumerator = targets->create_enumerator(targets);
264 while (enumerator->enumerate(enumerator, &target))
265 {
266 swima_record_t *sw_record;
267 char *tag = NULL, *name, *package, *version;
268 u_int installed;
269 chunk_t sw_id;
270 enumerator_t *e;
271
272 sw_id = target->get_sw_id(target, NULL);
273 name = strndup(sw_id.ptr, sw_id.len);
274
275 if (this->db)
276 {
277 e = this->db->query(this->db,
278 "SELECT package, version, installed "
279 "FROM sw_identifiers WHERE name = ?", DB_TEXT, name,
280 DB_TEXT, DB_TEXT, DB_UINT);
281 if (!e)
282 {
283 DBG1(DBG_IMC, "database query for sw_identifiers failed");
284 status = FAILED;
285 free(name);
286 break;
287 }
288 if (e->enumerate(e, &package, &version, &installed))
289 {
290 tag = swid_gen->generate_tag(swid_gen, name, package,
291 version, full && installed, pretty);
292 }
293 e->destroy(e);
294 }
295 else
296 {
297 tag = swid_gen->generate_tag(swid_gen, name, NULL, NULL,
298 full, pretty);
299 }
300 free(name);
301
302 if (tag)
303 {
304 DBG2(DBG_IMC, " %.*s", sw_id.len, sw_id.ptr);
305 sw_record = swima_record_create(0, sw_id, chunk_empty);
306 sw_record->set_source_id(sw_record, SOURCE_ID_GENERATOR);
307 sw_record->set_record(sw_record, chunk_from_str(tag));
308 this->inventory->add(this->inventory, sw_record);
309 free(tag);
310 }
311 }
312 enumerator->destroy(enumerator);
313 }
314 swid_gen->destroy(swid_gen);
315
316 return status;
317 }
318
319 static bool collect_tags(private_swima_collector_t *this, char *pathname,
320 swima_inventory_t *targets, bool is_swidtag_dir)
321 {
322 char *rel_name, *abs_name, *suffix, *pos;
323 chunk_t *swid_tag, sw_id, sw_locator;
324 swima_record_t *sw_record;
325 struct stat st;
326 bool success = FALSE, skip, is_new_swidtag_dir;
327 enumerator_t *enumerator;
328 int i;
329
330 if (!pathname)
331 {
332 return TRUE;
333 }
334
335 enumerator = enumerator_create_directory(pathname);
336 if (!enumerator)
337 {
338 DBG1(DBG_IMC, "directory '%s' can not be opened, %s",
339 pathname, strerror(errno));
340 return FALSE;
341 }
342
343 while (enumerator->enumerate(enumerator, &rel_name, &abs_name, &st))
344 {
345 if (S_ISDIR(st.st_mode))
346 {
347 skip = FALSE;
348
349 for (i = 0; i < countof(skip_directories); i++)
350 {
351 if (streq(abs_name, skip_directories[i]))
352 {
353 skip = TRUE;
354 break;
355 }
356 }
357
358 if (skip)
359 {
360 continue;
361 }
362
363 is_new_swidtag_dir = streq(rel_name, "swidtag");
364 if (is_new_swidtag_dir)
365 {
366 DBG2(DBG_IMC, "entering %s", pathname);
367 }
368 if (!collect_tags(this, abs_name, targets, is_swidtag_dir ||
369 is_new_swidtag_dir))
370 {
371 goto end;
372 }
373 if (is_new_swidtag_dir)
374 {
375 DBG2(DBG_IMC, "leaving %s", pathname);
376 }
377 }
378
379 if (!is_swidtag_dir)
380 {
381 continue;
382 }
383
384 /* found a swidtag file? */
385 suffix = strstr(rel_name, ".swidtag");
386 if (!suffix)
387 {
388 continue;
389 }
390
391 /* load the swidtag file */
392 swid_tag = chunk_map(abs_name, FALSE);
393 if (!swid_tag)
394 {
395 DBG1(DBG_IMC, " opening '%s' failed: %s", abs_name,
396 strerror(errno));
397 goto end;
398 }
399
400 /* extract software identity from SWID tag */
401 if (extract_sw_id(*swid_tag, &sw_id) != SUCCESS)
402 {
403 DBG1(DBG_IMC, "software id could not be extracted from SWID tag");
404 chunk_unmap(swid_tag);
405 goto end;
406 }
407
408 /* In case of a targeted request */
409 if (targets->get_count(targets))
410 {
411 enumerator_t *target_enumerator;
412 swima_record_t *target;
413 bool match = FALSE;
414
415 target_enumerator = targets->create_enumerator(targets);
416 while (target_enumerator->enumerate(target_enumerator, &target))
417 {
418 if (chunk_equals(target->get_sw_id(target, NULL), sw_id))
419 {
420 DBG2(DBG_IMC, " %.*s", sw_id.len, sw_id.ptr);
421 match = TRUE;
422 break;
423 }
424 }
425 target_enumerator->destroy(target_enumerator);
426
427 if (!match)
428 {
429 chunk_unmap(swid_tag);
430 chunk_free(&sw_id);
431 continue;
432 }
433 }
434 DBG2(DBG_IMC, " %s", rel_name);
435
436 pos = strstr(pathname, "/swidtag");
437 sw_locator = pos ? chunk_create(pathname, pos - pathname) : chunk_empty;
438 sw_record = swima_record_create(0, sw_id, sw_locator);
439 sw_record->set_source_id(sw_record, SOURCE_ID_COLLECTOR);
440 if (!this->sw_id_only)
441 {
442 sw_record->set_record(sw_record, *swid_tag);
443 }
444 this->inventory->add(this->inventory, sw_record);
445 chunk_unmap(swid_tag);
446 chunk_free(&sw_id);
447 }
448 success = TRUE;
449
450 end:
451 enumerator->destroy(enumerator);
452
453 return success;
454 }
455
456 METHOD(swima_collector_t, collect_inventory, swima_inventory_t*,
457 private_swima_collector_t *this, bool sw_id_only, swima_inventory_t *targets)
458 {
459 bool pretty, full;
460 char *directory;
461 status_t status;
462
463 directory = lib->settings->get_str(lib->settings,
464 "%s.plugins.imc-swima.swid_directory",
465 SWID_DIRECTORY, lib->ns);
466 pretty = lib->settings->get_bool(lib->settings,
467 "%s.plugins.imc-swima.swid_pretty",
468 FALSE, lib->ns);
469 full = lib->settings->get_bool(lib->settings,
470 "%s.plugins.imc-swima.swid_full",
471 FALSE, lib->ns);
472
473 /**
474 * Re-initialize collector
475 */
476 this->sw_id_only = sw_id_only;
477 this->inventory->clear(this->inventory);
478
479 /**
480 * Source 1: Tags are generated by a package manager
481 */
482 if (sw_id_only && this->db)
483 {
484 status = retrieve_inventory(this, targets);
485 }
486 else
487 {
488 status = generate_tags(this, targets, pretty, full);
489 }
490
491 /**
492 * Source 2: Collect swidtag files by iteratively entering all
493 * directories in the tree under the "directory" path.
494 */
495 DBG2(DBG_IMC, "SWID tag%s collection", sw_id_only ? " ID" : "");
496 collect_tags(this, directory, targets, FALSE);
497
498 return status == SUCCESS ? this->inventory : NULL;
499 }
500
501 METHOD(swima_collector_t, collect_events, swima_events_t*,
502 private_swima_collector_t *this, bool sw_id_only, swima_inventory_t *targets)
503 {
504 if (!sw_id_only || !this->db)
505 {
506 return NULL;
507 }
508
509 /**
510 * Re-initialize collector
511 */
512 this->sw_id_only = sw_id_only;
513 this->events->clear(this->events);
514
515 return retrieve_events(this, targets) == SUCCESS ? this->events : NULL;
516 }
517
518 METHOD(swima_collector_t, destroy, void,
519 private_swima_collector_t *this)
520 {
521 DESTROY_IF(this->db);
522 this->inventory->destroy(this->inventory);
523 this->events->destroy(this->events);
524 free(this);
525 }
526
527 /**
528 * See header
529 */
530 swima_collector_t *swima_collector_create(void)
531 {
532 private_swima_collector_t *this;
533 char *database;
534 uint32_t last_eid = 1, eid_epoch = 0x11223344;
535
536 INIT(this,
537 .public = {
538 .collect_inventory = _collect_inventory,
539 .collect_events = _collect_events,
540 .destroy = _destroy,
541 },
542 .inventory = swima_inventory_create(),
543 .events = swima_events_create(),
544 );
545
546 database = lib->settings->get_str(lib->settings,
547 "%s.plugins.imc-swima.swid_database", NULL, lib->ns);
548
549 /* If we have an URI, try to connect to sw_collector database */
550 if (database)
551 {
552 database_t *db = lib->db->create(lib->db, database);
553
554 if (db)
555 {
556 enumerator_t *e;
557
558 /* Get last event ID and corresponding epoch */
559 e = db->query(db,
560 "SELECT id, epoch FROM events ORDER BY timestamp DESC",
561 DB_UINT, DB_UINT);
562 if (!e || !e->enumerate(e, &last_eid, &eid_epoch))
563 {
564 DBG1(DBG_IMC, "database query for last event failed");
565 DESTROY_IF(e);
566 db->destroy(db);
567 }
568 else
569 {
570 /* The query worked, attach collector database permanently */
571 e->destroy(e);
572 this->db = db;
573 }
574 }
575 else
576 {
577 DBG1(DBG_IMC, "opening sw-collector database URI '%s' failed",
578 database);
579 }
580 }
581 if (!this->db)
582 {
583 /* Set the event ID epoch and last event ID smanually */
584 eid_epoch = lib->settings->get_int(lib->settings,
585 "%s.plugins.imc-swima.eid_epoch",
586 eid_epoch, lib->ns);
587 }
588 this->inventory->set_eid(this->inventory, last_eid, eid_epoch);
589 this->events->set_eid(this->events, last_eid, eid_epoch);
590
591 return &this->public;
592 }