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