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