summaryrefslogtreecommitdiff
path: root/src/sec-updater/sec-updater.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sec-updater/sec-updater.c')
-rw-r--r--src/sec-updater/sec-updater.c565
1 files changed, 565 insertions, 0 deletions
diff --git a/src/sec-updater/sec-updater.c b/src/sec-updater/sec-updater.c
new file mode 100644
index 000000000..e1d2baea2
--- /dev/null
+++ b/src/sec-updater/sec-updater.c
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2012-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 <getopt.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <syslog.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+#include <library.h>
+#include <utils/debug.h>
+
+#define EXIT_NO_UPDATES 80
+#define TMP_DEB_FILE "/tmp/sec-updater.deb"
+#define TMP_TAG_FILE "/tmp/sec-updater.tag"
+#define SWID_GEN_CMD "/usr/local/bin/swid_generator"
+#define TNC_MANAGE_CMD "/var/www/tnc/manage.py"
+
+typedef enum sec_update_state_t sec_update_state_t;
+
+enum sec_update_state_t {
+ SEC_UPDATE_STATE_BEGIN_PACKAGE,
+ SEC_UPDATE_STATE_VERSION,
+ SEC_UPDATE_STATE_FILENAME,
+ SEC_UPDATE_STATE_END_PACKAGE
+};
+
+typedef struct stats_t stats_t;
+
+struct stats_t {
+ time_t release;
+ int product;
+ int packages;
+ int new_versions;
+ int updated_versions;
+};
+
+/**
+ * global debug output variables
+ */
+static int debug_level = 1;
+static bool stderr_quiet = FALSE;
+
+/**
+ * sec_updater dbg function
+ */
+static void sec_updater_dbg(debug_t group, level_t level, char *fmt, ...)
+{
+ int priority = LOG_INFO;
+ char buffer[8192];
+ char *current = buffer, *next;
+ va_list args;
+
+ if (level <= debug_level)
+ {
+ if (!stderr_quiet)
+ {
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ }
+
+ /* 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;
+ }
+ }
+}
+
+/**
+ * atexit handler to close everything on shutdown
+ */
+static void cleanup(void)
+{
+ closelog();
+ library_deinit();
+}
+
+static void usage(void)
+{
+ printf("\
+Usage:\n\
+ sec-updater --help\n\
+ sec-updater [--debug <level>] [--quiet] [--security] --os <string>\n\
+ --arch <string> --uri <uri> --file <filename>\n\n\
+ Options:\n\
+ --help print usage information\n\
+ --debug <level> set debug level\n\
+ --quiet suppress debug output to stderr\n\
+ --os <string> operating system\n\
+ --arch <string> hw architecture\n\
+ --security set when parsing a file with security updates\n\
+ --file <filename> package information file to parse\n\
+ --uri <uri> uri where to download deb package from\n");
+ }
+
+/**
+ * Update the package database
+ */
+static bool update_database(database_t *db, char *package, char *version,
+ bool security, stats_t *stats, bool *new)
+{
+ int pid = 0, vid = 0, sec_flag;
+ bool first = TRUE, found = FALSE;
+ char *release;
+ enumerator_t *e;
+
+ /* increment package count */
+ stats->packages++;
+
+ /* set new output variable */
+ *new = FALSE;
+
+ /* check if package is already in database */
+ e = db->query(db, "SELECT id FROM packages WHERE name = ?",
+ DB_TEXT, package, DB_INT);
+ if (!e)
+ {
+ return FALSE;
+ }
+ if (!e->enumerate(e, &pid))
+ {
+ pid = 0;
+ }
+ e->destroy(e);
+
+ if (!pid)
+ {
+ return TRUE;
+ }
+
+ /* retrieve all package versions stored in database */
+ e = db->query(db,
+ "SELECT id, release, security FROM versions "
+ "WHERE product = ? AND package = ?",
+ DB_INT, stats->product, DB_INT, pid, DB_INT, DB_TEXT, DB_INT);
+ if (!e)
+ {
+ return FALSE;
+ }
+
+ while (e->enumerate(e, &vid, &release, &sec_flag))
+ {
+ char command[BUF_LEN];
+ char found_char = ' ';
+ bool update_version = FALSE;
+
+ if (streq(version, release))
+ {
+ found = TRUE;
+ found_char = '*';
+ }
+ else if (security)
+ {
+ snprintf(command, BUF_LEN, "dpkg --compare-versions %s lt %s",
+ release, version);
+ if (system(command) == 0)
+ {
+ found_char = '!';
+ if (!sec_flag)
+ {
+ if (db->execute(db, NULL, "UPDATE versions "
+ "SET security = 1 WHERE id = ?", DB_INT, vid) != 1)
+ {
+ DBG1(DBG_IMV, " could not update version");
+ e->destroy(e);
+ return FALSE;
+ }
+ update_version = TRUE;
+ stats->updated_versions++;
+ }
+ }
+ }
+ if (debug_level < 2 && !update_version)
+ {
+ continue;
+ }
+ if (first)
+ {
+ DBG1(DBG_IMV, "%s", package);
+ first = FALSE;
+ }
+ DBG1(DBG_IMV, " %c%s %s", found_char , sec_flag ? "s" : " ", release);
+ }
+ e->destroy(e);
+
+ if (!found)
+ {
+ if (first)
+ {
+ DBG1(DBG_IMV, "%s", package);
+ }
+ DBG1(DBG_IMV, " + %s", version);
+
+ if (db->execute(db, &vid,
+ "INSERT INTO versions "
+ "(package, product, release, security, time) "
+ "VALUES (?, ?, ?, 0, ?)", DB_INT, pid, DB_INT, stats->product,
+ DB_TEXT, version, DB_INT, stats->release) != 1)
+ {
+ DBG1(DBG_IMV, " could not store version to database");
+ return FALSE;
+ }
+ stats->new_versions++;
+ *new = TRUE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Process a package file and store updates in the database
+ */
+static int process_packages(char *path, char *os, char *arch, char *uri,
+ bool security)
+{
+ char line[BUF_LEN], product[BUF_LEN], command[BUF_LEN];
+ char *db_uri, *download_uri = NULL, *swid_regid, *swid_entity;
+ char *pos, *package = NULL, *version = NULL, *filename = NULL;
+ char *swid_gen_cmd, *tnc_manage_cmd, *tmp_deb_file, *tmp_tag_file;
+ sec_update_state_t state;
+ enumerator_t *e;
+ database_t *db;
+ int len, pid;
+ chunk_t deb = chunk_empty;
+ FILE *file;
+ stats_t stats;
+ bool success = TRUE, new;
+
+ /* initialize statistics */
+ memset(&stats, 0x00, sizeof(stats_t));
+
+ /* Set release date to current time */
+ stats.release = time(NULL);
+
+ /* opening package file */
+ file = fopen(path, "r");
+ if (!file)
+ {
+ DBG1(DBG_IMV, " could not open \"%s\"", path);
+ exit(EXIT_FAILURE);
+ }
+
+ /* connect package database */
+ db_uri = lib->settings->get_str(lib->settings, "sec-updater.database", NULL);
+ if (!db_uri)
+ {
+ DBG1(DBG_IMV, "database URI sec-updater.database not set");
+ fclose(file);
+ exit(EXIT_FAILURE);
+ }
+ db = lib->db->create(lib->db, db_uri);
+ if (!db)
+ {
+ DBG1(DBG_IMV, "could not connect to database '%s'", db_uri);
+ fclose(file);
+ exit(EXIT_FAILURE);
+ }
+
+ /* form product name by concatenating os and arch strings */
+ snprintf(product, BUF_LEN, "%s %s", os, arch);
+
+ /* check if product is already in database */
+ e = db->query(db, "SELECT id FROM products WHERE name = ?",
+ DB_TEXT, product, DB_INT);
+ if (e)
+ {
+ if (e->enumerate(e, &pid))
+ {
+ stats.product = pid;
+ }
+ e->destroy(e);
+ }
+ if (!stats.product)
+ {
+ if (db->execute(db, &pid, "INSERT INTO products (name) VALUES (?)",
+ DB_TEXT, product) != 1)
+ {
+ DBG1(DBG_IMV, "could not store product '%s' to database",
+ product);
+ fclose(file);
+ db->destroy(db);
+ exit(EXIT_FAILURE);
+ }
+ stats.product = pid;
+ }
+
+ /* get settings for the loop */
+ swid_regid = lib->settings->get_str(lib->settings,
+ "sec-updater.swid_gen.tag_creator.regid",
+ "strongswan.org");
+ swid_entity = lib->settings->get_str(lib->settings,
+ "sec-updater.swid_gen.tag_creator.name",
+ "strongSwan Project");
+ swid_gen_cmd = lib->settings->get_str(lib->settings,
+ "sec-updater.swid_gen.command", SWID_GEN_CMD);
+ tnc_manage_cmd = lib->settings->get_str(lib->settings,
+ "sec-updater.tnc_manage_command", TNC_MANAGE_CMD);
+ tmp_deb_file = lib->settings->get_str(lib->settings,
+ "sec-updater.tmp.deb_file", TMP_DEB_FILE);
+ tmp_tag_file = lib->settings->get_str(lib->settings,
+ "sec-updater.tmp.tag_file", TMP_TAG_FILE);
+
+ state = SEC_UPDATE_STATE_BEGIN_PACKAGE;
+
+ while (fgets(line, sizeof(line), file))
+ {
+ /* set read pointer to beginning of line */
+ pos = line;
+
+ switch (state)
+ {
+ case SEC_UPDATE_STATE_BEGIN_PACKAGE:
+ pos = strstr(pos, "Package: ");
+ if (!pos)
+ {
+ continue;
+ }
+ pos += 9;
+ package = pos;
+ pos = strchr(pos, '\n');
+ if (pos)
+ {
+ package = strndup(package, pos - package);
+ state = SEC_UPDATE_STATE_VERSION;
+ }
+ break;
+ case SEC_UPDATE_STATE_VERSION:
+ pos = strstr(pos, "Version: ");
+ if (!pos)
+ {
+ continue;
+ }
+ pos += 9;
+ version = pos;
+ pos = strchr(pos, '\n');
+ if (pos)
+ {
+ version = strndup(version, pos - version);
+ success = update_database(db, package, version, security,
+ &stats, &new);
+ state = (success && new) ? SEC_UPDATE_STATE_FILENAME :
+ SEC_UPDATE_STATE_END_PACKAGE;
+ }
+ break;
+ case SEC_UPDATE_STATE_FILENAME:
+ pos = strstr(pos, "Filename: ");
+ if (!pos)
+ {
+ continue;
+ }
+ state = SEC_UPDATE_STATE_END_PACKAGE;
+
+ pos += 10;
+ filename = pos;
+ pos = strchr(pos, '\n');
+ if (!pos)
+ {
+ break;
+ }
+ len = pos - filename;
+ if (asprintf(&download_uri, "%s/%.*s", uri, len, filename) == -1)
+ {
+ break;
+ }
+
+ /* retrieve deb package file from linux repository */
+ if (lib->fetcher->fetch(lib->fetcher, download_uri,
+ &deb, FETCH_END) != SUCCESS)
+ {
+ DBG1(DBG_IMV, " %s failed", download_uri);
+ break;
+ }
+ DBG1(DBG_IMV, " %s (%u bytes)", download_uri, deb.len);
+
+ /* store deb package file to temporary location */
+ if (!chunk_write(deb, tmp_deb_file, 0022, TRUE))
+ {
+ DBG1(DBG_IMV, " save to '%s' failed", tmp_deb_file);
+ break;
+ }
+
+ /* generate SWID tag for downloaded deb package */
+ snprintf(command, BUF_LEN, "%s swid --full --package-file %s "
+ "--regid %s --entity-name '%s' --os '%s' --arch '%s' "
+ ">> %s", swid_gen_cmd, tmp_deb_file, swid_regid,
+ swid_entity, os, arch, tmp_tag_file);
+ if (system(command) != 0)
+ {
+ DBG1(DBG_IMV, " tag generation failed");
+ break;
+ }
+ break;
+ case SEC_UPDATE_STATE_END_PACKAGE:
+ if (*pos != '\n')
+ {
+ continue;
+ }
+ free(package);
+ free(version);
+ free(download_uri);
+ chunk_free(&deb);
+ package = version = download_uri = NULL;
+
+ if (!success)
+ {
+ fclose(file);
+ db->destroy(db);
+ exit(EXIT_FAILURE);
+ }
+ state = SEC_UPDATE_STATE_BEGIN_PACKAGE;
+ }
+ }
+
+ free(package);
+ free(version);
+ free(download_uri);
+ fclose(file);
+ db->destroy(db);
+
+ /* import swid tags into strongTNC */
+ if (stats.new_versions > 0)
+ {
+ snprintf(command, BUF_LEN, "%s importswid %s",
+ tnc_manage_cmd, tmp_tag_file);
+ if (system(command) != 0)
+ {
+ DBG1(DBG_IMV, "tag import failed");
+ }
+ snprintf(command, BUF_LEN, "rm %s %s",
+ tmp_deb_file, tmp_tag_file);
+ if (system(command) != 0)
+ {
+ DBG1(DBG_IMV, "removing temporary files failed");
+ }
+ }
+
+ DBG1(DBG_IMV, "processed \"%s\": %d packages, %d new versions, "
+ "%d updated versions", path, stats.packages,
+ stats.new_versions, stats.updated_versions);
+
+ return (stats.new_versions + stats.updated_versions) ?
+ EXIT_SUCCESS : EXIT_NO_UPDATES;
+}
+
+static int do_args(int argc, char *argv[])
+{
+ char *filename = NULL, *arch = NULL, *os = NULL, *uri = NULL;
+ bool security = FALSE;
+
+ /* reinit getopt state */
+ optind = 0;
+
+ while (TRUE)
+ {
+ int c;
+
+ struct option long_opts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "arch", required_argument, NULL, 'a' },
+ { "debug", required_argument, NULL, 'd' },
+ { "file", required_argument, NULL, 'f' },
+ { "os", required_argument, NULL, 'o' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "security", no_argument, NULL, 's' },
+ { "uri", required_argument, NULL, 'u' },
+ { 0,0,0,0 }
+ };
+
+ c = getopt_long(argc, argv, "ha:d:f:o:qsu:", long_opts, NULL);
+ switch (c)
+ {
+ case EOF:
+ break;
+ case 'h':
+ usage();
+ exit(EXIT_SUCCESS);
+ case 'a':
+ arch = optarg;
+ continue;
+ case 'd':
+ debug_level = atoi(optarg);
+ continue;
+ case 'f':
+ filename = optarg;
+ continue;
+ case 'o':
+ os = optarg;
+ continue;
+ case 'q':
+ stderr_quiet = TRUE;
+ continue;
+ case 's':
+ security = TRUE;
+ continue;
+ case 'u':
+ uri = optarg;
+ continue;
+ }
+ break;
+ }
+
+ if (filename && os && arch && uri)
+ {
+ return process_packages(filename, os, arch, uri, security);
+ }
+ else
+ {
+ usage();
+ exit(EXIT_FAILURE);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ /* enable attest debugging hook */
+ dbg = sec_updater_dbg;
+ openlog("sec-updater", 0, LOG_DEBUG);
+
+ atexit(cleanup);
+
+ /* initialize library */
+ if (!library_init(NULL, "sec-updater"))
+ {
+ exit(SS_RC_LIBSTRONGSWAN_INTEGRITY);
+ }
+ if (!lib->plugins->load(lib->plugins,
+ lib->settings->get_str(lib->settings, "sec-updater.load",
+ "sqlite curl")))
+ {
+ exit(SS_RC_INITIALIZATION_FAILED);
+ }
+ exit(do_args(argc, argv));
+}
+