sw-collector: Moved to its own directory and added man page

This commit is contained in:
Andreas Steffen
2017-07-18 07:25:45 +02:00
parent 34cade8b84
commit 964bf73237
18 changed files with 133 additions and 22 deletions
+2
View File
@@ -0,0 +1,2 @@
sw-collector
sw-collector.8
+33
View File
@@ -0,0 +1,33 @@
sbin_PROGRAMS = sw-collector
sw_collector_SOURCES = \
sw-collector.c \
sw_collector_db.h sw_collector_db.c \
sw_collector_dpkg.h sw_collector_dpkg.c \
sw_collector_history.h sw_collector_history.c \
sw_collector_info.h sw_collector_info.c \
sw_collector_rest_api.h sw_collector_rest_api.c
sw-collector.o : $(top_builddir)/config.status
AM_CPPFLAGS = \
-I$(top_srcdir)/src/libstrongswan \
-I$(top_srcdir)/src/libtncif \
-I$(top_srcdir)/src/libimcv \
-DPLUGINS=\""random openssl sqlite curl"\"
AM_CFLAGS = $(json_CFLAGS)
sw_collector_LDADD = \
$(top_builddir)/src/libstrongswan/libstrongswan.la \
$(top_builddir)/src/libimcv/libimcv.la \
$(json_LIBS)
templatesdir = $(pkgdatadir)/templates/database/sw-collector
dist_templates_DATA = sw_collector_tables.sql
man8_MANS = sw-collector.8
CLEANFILES = $(man8_MANS)
+90
View File
@@ -0,0 +1,90 @@
.TH SW-COLLECTOR 1 "2017-07-15" "@PACKAGE_VERSION@" "strongSwan"
.
.SH "NAME"
.
sw-collector \- Extracts software installation events from dpkg history log
.
.SH "SYNOPSIS"
.
.SY "sw-collector"
.OP \-\-debug level
.OP \-\-quiet
.OP \-\-count event-count
.YS
.
.SY "sw-collector"
.OP \-\-debug level
.OP \-\-quiet
.BR \-\-list |\fB\-\-unregistered\fR|\fB\-\-generate\fR|\fB\-\-migrate
.YS
.
.SY "sw-collector"
.B \-h
|
.B \-\-help
.YS
.
.SH "DESCRIPTION"
.
.B sw-collector
extracts information about software package installation, update or removal
events from the apt history log and stores the software events in an SQLite
database. The retrieved history information is then merged and made consistent
with the actual list of installed software packages obtained with dpkg-query.
.
.SH "OPTIONS"
.
.TP
.B "\-h, \-\-help"
Prints usage information and a short summary of the available commands.
.TP
.BI "\-v, \-\-debug " level
Set debug level, default: 2.
.TP
.B "\-q, \-\-quiet
Disable debug output to stderr.
.TP
.B "\-l, \-\-list
Lists all software packages stored in the collector database showing their
installation status.
.TP
.B "\-u, \-\-unregistered
Lists all software packages residing in the local collector database but for
which no SWID tags exist yet in a central collector database reachable via a
REST interface.
.TP
.B "\-g, \-\-generate
Generates minimal SWID tags for all deleted software packages residing in the
local collector database but for which no SWID tags exist in a central collector
database reachable via a REST interface.
.TP
.B "\-m, \-\-migrate
Can be used to migrate collector database versions. Currently all architecture
suffixes are removed from dpkg package names.
.
.SH "CONFIGURATION"
.
The following parameters can be configured in strongswan.conf:
.P
sw-collector {
database = sqlite:///etc/pts/collector.db
history = /var/log/apt/history.log
first_time = 2016-04-22T20:55:14Z
rest_api {
uri = https://admin-user:[email protected]/api/
timeout = 120
}
tag_creator {
name = strongSwan Project
regid = strongswan.org
}
}
.
.SH "FILES"
.
@PKGDATADIR@/templates/database/sw-collector/sw_collector_tables.sql
.
.SH "SEE ALSO"
.
.BR strongswan.conf (5)
+632
View File
@@ -0,0 +1,632 @@
/*
* 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_info.h"
#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 <imv/imv_os_info.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] --list|-unregistered\n\
sw-collector [--debug <level>] [--quiet] ---generate|--migrate\n");
}
/**
* Parse command line options
*/
static collector_op_t do_args(int argc, char *argv[])
{
collector_op_t op = COLLECTOR_OP_EXTRACT;
/* 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' },
{ "generate", no_argument, NULL, 'g' },
{ "list", no_argument, NULL, 'l' },
{ "migrate", no_argument, NULL, 'm' },
{ "quiet", no_argument, NULL, 'q' },
{ "unregistered", no_argument, NULL, 'u' },
{ 0,0,0,0 }
};
c = getopt_long(argc, argv, "hc:d:lmqu", 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 'g':
op = COLLECTOR_OP_GENERATE;
continue;
case 'l':
op = COLLECTOR_OP_LIST;
continue;
case 'm':
op = COLLECTOR_OP_MIGRATE;
continue;
case 'q':
stderr_quiet = TRUE;
continue;
case 'u':
op = COLLECTOR_OP_UNREGISTERED;
continue;
default:
usage();
exit(EXIT_FAILURE);
}
break;
}
return op;
}
/**
* Extract software events from apt history log files
*/
static int extract_history(sw_collector_info_t *info, sw_collector_db_t *db)
{
sw_collector_history_t *history = NULL;
uint32_t epoch, last_eid, eid = 0;
char *history_path, *os, *last_time = NULL, rfc_time[21];
chunk_t *h, history_chunk, line, cmd;
os_type_t os_type;
int status = EXIT_FAILURE;
bool skip = TRUE;
/* check if OS supports apg/dpkg history logs */
info->get_os(info, &os);
os_type = info->get_os_type(info);
if (os_type != OS_TYPE_DEBIAN && os_type != OS_TYPE_UBUNTU)
{
DBG1(DBG_IMC, "%.*s not supported", os);
return EXIT_FAILURE;
}
/* 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, strerror(errno));
return EXIT_FAILURE;
}
history_chunk = *h;
/* Instantiate history extractor */
history = sw_collector_history_create(info, db, 1);
/* 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)
{
enumerator_t *e;
char *name, *package, *version;
uint32_t sw_id, count = 0, installed_count = 0, installed;
e = db->create_sw_enumerator(db, SW_QUERY_ALL, 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++;
}
e->destroy(e);
DBG1(DBG_IMC, "retrieved %u software identities with %u installed and %u "
"deleted", count, installed_count, count - installed_count);
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_DELETED;
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" : "deleted");
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_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 (!query_registry(rest_api, TRUE))
{
status = EXIT_FAILURE;
}
/* List deleted software identifiers not registered centrally */
if (!query_registry(rest_api, FALSE))
{
status = EXIT_FAILURE;
}
rest_api->destroy(rest_api);
return status;
}
/**
* Generate a minimalistic ISO 19770-2:2015 SWID tag
*/
static char* generate_tag(char *name, char *package, char *version,
char* entity, char *regid, char *product)
{
char *tag_id, *tag;
int res;
tag_id = strstr(name, "__");
if (!tag_id)
{
return NULL;
}
tag_id += 2;
res = asprintf(&tag, "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<SoftwareIdentity name=\"%s\" tagId=\"%s\" version=\"%s\" "
"versionScheme=\"alphanumeric\" "
"xmlns=\"http://standards.iso.org/iso/19770/-2/2015/schema.xsd\">"
"<Entity name=\"%s\" regid=\"%s\" role=\"tagCreator\"/>"
"<Meta product=\"%s\"/>"
"</SoftwareIdentity>",
package, tag_id, version, entity, regid, product);
return (res == -1) ? NULL : tag;
}
/**
* Generate a minimalistic ISO 19770-2:2015 SWID tag for
* all deleted SW identifiers that are not registered centrally
*/
static int generate_tags(sw_collector_info_t *info, sw_collector_db_t *db)
{
sw_collector_rest_api_t *rest_api;
char *pos, *name, *package, *version, *entity, *regid, *product, *tag;
enumerator_t *enumerator;
uint32_t sw_id;
int status = EXIT_FAILURE;
entity = lib->settings->get_str(lib->settings, "%s.tag_creator.name",
"strongSwan Project", lib->ns);
regid = lib->settings->get_str(lib->settings, "%s.tag_creator.regid",
"strongswan.org", lib->ns);
info->get_os(info, &product);
rest_api = sw_collector_rest_api_create(db);
if (!rest_api)
{
goto end;
}
enumerator = rest_api->create_sw_enumerator(rest_api, SW_QUERY_DELETED);
if (!enumerator)
{
goto end;
}
while (enumerator->enumerate(enumerator, &name))
{
sw_id = db->get_sw_id(db, name, &package, &version, NULL, NULL);
if (sw_id)
{
/* Remove architecture from package name */
pos = strchr(package, ':');
if (pos)
{
*pos = '\0';
}
tag = generate_tag(name, package, version, entity, regid, product);
if (tag)
{
printf("%s\n", tag);
free(tag);
count++;
}
free(package);
free(version);
}
}
enumerator->destroy(enumerator);
status = EXIT_SUCCESS;
DBG1(DBG_IMC, "%d tags for deleted unregistered software identifiers",
count);
end:
DESTROY_IF(rest_api);
return status;
}
/**
* Append missing architecture suffix to package entries in the database
*/
static int migrate(sw_collector_info_t *info, 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_info_t *info;
collector_op_t op;
char *uri, *tag_creator;
int status = EXIT_FAILURE;
op = do_args(argc, argv);
/* 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);
}
/* Attach OS info */
tag_creator = lib->settings->get_str(lib->settings, "%s.tag_creator.regid",
"strongswan.org", lib->ns);
info = sw_collector_info_create(tag_creator);
switch (op)
{
case COLLECTOR_OP_EXTRACT:
status = extract_history(info, db);
break;
case COLLECTOR_OP_LIST:
status = list_identifiers(db);
break;
case COLLECTOR_OP_UNREGISTERED:
status = unregistered_identifiers(db);
break;
case COLLECTOR_OP_GENERATE:
status = generate_tags(info, db);
break;
case COLLECTOR_OP_MIGRATE:
status = migrate(info, db);
break;
}
db->destroy(db);
info->destroy(info);
exit(status);
}
+379
View File
@@ -0,0 +1,379 @@
/*
* 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.
*/
#include "sw_collector_db.h"
#include "swima/swima_event.h"
typedef struct private_sw_collector_db_t private_sw_collector_db_t;
/**
* Private data of an sw_collector_db_t object.
*/
struct private_sw_collector_db_t {
/**
* Public members of sw_collector_db_state_t
*/
sw_collector_db_t public;
/**
* Epoch
*/
uint32_t epoch;
/**
* Event ID of last event stored in database
*/
uint32_t last_eid;
/**
* Software collector database
*/
database_t *db;
};
METHOD(sw_collector_db_t, add_event, uint32_t,
private_sw_collector_db_t *this, char *timestamp)
{
uint32_t eid = 0;
if (this->db->execute(this->db, &eid,
"INSERT INTO events (epoch, timestamp) VALUES (?, ?)",
DB_UINT, this->epoch, DB_TEXT, timestamp) != 1)
{
DBG1(DBG_IMC, "unable to insert event into database");
return 0;
}
return eid;
}
METHOD(sw_collector_db_t, get_last_event, bool,
private_sw_collector_db_t *this, uint32_t *eid, uint32_t *epoch,
char **last_time)
{
char *timestamp;
enumerator_t *e;
e = this->db->query(this->db,
"SELECT id, epoch, timestamp FROM events ORDER BY timestamp DESC",
DB_UINT, DB_UINT, DB_TEXT);
if (!e)
{
DBG1(DBG_IMC, "database query for event failed");
return FALSE;
}
if (e->enumerate(e, eid, epoch, &timestamp))
{
if (last_time)
{
*last_time = strdup(timestamp);
}
}
else
{
*eid = 0;
}
e->destroy(e);
return TRUE;
}
METHOD(sw_collector_db_t, add_sw_event, bool,
private_sw_collector_db_t *this, uint32_t eid, uint32_t sw_id,
uint8_t action)
{
if (this->db->execute(this->db, NULL,
"INSERT INTO sw_events (eid, sw_id, action) VALUES (?, ?, ?)",
DB_UINT, eid, DB_UINT, sw_id, DB_UINT, action) != 1)
{
DBG1(DBG_IMC, "unable to insert sw_event into database");
return FALSE;
}
return TRUE;
}
METHOD(sw_collector_db_t, set_sw_id, uint32_t,
private_sw_collector_db_t *this, char *name, char *package, char *version,
uint8_t source, bool installed)
{
uint32_t sw_id;
if (this->db->execute(this->db, &sw_id,
"INSERT INTO sw_identifiers "
"(name, package, version, source, installed) VALUES (?, ?, ?, ?, ?)",
DB_TEXT, name, DB_TEXT, package, DB_TEXT, version, DB_UINT, source,
DB_UINT, installed) != 1)
{
DBG1(DBG_IMC, "unable to insert sw_id into database");
return 0;
}
return sw_id;
}
METHOD(sw_collector_db_t, get_sw_id, uint32_t,
private_sw_collector_db_t *this, char *name, char **package, char **version,
uint8_t *source, bool *installed)
{
char *sw_package, *sw_version;
uint32_t sw_id = 0, sw_source, sw_installed;
enumerator_t *e;
/* Does software identifier already exist in database? */
e = this->db->query(this->db,
"SELECT id, package, version, source, installed "
"FROM sw_identifiers WHERE name = ?",
DB_TEXT, name, DB_UINT, DB_TEXT, DB_TEXT, DB_UINT, DB_UINT);
if (!e)
{
DBG1(DBG_IMC, "database query for sw_identifier failed");
return 0;
}
if (e->enumerate(e, &sw_id, &sw_package, &sw_version, &sw_source,
&sw_installed))
{
if (package)
{
*package = strdup(sw_package);
}
if (version)
{
*version = strdup(sw_version);
}
if (source)
{
*source = sw_source;
}
if (installed)
{
*installed = sw_installed;
}
}
e->destroy(e);
return sw_id;
}
METHOD(sw_collector_db_t, get_sw_id_count, uint32_t,
private_sw_collector_db_t *this, sw_collector_db_query_t type)
{
uint32_t count, installed;
enumerator_t *e;
if (type == SW_QUERY_ALL)
{
e = this->db->query(this->db,
"SELECT COUNT(installed) FROM sw_identifiers", DB_UINT);
}
else
{
installed = (type == SW_QUERY_INSTALLED);
e = this->db->query(this->db,
"SELECT COUNT(installed) FROM sw_identifiers WHERE installed = ?",
DB_UINT, installed, DB_UINT);
}
if (!e)
{
DBG1(DBG_IMC, "database query for sw_identifier count failed");
return 0;
}
if (!e->enumerate(e, &count))
{
count = 0;
}
e->destroy(e);
return count;
}
METHOD(sw_collector_db_t, update_sw_id, bool,
private_sw_collector_db_t *this, uint32_t sw_id, char *name, char *version,
bool installed)
{
int res;
if (name && version)
{
res = this->db->execute(this->db, NULL,
"UPDATE sw_identifiers SET name = ?, version = ?, installed = ? "
"WHERE id = ?", DB_TEXT, name, DB_TEXT, version, DB_UINT, installed,
DB_UINT, sw_id);
}
else
{
res = this->db->execute(this->db, NULL,
"UPDATE sw_identifiers SET installed = ? WHERE id = ?",
DB_UINT, installed, DB_UINT, sw_id);
}
if (res != 1)
{
DBG1(DBG_IMC, "unable to update software identifier in database");
return FALSE;
}
return TRUE;
}
METHOD(sw_collector_db_t, update_package, int,
private_sw_collector_db_t *this, char *package_filter, char *package)
{
int count;
count = this->db->execute(this->db, NULL,
"UPDATE sw_identifiers SET package = ? WHERE package LIKE ?",
DB_TEXT, package, DB_TEXT, package_filter);
if (count < 0)
{
DBG1(DBG_IMC, "unable to update package name in database");
}
return count;
}
METHOD(sw_collector_db_t, create_sw_enumerator, enumerator_t*,
private_sw_collector_db_t *this, sw_collector_db_query_t type, char *package)
{
enumerator_t *e;
u_int installed;
if (type == SW_QUERY_ALL)
{
if (package)
{
e = this->db->query(this->db,
"SELECT id, name, package, version, installed "
"FROM sw_identifiers WHERE package = ? ORDER BY name ASC",
DB_TEXT, package, DB_UINT, DB_TEXT, DB_TEXT, DB_TEXT, DB_UINT);
}
else
{
e = this->db->query(this->db,
"SELECT id, name, package, version, installed "
"FROM sw_identifiers ORDER BY name ASC",
DB_UINT, DB_TEXT, DB_TEXT, DB_TEXT, DB_UINT);
}
}
else
{
installed = (type == SW_QUERY_INSTALLED);
if (package)
{
e = this->db->query(this->db,
"SELECT id, name, package, version, installed "
"FROM sw_identifiers WHERE package = ? AND installed = ? "
"ORDER BY name ASC", DB_TEXT, package, DB_UINT, installed,
DB_UINT, DB_TEXT, DB_TEXT, DB_TEXT, DB_UINT);
}
else
{
e = this->db->query(this->db,
"SELECT id, name, package, version, installed "
"FROM sw_identifiers WHERE installed = ? ORDER BY name ASC",
DB_UINT, installed, DB_UINT, DB_TEXT, DB_TEXT, DB_TEXT, DB_UINT);
}
}
if (!e)
{
DBG1(DBG_IMC, "database query for sw_identifier count failed");
return NULL;
}
return e;
}
METHOD(sw_collector_db_t, destroy, void,
private_sw_collector_db_t *this)
{
this->db->destroy(this->db);
free(this);
}
/**
* Described in header.
*/
sw_collector_db_t *sw_collector_db_create(char *uri)
{
private_sw_collector_db_t *this;
uint32_t first_eid, last_eid;
char *first_time;
INIT(this,
.public = {
.add_event = _add_event,
.get_last_event = _get_last_event,
.add_sw_event = _add_sw_event,
.set_sw_id = _set_sw_id,
.get_sw_id = _get_sw_id,
.get_sw_id_count = _get_sw_id_count,
.update_sw_id = _update_sw_id,
.update_package = _update_package,
.create_sw_enumerator = _create_sw_enumerator,
.destroy = _destroy,
},
.db = lib->db->create(lib->db, uri),
);
if (!this->db)
{
DBG1(DBG_IMC, "opening database URI '%s' failed", uri);
return NULL;
}
/* Retrieve last event in database */
if (!get_last_event(this, &last_eid, &this->epoch, NULL))
{
destroy(this);
return NULL;
}
/* Create random epoch and first event if no events exist yet */
if (!last_eid)
{
rng_t *rng;
rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG);
if (!rng ||
!rng->get_bytes(rng, sizeof(uint32_t), (uint8_t*)&this->epoch))
{
DESTROY_IF(rng);
destroy(this);
DBG1(DBG_IMC, "generating random epoch value failed");
return NULL;
}
rng->destroy(rng);
/* strongTNC workaround - limit epoch to 31 bit unsigned integer */
this->epoch &= 0x7fffffff;
/* Create first event when the OS was installed */
first_time = lib->settings->get_str(lib->settings,
"sw-collector.first_time", "0000-00-00T00:00:00Z");
first_eid = add_event(this, first_time);
if (!first_eid)
{
destroy(this);
return NULL;
}
DBG0(DBG_IMC, "First-Date: %s, eid = %u, epoch = %u",
first_time, first_eid, this->epoch);
}
return &this->public;
}
+155
View File
@@ -0,0 +1,155 @@
/*
* 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.
*/
/**
* @defgroup sw_collector_db_t sw_collector_db
* @{ @ingroup imc_swima
*/
#ifndef SW_COLLECTOR_DB_H_
#define SW_COLLECTOR_DB_H_
#include <library.h>
typedef struct sw_collector_db_t sw_collector_db_t;
typedef enum sw_collector_db_query_t sw_collector_db_query_t;
/**
* Type of software identifier queries
*/
enum sw_collector_db_query_t {
SW_QUERY_ALL,
SW_QUERY_INSTALLED,
SW_QUERY_DELETED
};
/**
* Software collector database object
*/
struct sw_collector_db_t {
/**
* bAdd event to database
*
* @param timestamp Timestamp in 20 octet RFC 3339 format
* @return Primary key pointing to event ID or 0 if failed
*/
uint32_t (*add_event)(sw_collector_db_t *this, char *timestamp);
/**
* Get last event, zero EID if none exists
*
* @param eid Primary key pointing to last event
* @param epoch Epoch
* @param last_time Timestamp in 20 octet RFC 3339 format of last event
* @return
*/
bool (*get_last_event)(sw_collector_db_t *this, uint32_t *eid,
uint32_t *epoch, char **last_time);
/**
* Add software identifier event to database
*
* @param eid Foreign key pointing to an event ID
* @param sw_id Foreign key pointing to a software identifier
* @param action 1 for CREATION, 2 for deletion
* @return TRUE if successful
*/
bool (*add_sw_event)(sw_collector_db_t *this, uint32_t eid, uint32_t sw_id,
uint8_t action);
/**
* Set software_identifier, checking if the identifier already exists
*
* @param name Software identifier
* @param package Software package
* @param version Version of software package
* @param source Source ID of the software collector
* @param installed Installation status to be set, TRUE if installed
* @return Primary key pointing to SW ID or 0 if failed
*/
uint32_t (*set_sw_id)(sw_collector_db_t *this, char *name, char *package,
char *version, uint8_t source, bool installed);
/**
* Get software_identifier record
*
* @param name Software identifier
* @param package Software package
* @param version Version of software package
* @param source Source ID of the software collector
* @param installed Installation status
* @return Primary key pointing to SW ID or 0 if failed
*/
uint32_t (*get_sw_id)(sw_collector_db_t *this, char *name, char **package,
char **version, uint8_t *source, bool *installed);
/**
* Get number of installed or deleted software identifiers
*
* @param type Query type (ALL, INSTALLED, DELETED)
* @return Count
*/
uint32_t (*get_sw_id_count)(sw_collector_db_t *this,
sw_collector_db_query_t type);
/**
* Update the software identifier version
*
* @param sw_id Primary key of software identifier
* @param name Software identifier
* @param version Package version
* @param installed Installation status
* @return TRUE if update successful
*/
bool (*update_sw_id)(sw_collector_db_t *this, uint32_t sw_id, char *name,
char *version, bool installed);
/**
* Update the package name
*
* @param package_filter Package name[s] to be changed
* @param package New package name
* @return TRUE if update successful
*/
int (*update_package)(sw_collector_db_t *this, char *package_filter,
char *package);
/**
* Enumerate over all collected [installed] software identities
*
* @param type Query type (ALL, INSTALLED, DELETED)
* @param package If not NULL enumerate over all package versions
* @return Enumerator
*/
enumerator_t* (*create_sw_enumerator)(sw_collector_db_t *this,
sw_collector_db_query_t type,
char *package);
/**
* Destroy sw_collector_db_t object
*/
void (*destroy)(sw_collector_db_t *this);
};
/**
* Create an sw_collector_db_t instance
*
* @param uri database URI
*/
sw_collector_db_t* sw_collector_db_create(char *uri);
#endif /** SW_COLLECTOR_DB_H_ @}*/
+152
View File
@@ -0,0 +1,152 @@
/*
* 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 "sw_collector_dpkg.h"
typedef struct private_sw_collector_dpkg_t private_sw_collector_dpkg_t;
/**
* Private data of an sw_collector_dpkg_t object.
*/
struct private_sw_collector_dpkg_t {
/**
* Public members of sw_collector_dpkg_state_t
*/
sw_collector_dpkg_t public;
};
typedef struct {
/** public enumerator interface */
enumerator_t public;
/** dpkg output stream */
FILE *file;
/** current dpkg output line */
char line[BUF_LEN];
} dpkg_enumerator_t;
METHOD(enumerator_t, enumerate, bool,
dpkg_enumerator_t *this, va_list args)
{
char **package, **arch, **version, *state, *pos;
VA_ARGS_VGET(args, package, arch, version);
while (TRUE)
{
if (!fgets(this->line, BUF_LEN, this->file))
{
return FALSE;
}
*package = this->line;
pos = strchr(this->line, '\t');
if (!pos)
{
return FALSE;
}
*pos = '\0';
*arch = ++pos;
pos = strchr(pos, '\t');
if (!pos)
{
return FALSE;
}
*pos = '\0';
*version = ++pos;
pos = strchr(pos, '\t');
if (!pos)
{
return FALSE;
}
*pos = '\0';
state = ++pos;
pos = strchr(pos, '\n');
if (!pos)
{
return FALSE;
}
*pos = '\0';
if (streq(state, "install ok installed"))
{
return TRUE;
}
}
}
METHOD(enumerator_t, enumerator_destroy, void,
dpkg_enumerator_t *this)
{
pclose(this->file);
free(this);
}
METHOD(sw_collector_dpkg_t, create_sw_enumerator, enumerator_t*,
private_sw_collector_dpkg_t *this)
{
dpkg_enumerator_t *enumerator;
char cmd[] = "dpkg-query -W -f="
"\'${Package}\t${Architecture}\t${Version}\t${Status}\n\'";
FILE *file;
file = popen(cmd, "r");
if (!file)
{
DBG1(DBG_IMC, "failed to run dpgk-query command");
return NULL;
}
INIT(enumerator,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate,
.destroy = _enumerator_destroy,
},
.file = file,
);
return &enumerator->public;
}
METHOD(sw_collector_dpkg_t, destroy, void,
private_sw_collector_dpkg_t *this)
{
free(this);
}
/**
* Described in header.
*/
sw_collector_dpkg_t *sw_collector_dpkg_create(void)
{
private_sw_collector_dpkg_t *this;
INIT(this,
.public = {
.create_sw_enumerator = _create_sw_enumerator,
.destroy = _destroy,
},
);
return &this->public;
}
+53
View File
@@ -0,0 +1,53 @@
/*
* 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.
*/
/**
* @defgroup sw_collector_dpkg_t sw_collector_dpkg
* @{ @ingroup sw_collector
*/
#ifndef SW_COLLECTOR_DPKG_H_
#define SW_COLLECTOR_DPKG_H_
#include <library.h>
typedef struct sw_collector_dpkg_t sw_collector_dpkg_t;
/**
* Software collector dpkg object
*/
struct sw_collector_dpkg_t {
/**
* List of installed software identifiers managed by the
* Debian "dpkg" package manager
*
* @return Enumerator
*/
enumerator_t* (*create_sw_enumerator)(sw_collector_dpkg_t *this);
/**
* Destroy sw_collector_dpkg_t object
*/
void (*destroy)(sw_collector_dpkg_t *this);
};
/**
* Create an sw_collector_dpkg_t instance
*/
sw_collector_dpkg_t* sw_collector_dpkg_create(void);
#endif /** SW_COLLECTOR_DPKG_H_ @}*/
+503
View File
@@ -0,0 +1,503 @@
/*
* 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 <time.h>
#include "sw_collector_history.h"
#include "sw_collector_dpkg.h"
#include "swima/swima_event.h"
typedef struct private_sw_collector_history_t private_sw_collector_history_t;
/**
* Private data of an sw_collector_history_t object.
*/
struct private_sw_collector_history_t {
/**
* Public members of sw_collector_history_state_t
*/
sw_collector_history_t public;
/**
* Software Event Source Number
*/
uint8_t source;
/**
* Reference to OS info object
*/
sw_collector_info_t *info;
/**
* Reference to collector database
*/
sw_collector_db_t *db;
};
/**
* Define auxiliary package_t list item object
*/
typedef struct package_t package_t;
struct package_t {
char *package;
char *version;
char *old_version;
char *sw_id;
char *old_sw_id;
};
/**
* Create package_t list item object
*/
static package_t* create_package(sw_collector_info_t *info, chunk_t package,
chunk_t version, chunk_t old_version)
{
package_t *this;
INIT(this,
.package = strndup(package.ptr, package.len),
.version = strndup(version.ptr, version.len),
.old_version = strndup(old_version.ptr, old_version.len),
)
this->sw_id = info->create_sw_id(info, this->package, this->version);
if (old_version.len)
{
this->old_sw_id = info->create_sw_id(info, this->package,
this->old_version);
}
return this;
}
/**
* Free package_t list item object
*/
static void free_package(package_t *this)
{
if (this)
{
free(this->package);
free(this->version);
free(this->old_version);
free(this->sw_id);
free(this->old_sw_id);
free(this);
}
}
/**
* Extract and parse a single package item
*/
static package_t* extract_package(chunk_t item, sw_collector_info_t *info,
sw_collector_history_op_t op)
{
chunk_t package, package_stripped, version, old_version;
package_t *p;
/* extract package name */
if (!extract_token(&package, ' ', &item))
{
fprintf(stderr, "version not found.\n");
return NULL;
}
item = chunk_skip(item, 1);
/* strip architecture suffix if present */
if (extract_token(&package_stripped, ':', &package))
{
package = package_stripped;
}
/* extract versions */
version = old_version = chunk_empty;
if (item.len > 0)
{
if (extract_token(&version, ',', &item))
{
eat_whitespace(&item);
if (!match("automatic", &item))
{
old_version = version;
version = item;
}
}
else
{
version = item;
}
}
p = create_package(info, package, version, old_version);
/* generate log entry */
if (op == SW_OP_UPGRADE)
{
DBG2(DBG_IMC, " %s (%s, %s)", p->package, p->old_version, p->version);
DBG2(DBG_IMC, " +%s", p->sw_id);
DBG2(DBG_IMC, " -%s", p->old_sw_id);
}
else
{
DBG2(DBG_IMC, " %s (%s)", p->package, p->version);
DBG2(DBG_IMC, " %s%s", (op == SW_OP_INSTALL) ? "+" : "-", p->sw_id);
}
return p;
}
METHOD(sw_collector_history_t, extract_timestamp, bool,
private_sw_collector_history_t *this, chunk_t args, char *buf)
{
struct tm loc, utc;
chunk_t t1, t2;
time_t t;
/* Break down local time with format t1 = yyyy-mm-dd and t2 = hh:mm:ss */
if (!eat_whitespace(&args) || !extract_token(&t1, ' ', &args) ||
!eat_whitespace(&args) || t1.len != 10 || args.len != 8)
{
DBG1(DBG_IMC, "unable to parse start-date");
return FALSE;
}
t2 = args;
if (sscanf(t1.ptr, "%4d-%2d-%2d",
&loc.tm_year, &loc.tm_mon, &loc.tm_mday) != 3)
{
DBG1(DBG_IMC, "unable to parse date format yyyy-mm-dd");
return FALSE;
}
loc.tm_year -= 1900;
loc.tm_mon -= 1;
loc.tm_isdst = -1;
if (sscanf(t2.ptr, "%2d:%2d:%2d",
&loc.tm_hour, &loc.tm_min, &loc.tm_sec) != 3)
{
DBG1(DBG_IMC, "unable to parse time format hh:mm:ss");
return FALSE;
}
/* Convert from local time to UTC */
t = mktime(&loc);
gmtime_r(&t, &utc);
utc.tm_year += 1900;
utc.tm_mon += 1;
/* Form timestamp according to RFC 3339 (20 characters) */
snprintf(buf, 21, "%4d-%02d-%02dT%02d:%02d:%02dZ",
utc.tm_year, utc.tm_mon, utc.tm_mday,
utc.tm_hour, utc.tm_min, utc.tm_sec);
return TRUE;
}
METHOD(sw_collector_history_t, extract_packages, bool,
private_sw_collector_history_t *this, chunk_t args, uint32_t eid,
sw_collector_history_op_t op)
{
bool success = FALSE;
package_t *p = NULL;
chunk_t item;
eat_whitespace(&args);
while (extract_token(&item, ')', &args))
{
char *del_sw_id = NULL, *del_version = NULL;
char *nx, *px, *vx, *v1;
bool installed;
u_int sw_idx, ix;
uint32_t sw_id, sw_id_epoch_less = 0;
enumerator_t *e;
p = extract_package(item, this->info, op);
if (!p)
{
goto end;
}
/* packages without version information cannot be handled */
if (strlen(p->version) == 0)
{
free_package(p);
continue;
}
switch (op)
{
case SW_OP_REMOVE:
/* prepare subsequent deletion sw event */
del_sw_id = p->sw_id;
del_version = p->version;
break;
case SW_OP_UPGRADE:
/* prepare subsequent deletion sw event */
del_sw_id = p->old_sw_id;
del_version = p->old_version;
/* fall through to next case */
case SW_OP_INSTALL:
sw_id = this->db->get_sw_id(this->db, p->sw_id, NULL, NULL,
NULL, &installed);
if (sw_id)
{
/* sw identifier exists - update state to 'installed' */
if (installed)
{
/* this case should not occur */
DBG1(DBG_IMC, " warning: sw_id %d is already "
"installed", sw_id);
}
else if (!this->db->update_sw_id(this->db, sw_id, NULL,
NULL, TRUE))
{
goto end;
}
}
else
{
/* new sw identifier - create with state 'installed' */
sw_id = this->db->set_sw_id(this->db, p->sw_id, p->package,
p->version, this->source, TRUE);
if (!sw_id)
{
goto end;
}
}
/* add creation sw event with current eid */
if (!this->db->add_sw_event(this->db, eid, sw_id,
SWIMA_EVENT_ACTION_CREATION))
{
goto end;
}
break;
}
if (op != SW_OP_INSTALL)
{
sw_id = 0;
/* look for existing installed package versions */
e = this->db->create_sw_enumerator(this->db, SW_QUERY_INSTALLED,
p->package);
if (!e)
{
goto end;
}
while (e->enumerate(e, &sw_idx, &nx, &px, &vx, &ix))
{
if (streq(vx, del_version))
{
/* full match with epoch */
sw_id = sw_idx;
break;
}
v1 = strchr(vx, ':');
if (v1 && streq(++v1, del_version))
{
/* match with stripped epoch */
sw_id_epoch_less = sw_idx;
}
}
e->destroy(e);
if (!sw_id && sw_id_epoch_less)
{
/* no full match - fall back to epoch-less match */
sw_id = sw_id_epoch_less;
}
if (sw_id)
{
/* sw identifier exists - update state to 'deleted' */
if (!this->db->update_sw_id(this->db, sw_id, NULL, NULL, FALSE))
{
goto end;
}
}
else
{
/* new sw identifier - create with state 'deleted' */
sw_id = this->db->set_sw_id(this->db, del_sw_id, p->package,
del_version, this->source, FALSE);
/* add creation sw event with eid = 1 */
if (!sw_id || !this->db->add_sw_event(this->db, 1, sw_id,
SWIMA_EVENT_ACTION_CREATION))
{
goto end;
}
}
/* add creation sw event with current eid */
if (!this->db->add_sw_event(this->db, eid, sw_id,
SWIMA_EVENT_ACTION_DELETION))
{
goto end;
}
}
free_package(p);
if (args.len < 2)
{
break;
}
args = chunk_skip(args, 2);
}
p = NULL;
success = TRUE;
end:
free_package(p);
return success;
}
METHOD(sw_collector_history_t, merge_installed_packages, bool,
private_sw_collector_history_t *this)
{
uint32_t sw_id, count = 0;
char *package, *arch, *version, *v1, *name, *n1;
bool installed, success = FALSE;
sw_collector_dpkg_t *dpkg;
enumerator_t *enumerator;
DBG1(DBG_IMC, "Merging:");
dpkg = sw_collector_dpkg_create();
if (!dpkg)
{
return FALSE;
}
enumerator = dpkg->create_sw_enumerator(dpkg);
while (enumerator->enumerate(enumerator, &package, &arch, &version))
{
name = this->info->create_sw_id(this->info, package, version);
DBG3(DBG_IMC, " %s merged", name);
sw_id = this->db->get_sw_id(this->db, name, NULL, NULL, NULL,
&installed);
if (sw_id)
{
if (!installed)
{
DBG1(DBG_IMC, " warning: existing sw_id %u"
" is not installed", sw_id);
if (!this->db->update_sw_id(this->db, sw_id, name, version,
TRUE))
{
free(name);
goto end;
}
}
}
else
{
/* check for a Debian epoch number */
v1 = strchr(version, ':');
if (v1)
{
/* check for existing and installed epoch-less version */
n1 = this->info->create_sw_id(this->info, package, ++v1);
sw_id = this->db->get_sw_id(this->db, n1, NULL, NULL, NULL,
&installed);
free(n1);
if (sw_id && installed)
{
/* add epoch to existing version */
if (!this->db->update_sw_id(this->db, sw_id, name, version,
installed))
{
free(name);
goto end;
}
}
else
{
sw_id = 0;
}
}
}
if (!sw_id)
{
/* new sw identifier - create with state 'installed' */
sw_id = this->db->set_sw_id(this->db, name, package, version,
this->source, TRUE);
/* add creation sw event with eid = 1 */
if (!sw_id || !this->db->add_sw_event(this->db, 1, sw_id,
SWIMA_EVENT_ACTION_CREATION))
{
free(name);
goto end;
}
}
free(name);
count++;
}
success = TRUE;
DBG1(DBG_IMC, " merged %u installed packages, %u registered in database",
count, this->db->get_sw_id_count(this->db, SW_QUERY_INSTALLED));
end:
enumerator->destroy(enumerator);
dpkg->destroy(dpkg);
return success;
}
METHOD(sw_collector_history_t, destroy, void,
private_sw_collector_history_t *this)
{
free(this);
}
/**
* Described in header.
*/
sw_collector_history_t *sw_collector_history_create(sw_collector_info_t *info,
sw_collector_db_t *db,
uint8_t source)
{
private_sw_collector_history_t *this;
INIT(this,
.public = {
.extract_timestamp = _extract_timestamp,
.extract_packages = _extract_packages,
.merge_installed_packages = _merge_installed_packages,
.destroy = _destroy,
},
.source = source,
.info = info,
.db = db,
);
return &this->public;
}
+95
View File
@@ -0,0 +1,95 @@
/*
* 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.
*/
/**
* @defgroup sw_collector_history_t sw_collector_history
* @{ @ingroup sw_collector
*/
#ifndef SW_COLLECTOR_HISTORY_H_
#define SW_COLLECTOR_HISTORY_H_
#include "sw_collector_history.h"
#include "sw_collector_info.h"
#include "sw_collector_db.h"
#include <library.h>
#include <utils/debug.h>
#include <utils/lexparser.h>
typedef struct sw_collector_history_t sw_collector_history_t;
typedef enum sw_collector_history_op_t sw_collector_history_op_t;
/**
* Define major history event operations
*/
enum sw_collector_history_op_t {
SW_OP_INSTALL,
SW_OP_UPGRADE,
SW_OP_REMOVE
};
/**
* Software collector history object
*/
struct sw_collector_history_t {
/**
* Extract timestamp from event in installation history
*
* @param args Arguments to be processed
* @param buf timestamp buffer for 21 byte RFC 3339 string
* @return TRUE if extraction succeeded
*/
bool (*extract_timestamp)(sw_collector_history_t *this, chunk_t args,
char *buf);
/**
* Extract packages from event in installation history
*
* @param args Arguments to be processed
* @param eid Primary key pointing to current event
* @param op Extraction operation
* @return TRUE if extraction succeeded
*/
bool (*extract_packages)(sw_collector_history_t *this, chunk_t args,
uint32_t eid, sw_collector_history_op_t op);
/**
* Merge packages from initial installation
*
* @return TRUE if merge succeeded
*/
bool (*merge_installed_packages)(sw_collector_history_t *this);
/**
* Destroy sw_collector_history_t object
*/
void (*destroy)(sw_collector_history_t *this);
};
/**
* Create an sw_collector_history_t instance
*
* @param info Internal reference to collector info
* @param db Internal reference to collector database
* @param source Software event source number
*/
sw_collector_history_t* sw_collector_history_create(sw_collector_info_t *info,
sw_collector_db_t *db,
uint8_t source);
#endif /** SW_COLLECTOR_HISTORY_H_ @}*/
+170
View File
@@ -0,0 +1,170 @@
/*
* 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 "sw_collector_info.h"
#include <library.h>
#include <utils/lexparser.h>
typedef struct private_sw_collector_info_t private_sw_collector_info_t;
/**
* Private data of an sw_collector_info_t object.
*/
struct private_sw_collector_info_t {
/**
* Public members of sw_collector_info_state_t
*/
sw_collector_info_t public;
/**
* tagCreator
*/
char *tag_creator;
/**
* OS string 'Name_Version-Arch'
*/
char *os;
/**
* Product string 'Name Version Arch'
*/
char *product;
/**
* OS info about endpoint
*/
imc_os_info_t *os_info;
};
/**
* Replaces invalid character by a valid one
*/
static void sanitize_uri(char *uri, char a, char b)
{
char *pos = uri;
while (TRUE)
{
pos = strchr(pos, a);
if (!pos)
{
break;
}
*pos = b;
pos++;
}
}
METHOD(sw_collector_info_t, get_os_type, os_type_t,
private_sw_collector_info_t *this)
{
return this->os_info->get_type(this->os_info);
}
METHOD(sw_collector_info_t, get_os, char*,
private_sw_collector_info_t *this, char **product)
{
if (product)
{
*product = this->product;
}
return this->os;
}
METHOD(sw_collector_info_t, create_sw_id, char*,
private_sw_collector_info_t *this, char *package, char *version)
{
char *sw_id;
if (asprintf(&sw_id, "%s__%s-%s%s%s", this->tag_creator, this->os,
package, strlen(version) ? "-" : "", version) == -1)
{
return NULL;
}
sanitize_uri(sw_id, ':', '~');
sanitize_uri(sw_id, '+', '~');
return sw_id;
}
METHOD(sw_collector_info_t, destroy, void,
private_sw_collector_info_t *this)
{
this->os_info->destroy(this->os_info);
free(this->os);
free(this->product);
free(this->tag_creator);
free(this);
}
/**
* Described in header.
*/
sw_collector_info_t *sw_collector_info_create(char *tag_creator)
{
private_sw_collector_info_t *this;
chunk_t os_name, os_version, os_arch;
INIT(this,
.public = {
.get_os_type = _get_os_type,
.get_os = _get_os,
.create_sw_id = _create_sw_id,
.destroy = _destroy,
},
.os_info = imc_os_info_create(),
.tag_creator = strdup(tag_creator),
);
os_name = this->os_info->get_name(this->os_info);
os_arch = this->os_info->get_version(this->os_info);
/* get_version() returns version followed by arch */
if (!extract_token(&os_version, ' ', &os_arch))
{
DBG1(DBG_IMC, "separation of OS version from arch failed");
destroy(this);
return NULL;
}
/* construct OS string */
if (asprintf(&this->os, "%.*s_%.*s-%.*s", os_name.len, os_name.ptr,
os_version.len, os_version.ptr,
os_arch.len, os_arch.ptr) == -1)
{
DBG1(DBG_IMC, "constructon of OS string failed");
destroy(this);
return NULL;
}
/* construct product string */
if (asprintf(&this->product, "%.*s %.*s %.*s", os_name.len, os_name.ptr,
os_version.len, os_version.ptr,
os_arch.len, os_arch.ptr) == -1)
{
DBG1(DBG_IMC, "constructon of product string failed");
destroy(this);
return NULL;
}
return &this->public;
}
+69
View File
@@ -0,0 +1,69 @@
/*
* 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.
*/
/**
* @defgroup sw_collector_info_t sw_collector_info
* @{ @ingroup sw_collector
*/
#ifndef SW_COLLECTOR_INFO_H_
#define SW_COLLECTOR_INFO_H_
typedef struct sw_collector_info_t sw_collector_info_t;
#include "imc/imc_os_info.h"
struct sw_collector_info_t {
/**
* Get OS type
*
* @return OS type
*/
os_type_t (*get_os_type)(sw_collector_info_t *this);
/**
* Get OS and product strings
*
* @param product Product string 'Name Version Arch'
* @return OS string 'Name_Version-Arch'
*/
char* (*get_os)(sw_collector_info_t *this, char **product);
/**
* Create software identifier including tagCreator and OS
*
* @param package Package string
* @param version Version string
* @return Software Identifier string
*/
char* (*create_sw_id)(sw_collector_info_t *this, char *package,
char *version);
/**
* Destroy sw_collector_info_t object
*/
void (*destroy)(sw_collector_info_t *this);
};
/**
* Create an sw_collector_info_t instance
*
* @param tag_creator Regid of tagCreator
*/
sw_collector_info_t* sw_collector_info_create(char *tag_creator);
#endif /** SW_COLLECTOR_INFO_H_ @}*/
+200
View File
@@ -0,0 +1,200 @@
/*
* 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.
*/
#include "sw_collector_rest_api.h"
#include <rest/rest.h>
#include <utils/debug.h>
typedef struct private_sw_collector_rest_api_t private_sw_collector_rest_api_t;
/**
* Private data of an sw_collector_rest_api_t object.
*/
struct private_sw_collector_rest_api_t {
/**
* Public members of sw_collector_rest_api_state_t
*/
sw_collector_rest_api_t public;
/**
* Software collector database
*/
sw_collector_db_t *db;
/**
* REST API of central collector database
*/
rest_t *rest_api;
};
/**
* Put all locally retrieved software identifiers into a json object
*/
static json_object* create_rest_request(private_sw_collector_rest_api_t *this,
sw_collector_db_query_t type)
{
json_object *jrequest, *jarray, *jstring;
char *name, *package, *version;
uint32_t sw_id, i;
enumerator_t *e;
jrequest = json_object_new_object();
jarray = json_object_new_array();
json_object_object_add(jrequest, "data", jarray);
e = this->db->create_sw_enumerator(this->db, type, NULL);
if (!e)
{
return NULL;
}
while (e->enumerate(e, &sw_id, &name, &package, &version, &i))
{
jstring = json_object_new_string(name);
json_object_array_add(jarray, jstring);
}
e->destroy(e);
return jrequest;
}
typedef struct {
/** public enumerator interface */
enumerator_t public;
/** enumerated json array */
json_object *jarray;
/** current index +1, initialized at 0 */
int idx;
} json_array_enumerator_t;
METHOD(enumerator_t, enumerate, bool,
json_array_enumerator_t *this, va_list args)
{
json_object *jvalue;
char **out;
VA_ARGS_VGET(args, out);
if (this->idx >= json_object_array_length(this->jarray))
{
return FALSE;
}
jvalue = json_object_array_get_idx(this->jarray, this->idx++);
if (json_object_get_type(jvalue) != json_type_string)
{
DBG1(DBG_IMC, "json_string element expected in json_array");
return FALSE;
}
*out = (char*)json_object_get_string(jvalue);
return TRUE;
}
METHOD(enumerator_t, enumerator_destroy, void,
json_array_enumerator_t *this)
{
json_object_put(this->jarray);
free(this);
}
METHOD(sw_collector_rest_api_t, create_sw_enumerator, enumerator_t*,
private_sw_collector_rest_api_t *this, sw_collector_db_query_t type)
{
json_array_enumerator_t *enumerator;
json_object *jrequest, *jresponse;
char cmd[BUF_LEN];
status_t status;
jrequest = create_rest_request(this, type);
if (!jrequest)
{
return NULL;
}
snprintf(cmd, BUF_LEN, "sessions/0/swid-measurement/");
status = this->rest_api->post(this->rest_api, cmd, jrequest, &jresponse);
json_object_put(jrequest);
switch (status)
{
case SUCCESS:
case NOT_FOUND:
jresponse = json_object_new_array();
break;
case NEED_MORE:
if (json_object_get_type(jresponse) != json_type_array)
{
DBG1(DBG_IMC, "REST response was not a json_array");
json_object_put(jresponse);
return NULL;
}
break;
case FAILED:
default:
return NULL;
}
INIT(enumerator,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate,
.destroy = _enumerator_destroy,
},
.jarray = jresponse,
);
return &enumerator->public;
}
METHOD(sw_collector_rest_api_t, destroy, void,
private_sw_collector_rest_api_t *this)
{
this->rest_api->destroy(this->rest_api);
free(this);
}
/**
* Described in header.
*/
sw_collector_rest_api_t *sw_collector_rest_api_create(sw_collector_db_t *db)
{
private_sw_collector_rest_api_t *this;
int timeout;
char *uri;
uri = lib->settings->get_str(lib->settings, "%s.rest_api.uri", NULL,
lib->ns);
timeout = lib->settings->get_int(lib->settings, "%s.rest_api.timeout", 120,
lib->ns);
if (!uri)
{
DBG1(DBG_IMC, "REST URI to central collector database not set");
return NULL;
}
INIT(this,
.public = {
.create_sw_enumerator = _create_sw_enumerator,
.destroy = _destroy,
},
.db = db,
.rest_api = rest_create(uri, timeout),
);
return &this->public;
}
+57
View File
@@ -0,0 +1,57 @@
/*
* 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.
*/
/**
* @defgroup sw_collector_rest_api_t sw_collector_rest_api
* @{ @ingroup imc_swima
*/
#ifndef SW_COLLECTOR_REST_API_H_
#define SW_COLLECTOR_REST_API_H_
#include "sw_collector_db.h"
typedef struct sw_collector_rest_api_t sw_collector_rest_api_t;
/**
* Software collector REST API object
*/
struct sw_collector_rest_api_t {
/**
* List of locally stored software identifiers that are not registered
* in a central collector database
*
* @param type Query type (ALL, INSTALLED, DELETED)
* @return Enumerator
*/
enumerator_t* (*create_sw_enumerator)(sw_collector_rest_api_t *this,
sw_collector_db_query_t type);
/**
* Destroy sw_collector_rest_api_t object
*/
void (*destroy)(sw_collector_rest_api_t *this);
};
/**
* Create an sw_collector_rest_api_t instance
*
* @param db Software collector database to be used
*/
sw_collector_rest_api_t* sw_collector_rest_api_create(sw_collector_db_t *db);
#endif /** SW_COLLECTOR_REST_API_H_ @}*/
+31
View File
@@ -0,0 +1,31 @@
/* SQLit database for an Endpoint Collector */
DROP TABLE IF EXISTS "events";
CREATE TABLE "events" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"epoch" INTEGER NOT NULL,
"timestamp" CHAR(20) NOT NULL
);
DROP TABLE IF EXISTS "sw_identifiers";
CREATE TABLE "sw_identifiers" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" VARCHAR(255) NOT NULL,
"package" VARCHAR(255) NOT NULL,
"version" VARCHAR(255) NOT NULL,
"source" INTEGER DEFAULT 0,
"installed" INTEGER DEFAULT 1,
"tag" TEXT
);
DROP INDEX IF EXISTS "sw_identifiers_name";
CREATE INDEX "sw_identifiers_name" ON "sw_identifiers" (
"name"
);
DROP TABLE IF EXISTS "sw_events";
CREATE TABLE "sw_events" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"eid" INTEGER REFERENCES "events" ("id"),
"sw_id" INTEGER NOT NULL REFERENCES "sw_identifiers" ("id"),
"action" INTEGER NOT NULL
);