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