sw-collector: Avoid naming conflicts with local count variables
[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 max_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 case 'C':
169 op = COLLECTOR_OP_CHECK;
170 continue;
171 case 'c':
172 max_count = atoi(optarg);
173 continue;
174 case 'd':
175 debug_level = atoi(optarg);
176 continue;
177 case 'f':
178 full = TRUE;
179 continue;
180 case 'g':
181 op = COLLECTOR_OP_GENERATE;
182 continue;
183 case 'i':
184 installed = TRUE;
185 continue;
186 case 'l':
187 op = COLLECTOR_OP_LIST;
188 continue;
189 case 'm':
190 op = COLLECTOR_OP_MIGRATE;
191 continue;
192 case 'q':
193 stderr_quiet = TRUE;
194 continue;
195 case 'r':
196 removed = TRUE;
197 continue;
198 case 'u':
199 op = COLLECTOR_OP_UNREGISTERED;
200 continue;
201 default:
202 usage();
203 exit(EXIT_FAILURE);
204 }
205 break;
206 }
207
208 if ((!installed && !removed) || (installed && removed))
209 {
210 *query_type = SW_QUERY_ALL;
211 }
212 else if (installed)
213 {
214 *query_type = SW_QUERY_INSTALLED;
215 }
216 else
217 {
218 *query_type = SW_QUERY_REMOVED;
219 }
220 *full_tags = full;
221
222 return op;
223 }
224
225 /**
226 * Extract software events from apt history log files
227 */
228 static int extract_history(sw_collector_db_t *db)
229 {
230 sw_collector_history_t *history = NULL;
231 uint32_t epoch, last_eid, eid = 0;
232 char *history_path, *last_time = NULL, rfc_time[21];
233 chunk_t *h, history_chunk, line, cmd;
234 int status = EXIT_FAILURE;
235 bool skip = TRUE;
236
237 /* open history file for reading */
238 history_path = lib->settings->get_str(lib->settings, "%s.history", NULL,
239 lib->ns);
240 if (!history_path)
241 {
242 fprintf(stderr, "sw-collector.history path not set.\n");
243 return EXIT_FAILURE;
244 }
245 h = chunk_map(history_path, FALSE);
246 if (!h)
247 {
248 fprintf(stderr, "opening '%s' failed: %s", history_path,
249 strerror(errno));
250 return EXIT_FAILURE;
251 }
252 history_chunk = *h;
253
254 /* Instantiate history extractor */
255 history = sw_collector_history_create(db, 1);
256 if (!history)
257 {
258 chunk_unmap(h);
259 return EXIT_FAILURE;
260 }
261
262 /* retrieve last event in database */
263 if (!db->get_last_event(db, &last_eid, &epoch, &last_time) || !last_eid)
264 {
265 goto end;
266 }
267 DBG0(DBG_IMC, "Last-Event: %s, eid = %u, epoch = %u",
268 last_time, last_eid, epoch);
269
270 /* parse history file */
271 while (fetchline(&history_chunk, &line))
272 {
273 if (line.len == 0)
274 {
275 continue;
276 }
277 if (!extract_token(&cmd, ':', &line))
278 {
279 fprintf(stderr, "terminator symbol ':' not found.\n");
280 goto end;
281 }
282 if (match("Start-Date", &cmd))
283 {
284 if (!history->extract_timestamp(history, line, rfc_time))
285 {
286 goto end;
287 }
288
289 /* have we reached new history entries? */
290 if (skip && strcmp(rfc_time, last_time) > 0)
291 {
292 skip = FALSE;
293 }
294 if (skip)
295 {
296 continue;
297 }
298
299 /* insert new event into database */
300 eid = db->add_event(db, rfc_time);
301 if (!eid)
302 {
303 goto end;
304 }
305 DBG1(DBG_IMC, "Start-Date: %s, eid = %u, epoch = %u",
306 rfc_time, eid, epoch);
307 }
308 else if (skip)
309 {
310 /* skip old history entries which have already been processed */
311 continue;
312 }
313 else if (match("Install", &cmd))
314 {
315 DBG1(DBG_IMC, " Install:");
316 if (!history->extract_packages(history, line, eid, SW_OP_INSTALL))
317 {
318 goto end;
319 }
320 }
321 else if (match("Upgrade", &cmd))
322 {
323 DBG1(DBG_IMC, " Upgrade:");
324 if (!history->extract_packages(history, line, eid, SW_OP_UPGRADE))
325 {
326 goto end;
327 }
328 }
329 else if (match("Remove", &cmd))
330 {
331 DBG1(DBG_IMC, " Remove:");
332 if (!history->extract_packages(history, line, eid, SW_OP_REMOVE))
333 {
334 goto end;
335 }
336 }
337 else if (match("Purge", &cmd))
338 {
339 DBG1(DBG_IMC, " Purge:");
340 if (!history->extract_packages(history, line, eid, SW_OP_REMOVE))
341 {
342 goto end;
343 }
344 }
345 else if (match("End-Date", &cmd))
346 {
347 /* Process 'max_count' events at a time */
348 if (max_count > 0 && eid - last_eid == max_count)
349 {
350 fprintf(stderr, "added %d events\n", max_count);
351 goto end;
352 }
353 }
354 }
355
356 if (history->merge_installed_packages(history))
357 {
358 status = EXIT_SUCCESS;
359 }
360
361 end:
362 free(last_time);
363 history->destroy(history);
364 chunk_unmap(h);
365
366 return status;
367 }
368
369 /**
370 * List all endpoint software identifiers stored in local collector database
371 */
372 static int list_identifiers(sw_collector_db_t *db, sw_collector_db_query_t type)
373 {
374 enumerator_t *e;
375 char *name, *package, *version;
376 uint32_t sw_id, count = 0, installed_count = 0, removed_count, installed;
377
378 e = db->create_sw_enumerator(db, type, NULL);
379 if (!e)
380 {
381 return EXIT_FAILURE;
382 }
383 while (e->enumerate(e, &sw_id, &name, &package, &version, &installed))
384 {
385 printf("%s,%s,%s,%d\n", name, package, version, installed);
386 if (installed)
387 {
388 installed_count++;
389 }
390 count++;
391 }
392 removed_count = count - installed_count;
393 e->destroy(e);
394
395 switch (type)
396 {
397 case SW_QUERY_ALL:
398 DBG1(DBG_IMC, "retrieved %u software identities with %u installed "
399 "and %u removed", count, installed_count, removed_count);
400 break;
401 case SW_QUERY_INSTALLED:
402 DBG1(DBG_IMC, "retrieved %u installed software identities", count);
403 break;
404 case SW_QUERY_REMOVED:
405 DBG1(DBG_IMC, "retrieved %u removed software identities", count);
406 break;
407 }
408
409 return EXIT_SUCCESS;
410 }
411
412 static bool query_registry(sw_collector_rest_api_t *rest_api, bool installed)
413 {
414 sw_collector_db_query_t type;
415 enumerator_t *enumerator;
416 char *sw_id;
417 int count = 0;
418
419 type = installed ? SW_QUERY_INSTALLED : SW_QUERY_REMOVED;
420 enumerator = rest_api->create_sw_enumerator(rest_api, type);
421 if (!enumerator)
422 {
423 return FALSE;
424 }
425 while (enumerator->enumerate(enumerator, &sw_id))
426 {
427 printf("%s,%s\n", sw_id, installed ? "1" : "0");
428 count++;
429 }
430 enumerator->destroy(enumerator);
431 DBG1(DBG_IMC, "%d %s software identifiers not registered", count,
432 installed ? "installed" : "removed");
433 return TRUE;
434 }
435
436
437 /**
438 * List all endpoint software identifiers stored in local collector database
439 * that are not registered yet in central collelector database
440 */
441 static int unregistered_identifiers(sw_collector_db_t *db,
442 sw_collector_db_query_t type)
443 {
444 sw_collector_rest_api_t *rest_api;
445 int status = EXIT_SUCCESS;
446
447 rest_api = sw_collector_rest_api_create(db);
448 if (!rest_api)
449 {
450 return EXIT_FAILURE;
451 }
452
453 /* List installed software identifiers not registered centrally */
454 if (type != SW_QUERY_REMOVED && !query_registry(rest_api, TRUE))
455 {
456 status = EXIT_FAILURE;
457 }
458
459 /* List removed software identifiers not registered centrally */
460 if (type != SW_QUERY_INSTALLED && !query_registry(rest_api, FALSE))
461 {
462 status = EXIT_FAILURE;
463 }
464 rest_api->destroy(rest_api);
465
466 return status;
467 }
468
469 /**
470 * Generate ISO 19770-2:2015 SWID tags for [installed|removed|all]
471 * SW identifiers that are not registered centrally
472 */
473 static int generate_tags(sw_collector_db_t *db, bool full_tags,
474 sw_collector_db_query_t type)
475 {
476 swid_gen_t * swid_gen;
477 sw_collector_rest_api_t *rest_api;
478 char *name, *package, *version, *tag;
479 enumerator_t *enumerator;
480 uint32_t sw_id;
481 bool installed;
482 int count = 0, installed_count = 0, status = EXIT_FAILURE;
483
484 swid_gen = swid_gen_create();
485 rest_api = sw_collector_rest_api_create(db);
486 if (!rest_api)
487 {
488 goto end;
489 }
490
491 enumerator = rest_api->create_sw_enumerator(rest_api, type);
492 if (!enumerator)
493 {
494 goto end;
495 }
496 while (enumerator->enumerate(enumerator, &name))
497 {
498 sw_id = db->get_sw_id(db, name, &package, &version, NULL, &installed);
499 if (sw_id)
500 {
501 tag = swid_gen->generate_tag(swid_gen, name, package, version,
502 full_tags && installed, FALSE);
503 if (tag)
504 {
505 DBG2(DBG_IMC, " creating %s", name);
506 printf("%s\n", tag);
507 free(tag);
508 count++;
509 if (installed)
510 {
511 installed_count++;
512 }
513 }
514 free(package);
515 free(version);
516 }
517 }
518 enumerator->destroy(enumerator);
519 status = EXIT_SUCCESS;
520
521 switch (type)
522 {
523 case SW_QUERY_ALL:
524 DBG1(DBG_IMC, "created %d tags for unregistered software "
525 "identifiers with %d installed and %d removed", count,
526 installed_count, count - installed_count);
527 break;
528 case SW_QUERY_INSTALLED:
529 DBG1(DBG_IMC, "created %d tags for unregistered installed software "
530 "identifiers", count);
531 break;
532 case SW_QUERY_REMOVED:
533 DBG1(DBG_IMC, "created %d tags for unregistered removed software "
534 "identifiers", count);
535 break;
536 }
537
538 end:
539 swid_gen->destroy(swid_gen);
540 DESTROY_IF(rest_api);
541
542 return status;
543 }
544
545 /**
546 * Remove architecture suffix from package entries in the database
547 */
548 static int migrate(sw_collector_db_t *db)
549 {
550 sw_collector_dpkg_t *dpkg;
551
552 char *package, *arch, *version;
553 char package_filter[BUF_LEN];
554 int res, count = 0;
555 int status = EXIT_SUCCESS;
556 enumerator_t *enumerator;
557
558 dpkg = sw_collector_dpkg_create();
559 if (!dpkg)
560 {
561 return FAILED;
562 }
563
564 enumerator = dpkg->create_sw_enumerator(dpkg);
565 while (enumerator->enumerate(enumerator, &package, &arch, &version))
566 {
567
568 /* Look for package names with architecture suffix */
569 snprintf(package_filter, BUF_LEN, "%s:%%", package);
570
571 res = db->update_package(db, package_filter, package);
572 if (res < 0)
573 {
574 status = EXIT_FAILURE;
575 break;
576 }
577 else if (res > 0)
578 {
579 count += res;
580 DBG2(DBG_IMC, "%s: removed arch suffix %d times", package, res);
581 }
582 }
583 enumerator->destroy(enumerator);
584 dpkg->destroy(dpkg);
585
586 DBG1(DBG_IMC, "migrated %d sw identifier records", count);
587
588 return status;
589 }
590
591 /**
592 * Free hashtable entry
593 */
594 static void free_entry(void *value, void *key)
595 {
596 free(value);
597 free(key);
598 }
599
600 /**
601 * Check consistency of installed software identifiers in collector database
602 */
603 static int check(sw_collector_db_t *db)
604 {
605 sw_collector_dpkg_t *dpkg;
606 swid_gen_info_t *info;
607 hashtable_t *table;
608 enumerator_t *e;
609 char *dpkg_name, *name, *package, *arch, *version;
610 uint32_t sw_id, count = 0, installed;
611
612 dpkg = sw_collector_dpkg_create();
613 if (!dpkg)
614 {
615 return EXIT_FAILURE;
616 }
617 info = swid_gen_info_create();
618 table = hashtable_create(hashtable_hash_str, hashtable_equals_str, 4096);
619
620 /* Store all installed sw identifiers (according to dpkg) in hashtable */
621 e = dpkg->create_sw_enumerator(dpkg);
622 while (e->enumerate(e, &package, &arch, &version))
623 {
624 dpkg_name = info->create_sw_id(info, package, version);
625 table->put(table, strdup(package), dpkg_name);
626 }
627 e->destroy(e);
628
629 info->destroy(info);
630 dpkg->destroy(dpkg);
631
632 e = db->create_sw_enumerator(db, SW_QUERY_ALL, NULL);
633 if (!e)
634 {
635 table->destroy_function(table, (void*)free_entry);
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 }