2 * Copyright (C) 2017 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
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>.
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
16 #include "swima_collector.h"
18 #include <collections/linked_list.h>
19 #include <bio/bio_writer.h>
20 #include <utils/debug.h>
29 #define SOURCE_ID_GENERATOR 1
30 #define SOURCE_ID_COLLECTOR 2
32 #define SWID_GENERATOR "/usr/local/bin/swid_generator"
35 * Directories to be skipped by collector
37 static const char* skip_directories
[] = {
41 "/usr/share/gnome/help"
44 typedef struct private_swima_collector_t private_swima_collector_t
;
47 * Private data of a swima_collector_t object.
50 struct private_swima_collector_t
{
53 * Public swima_collector_t interface.
55 swima_collector_t
public;
58 * Collect Software Identifiers only
63 * Software Collector Database [if it exists]
68 * List of Software [Identifier] records
70 swima_inventory_t
*inventory
;
73 * List of Software [Identifier] events
75 swima_events_t
*events
;
80 * Extract Software Identifier from SWID tag
82 static status_t
extract_sw_id(chunk_t swid_tag
, chunk_t
*sw_id
)
84 char *pos
, *tag
, *tagid
, *regid
;
85 size_t len
, tagid_len
, regid_len
;
86 status_t status
= NOT_FOUND
;
88 /* Copy at most 1023 bytes of the SWID tag and null-terminate it */
89 len
= min(1023, swid_tag
.len
);
90 pos
= tag
= strndup(swid_tag
.ptr
, len
);
92 tagid
= strstr(pos
, "tagId=\"");
98 len
-= tagid
- pos
- 7;
100 pos
= strchr(tagid
, '"');
105 tagid_len
= pos
- tagid
;
107 regid
= strstr(pos
, "regid=\"");
113 len
-= regid
- pos
- 7;
115 pos
= strchr(regid
, '"');
120 regid_len
= pos
- regid
;
122 *sw_id
= chunk_cat("ccc", chunk_create(regid
, regid_len
),
123 chunk_from_chars('_','_'),
124 chunk_create(tagid
, tagid_len
));
133 * Read SWID tags issued by the swid_generator tool
135 static status_t
read_swid_tags(private_swima_collector_t
*this, FILE *file
)
137 swima_record_t
*sw_record
;
138 bio_writer_t
*writer
;
139 chunk_t sw_id
, swid_tag
;
140 bool more_tags
= TRUE
, last_newline
;
148 writer
= bio_writer_create(512);
151 if (!fgets(line
, sizeof(line
), file
))
158 if (last_newline
&& line
[0] == '\n')
164 last_newline
= (line
[len
-1] == '\n');
165 writer
->write_data(writer
, chunk_create(line
, len
));
168 swid_tag
= writer
->get_buf(writer
);
170 if (swid_tag
.len
> 1)
172 /* remove trailing newline if present */
173 if (swid_tag
.ptr
[swid_tag
.len
- 1] == '\n')
177 DBG3(DBG_IMC
, " %.*s", swid_tag
.len
, swid_tag
.ptr
);
179 status
= extract_sw_id(swid_tag
, &sw_id
);
180 if (status
!= SUCCESS
)
182 DBG1(DBG_IMC
, "software id could not be extracted from tag");
183 writer
->destroy(writer
);
186 sw_record
= swima_record_create(0, sw_id
, chunk_empty
);
187 sw_record
->set_source_id(sw_record
, SOURCE_ID_GENERATOR
);
188 sw_record
->set_record(sw_record
, swid_tag
);
189 this->inventory
->add(this->inventory
, sw_record
);
192 writer
->destroy(writer
);
199 * Read Software Identifiers issued by the swid_generator tool
201 static status_t
read_swid_tag_ids(private_swima_collector_t
*this, FILE *file
)
203 swima_record_t
*sw_record
;
210 if (!fgets(line
, sizeof(line
), file
))
216 /* remove trailing newline if present */
217 if (len
> 0 && line
[len
- 1] == '\n')
221 DBG3(DBG_IMC
, " %.*s", len
, line
);
223 sw_id
= chunk_create(line
, len
);
224 sw_record
= swima_record_create(0, sw_id
, chunk_empty
);
225 sw_record
->set_source_id(sw_record
, SOURCE_ID_GENERATOR
);
226 this->inventory
->add(this->inventory
, sw_record
);
230 static status_t
retrieve_inventory(private_swima_collector_t
*this,
231 swima_inventory_t
*targets
)
234 uint32_t record_id
, source
;
235 swima_record_t
*sw_record
;
239 /* Retrieve complete software identifier inventory */
240 e
= this->db
->query(this->db
,
241 "SELECT id, name, source FROM sw_identifiers WHERE installed = 1 "
242 "ORDER BY name ASC", DB_UINT
, DB_TEXT
, DB_UINT
);
245 DBG1(DBG_IMC
, "database query for installed sw_identifiers failed");
248 while (e
->enumerate(e
, &record_id
, &name
, &source
))
250 sw_id
= chunk_from_str(name
);
251 sw_record
= swima_record_create(record_id
, sw_id
, chunk_empty
);
252 sw_record
->set_source_id(sw_record
, source
);
253 this->inventory
->add(this->inventory
, sw_record
);
260 static status_t
retrieve_events(private_swima_collector_t
*this,
261 swima_inventory_t
*targets
)
264 char *name
, *timestamp
;
265 uint32_t record_id
, source
, action
, eid
, earliest_eid
;
266 chunk_t sw_id
, ev_ts
;
267 swima_record_t
*sw_record
;
268 swima_event_t
*sw_event
;
270 earliest_eid
= targets
->get_eid(targets
, NULL
);
272 /* Retrieve complete software identifier inventory */
273 e
= this->db
->query(this->db
,
274 "SELECT e.id, e.timestamp, i.id, i.name, i.source, s.action "
275 "FROM sw_events as s JOIN events AS e ON s.eid = e.id "
276 "JOIN sw_identifiers as i ON s.sw_id = i.id WHERE s.eid >= ?"
277 "ORDER BY s.eid, i.name, s.action ASC", DB_UINT
, earliest_eid
,
278 DB_UINT
, DB_TEXT
, DB_UINT
, DB_TEXT
, DB_UINT
, DB_UINT
);
281 DBG1(DBG_IMC
, "database query for sw_events failed");
284 while (e
->enumerate(e
, &eid
, ×tamp
, &record_id
, &name
, &source
, &action
))
286 sw_id
= chunk_from_str(name
);
287 ev_ts
= chunk_from_str(timestamp
);
288 sw_record
= swima_record_create(record_id
, sw_id
, chunk_empty
);
289 sw_record
->set_source_id(sw_record
, source
);
290 sw_event
= swima_event_create(eid
, ev_ts
, action
, sw_record
);
291 this->events
->add(this->events
, sw_event
);
298 static status_t
generate_tags(private_swima_collector_t
*this, char *generator
,
299 swima_inventory_t
*targets
, bool pretty
, bool full
)
302 char command
[BUF_LEN
];
303 char doc_separator
[] = "'\n\n'";
305 status_t status
= SUCCESS
;
307 if (targets
->get_count(targets
) == 0)
309 /* Assemble the SWID generator command */
310 if (this->sw_id_only
)
312 snprintf(command
, BUF_LEN
, "%s software-id", generator
);
316 snprintf(command
, BUF_LEN
, "%s swid --doc-separator %s%s%s",
317 generator
, doc_separator
, pretty ?
" --pretty" : "",
318 full ?
" --full" : "");
321 /* Open a pipe stream for reading the SWID generator output */
322 file
= popen(command
, "r");
325 DBG1(DBG_IMC
, "failed to run swid_generator command");
326 return NOT_SUPPORTED
;
329 if (this->sw_id_only
)
331 DBG2(DBG_IMC
, "SWID tag ID generation by package manager");
332 status
= read_swid_tag_ids(this, file
);
336 DBG2(DBG_IMC
, "SWID tag generation by package manager");
337 status
= read_swid_tags(this, file
);
341 else if (!this->sw_id_only
)
343 swima_record_t
*target
;
344 enumerator_t
*enumerator
;
347 enumerator
= targets
->create_enumerator(targets
);
348 while (enumerator
->enumerate(enumerator
, &target
))
350 sw_id
= target
->get_sw_id(target
, NULL
);
352 /* Assemble the SWID generator command */
353 snprintf(command
, BUF_LEN
, "%s swid --software-id %.*s%s%s",
354 generator
, sw_id
.len
, sw_id
.ptr
,
355 pretty ?
" --pretty" : "", full ?
" --full" : "");
357 /* Open a pipe stream for reading the SWID generator output */
358 file
= popen(command
, "r");
361 DBG1(DBG_IMC
, "failed to run swid_generator command");
362 return NOT_SUPPORTED
;
364 status
= read_swid_tags(this, file
);
367 if (status
!= SUCCESS
)
372 enumerator
->destroy(enumerator
);
378 static bool collect_tags(private_swima_collector_t
*this, char *pathname
,
379 swima_inventory_t
*targets
, bool is_swidtag_dir
)
381 char *rel_name
, *abs_name
, *suffix
, *pos
;
382 chunk_t
*swid_tag
, sw_id
, sw_locator
;
383 swima_record_t
*sw_record
;
385 bool success
= FALSE
, skip
, is_new_swidtag_dir
;
386 enumerator_t
*enumerator
;
389 enumerator
= enumerator_create_directory(pathname
);
392 DBG1(DBG_IMC
, "directory '%s' can not be opened, %s",
393 pathname
, strerror(errno
));
397 while (enumerator
->enumerate(enumerator
, &rel_name
, &abs_name
, &st
))
399 if (S_ISDIR(st
.st_mode
))
403 for (i
= 0; i
< countof(skip_directories
); i
++)
405 if (streq(abs_name
, skip_directories
[i
]))
417 is_new_swidtag_dir
= streq(rel_name
, "swidtag");
418 if (is_new_swidtag_dir
)
420 DBG2(DBG_IMC
, "entering %s", pathname
);
422 if (!collect_tags(this, abs_name
, targets
, is_swidtag_dir
||
427 if (is_new_swidtag_dir
)
429 DBG2(DBG_IMC
, "leaving %s", pathname
);
438 /* found a swidtag file? */
439 suffix
= strstr(rel_name
, ".swidtag");
445 /* load the swidtag file */
446 swid_tag
= chunk_map(abs_name
, FALSE
);
449 DBG1(DBG_IMC
, " opening '%s' failed: %s", abs_name
,
454 /* extract software identity from SWID tag */
455 if (extract_sw_id(*swid_tag
, &sw_id
) != SUCCESS
)
457 DBG1(DBG_IMC
, "software id could not be extracted from SWID tag");
458 chunk_unmap(swid_tag
);
462 /* In case of a targeted request */
463 if (targets
->get_count(targets
))
465 enumerator_t
*target_enumerator
;
466 swima_record_t
*target
;
469 target_enumerator
= targets
->create_enumerator(targets
);
470 while (target_enumerator
->enumerate(target_enumerator
, &target
))
472 if (chunk_equals(target
->get_sw_id(target
, NULL
), sw_id
))
478 target_enumerator
->destroy(target_enumerator
);
482 chunk_unmap(swid_tag
);
487 DBG2(DBG_IMC
, " %s", rel_name
);
489 pos
= strstr(pathname
, "/swidtag");
490 sw_locator
= pos ?
chunk_create(pathname
, pos
- pathname
) : chunk_empty
;
491 sw_record
= swima_record_create(0, sw_id
, sw_locator
);
492 sw_record
->set_source_id(sw_record
, SOURCE_ID_COLLECTOR
);
493 if (!this->sw_id_only
)
495 sw_record
->set_record(sw_record
, *swid_tag
);
497 this->inventory
->add(this->inventory
, sw_record
);
498 chunk_unmap(swid_tag
);
504 enumerator
->destroy(enumerator
);
509 METHOD(swima_collector_t
, collect_inventory
, swima_inventory_t
*,
510 private_swima_collector_t
*this, bool sw_id_only
, swima_inventory_t
*targets
)
512 char *directory
, *generator
;
516 directory
= lib
->settings
->get_str(lib
->settings
,
517 "%s.plugins.imc-swima.swid_directory",
518 SWID_DIRECTORY
, lib
->ns
);
519 generator
= lib
->settings
->get_str(lib
->settings
,
520 "%s.plugins.imc-swima.swid_generator",
521 SWID_GENERATOR
, lib
->ns
);
522 pretty
= lib
->settings
->get_bool(lib
->settings
,
523 "%s.plugins.imc-swima.swid_pretty",
525 full
= lib
->settings
->get_bool(lib
->settings
,
526 "%s.plugins.imc-swima.swid_full",
530 * Re-initialize collector
532 this->sw_id_only
= sw_id_only
;
533 this->inventory
->clear(this->inventory
);
536 * Source 1: Tags are generated by a package manager
538 if (sw_id_only
&& this->db
)
540 status
= retrieve_inventory(this, targets
);
544 status
= generate_tags(this, generator
, targets
, pretty
, full
);
548 * Source 2: Collect swidtag files by iteratively entering all
549 * directories in the tree under the "directory" path.
551 collect_tags(this, directory
, targets
, FALSE
);
553 return status
== SUCCESS ?
this->inventory
: NULL
;
556 METHOD(swima_collector_t
, collect_events
, swima_events_t
*,
557 private_swima_collector_t
*this, bool sw_id_only
, swima_inventory_t
*targets
)
559 if (!sw_id_only
|| !this->db
)
565 * Re-initialize collector
567 this->sw_id_only
= sw_id_only
;
568 this->events
->clear(this->events
);
570 return retrieve_events(this, targets
) == SUCCESS ?
this->events
: NULL
;
573 METHOD(swima_collector_t
, destroy
, void,
574 private_swima_collector_t
*this)
576 DESTROY_IF(this->db
);
577 this->inventory
->destroy(this->inventory
);
578 this->events
->destroy(this->events
);
585 swima_collector_t
*swima_collector_create(void)
587 private_swima_collector_t
*this;
589 uint32_t last_eid
= 1, eid_epoch
= 0x11223344;
593 .collect_inventory
= _collect_inventory
,
594 .collect_events
= _collect_events
,
597 .inventory
= swima_inventory_create(),
598 .events
= swima_events_create(),
601 database
= lib
->settings
->get_str(lib
->settings
,
602 "%s.plugins.imc-swima.swid_database", NULL
, lib
->ns
);
604 /* If we have an URI, try to connect to sw_collector database */
607 database_t
*db
= lib
->db
->create(lib
->db
, database
);
613 /* Get last event ID and corresponding epoch */
615 "SELECT id, epoch FROM events ORDER BY timestamp DESC",
617 if (!e
|| !e
->enumerate(e
, &last_eid
, &eid_epoch
))
619 DBG1(DBG_IMC
, "database query for last event failed");
625 /* The query worked, attach collector database permanently */
632 DBG1(DBG_IMC
, "opening sw-collector database URI '%s' failed",
638 /* Set the event ID epoch and last event ID smanually */
639 eid_epoch
= lib
->settings
->get_int(lib
->settings
,
640 "%s.plugins.imc-swima.eid_epoch",
643 this->inventory
->set_eid(this->inventory
, last_eid
, eid_epoch
);
644 this->events
->set_eid(this->events
, last_eid
, eid_epoch
);
646 return &this->public;