sw-collector: Added --full option
[strongswan.git] / src / sw-collector / sw-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 #define _GNU_SOURCE
17 #include <stdio.h>
18 #include <string.h>
19 #include <errno.h>
20 #include <getopt.h>
21 #include <unistd.h>
22 #ifdef HAVE_SYSLOG
23 # include <syslog.h>
24 #endif
25
26 #include "sw_collector_info.h"
27 #include "sw_collector_db.h"
28 #include "sw_collector_history.h"
29 #include "sw_collector_rest_api.h"
30 #include "sw_collector_dpkg.h"
31 #
32 #include <library.h>
33 #include <bio/bio_writer.h>
34 #include <utils/debug.h>
35 #include <utils/lexparser.h>
36
37 #include <imv/imv_os_info.h>
38
39 #define SWID_GENERATOR "/usr/local/bin/swid_generator"
40
41 /**
42 * global debug output variables
43 */
44 static int debug_level = 2;
45 static bool stderr_quiet = FALSE;
46 static int count = 0;
47
48 typedef enum collector_op_t collector_op_t;
49
50 enum collector_op_t {
51 COLLECTOR_OP_EXTRACT,
52 COLLECTOR_OP_LIST,
53 COLLECTOR_OP_UNREGISTERED,
54 COLLECTOR_OP_GENERATE,
55 COLLECTOR_OP_MIGRATE
56 };
57
58 /**
59 * sw_collector dbg function
60 */
61 static void sw_collector_dbg(debug_t group, level_t level, char *fmt, ...)
62 {
63 va_list args;
64
65 if (level <= debug_level)
66 {
67 if (!stderr_quiet)
68 {
69 va_start(args, fmt);
70 vfprintf(stderr, fmt, args);
71 fprintf(stderr, "\n");
72 va_end(args);
73 }
74
75 #ifdef HAVE_SYSLOG
76 {
77 int priority = LOG_INFO;
78 char buffer[8192];
79 char *current = buffer, *next;
80
81 /* write in memory buffer first */
82 va_start(args, fmt);
83 vsnprintf(buffer, sizeof(buffer), fmt, args);
84 va_end(args);
85
86 /* do a syslog with every line */
87 while (current)
88 {
89 next = strchr(current, '\n');
90 if (next)
91 {
92 *(next++) = '\0';
93 }
94 syslog(priority, "%s\n", current);
95 current = next;
96 }
97 }
98 #endif /* HAVE_SYSLOG */
99 }
100 }
101
102 /**
103 * atexit handler
104 */
105 static void cleanup(void)
106 {
107 library_deinit();
108 #ifdef HAVE_SYSLOG
109 closelog();
110 #endif
111 }
112
113 /**
114 * Display usage of sw-collector command
115 */
116 static void usage(void)
117 {
118 printf("\
119 Usage:\n\
120 sw-collector --help\n\
121 sw-collector [--debug <level>] [--quiet] [--count <event count>]\n\
122 sw-collector [--debug <level>] [--quiet] [--installed|--removed] \
123 --list|-unregistered\n\
124 sw-collector [--debug <level>] [--quiet] [--installed|--removed] \
125 [--full] --generate\n\
126 sw-collector [--debug <level>] [--quiet] --migrate\n");
127 }
128
129 /**
130 * Parse command line options
131 */
132 static collector_op_t do_args(int argc, char *argv[], bool *full_tags,
133 sw_collector_db_query_t *query_type)
134 {
135 collector_op_t op = COLLECTOR_OP_EXTRACT;
136 bool installed = FALSE, removed = FALSE, full = FALSE;
137
138 /* reinit getopt state */
139 optind = 0;
140
141 while (TRUE)
142 {
143 int c;
144
145 struct option long_opts[] = {
146 { "help", no_argument, NULL, 'h' },
147 { "count", required_argument, NULL, 'c' },
148 { "debug", required_argument, NULL, 'd' },
149 { "full", no_argument, NULL, 'f' },
150 { "generate", no_argument, NULL, 'g' },
151 { "installed", no_argument, NULL, 'i' },
152 { "list", no_argument, NULL, 'l' },
153 { "migrate", no_argument, NULL, 'm' },
154 { "quiet", no_argument, NULL, 'q' },
155 { "removed", no_argument, NULL, 'r' },
156 { "unregistered", no_argument, NULL, 'u' },
157 { 0,0,0,0 }
158 };
159
160 c = getopt_long(argc, argv, "hc:d:fgilmqru", long_opts, NULL);
161 switch (c)
162 {
163 case EOF:
164 break;
165 case 'h':
166 usage();
167 exit(SUCCESS);
168 break;
169 case 'c':
170 count = atoi(optarg);
171 continue;
172 case 'd':
173 debug_level = atoi(optarg);
174 continue;
175 case 'f':
176 full = TRUE;
177 continue;
178 case 'g':
179 op = COLLECTOR_OP_GENERATE;
180 continue;
181 case 'i':
182 installed = TRUE;
183 continue;
184 case 'l':
185 op = COLLECTOR_OP_LIST;
186 continue;
187 case 'm':
188 op = COLLECTOR_OP_MIGRATE;
189 continue;
190 case 'q':
191 stderr_quiet = TRUE;
192 continue;
193 case 'r':
194 removed = TRUE;
195 continue;
196 case 'u':
197 op = COLLECTOR_OP_UNREGISTERED;
198 continue;
199 default:
200 usage();
201 exit(EXIT_FAILURE);
202 }
203 break;
204 }
205
206 if ((!installed && !removed) || (installed && removed))
207 {
208 *query_type = SW_QUERY_ALL;
209 }
210 else if (installed)
211 {
212 *query_type = SW_QUERY_INSTALLED;
213 }
214 else
215 {
216 *query_type = SW_QUERY_REMOVED;
217 }
218 *full_tags = full;
219
220 return op;
221 }
222
223 /**
224 * Extract software events from apt history log files
225 */
226 static int extract_history(sw_collector_info_t *info, sw_collector_db_t *db)
227 {
228 sw_collector_history_t *history = NULL;
229 uint32_t epoch, last_eid, eid = 0;
230 char *history_path, *os, *last_time = NULL, rfc_time[21];
231 chunk_t *h, history_chunk, line, cmd;
232 os_type_t os_type;
233 int status = EXIT_FAILURE;
234 bool skip = TRUE;
235
236 /* check if OS supports apg/dpkg history logs */
237 info->get_os(info, &os);
238 os_type = info->get_os_type(info);
239
240 if (os_type != OS_TYPE_DEBIAN && os_type != OS_TYPE_UBUNTU)
241 {
242 DBG1(DBG_IMC, "%.*s not supported", os);
243 return EXIT_FAILURE;
244 }
245
246 /* open history file for reading */
247 history_path= lib->settings->get_str(lib->settings, "%s.history", NULL,
248 lib->ns);
249 if (!history_path)
250 {
251 fprintf(stderr, "sw-collector.history path not set.\n");
252 return EXIT_FAILURE;
253 }
254 h = chunk_map(history_path, FALSE);
255 if (!h)
256 {
257 fprintf(stderr, "opening '%s' failed: %s", history, strerror(errno));
258 return EXIT_FAILURE;
259 }
260 history_chunk = *h;
261
262 /* Instantiate history extractor */
263 history = sw_collector_history_create(info, db, 1);
264
265 /* retrieve last event in database */
266 if (!db->get_last_event(db, &last_eid, &epoch, &last_time) || !last_eid)
267 {
268 goto end;
269 }
270 DBG0(DBG_IMC, "Last-Event: %s, eid = %u, epoch = %u",
271 last_time, last_eid, epoch);
272
273 /* parse history file */
274 while (fetchline(&history_chunk, &line))
275 {
276 if (line.len == 0)
277 {
278 continue;
279 }
280 if (!extract_token(&cmd, ':', &line))
281 {
282 fprintf(stderr, "terminator symbol ':' not found.\n");
283 goto end;
284 }
285 if (match("Start-Date", &cmd))
286 {
287 if (!history->extract_timestamp(history, line, rfc_time))
288 {
289 goto end;
290 }
291
292 /* have we reached new history entries? */
293 if (skip && strcmp(rfc_time, last_time) > 0)
294 {
295 skip = FALSE;
296 }
297 if (skip)
298 {
299 continue;
300 }
301
302 /* insert new event into database */
303 eid = db->add_event(db, rfc_time);
304 if (!eid)
305 {
306 goto end;
307 }
308 DBG1(DBG_IMC, "Start-Date: %s, eid = %u, epoch = %u",
309 rfc_time, eid, epoch);
310 }
311 else if (skip)
312 {
313 /* skip old history entries which have already been processed */
314 continue;
315 }
316 else if (match("Install", &cmd))
317 {
318 DBG1(DBG_IMC, " Install:");
319 if (!history->extract_packages(history, line, eid, SW_OP_INSTALL))
320 {
321 goto end;
322 }
323 }
324 else if (match("Upgrade", &cmd))
325 {
326 DBG1(DBG_IMC, " Upgrade:");
327 if (!history->extract_packages(history, line, eid, SW_OP_UPGRADE))
328 {
329 goto end;
330 }
331 }
332 else if (match("Remove", &cmd))
333 {
334 DBG1(DBG_IMC, " Remove:");
335 if (!history->extract_packages(history, line, eid, SW_OP_REMOVE))
336 {
337 goto end;
338 }
339 }
340 else if (match("Purge", &cmd))
341 {
342 DBG1(DBG_IMC, " Purge:");
343 if (!history->extract_packages(history, line, eid, SW_OP_REMOVE))
344 {
345 goto end;
346 }
347 }
348 else if (match("End-Date", &cmd))
349 {
350 /* Process 'count' events at a time */
351 if (count > 0 && eid - last_eid == count)
352 {
353 fprintf(stderr, "added %d events\n", count);
354 goto end;
355 }
356 }
357 }
358
359 if (history->merge_installed_packages(history))
360 {
361 status = EXIT_SUCCESS;
362 }
363
364 end:
365 free(last_time);
366 history->destroy(history);
367 chunk_unmap(h);
368
369 return status;
370 }
371
372 /**
373 * List all endpoint software identifiers stored in local collector database
374 */
375 static int list_identifiers(sw_collector_db_t *db, sw_collector_db_query_t type)
376 {
377 enumerator_t *e;
378 char *name, *package, *version;
379 uint32_t sw_id, count = 0, installed_count = 0, removed_count, installed;
380
381 e = db->create_sw_enumerator(db, type, NULL);
382 if (!e)
383 {
384 return EXIT_FAILURE;
385 }
386 while (e->enumerate(e, &sw_id, &name, &package, &version, &installed))
387 {
388 printf("%s,%s,%s,%d\n", name, package, version, installed);
389 if (installed)
390 {
391 installed_count++;
392 }
393 count++;
394 }
395 removed_count = count - installed_count;
396 e->destroy(e);
397
398 switch (type)
399 {
400 case SW_QUERY_ALL:
401 DBG1(DBG_IMC, "retrieved %u software identities with %u installed "
402 "and %u removed", count, installed_count, removed_count);
403 break;
404 case SW_QUERY_INSTALLED:
405 DBG1(DBG_IMC, "retrieved %u installed software identities", count);
406 break;
407 case SW_QUERY_REMOVED:
408 DBG1(DBG_IMC, "retrieved %u removed software identities", count);
409 break;
410 }
411
412 return EXIT_SUCCESS;
413 }
414
415 static bool query_registry(sw_collector_rest_api_t *rest_api, bool installed)
416 {
417 sw_collector_db_query_t type;
418 enumerator_t *enumerator;
419 char *sw_id;
420 int count = 0;
421
422 type = installed ? SW_QUERY_INSTALLED : SW_QUERY_REMOVED;
423 enumerator = rest_api->create_sw_enumerator(rest_api, type);
424 if (!enumerator)
425 {
426 return FALSE;
427 }
428 while (enumerator->enumerate(enumerator, &sw_id))
429 {
430 printf("%s,%s\n", sw_id, installed ? "1" : "0");
431 count++;
432 }
433 enumerator->destroy(enumerator);
434 DBG1(DBG_IMC, "%d %s software identifiers not registered", count,
435 installed ? "installed" : "removed");
436 return TRUE;
437 }
438
439
440 /**
441 * List all endpoint software identifiers stored in local collector database
442 * that are not registered yet in central collelector database
443 */
444 static int unregistered_identifiers(sw_collector_db_t *db,
445 sw_collector_db_query_t type)
446 {
447 sw_collector_rest_api_t *rest_api;
448 int status = EXIT_SUCCESS;
449
450 rest_api = sw_collector_rest_api_create(db);
451 if (!rest_api)
452 {
453 return EXIT_FAILURE;
454 }
455
456 /* List installed software identifiers not registered centrally */
457 if (type != SW_QUERY_REMOVED && !query_registry(rest_api, TRUE))
458 {
459 status = EXIT_FAILURE;
460 }
461
462 /* List removed software identifiers not registered centrally */
463 if (type != SW_QUERY_INSTALLED && !query_registry(rest_api, FALSE))
464 {
465 status = EXIT_FAILURE;
466 }
467 rest_api->destroy(rest_api);
468
469 return status;
470 }
471
472 /**
473 * Generate a either a full or a minimalistic ISO 19770-2:2015 SWID tag
474 */
475 static char* generate_tag(char *name, char *package, char *version,
476 char* entity, char *regid, char *product,
477 bool full_tag, char *generator)
478 {
479 char *tag = NULL;
480
481 if (full_tag)
482 {
483 size_t tag_buf_len = 8192;
484 char tag_buf[tag_buf_len], command[BUF_LEN];
485 bio_writer_t *writer;
486 chunk_t tag_chunk;
487 FILE *file;
488
489 /* Compose the SWID generator command */
490 snprintf(command, BUF_LEN, "%s swid --full --regid %s --entity-name "
491 "\"%s\" --package %s", generator, regid, entity, package);
492 \
493 /* Open a pipe stream for reading the SWID generator output */
494 file = popen(command, "r");
495 if (file)
496 {
497 writer = bio_writer_create(tag_buf_len);
498 while (TRUE)
499 {
500 if (!fgets(tag_buf, tag_buf_len, file))
501 {
502 break;
503 }
504 writer->write_data(writer,
505 chunk_create(tag_buf, strlen(tag_buf)));
506 }
507 pclose(file);
508 tag_chunk = writer->extract_buf(writer);
509 writer->destroy(writer);
510 if (tag_chunk.len > 1)
511 {
512 tag = tag_chunk.ptr;
513 tag[tag_chunk.len - 1] = '\0';
514 }
515 }
516 else
517 {
518 DBG1(DBG_IMC, "failed to run swid_generator command");
519 }
520 }
521
522 /* Generate minimalistic SWID tag */
523 if (!tag)
524 {
525 char *tag_id;
526
527 tag_id = strstr(name, "__");
528 if (!tag_id)
529 {
530 return NULL;
531 }
532 tag_id += 2;
533
534 if (asprintf(&tag, "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
535 "<SoftwareIdentity name=\"%s\" tagId=\"%s\" version=\"%s\" "
536 "versionScheme=\"alphanumeric\" "
537 "xmlns=\"http://standards.iso.org/iso/19770/-2/2015/schema.xsd\">"
538 "<Entity name=\"%s\" regid=\"%s\" role=\"tagCreator\"/>"
539 "<Meta product=\"%s\"/>"
540 "</SoftwareIdentity>",
541 package, tag_id, version, entity, regid, product) == -1)
542 {
543 tag = NULL;
544 }
545 }
546
547 return tag;
548 }
549
550 /**
551 * Generate a minimalistic ISO 19770-2:2015 SWID tag for
552 * all removed SW identifiers that are not registered centrally
553 */
554 static int generate_tags(sw_collector_info_t *info, sw_collector_db_t *db,
555 bool full_tags, sw_collector_db_query_t type)
556 {
557 sw_collector_rest_api_t *rest_api;
558 char *name, *package, *version, *entity, *regid, *product, *generator, *tag;
559 enumerator_t *enumerator;
560 uint32_t sw_id;
561 bool installed;
562 int count = 0, installed_count = 0, status = EXIT_FAILURE;
563
564 entity = lib->settings->get_str(lib->settings, "%s.tag_creator.name",
565 "strongSwan Project", lib->ns);
566 regid = lib->settings->get_str(lib->settings, "%s.tag_creator.regid",
567 "strongswan.org", lib->ns);
568 generator = lib->settings->get_str(lib->settings, "%s.swid_generator",
569 SWID_GENERATOR, lib->ns);
570 info->get_os(info, &product);
571
572 rest_api = sw_collector_rest_api_create(db);
573 if (!rest_api)
574 {
575 goto end;
576 }
577
578 enumerator = rest_api->create_sw_enumerator(rest_api, type);
579 if (!enumerator)
580 {
581 goto end;
582 }
583 while (enumerator->enumerate(enumerator, &name))
584 {
585 sw_id = db->get_sw_id(db, name, &package, &version, NULL, &installed);
586 if (sw_id)
587 {
588 tag = generate_tag(name, package, version, entity, regid, product,
589 full_tags && installed, generator);
590 if (tag)
591 {
592 DBG2(DBG_IMC, " creating %s", name);
593 printf("%s\n", tag);
594 free(tag);
595 count++;
596 if (installed)
597 {
598 installed_count++;
599 }
600 }
601 free(package);
602 free(version);
603 }
604 }
605 enumerator->destroy(enumerator);
606 status = EXIT_SUCCESS;
607
608 switch (type)
609 {
610 case SW_QUERY_ALL:
611 DBG1(DBG_IMC, "created %d tags for unregistered software "
612 "identifiers with %d installed and %d removed", count,
613 installed_count, count - installed_count);
614 break;
615 case SW_QUERY_INSTALLED:
616 DBG1(DBG_IMC, "created %d tags for unregistered installed software "
617 "identifiers", count);
618 break;
619 case SW_QUERY_REMOVED:
620 DBG1(DBG_IMC, "created %d tags for unregistered removed software "
621 "identifiers", count);
622 break;
623 }
624
625 end:
626 DESTROY_IF(rest_api);
627
628 return status;
629 }
630
631 /**
632 * Append missing architecture suffix to package entries in the database
633 */
634 static int migrate(sw_collector_info_t *info, sw_collector_db_t *db)
635 {
636 sw_collector_dpkg_t *dpkg;
637
638 char *package, *arch, *version;
639 char package_filter[BUF_LEN];
640 int res, count = 0;
641 int status = EXIT_SUCCESS;
642 enumerator_t *enumerator;
643
644 dpkg = sw_collector_dpkg_create();
645 if (!dpkg)
646 {
647 return FAILED;
648 }
649
650 enumerator = dpkg->create_sw_enumerator(dpkg);
651 while (enumerator->enumerate(enumerator, &package, &arch, &version))
652 {
653
654 /* Look for package names with architecture suffix */
655 snprintf(package_filter, BUF_LEN, "%s:%%", package);
656
657 res = db->update_package(db, package_filter, package);
658 if (res < 0)
659 {
660 status = EXIT_FAILURE;
661 break;
662 }
663 else if (res > 0)
664 {
665 count += res;
666 DBG2(DBG_IMC, "%s: removed arch suffix %d times", package, res);
667 }
668 }
669 enumerator->destroy(enumerator);
670 dpkg->destroy(dpkg);
671
672 DBG1(DBG_IMC, "migrated %d sw identifier records", count);
673
674 return status;
675 }
676
677
678 int main(int argc, char *argv[])
679 {
680 sw_collector_db_t *db = NULL;
681 sw_collector_db_query_t query_type;
682 sw_collector_info_t *info;
683 collector_op_t op;
684 bool full_tags;
685 char *uri, *tag_creator;
686 int status = EXIT_FAILURE;
687
688 op = do_args(argc, argv, &full_tags, &query_type);
689
690 /* enable sw_collector debugging hook */
691 dbg = sw_collector_dbg;
692 #ifdef HAVE_SYSLOG
693 openlog("sw-collector", 0, LOG_DEBUG);
694 #endif
695
696 atexit(cleanup);
697
698 /* initialize library */
699 if (!library_init(NULL, "sw-collector"))
700 {
701 exit(SS_RC_LIBSTRONGSWAN_INTEGRITY);
702 }
703
704 /* load sw-collector plugins */
705 if (!lib->plugins->load(lib->plugins,
706 lib->settings->get_str(lib->settings, "%s.load", PLUGINS, lib->ns)))
707 {
708 exit(SS_RC_INITIALIZATION_FAILED);
709 }
710
711 /* connect to sw-collector database */
712 uri = lib->settings->get_str(lib->settings, "%s.database", NULL, lib->ns);
713 if (!uri)
714 {
715 fprintf(stderr, "sw-collector.database URI not set.\n");
716 exit(EXIT_FAILURE);
717 }
718 db = sw_collector_db_create(uri);
719 if (!db)
720 {
721 fprintf(stderr, "connection to sw-collector database failed.\n");
722 exit(EXIT_FAILURE);
723 }
724
725 /* Attach OS info */
726 tag_creator = lib->settings->get_str(lib->settings, "%s.tag_creator.regid",
727 "strongswan.org", lib->ns);
728 info = sw_collector_info_create(tag_creator);
729
730 switch (op)
731 {
732 case COLLECTOR_OP_EXTRACT:
733 status = extract_history(info, db);
734 break;
735 case COLLECTOR_OP_LIST:
736 status = list_identifiers(db, query_type);
737 break;
738 case COLLECTOR_OP_UNREGISTERED:
739 status = unregistered_identifiers(db, query_type);
740 break;
741 case COLLECTOR_OP_GENERATE:
742 status = generate_tags(info, db, full_tags, query_type);
743 break;
744 case COLLECTOR_OP_MIGRATE:
745 status = migrate(info, db);
746 break;
747 }
748 db->destroy(db);
749 info->destroy(info);
750
751 exit(status);
752 }