summaryrefslogtreecommitdiff
path: root/src/sw-collector/sw-collector.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sw-collector/sw-collector.c')
-rw-r--r--src/sw-collector/sw-collector.c652
1 files changed, 652 insertions, 0 deletions
diff --git a/src/sw-collector/sw-collector.c b/src/sw-collector/sw-collector.c
new file mode 100644
index 000000000..e673dd657
--- /dev/null
+++ b/src/sw-collector/sw-collector.c
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2017 Andreas Steffen
+ * HSR Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <unistd.h>
+#ifdef HAVE_SYSLOG
+# include <syslog.h>
+#endif
+
+#include "sw_collector_db.h"
+#include "sw_collector_history.h"
+#include "sw_collector_rest_api.h"
+#include "sw_collector_dpkg.h"
+#
+#include <library.h>
+#include <utils/debug.h>
+#include <utils/lexparser.h>
+
+#include <swid_gen/swid_gen.h>
+
+/**
+ * global debug output variables
+ */
+static int debug_level = 2;
+static bool stderr_quiet = FALSE;
+static int count = 0;
+
+typedef enum collector_op_t collector_op_t;
+
+enum collector_op_t {
+ COLLECTOR_OP_EXTRACT,
+ COLLECTOR_OP_LIST,
+ COLLECTOR_OP_UNREGISTERED,
+ COLLECTOR_OP_GENERATE,
+ COLLECTOR_OP_MIGRATE
+};
+
+/**
+ * sw_collector dbg function
+ */
+static void sw_collector_dbg(debug_t group, level_t level, char *fmt, ...)
+{
+ va_list args;
+
+ if (level <= debug_level)
+ {
+ if (!stderr_quiet)
+ {
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ }
+
+#ifdef HAVE_SYSLOG
+ {
+ int priority = LOG_INFO;
+ char buffer[8192];
+ char *current = buffer, *next;
+
+ /* write in memory buffer first */
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, args);
+ va_end(args);
+
+ /* do a syslog with every line */
+ while (current)
+ {
+ next = strchr(current, '\n');
+ if (next)
+ {
+ *(next++) = '\0';
+ }
+ syslog(priority, "%s\n", current);
+ current = next;
+ }
+ }
+#endif /* HAVE_SYSLOG */
+ }
+}
+
+/**
+ * atexit handler
+ */
+static void cleanup(void)
+{
+ library_deinit();
+#ifdef HAVE_SYSLOG
+ closelog();
+#endif
+}
+
+/**
+ * Display usage of sw-collector command
+ */
+static void usage(void)
+{
+ printf("\
+Usage:\n\
+ sw-collector --help\n\
+ sw-collector [--debug <level>] [--quiet] [--count <event count>]\n\
+ sw-collector [--debug <level>] [--quiet] [--installed|--removed] \
+--list|-unregistered\n\
+ sw-collector [--debug <level>] [--quiet] [--installed|--removed] \
+[--full] --generate\n\
+ sw-collector [--debug <level>] [--quiet] --migrate\n");
+}
+
+/**
+ * Parse command line options
+ */
+static collector_op_t do_args(int argc, char *argv[], bool *full_tags,
+ sw_collector_db_query_t *query_type)
+{
+ collector_op_t op = COLLECTOR_OP_EXTRACT;
+ bool installed = FALSE, removed = FALSE, full = FALSE;
+
+ /* reinit getopt state */
+ optind = 0;
+
+ while (TRUE)
+ {
+ int c;
+
+ struct option long_opts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "count", required_argument, NULL, 'c' },
+ { "debug", required_argument, NULL, 'd' },
+ { "full", no_argument, NULL, 'f' },
+ { "generate", no_argument, NULL, 'g' },
+ { "installed", no_argument, NULL, 'i' },
+ { "list", no_argument, NULL, 'l' },
+ { "migrate", no_argument, NULL, 'm' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "removed", no_argument, NULL, 'r' },
+ { "unregistered", no_argument, NULL, 'u' },
+ { 0,0,0,0 }
+ };
+
+ c = getopt_long(argc, argv, "hc:d:fgilmqru", long_opts, NULL);
+ switch (c)
+ {
+ case EOF:
+ break;
+ case 'h':
+ usage();
+ exit(SUCCESS);
+ break;
+ case 'c':
+ count = atoi(optarg);
+ continue;
+ case 'd':
+ debug_level = atoi(optarg);
+ continue;
+ case 'f':
+ full = TRUE;
+ continue;
+ case 'g':
+ op = COLLECTOR_OP_GENERATE;
+ continue;
+ case 'i':
+ installed = TRUE;
+ continue;
+ case 'l':
+ op = COLLECTOR_OP_LIST;
+ continue;
+ case 'm':
+ op = COLLECTOR_OP_MIGRATE;
+ continue;
+ case 'q':
+ stderr_quiet = TRUE;
+ continue;
+ case 'r':
+ removed = TRUE;
+ continue;
+ case 'u':
+ op = COLLECTOR_OP_UNREGISTERED;
+ continue;
+ default:
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ break;
+ }
+
+ if ((!installed && !removed) || (installed && removed))
+ {
+ *query_type = SW_QUERY_ALL;
+ }
+ else if (installed)
+ {
+ *query_type = SW_QUERY_INSTALLED;
+ }
+ else
+ {
+ *query_type = SW_QUERY_REMOVED;
+ }
+ *full_tags = full;
+
+ return op;
+}
+
+/**
+ * Extract software events from apt history log files
+ */
+static int extract_history(sw_collector_db_t *db)
+{
+ sw_collector_history_t *history = NULL;
+ uint32_t epoch, last_eid, eid = 0;
+ char *history_path, *last_time = NULL, rfc_time[21];
+ chunk_t *h, history_chunk, line, cmd;
+ int status = EXIT_FAILURE;
+ bool skip = TRUE;
+
+ /* open history file for reading */
+ history_path = lib->settings->get_str(lib->settings, "%s.history", NULL,
+ lib->ns);
+ if (!history_path)
+ {
+ fprintf(stderr, "sw-collector.history path not set.\n");
+ return EXIT_FAILURE;
+ }
+ h = chunk_map(history_path, FALSE);
+ if (!h)
+ {
+ fprintf(stderr, "opening '%s' failed: %s", history_path,
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ history_chunk = *h;
+
+ /* Instantiate history extractor */
+ history = sw_collector_history_create(db, 1);
+ if (!history)
+ {
+ return EXIT_FAILURE;
+ }
+
+ /* retrieve last event in database */
+ if (!db->get_last_event(db, &last_eid, &epoch, &last_time) || !last_eid)
+ {
+ goto end;
+ }
+ DBG0(DBG_IMC, "Last-Event: %s, eid = %u, epoch = %u",
+ last_time, last_eid, epoch);
+
+ /* parse history file */
+ while (fetchline(&history_chunk, &line))
+ {
+ if (line.len == 0)
+ {
+ continue;
+ }
+ if (!extract_token(&cmd, ':', &line))
+ {
+ fprintf(stderr, "terminator symbol ':' not found.\n");
+ goto end;
+ }
+ if (match("Start-Date", &cmd))
+ {
+ if (!history->extract_timestamp(history, line, rfc_time))
+ {
+ goto end;
+ }
+
+ /* have we reached new history entries? */
+ if (skip && strcmp(rfc_time, last_time) > 0)
+ {
+ skip = FALSE;
+ }
+ if (skip)
+ {
+ continue;
+ }
+
+ /* insert new event into database */
+ eid = db->add_event(db, rfc_time);
+ if (!eid)
+ {
+ goto end;
+ }
+ DBG1(DBG_IMC, "Start-Date: %s, eid = %u, epoch = %u",
+ rfc_time, eid, epoch);
+ }
+ else if (skip)
+ {
+ /* skip old history entries which have already been processed */
+ continue;
+ }
+ else if (match("Install", &cmd))
+ {
+ DBG1(DBG_IMC, " Install:");
+ if (!history->extract_packages(history, line, eid, SW_OP_INSTALL))
+ {
+ goto end;
+ }
+ }
+ else if (match("Upgrade", &cmd))
+ {
+ DBG1(DBG_IMC, " Upgrade:");
+ if (!history->extract_packages(history, line, eid, SW_OP_UPGRADE))
+ {
+ goto end;
+ }
+ }
+ else if (match("Remove", &cmd))
+ {
+ DBG1(DBG_IMC, " Remove:");
+ if (!history->extract_packages(history, line, eid, SW_OP_REMOVE))
+ {
+ goto end;
+ }
+ }
+ else if (match("Purge", &cmd))
+ {
+ DBG1(DBG_IMC, " Purge:");
+ if (!history->extract_packages(history, line, eid, SW_OP_REMOVE))
+ {
+ goto end;
+ }
+ }
+ else if (match("End-Date", &cmd))
+ {
+ /* Process 'count' events at a time */
+ if (count > 0 && eid - last_eid == count)
+ {
+ fprintf(stderr, "added %d events\n", count);
+ goto end;
+ }
+ }
+ }
+
+ if (history->merge_installed_packages(history))
+ {
+ status = EXIT_SUCCESS;
+ }
+
+end:
+ free(last_time);
+ history->destroy(history);
+ chunk_unmap(h);
+
+ return status;
+}
+
+/**
+ * List all endpoint software identifiers stored in local collector database
+ */
+static int list_identifiers(sw_collector_db_t *db, sw_collector_db_query_t type)
+{
+ enumerator_t *e;
+ char *name, *package, *version;
+ uint32_t sw_id, count = 0, installed_count = 0, removed_count, installed;
+
+ e = db->create_sw_enumerator(db, type, NULL);
+ if (!e)
+ {
+ return EXIT_FAILURE;
+ }
+ while (e->enumerate(e, &sw_id, &name, &package, &version, &installed))
+ {
+ printf("%s,%s,%s,%d\n", name, package, version, installed);
+ if (installed)
+ {
+ installed_count++;
+ }
+ count++;
+ }
+ removed_count = count - installed_count;
+ e->destroy(e);
+
+ switch (type)
+ {
+ case SW_QUERY_ALL:
+ DBG1(DBG_IMC, "retrieved %u software identities with %u installed "
+ "and %u removed", count, installed_count, removed_count);
+ break;
+ case SW_QUERY_INSTALLED:
+ DBG1(DBG_IMC, "retrieved %u installed software identities", count);
+ break;
+ case SW_QUERY_REMOVED:
+ DBG1(DBG_IMC, "retrieved %u removed software identities", count);
+ break;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+static bool query_registry(sw_collector_rest_api_t *rest_api, bool installed)
+{
+ sw_collector_db_query_t type;
+ enumerator_t *enumerator;
+ char *sw_id;
+ int count = 0;
+
+ type = installed ? SW_QUERY_INSTALLED : SW_QUERY_REMOVED;
+ enumerator = rest_api->create_sw_enumerator(rest_api, type);
+ if (!enumerator)
+ {
+ return FALSE;
+ }
+ while (enumerator->enumerate(enumerator, &sw_id))
+ {
+ printf("%s,%s\n", sw_id, installed ? "1" : "0");
+ count++;
+ }
+ enumerator->destroy(enumerator);
+ DBG1(DBG_IMC, "%d %s software identifiers not registered", count,
+ installed ? "installed" : "removed");
+ return TRUE;
+}
+
+
+/**
+ * List all endpoint software identifiers stored in local collector database
+ * that are not registered yet in central collelector database
+ */
+static int unregistered_identifiers(sw_collector_db_t *db,
+ sw_collector_db_query_t type)
+{
+ sw_collector_rest_api_t *rest_api;
+ int status = EXIT_SUCCESS;
+
+ rest_api = sw_collector_rest_api_create(db);
+ if (!rest_api)
+ {
+ return EXIT_FAILURE;
+ }
+
+ /* List installed software identifiers not registered centrally */
+ if (type != SW_QUERY_REMOVED && !query_registry(rest_api, TRUE))
+ {
+ status = EXIT_FAILURE;
+ }
+
+ /* List removed software identifiers not registered centrally */
+ if (type != SW_QUERY_INSTALLED && !query_registry(rest_api, FALSE))
+ {
+ status = EXIT_FAILURE;
+ }
+ rest_api->destroy(rest_api);
+
+ return status;
+}
+
+/**
+ * Generate ISO 19770-2:2015 SWID tags for [installed|removed|all]
+ * SW identifiers that are not registered centrally
+ */
+static int generate_tags(sw_collector_db_t *db, bool full_tags,
+ sw_collector_db_query_t type)
+{
+ swid_gen_t * swid_gen;
+ sw_collector_rest_api_t *rest_api;
+ char *name, *package, *version, *tag;
+ enumerator_t *enumerator;
+ uint32_t sw_id;
+ bool installed;
+ int count = 0, installed_count = 0, status = EXIT_FAILURE;
+
+ swid_gen = swid_gen_create();
+ rest_api = sw_collector_rest_api_create(db);
+ if (!rest_api)
+ {
+ goto end;
+ }
+
+ enumerator = rest_api->create_sw_enumerator(rest_api, type);
+ if (!enumerator)
+ {
+ goto end;
+ }
+ while (enumerator->enumerate(enumerator, &name))
+ {
+ sw_id = db->get_sw_id(db, name, &package, &version, NULL, &installed);
+ if (sw_id)
+ {
+ tag = swid_gen->generate_tag(swid_gen, name, package, version,
+ full_tags && installed, FALSE);
+ if (tag)
+ {
+ DBG2(DBG_IMC, " creating %s", name);
+ printf("%s\n", tag);
+ free(tag);
+ count++;
+ if (installed)
+ {
+ installed_count++;
+ }
+ }
+ free(package);
+ free(version);
+ }
+ }
+ enumerator->destroy(enumerator);
+ status = EXIT_SUCCESS;
+
+ switch (type)
+ {
+ case SW_QUERY_ALL:
+ DBG1(DBG_IMC, "created %d tags for unregistered software "
+ "identifiers with %d installed and %d removed", count,
+ installed_count, count - installed_count);
+ break;
+ case SW_QUERY_INSTALLED:
+ DBG1(DBG_IMC, "created %d tags for unregistered installed software "
+ "identifiers", count);
+ break;
+ case SW_QUERY_REMOVED:
+ DBG1(DBG_IMC, "created %d tags for unregistered removed software "
+ "identifiers", count);
+ break;
+ }
+
+end:
+ swid_gen->destroy(swid_gen);
+ DESTROY_IF(rest_api);
+
+ return status;
+}
+
+/**
+ * Append missing architecture suffix to package entries in the database
+ */
+static int migrate(sw_collector_db_t *db)
+{
+ sw_collector_dpkg_t *dpkg;
+
+ char *package, *arch, *version;
+ char package_filter[BUF_LEN];
+ int res, count = 0;
+ int status = EXIT_SUCCESS;
+ enumerator_t *enumerator;
+
+ dpkg = sw_collector_dpkg_create();
+ if (!dpkg)
+ {
+ return FAILED;
+ }
+
+ enumerator = dpkg->create_sw_enumerator(dpkg);
+ while (enumerator->enumerate(enumerator, &package, &arch, &version))
+ {
+
+ /* Look for package names with architecture suffix */
+ snprintf(package_filter, BUF_LEN, "%s:%%", package);
+
+ res = db->update_package(db, package_filter, package);
+ if (res < 0)
+ {
+ status = EXIT_FAILURE;
+ break;
+ }
+ else if (res > 0)
+ {
+ count += res;
+ DBG2(DBG_IMC, "%s: removed arch suffix %d times", package, res);
+ }
+ }
+ enumerator->destroy(enumerator);
+ dpkg->destroy(dpkg);
+
+ DBG1(DBG_IMC, "migrated %d sw identifier records", count);
+
+ return status;
+}
+
+
+int main(int argc, char *argv[])
+{
+ sw_collector_db_t *db = NULL;
+ sw_collector_db_query_t query_type;
+ collector_op_t op;
+ bool full_tags;
+ char *uri;
+ int status = EXIT_FAILURE;
+
+ op = do_args(argc, argv, &full_tags, &query_type);
+
+ /* enable sw_collector debugging hook */
+ dbg = sw_collector_dbg;
+#ifdef HAVE_SYSLOG
+ openlog("sw-collector", 0, LOG_DEBUG);
+#endif
+
+ atexit(cleanup);
+
+ /* initialize library */
+ if (!library_init(NULL, "sw-collector"))
+ {
+ exit(SS_RC_LIBSTRONGSWAN_INTEGRITY);
+ }
+
+ /* load sw-collector plugins */
+ if (!lib->plugins->load(lib->plugins,
+ lib->settings->get_str(lib->settings, "%s.load", PLUGINS, lib->ns)))
+ {
+ exit(SS_RC_INITIALIZATION_FAILED);
+ }
+
+ /* connect to sw-collector database */
+ uri = lib->settings->get_str(lib->settings, "%s.database", NULL, lib->ns);
+ if (!uri)
+ {
+ fprintf(stderr, "sw-collector.database URI not set.\n");
+ exit(EXIT_FAILURE);
+ }
+ db = sw_collector_db_create(uri);
+ if (!db)
+ {
+ fprintf(stderr, "connection to sw-collector database failed.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ switch (op)
+ {
+ case COLLECTOR_OP_EXTRACT:
+ status = extract_history(db);
+ break;
+ case COLLECTOR_OP_LIST:
+ status = list_identifiers(db, query_type);
+ break;
+ case COLLECTOR_OP_UNREGISTERED:
+ status = unregistered_identifiers(db, query_type);
+ break;
+ case COLLECTOR_OP_GENERATE:
+ status = generate_tags(db, full_tags, query_type);
+ break;
+ case COLLECTOR_OP_MIGRATE:
+ status = migrate(db);
+ break;
+ }
+ db->destroy(db);
+
+ exit(status);
+}