swid-gen: Share SWID generator between sw-collector, imc-swima and imc-swid
[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 <utils/debug.h>
34 #include <utils/lexparser.h>
35
36 #include <imv/imv_os_info.h>
37 #include <swid_gen/swid_gen.h>
38
39 /**
40 * global debug output variables
41 */
42 static int debug_level = 2;
43 static bool stderr_quiet = FALSE;
44 static int count = 0;
45
46 typedef enum collector_op_t collector_op_t;
47
48 enum collector_op_t {
49 COLLECTOR_OP_EXTRACT,
50 COLLECTOR_OP_LIST,
51 COLLECTOR_OP_UNREGISTERED,
52 COLLECTOR_OP_GENERATE,
53 COLLECTOR_OP_MIGRATE
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 }
126
127 /**
128 * Parse command line options
129 */
130 static collector_op_t do_args(int argc, char *argv[], bool *full_tags,
131 sw_collector_db_query_t *query_type)
132 {
133 collector_op_t op = COLLECTOR_OP_EXTRACT;
134 bool installed = FALSE, removed = FALSE, full = FALSE;
135
136 /* reinit getopt state */
137 optind = 0;
138
139 while (TRUE)
140 {
141 int c;
142
143 struct option long_opts[] = {
144 { "help", no_argument, NULL, 'h' },
145 { "count", required_argument, NULL, 'c' },
146 { "debug", required_argument, NULL, 'd' },
147 { "full", no_argument, NULL, 'f' },
148 { "generate", no_argument, NULL, 'g' },
149 { "installed", no_argument, NULL, 'i' },
150 { "list", no_argument, NULL, 'l' },
151 { "migrate", no_argument, NULL, 'm' },
152 { "quiet", no_argument, NULL, 'q' },
153 { "removed", no_argument, NULL, 'r' },
154 { "unregistered", no_argument, NULL, 'u' },
155 { 0,0,0,0 }
156 };
157
158 c = getopt_long(argc, argv, "hc:d:fgilmqru", long_opts, NULL);
159 switch (c)
160 {
161 case EOF:
162 break;
163 case 'h':
164 usage();
165 exit(SUCCESS);
166 break;
167 case 'c':
168 count = atoi(optarg);
169 continue;
170 case 'd':
171 debug_level = atoi(optarg);
172 continue;
173 case 'f':
174 full = TRUE;
175 continue;
176 case 'g':
177 op = COLLECTOR_OP_GENERATE;
178 continue;
179 case 'i':
180 installed = TRUE;
181 continue;
182 case 'l':
183 op = COLLECTOR_OP_LIST;
184 continue;
185 case 'm':
186 op = COLLECTOR_OP_MIGRATE;
187 continue;
188 case 'q':
189 stderr_quiet = TRUE;
190 continue;
191 case 'r':
192 removed = TRUE;
193 continue;
194 case 'u':
195 op = COLLECTOR_OP_UNREGISTERED;
196 continue;
197 default:
198 usage();
199 exit(EXIT_FAILURE);
200 }
201 break;
202 }
203
204 if ((!installed && !removed) || (installed && removed))
205 {
206 *query_type = SW_QUERY_ALL;
207 }
208 else if (installed)
209 {
210 *query_type = SW_QUERY_INSTALLED;
211 }
212 else
213 {
214 *query_type = SW_QUERY_REMOVED;
215 }
216 *full_tags = full;
217
218 return op;
219 }
220
221 /**
222 * Extract software events from apt history log files
223 */
224 static int extract_history(sw_collector_info_t *info, sw_collector_db_t *db)
225 {
226 sw_collector_history_t *history = NULL;
227 uint32_t epoch, last_eid, eid = 0;
228 char *history_path, *os, *last_time = NULL, rfc_time[21];
229 chunk_t *h, history_chunk, line, cmd;
230 os_type_t os_type;
231 int status = EXIT_FAILURE;
232 bool skip = TRUE;
233
234 /* check if OS supports apg/dpkg history logs */
235 info->get_os(info, &os);
236 os_type = info->get_os_type(info);
237
238 if (os_type != OS_TYPE_DEBIAN && os_type != OS_TYPE_UBUNTU)
239 {
240 DBG1(DBG_IMC, "%.*s not supported", os);
241 return EXIT_FAILURE;
242 }
243
244 /* open history file for reading */
245 history_path= lib->settings->get_str(lib->settings, "%s.history", NULL,
246 lib->ns);
247 if (!history_path)
248 {
249 fprintf(stderr, "sw-collector.history path not set.\n");
250 return EXIT_FAILURE;
251 }
252 h = chunk_map(history_path, FALSE);
253 if (!h)
254 {
255 fprintf(stderr, "opening '%s' failed: %s", history, strerror(errno));
256 return EXIT_FAILURE;
257 }
258 history_chunk = *h;
259
260 /* Instantiate history extractor */
261 history = sw_collector_history_create(info, db, 1);
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_info_t *info, sw_collector_db_t *db,
475 bool full_tags, 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 * Append missing architecture suffix to package entries in the database
548 */
549 static int migrate(sw_collector_info_t *info, 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 int main(int argc, char *argv[])
594 {
595 sw_collector_db_t *db = NULL;
596 sw_collector_db_query_t query_type;
597 sw_collector_info_t *info;
598 collector_op_t op;
599 bool full_tags;
600 char *uri, *tag_creator;
601 int status = EXIT_FAILURE;
602
603 op = do_args(argc, argv, &full_tags, &query_type);
604
605 /* enable sw_collector debugging hook */
606 dbg = sw_collector_dbg;
607 #ifdef HAVE_SYSLOG
608 openlog("sw-collector", 0, LOG_DEBUG);
609 #endif
610
611 atexit(cleanup);
612
613 /* initialize library */
614 if (!library_init(NULL, "sw-collector"))
615 {
616 exit(SS_RC_LIBSTRONGSWAN_INTEGRITY);
617 }
618
619 /* load sw-collector plugins */
620 if (!lib->plugins->load(lib->plugins,
621 lib->settings->get_str(lib->settings, "%s.load", PLUGINS, lib->ns)))
622 {
623 exit(SS_RC_INITIALIZATION_FAILED);
624 }
625
626 /* connect to sw-collector database */
627 uri = lib->settings->get_str(lib->settings, "%s.database", NULL, lib->ns);
628 if (!uri)
629 {
630 fprintf(stderr, "sw-collector.database URI not set.\n");
631 exit(EXIT_FAILURE);
632 }
633 db = sw_collector_db_create(uri);
634 if (!db)
635 {
636 fprintf(stderr, "connection to sw-collector database failed.\n");
637 exit(EXIT_FAILURE);
638 }
639
640 /* Attach OS info */
641 tag_creator = lib->settings->get_str(lib->settings, "%s.tag_creator.regid",
642 "strongswan.org", lib->ns);
643 info = sw_collector_info_create(tag_creator);
644
645 switch (op)
646 {
647 case COLLECTOR_OP_EXTRACT:
648 status = extract_history(info, db);
649 break;
650 case COLLECTOR_OP_LIST:
651 status = list_identifiers(db, query_type);
652 break;
653 case COLLECTOR_OP_UNREGISTERED:
654 status = unregistered_identifiers(db, query_type);
655 break;
656 case COLLECTOR_OP_GENERATE:
657 status = generate_tags(info, db, full_tags, query_type);
658 break;
659 case COLLECTOR_OP_MIGRATE:
660 status = migrate(info, db);
661 break;
662 }
663 db->destroy(db);
664 info->destroy(info);
665
666 exit(status);
667 }