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