/*
 * Copyright (C) 2011-2017 Andreas Steffen
 *
 * Copyright (C) secunet Security Networks AG
 *
 * 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 <libgen.h>
#include <time.h>

#include <tncif_names.h>

#include "attest_db.h"

#include "imcv.h"
#include "pts/pts_meas_algo.h"
#include "pts/pts_file_meas.h"
#include "pts/components/pts_comp_func_name.h"

#define IMA_MAX_NAME_LEN	255
#define DEVICE_MAX_LEN		 20

typedef struct private_attest_db_t private_attest_db_t;

/**
 * Private data of an attest_db_t object.
 */
struct private_attest_db_t {

	/**
	 * Public members of attest_db_state_t
	 */
	attest_db_t public;

	/**
	 * Component Functional Name to be queried
	 */
	pts_comp_func_name_t *cfn;

	/**
	 * Primary key of the Component Functional Name to be queried
	 */
	int cid;

	/**
	 * TRUE if Component Functional Name has been set
	 */
	bool comp_set;

	/**
	 * Directory containing the Measurement file to be queried
	 */
	char *dir;

	/**
	 * Primary key of the directory to be queried
	 */
	int did;

	/**
	 * Measurement file to be queried
	 */
	char *file;

	/**
	 * Primary key of measurement file to be queried
	 */
	int fid;

	/**
	 * Directory where file measurement are to be taken
	 */
	char *meas_dir;

	/**
	 *  AIK to be queried
	 */
	chunk_t key;

	/**
	 * Primary key of the AIK to be queried
	 */
	int kid;

	/**
	 * TRUE if AIK has been set
	 */
	bool key_set;

	/**
	 * Software package to be queried
	 */
	char *package;

	/**
	 * Primary key of software package to be queried
	 */
	int gid;

	/**
	 * TRUE if package has been set
	 */
	bool package_set;

	/**
	 * Software product to be queried
	 */
	char *product;

	/**
	 * Primary key of software product to be queried
	 */
	int pid;

	/**
	 * TRUE if product has been set
	 */
	bool product_set;

	/**
	 * Software package version to be queried
	 */
	char *version;

	/**
	 * Primary key of software package version to be queried
	 */
	int vid;

	/**
	 * TRUE if version has been set
	 */
	bool version_set;

	/**
	 * TRUE if relative filenames are to be used
	 */
	bool relative;

	/**
	 * TRUE if dates are to be displayed in UTC
	 */
	bool utc;

	/**
	 * Package security or blacklist state
	 */
	os_package_state_t package_state;

	/**
	 * Sequence number for ordering entries
	 */
	int seq_no;

	/**
	 * File measurement hash algorithm
	 */
	pts_meas_algorithms_t algo;

	/**
	 * Optional owner (user/host name)
	 */
	char *owner;

	/**
	 * Attestation database
	 */
	database_t *db;

};

static char* print_cfn(pts_comp_func_name_t *cfn)
{
	static char buf[BUF_LEN];
	char flags[8];
	int type, vid, name, qualifier, n;
	enum_name_t *names, *types;

	vid = cfn->get_vendor_id(cfn);
	name = cfn->get_name(cfn);
	qualifier = cfn->get_qualifier(cfn);
	n = snprintf(buf, BUF_LEN, "0x%06x/0x%08x-0x%02x", vid, name, qualifier);

	names = imcv_pts_components->get_comp_func_names(imcv_pts_components, vid);
	types = imcv_pts_components->get_qualifier_type_names(imcv_pts_components,
														  vid);
	type =  imcv_pts_components->get_qualifier(imcv_pts_components, cfn, flags);
	if (names && types)
	{
		n = snprintf(buf + n, BUF_LEN - n, " %N/%N [%s] %N",
					 pen_names, vid, names, name, flags, types, type);
	}
	return buf;
}

/**
 * Get the directory separator to append to a path
 */
static const char* get_separator(const char *path)
{
	if (streq(path, DIRECTORY_SEPARATOR))
	{	/* root directory on Unix file system, no separator */
		return "";
	}
	else
	{	/* non-root or Windows path, use system specific separator */
		return DIRECTORY_SEPARATOR;
	}
}

METHOD(attest_db_t, set_component, bool,
	private_attest_db_t *this, char *comp, bool create)
{
	enumerator_t *e;
	char *pos1, *pos2;
	int vid, name, qualifier;
	pts_comp_func_name_t *cfn;

	if (this->comp_set)
	{
		printf("component has already been set\n");
		return FALSE;
	}

	/* parse component string */
	pos1 = strchr(comp, '/');
	pos2 = strchr(comp, '-');
	if (!pos1 || !pos2)
	{
		printf("component string must have the form \"vendor_id/name-qualifier\"\n");
		return FALSE;
	}
	vid       = atoi(comp);
	name      = atoi(pos1 + 1);
	qualifier = atoi(pos2 + 1);
	cfn = pts_comp_func_name_create(vid, name, qualifier);

	e = this->db->query(this->db,
					   "SELECT id FROM components "
					   "WHERE vendor_id = ? AND name = ? AND qualifier = ?",
						DB_UINT, vid, DB_INT, name, DB_INT, qualifier, DB_INT);
	if (e)
	{
		if (e->enumerate(e, &this->cid))
		{
			this->comp_set = TRUE;
			this->cfn = cfn;
		}
		e->destroy(e);
	}
	if (this->comp_set)
	{
		return TRUE;
	}

	if (!create)
	{
		printf("component '%s' not found in database\n", print_cfn(cfn));
		cfn->destroy(cfn);
		return FALSE;
	}

	/* Add a new database entry */
	this->comp_set = this->db->execute(this->db, &this->cid,
						"INSERT INTO components (vendor_id, name, qualifier) "
						"VALUES (?, ?, ?)",
						DB_INT, vid, DB_INT, name, DB_INT, qualifier) == 1;

	printf("component '%s' %sinserted into database\n", print_cfn(cfn),
		   this->comp_set ? "" : "could not be ");
	if (this->comp_set)
	{
		this->cfn = cfn;
	}
	else
	{
		cfn->destroy(cfn);
	}
	return this->comp_set;
}

METHOD(attest_db_t, set_cid, bool,
	private_attest_db_t *this, int cid)
{
	enumerator_t *e;
	int vid, name, qualifier;

	if (this->comp_set)
	{
		printf("component has already been set\n");
		return FALSE;
	}
	this->cid = cid;

	e = this->db->query(this->db, "SELECT vendor_id, name, qualifier "
								  "FROM components WHERE id = ?",
						DB_UINT, cid, DB_INT, DB_INT, DB_INT);
	if (e)
	{
		if (e->enumerate(e, &vid, &name, &qualifier))
		{
			this->cfn = pts_comp_func_name_create(vid, name, qualifier);
			this->comp_set = TRUE;
		}
		else
		{
			printf("no component found with cid %d\n", cid);
		}
		e->destroy(e);
	}
	return this->comp_set;
}

METHOD(attest_db_t, set_directory, bool,
	private_attest_db_t *this, char *dir, bool create)
{
	enumerator_t *e;
	int did;
	size_t len;

	if (this->did)
	{
		printf("directory has already been set\n");
		return FALSE;
	}

	/* remove trailing '/' or '\' character if not root directory */
	len = strlen(dir);
	if (len > 1 && path_is_separator(dir[len-1]))
	{
		dir[len-1] = '\0';
	}
	this->dir = strdup(dir);

	e = this->db->query(this->db,
						"SELECT id FROM directories WHERE path = ?",
						DB_TEXT, dir, DB_INT);
	if (e)
	{
		if (e->enumerate(e, &did))
		{
			this->did = did;
		}
		e->destroy(e);
	}
	if (this->did)
	{
		return TRUE;
	}

	if (!create)
	{
		printf("directory '%s' not found in database\n", dir);
		return FALSE;
	}

	/* Add a new database entry */
	if (1 == this->db->execute(this->db, &did,
				"INSERT INTO directories (path) VALUES (?)", DB_TEXT, dir))
	{
		this->did = did;
	}
	printf("directory '%s' %sinserted into database\n", dir,
			this->did ? "" : "could not be ");

	return this->did > 0;
}

METHOD(attest_db_t, set_did, bool,
	private_attest_db_t *this, int did)
{
	enumerator_t *e;
	char *dir;

	if (this->did)
	{
		printf("directory has already been set\n");
		return FALSE;
	}

	e = this->db->query(this->db, "SELECT path FROM directories WHERE id = ?",
						DB_UINT, did, DB_TEXT);
	if (e)
	{
		if (e->enumerate(e, &dir))
		{
			this->dir = strdup(dir);
			this->did = did;
		}
		else
		{
			printf("no directory found with did %d\n", did);
		}
		e->destroy(e);
	}
	return this->did > 0;
}

METHOD(attest_db_t, set_file, bool,
	private_attest_db_t *this, char *file, bool create)
{
	int fid;
	enumerator_t *e;

	if (this->file)
	{
		printf("file has already been set\n");
		return FALSE;
	}
	this->file = strdup(file);

	if (!this->did)
	{
		return TRUE;
	}
	e = this->db->query(this->db, "SELECT id FROM files "
						"WHERE dir = ? AND name = ?",
						DB_INT, this->did, DB_TEXT, file, DB_INT);
	if (e)
	{
		if (e->enumerate(e, &fid))
		{
			this->fid = fid;
		}
		e->destroy(e);
	}
	if (this->fid)
	{
		return TRUE;
	}

	if (!create)
	{
		printf("file '%s%s%s' not found in database\n",
			   this->dir, get_separator(this->dir), file);
		return FALSE;
	}

	/* Add a new database entry */
	if (1 == this->db->execute(this->db, &fid,
							   "INSERT INTO files (dir, name) VALUES (?, ?)",
							   DB_INT, this->did, DB_TEXT, file))
	{
		this->fid = fid;
	}
	printf("file '%s%s%s' %sinserted into database\n", this->dir,
		   get_separator(this->dir), file, this->fid ? "" : "could not be ");

	return this->fid > 0;
}

METHOD(attest_db_t, set_fid, bool,
	private_attest_db_t *this, int fid)
{
	enumerator_t *e;
	int did;
	char *file;

	if (this->fid)
	{
		printf("file has already been set\n");
		return FALSE;
	}

	e = this->db->query(this->db, "SELECT dir, name FROM files WHERE id = ?",
						DB_UINT, fid, DB_INT, DB_TEXT);
	if (e)
	{
		if (e->enumerate(e, &did, &file))
		{
			if (did)
			{
				set_did(this, did);
			}
			this->file = strdup(file);
			this->fid = fid;
		}
		else
		{
			printf("no file found with fid %d\n", fid);
		}
		e->destroy(e);
	}
	return this->fid > 0;
}

METHOD(attest_db_t, set_meas_directory, bool,
	private_attest_db_t *this, char *dir)
{
	size_t len;

	/* remove trailing '/' character if not root directory */
	len = strlen(dir);
	if (len > 1 && dir[len-1] == '/')
	{
		dir[len-1] = '\0';
	}
	this->meas_dir = strdup(dir);

	return TRUE;
}

METHOD(attest_db_t, set_key, bool,
	private_attest_db_t *this, chunk_t key, bool create)
{
	enumerator_t *e;
	char *owner;

	if (this->key_set)
	{
		printf("key has already been set\n");
		return FALSE;
	}
	this->key = key;

	e = this->db->query(this->db, "SELECT id, owner FROM keys WHERE keyid= ?",
						DB_BLOB, this->key, DB_INT, DB_TEXT);
	if (e)
	{
		if (e->enumerate(e, &this->kid, &owner))
		{
			free(this->owner);
			this->owner = strdup(owner);
			this->key_set = TRUE;
		}
		e->destroy(e);
	}
	if (this->key_set)
	{
		return TRUE;
	}

	if (!create)
	{
		printf("key '%#B' not found in database\n", &this->key);
		return FALSE;
	}

	/* Add a new database entry */
	if (!this->owner)
	{
		this->owner = strdup("");
	}
	this->key_set = this->db->execute(this->db, &this->kid,
								"INSERT INTO keys (keyid, owner) VALUES (?, ?)",
								DB_BLOB, this->key, DB_TEXT, this->owner) == 1;

	printf("key '%#B' %sinserted into database\n", &this->key,
		   this->key_set ? "" : "could not be ");

	return this->key_set;

};

METHOD(attest_db_t, set_kid, bool,
	private_attest_db_t *this, int kid)
{
	enumerator_t *e;
	chunk_t key;
	char *owner;

	if (this->key_set)
	{
		printf("key has already been set\n");
		return FALSE;
	}
	this->kid = kid;

	e = this->db->query(this->db, "SELECT keyid, owner FROM keys WHERE id = ?",
						DB_UINT, kid, DB_BLOB, DB_TEXT);
	if (e)
	{
		if (e->enumerate(e, &key, &owner))
		{
			this->owner = strdup(owner);
			this->key = chunk_clone(key);
			this->key_set = TRUE;
		}
		else
		{
			printf("no key found with kid %d\n", kid);
		}
		e->destroy(e);
	}
	return this->key_set;

};

METHOD(attest_db_t, set_product, bool,
	private_attest_db_t *this, char *product, bool create)
{
	enumerator_t *e;

	if (this->product_set)
	{
		printf("product has already been set\n");
		return FALSE;
	}
	this->product = strdup(product);

	e = this->db->query(this->db, "SELECT id FROM products WHERE name = ?",
						DB_TEXT, product, DB_INT);
	if (e)
	{
		if (e->enumerate(e, &this->pid))
		{
			this->product_set = TRUE;
		}
		e->destroy(e);
	}
	if (this->product_set)
	{
		return TRUE;
	}

	if (!create)
	{
		printf("product '%s' not found in database\n", product);
		return FALSE;
	}

	/* Add a new database entry */
	this->product_set = this->db->execute(this->db, &this->pid,
									"INSERT INTO products (name) VALUES (?)",
									DB_TEXT, product) == 1;

	printf("product '%s' %sinserted into database\n", product,
		   this->product_set ? "" : "could not be ");

	return this->product_set;
}

METHOD(attest_db_t, set_pid, bool,
	private_attest_db_t *this, int pid)
{
	enumerator_t *e;
	char *product;

	if (this->product_set)
	{
		printf("product has already been set\n");
		return FALSE;
	}
	this->pid = pid;

	e = this->db->query(this->db, "SELECT name FROM products WHERE id = ?",
						DB_UINT, pid, DB_TEXT);
	if (e)
	{
		if (e->enumerate(e, &product))
		{
			this->product = strdup(product);
			this->product_set = TRUE;
		}
		else
		{
			printf("no product found with pid %d in database\n", pid);
		}
		e->destroy(e);
	}
	return this->product_set;
}

METHOD(attest_db_t, set_package, bool,
	private_attest_db_t *this, char *package, bool create)
{
	enumerator_t *e;

	if (this->package_set)
	{
		printf("package has already been set\n");
		return FALSE;
	}
	this->package = strdup(package);

	e = this->db->query(this->db, "SELECT id FROM packages WHERE name = ?",
						DB_TEXT, package, DB_INT);
	if (e)
	{
		if (e->enumerate(e, &this->gid))
		{
			this->package_set = TRUE;
		}
		e->destroy(e);
	}
	if (this->package_set)
	{
		return TRUE;
	}

	if (!create)
	{
		printf("package '%s' not found in database\n", package);
		return FALSE;
	}

	/* Add a new database entry */
	this->package_set = this->db->execute(this->db, &this->gid,
									"INSERT INTO packages (name) VALUES (?)",
									DB_TEXT, package) == 1;

	printf("package '%s' %sinserted into database\n", package,
		   this->package_set ? "" : "could not be ");

	return this->package_set;
}

METHOD(attest_db_t, set_gid, bool,
	private_attest_db_t *this, int gid)
{
	enumerator_t *e;
	char *package;

	if (this->package_set)
	{
		printf("package has already been set\n");
		return FALSE;
	}
	this->gid = gid;

	e = this->db->query(this->db, "SELECT name FROM packages WHERE id = ?",
						DB_UINT, gid, DB_TEXT);
	if (e)
	{
		if (e->enumerate(e, &package))
		{
			this->package = strdup(package);
			this->package_set = TRUE;
		}
		else
		{
			printf("no package found with gid %d in database\n", gid);
		}
		e->destroy(e);
	}
	return this->package_set;
}

METHOD(attest_db_t, set_version, bool,
	private_attest_db_t *this, char *version)
{
	if (this->version_set)
	{
		printf("version has already been set\n");
		return FALSE;
	}
	this->version = strdup(version);
	this->version_set = TRUE;

	return TRUE;
}


METHOD(attest_db_t, set_algo, void,
	private_attest_db_t *this, pts_meas_algorithms_t algo)
{
	this->algo = algo;
}

METHOD(attest_db_t, set_relative, void,
	private_attest_db_t *this)
{
	this->relative = TRUE;
}

METHOD(attest_db_t, set_package_state, void,
	private_attest_db_t *this, os_package_state_t package_state)
{
	this->package_state = package_state;
}

METHOD(attest_db_t, set_sequence, void,
	private_attest_db_t *this, int seq_no)
{
	this->seq_no = seq_no;
}

METHOD(attest_db_t, set_owner, void,
	private_attest_db_t *this, char *owner)
{
	free(this->owner);
	this->owner = strdup(owner);
}

METHOD(attest_db_t, set_utc, void,
	private_attest_db_t *this)
{
	this->utc = TRUE;
}

METHOD(attest_db_t, list_components, void,
	private_attest_db_t *this)
{
	enumerator_t *e;
	pts_comp_func_name_t *cfn;
	int seq_no, cid, vid, name, qualifier, count = 0;

	if (this->kid)
	{
		e = this->db->query(this->db,
				"SELECT kc.seq_no, c.id, c.vendor_id, c.name, c.qualifier "
				"FROM components AS c "
				"JOIN key_component AS kc ON c.id = kc.component "
				"WHERE kc.key = ? ORDER BY kc.seq_no",
				DB_UINT, this->kid, DB_INT, DB_INT, DB_INT, DB_INT, DB_INT);
		if (e)
		{
			while (e->enumerate(e,  &cid, &seq_no, &vid, &name, &qualifier))
			{
				cfn   = pts_comp_func_name_create(vid, name, qualifier);
				printf("%4d: #%-2d %s\n", seq_no, cid, print_cfn(cfn));
				cfn->destroy(cfn);
				count++;
			}
			e->destroy(e);
			printf("%d component%s found for key %#B\n", count,
				  (count == 1) ? "" : "s", &this->key);
		}
	}
	else
	{
		e = this->db->query(this->db,
				"SELECT id, vendor_id, name, qualifier FROM components "
				"ORDER BY vendor_id, name, qualifier",
				DB_INT, DB_INT, DB_INT, DB_INT);
		if (e)
		{
			while (e->enumerate(e,  &cid, &vid, &name, &qualifier))
			{
				cfn   = pts_comp_func_name_create(vid, name, qualifier);
				printf("%4d: %s\n", cid, print_cfn(cfn));
				cfn->destroy(cfn);
				count++;
			}
			e->destroy(e);
			printf("%d component%s found\n", count, (count == 1) ? "" : "s");
		}
	}
}

METHOD(attest_db_t, list_devices, void,
	private_attest_db_t *this)
{
	enumerator_t *e, *e_ar;
	chunk_t ar_id_value = chunk_empty;
	char *product, *device, *description;
	time_t timestamp;
	int id, last_id = 0, ar_id = 0, last_ar_id = 0, device_count = 0, trusted;
	int session_id, rec;
	uint32_t ar_id_type;
	u_int tstamp;

	e = this->db->query(this->db,
			"SELECT d.id, d.value, d.trusted, d.description, "
			"s.id, s.time, s.identity, s.rec, p.name "
			"FROM devices AS d "
			"JOIN sessions AS s ON d.id = s.device "
			"JOIN products AS p ON p.id = s.product "
			"ORDER BY d.value, s.time DESC", DB_INT, DB_TEXT, DB_INT, DB_TEXT,
			 DB_INT, DB_UINT, DB_INT, DB_INT, DB_TEXT);

	if (e)
	{
		while (e->enumerate(e, &id, &device, &trusted, &description,
							   &session_id, &tstamp, &ar_id, &rec, &product))
		{
			if (id != last_id)
			{
				printf("%4d: %s %s - %s - %s\n", id, trusted ? "+" : "-",
												 device, product, description);
				device_count++;
				last_id = id;
			}
			timestamp = tstamp;
			printf("%4d:   %T", session_id, &timestamp, this->utc);
			if (ar_id)
			{
				if (ar_id != last_ar_id)
				{
					chunk_free(&ar_id_value);
					e_ar = this->db->query(this->db,
								"SELECT type, value FROM identities "
								"WHERE id = ?", DB_INT, ar_id, DB_INT, DB_BLOB);
					if (e_ar)
					{
						e_ar->enumerate(e_ar, &ar_id_type, &ar_id_value);
						ar_id_value = chunk_clone(ar_id_value);
						e_ar->destroy(e_ar);
					}
				}
				if (ar_id_value.len)
				{
					printf(" %.*s", (int)ar_id_value.len, ar_id_value.ptr);
				}
				last_ar_id = ar_id;
			}
			printf(" - %N\n", TNC_IMV_Action_Recommendation_names, rec);
		}
		e->destroy(e);
		free(ar_id_value.ptr);

		printf("%d device%s found\n", device_count,
									 (device_count == 1) ? "" : "s");
	}
}

METHOD(attest_db_t, list_keys, void,
	private_attest_db_t *this)
{
	enumerator_t *e;
	chunk_t keyid;
	char *owner;
	int kid, count = 0;

	if (this->cid)
	{
		e = this->db->query(this->db,
				"SELECT k.id, k.keyid, k.owner FROM keys AS k "
				"JOIN key_component AS kc ON k.id = kc.key "
				"WHERE kc.component = ? ORDER BY k.keyid",
				DB_UINT, this->cid, DB_INT, DB_BLOB, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &kid, &keyid, &owner))
			{
				printf("%4d: %#B '%s'\n", kid, &keyid, owner);
				count++;
			}
			e->destroy(e);
		}
	}
	else
	{
		e = this->db->query(this->db, "SELECT id, keyid, owner FROM keys "
				"ORDER BY keyid",
				DB_INT, DB_BLOB, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &kid, &keyid, &owner))
			{
				printf("%4d: %#B '%s'\n", kid, &keyid, owner);
				count++;
			}
			e->destroy(e);
		}
	}

	printf("%d key%s found", count, (count == 1) ? "" : "s");
	if (this->comp_set)
	{
		printf(" for component '%s'", print_cfn(this->cfn));
	}
	printf("\n");
}

METHOD(attest_db_t, list_files, void,
	private_attest_db_t *this)
{
	enumerator_t *e;
	char *dir, *file;
	int did, last_did = 0, fid, count = 0;

	if (this->did)
	{
		e = this->db->query(this->db,
				"SELECT id, name FROM files WHERE dir = ? ORDER BY name",
				DB_INT, this->did, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &fid, &file))
			{
				printf("%6d: %s\n", fid, file);
				count++;
			}
			e->destroy(e);
		}
		printf("%d file%s found in directory '%s'\n", count,
			  (count == 1) ? "" : "s", this->dir);
	}
	else
	{
		e = this->db->query(this->db,
				"SELECT d.id, d.path, f.id, f.name FROM files AS f "
				"JOIN directories AS d ON f.dir = d.id "
				"ORDER BY d.path, f.name",
				DB_INT, DB_TEXT, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &did, &dir, &fid, &file))
			{
				if (did != last_did)
				{
					printf("%6d: %s\n", did, dir);
					last_did = did;
				}
				printf("%6d:   %s\n", fid, file);
				count++;
			}
			e->destroy(e);
		}
		printf("%d file%s found\n", count, (count == 1) ? "" : "s");
	}
}

METHOD(attest_db_t, list_directories, void,
	private_attest_db_t *this)
{
	enumerator_t *e;
	char *dir;
	int did, count = 0;

	if (this->file)
	{
		e = this->db->query(this->db,
				"SELECT d.id, d.path FROM directories AS d "
				"JOIN files AS f ON f.dir = d.id WHERE f.name = ? "
				"ORDER BY path", DB_TEXT, this->file, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &did, &dir))
			{
				printf("%4d: %s\n", did, dir);
				count++;
			}
			e->destroy(e);
		}
		printf("%d director%s found containing file '%s'\n", count,
			  (count == 1) ? "y" : "ies", this->file);
	}
	else
	{
		e = this->db->query(this->db,
				"SELECT id, path FROM directories ORDER BY path",
				DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &did, &dir))
			{
				printf("%4d: %s\n", did, dir);
				count++;
			}
			e->destroy(e);
		}
		printf("%d director%s found\n", count, (count == 1) ? "y" : "ies");
	}
}

METHOD(attest_db_t, list_packages, void,
	private_attest_db_t *this)
{
	enumerator_t *e;
	char *package, *version;
	os_package_state_t package_state;
	int blacklist, security, gid, gid_old = 0, spaces, count = 0, t;
	time_t timestamp;

	if (this->pid)
	{
		e = this->db->query(this->db,
				"SELECT p.id, p.name, "
				"v.release, v.security, v.blacklist, v.time "
				"FROM packages AS p JOIN versions AS v ON v.package = p.id "
				"WHERE v.product = ? ORDER BY p.name, v.release",
				DB_INT, this->pid,
				DB_INT, DB_TEXT, DB_TEXT, DB_INT, DB_INT, DB_INT);
		if (e)
		{
			while (e->enumerate(e, &gid, &package,
								   &version, &security, &blacklist, &t))
			{
				if (gid != gid_old)
				{
					printf("%5d: %s,", gid, package);
					gid_old = gid;
				}
				else
				{
					spaces = 8 + strlen(package);
					while (spaces--)
					{
						printf(" ");
					}
				}
				timestamp = t;
				if (blacklist)
				{
					package_state = OS_PACKAGE_STATE_BLACKLIST;
				}
				else
				{
					package_state = security ? OS_PACKAGE_STATE_SECURITY :
											   OS_PACKAGE_STATE_UPDATE;
				}
				printf(" %T (%s)%N\n", &timestamp, this->utc, version,
					 os_package_state_names, package_state);
				count++;
			}
			e->destroy(e);
		}
	}
	else
	{
		e = this->db->query(this->db, "SELECT id, name FROM packages "
				"ORDER BY name",
				DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &gid, &package))
			{
				printf("%4d: %s\n", gid, package);
				count++;
			}
			e->destroy(e);
		}
	}

	printf("%d package%s found", count, (count == 1) ? "" : "s");
	if (this->product_set)
	{
		printf(" for product '%s'", this->product);
	}
	printf("\n");
}

METHOD(attest_db_t, list_products, void,
	private_attest_db_t *this)
{
	enumerator_t *e;
	char *product;
	int pid, meas, meta, count = 0;

	if (this->fid)
	{
		e = this->db->query(this->db,
				"SELECT p.id, p.name, pf.measurement, pf.metadata "
				"FROM products AS p "
				"JOIN product_file AS pf ON p.id = pf.product "
				"WHERE pf.file = ? ORDER BY p.name",
				DB_UINT, this->fid, DB_INT, DB_TEXT, DB_INT, DB_INT);
		if (e)
		{
			while (e->enumerate(e, &pid, &product, &meas, &meta))
			{
				printf("%4d: |%s%s| %s\n", pid, meas ? "M":" ", meta ? "T":" ",
										   product);
				count++;
			}
			e->destroy(e);
		}
	}
	else
	{
		e = this->db->query(this->db, "SELECT id, name FROM products "
				"ORDER BY name",
				DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &pid, &product))
			{
				printf("%4d: %s\n", pid, product);
				count++;
			}
			e->destroy(e);
		}
	}

	printf("%d product%s found", count, (count == 1) ? "" : "s");
	if (this->fid)
	{
		printf(" for file '%s'", this->file);
	}
	printf("\n");
}

METHOD(attest_db_t, list_hashes, void,
	private_attest_db_t *this)
{
	enumerator_t *e;
	char *file, *dir, *product, *hash;
	int id, fid, fid_old = 0, did, did_old = 0, pid, pid_old = 0, count = 0;

	if (this->pid && this->fid && this->did)
	{
		printf("%6d: %s\n", this->did, this->dir);
		printf("%6d:   %s\n", this->fid, this->file);
		e = this->db->query(this->db,
				"SELECT h.id, h.hash FROM file_hashes AS h "
				"JOIN versions AS v ON h.version = v.id "
				"WHERE h.algo = ? AND h.file = ? AND v.product = ?",
				DB_INT, this->algo, DB_INT, this->fid, DB_INT, this->pid,
				DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &id, &hash))
			{
				printf("%6d:     %s\n", id, hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found for product '%s'\n", count,
				   pts_meas_algorithm_names, this->algo,
				   (count == 1) ? "" : "s", this->product);
		}
	}
	else if (this->pid && this->file)
	{
		e = this->db->query(this->db,
				"SELECT h.id, h.hash, f.id, d.id, d.path "
				"FROM file_hashes AS h "
				"JOIN files AS f ON h.file = f.id "
				"JOIN directories AS d ON f.dir = d.id "
				"JOIN versions AS v ON h.version = v.id "
				"WHERE h.algo = ? AND v.product = ? AND f.name = ? "
				"ORDER BY d.path, f.name, h.hash",
				DB_INT, this->algo, DB_INT, this->pid, DB_TEXT, this->file,
				DB_INT, DB_TEXT, DB_INT, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &id, &hash, &fid, &did, &dir))
			{
				if (did != did_old)
				{
					printf("%6d: %s\n", did, dir);
					did_old = did;
				}
				if (fid != fid_old)
				{
					printf("%6d:   %s\n", fid, this->file);
					fid_old = fid;
				}
				printf("%6d:     %s\n", id, hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found for product '%s'\n", count,
				   pts_meas_algorithm_names, this->algo,
				   (count == 1) ? "" : "s", this->product);
		}
	}
	else if (this->pid && this->did)
	{
		printf("%6d: %s\n", this->did, this->dir);
		e = this->db->query(this->db,
				"SELECT h.id, h.hash, f.id, f.name "
				"FROM file_hashes AS h "
				"JOIN files AS f ON h.file = f.id "
				"JOIN versions AS v ON h.version = v.id "
				"WHERE h.algo = ? AND v.product = ? AND f.dir = ? "
				"ORDER BY f.name, h.hash",
				DB_INT, this->algo, DB_INT, this->pid, DB_INT, this->did,
				DB_INT, DB_TEXT, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &id, &hash, &fid, &file))
			{
				if (fid != fid_old)
				{
					printf("%6d:   %s\n", fid, file);
					fid_old = fid;
				}
				printf("%6d:     %s\n", id, hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found for product '%s'\n", count,
				   pts_meas_algorithm_names, this->algo,
				   (count == 1) ? "" : "s", this->product);
		}
	}
	else if (this->pid)
	{
		e = this->db->query(this->db,
				"SELECT h.id, h.hash, f.id, f.name, d.id, d.path "
				"FROM file_hashes AS h "
				"JOIN files AS f ON h.file = f.id "
				"JOIN directories AS d ON f.dir = d.id "
				"JOIN versions AS v ON h.version = v.id "
				"WHERE h.algo = ? AND v.product = ? "
				"ORDER BY d.path, f.name, h.hash",
				DB_INT, this->algo, DB_INT, this->pid,
				DB_INT, DB_TEXT, DB_INT, DB_TEXT, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &id, &hash, &fid, &file, &did, &dir))
			{
				if (did != did_old)
				{
					printf("%6d: %s\n", did, dir);
					did_old = did;
				}
				if (fid != fid_old)
				{
					printf("%6d:   %s\n", fid, file);
					fid_old = fid;
				}
				printf("%6d:     %s\n", id, hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found for product '%s'\n", count,
				   pts_meas_algorithm_names, this->algo,
				   (count == 1) ? "" : "s", this->product);
		}
	}
	else if (this->fid && this->did)
	{
		e = this->db->query(this->db,
				"SELECT h.id, h.hash, p.id, p.name FROM file_hashes AS h "
				"JOIN versions AS v ON h.version = v.id "
				"JOIN products AS p ON v.product = p.id "
				"WHERE h.algo = ? AND h.file = ? "
				"ORDER BY p.name, h.hash",
				DB_INT, this->algo, DB_INT, this->fid,
				DB_INT, DB_TEXT, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &id, &hash, &pid, &product))
			{
				if (pid != pid_old)
				{
					printf("%6d: %s\n", pid, product);
					pid_old = pid;
				}
				printf("%6d:   %s\n", id, hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found for file '%s%s%s'\n", count,
				   pts_meas_algorithm_names, this->algo,
				   (count == 1) ? "" : "s", this->dir,
				   get_separator(this->dir), this->file);
		}
	}
	else if (this->file)
	{
		e = this->db->query(this->db,
				"SELECT h.id, h.hash, f.id, d.id, d.path, p.id, p.name "
				"FROM file_hashes AS h "
				"JOIN files AS f ON h.file = f.id "
				"JOIN directories AS d ON f.dir = d.id "
				"JOIN versions AS v ON h.version = v.id "
				"JOIN products AS p ON v.product = p.id "
				"WHERE h.algo = ? AND f.name = ? "
				"ORDER BY d.path, f.name, p.name, h.hash",
				DB_INT, this->algo, DB_TEXT, this->file,
				DB_INT, DB_TEXT, DB_INT, DB_INT, DB_TEXT, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &id, &hash, &fid, &did, &dir, &pid, &product))
			{
				if (did != did_old)
				{
					printf("%6d: %s\n", did, dir);
					did_old = did;
				}
				if (fid != fid_old)
				{
					printf("%6d:   %s\n", fid, this->file);
					fid_old = fid;
					pid_old = 0;
				}
				if (pid != pid_old)
				{
					printf("%6d:     %s\n", pid, product);
					pid_old = pid;
				}
				printf("%6d:     %s\n", id, hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found\n", count, pts_meas_algorithm_names,
				   this->algo, (count == 1) ? "" : "s");
		}

	}
	else if (this->did)
	{
		e = this->db->query(this->db,
				"SELECT h.id, h.hash, f.id, f.name, p.id, p.name "
				"FROM file_hashes AS h "
				"JOIN files AS f ON h.file = f.id "
				"JOIN versions AS v ON h.version = v.id "
				"JOIN products AS p ON v.product = p.id "
				"WHERE h.algo = ? AND f.dir = ? "
				"ORDER BY f.name, p.name, h.hash",
				DB_INT, this->algo, DB_INT, this->did,
				DB_INT, DB_TEXT, DB_INT, DB_TEXT, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &id, &hash, &fid, &file, &pid, &product))
			{
				if (fid != fid_old)
				{
					printf("%6d: %s\n", fid, file);
					fid_old = fid;
					pid_old = 0;
				}
				if (pid != pid_old)
				{
					printf("%6d:   %s\n", pid, product);
					pid_old = pid;
				}
				printf("%6d:     %s\n", id, hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found for directory '%s'\n", count,
				   pts_meas_algorithm_names, this->algo,
				   (count == 1) ? "" : "s", this->dir);
		}
	}
	else
	{
		e = this->db->query(this->db,
				"SELECT h.id, h.hash, f.id, f.name, d.id, d.path, p.id, p.name "
				"FROM file_hashes AS h "
				"JOIN files AS f ON h.file = f.id "
				"JOIN directories AS d ON f.dir = d.id "
				"JOIN versions AS v ON h.version = v.id "
				"JOIN products AS p on v.product = p.id "
				"WHERE h.algo = ? "
				"ORDER BY d.path, f.name, p.name, h.hash",
				DB_INT, this->algo, DB_INT, DB_TEXT, DB_INT, DB_TEXT,
				DB_INT, DB_TEXT, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &id, &hash, &fid, &file, &did, &dir, &pid,
								&product))
			{
				if (did != did_old)
				{
					printf("%6d: %s\n", did, dir);
					did_old = did;
				}
				if (fid != fid_old)
				{
					printf("%6d:   %s\n", fid, file);
					fid_old = fid;
					pid_old = 0;
				}
				if (pid != pid_old)
				{
					printf("%6d:     %s\n", pid, product);
					pid_old = pid;
				}
				printf("%6d:       %s\n", id, hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found\n", count, pts_meas_algorithm_names,
				   this->algo, (count == 1) ? "" : "s");
		}
	}
}

METHOD(attest_db_t, list_measurements, void,
	private_attest_db_t *this)
{
	enumerator_t *e;
	chunk_t hash, keyid;
	pts_comp_func_name_t *cfn;
	char *owner;
	int seq_no, pcr, vid, name, qualifier;
	int cid, cid_old = 0, kid, kid_old = 0, count = 0;

	if (this->kid && this->cid)
	{
		e = this->db->query(this->db,
				"SELECT ch.seq_no, ch.pcr, ch.hash, k.owner "
				"FROM component_hashes AS ch "
				"JOIN keys AS k ON k.id = ch.key "
				"WHERE ch.algo = ? AND ch.key = ? AND ch.component = ? "
				"ORDER BY seq_no",
				DB_INT, this->algo, DB_UINT, this->kid, DB_UINT, this->cid,
				DB_INT, DB_INT, DB_BLOB, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &seq_no, &pcr, &hash, &owner))
			{
				if (this->kid != kid_old)
				{
					printf("%4d: %#B '%s'\n", this->kid, &this->key, owner);
					kid_old = this->kid;
				}
				printf("%7d %02d %#B\n", seq_no, pcr, &hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found for component '%s'\n", count,
				   pts_meas_algorithm_names, this->algo,
				   (count == 1) ? "" : "s", print_cfn(this->cfn));
		}
	}
	else if (this->cid)
	{
		e = this->db->query(this->db,
				"SELECT ch.seq_no, ch.pcr, ch.hash, k.id, k.keyid, k.owner "
				"FROM component_hashes AS ch "
				"JOIN keys AS k ON k.id = ch.key "
				"WHERE ch.algo = ? AND ch.component = ? "
				"ORDER BY keyid, seq_no",
				DB_INT, this->algo, DB_UINT, this->cid,
				DB_INT, DB_INT, DB_BLOB, DB_INT, DB_BLOB, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &seq_no, &pcr, &hash, &kid, &keyid, &owner))
			{
				if (kid != kid_old)
				{
					printf("%4d: %#B '%s'\n", kid, &keyid, owner);
					kid_old = kid;
				}
				printf("%7d %02d %#B\n", seq_no, pcr, &hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found for component '%s'\n", count,
				   pts_meas_algorithm_names, this->algo,
				   (count == 1) ? "" : "s", print_cfn(this->cfn));
		}

	}
	else if (this->kid)
	{
		e = this->db->query(this->db,
				"SELECT ch.seq_no, ch.pcr, ch.hash, "
				"c.id, c.vendor_id, c.name, c.qualifier "
				"FROM component_hashes AS ch "
				"JOIN components AS c ON c.id = ch.component "
				"WHERE ch.algo = ? AND ch.key = ? "
				"ORDER BY vendor_id, name, qualifier, seq_no",
				DB_INT, this->algo, DB_UINT, this->kid, DB_INT, DB_INT, DB_BLOB,
				DB_INT, DB_INT, DB_INT, DB_INT);
		if (e)
		{
			while (e->enumerate(e, &seq_no, &pcr, &hash, &cid, &vid, &name,
								   &qualifier))
			{
				if (cid != cid_old)
				{
					cfn = pts_comp_func_name_create(vid, name, qualifier);
					printf("%4d: %s\n", cid, print_cfn(cfn));
					cfn->destroy(cfn);
					cid_old = cid;
				}
				printf("%5d %02d %#B\n", seq_no, pcr, &hash);
				count++;
			}
			e->destroy(e);

			printf("%d %N value%s found for key %#B '%s'\n", count,
				   pts_meas_algorithm_names, this->algo,
				   (count == 1) ? "" : "s", &this->key, this->owner);
		}
	}
}

METHOD(attest_db_t, list_sessions, void,
	private_attest_db_t *this)
{
	enumerator_t *e;
	chunk_t identity;
	char *product, *device;
	int session_id, conn_id, rec, device_len;
	time_t created;
	u_int t;

	e = this->db->query(this->db,
			"SELECT s.id, s.time, s.connection, s.rec, p.name, d.value, i.value "
			"FROM sessions AS s "
			"LEFT JOIN products AS p ON s.product = p.id "
			"LEFT JOIN devices AS d ON s.device = d.id "
			"LEFT JOIN identities AS i ON s.identity = i.id "
			"ORDER BY s.time DESC",
			 DB_INT, DB_UINT, DB_INT, DB_INT, DB_TEXT, DB_TEXT, DB_BLOB);
	if (e)
	{
		while (e->enumerate(e, &session_id, &t, &conn_id, &rec, &product,
							   &device, &identity))
		{
			created = t;
			product = product ? product : "-";
			device = strlen(device) ? device : "-";
			device_len = min(strlen(device), DEVICE_MAX_LEN);
			identity = identity.len ? identity : chunk_from_str("-");
			printf("%4d: %T %2d %-20s %.*s%*s%.*s - %N\n", session_id, &created,
				   this->utc, conn_id, product, device_len, device,
				   DEVICE_MAX_LEN - device_len + 1, " ", (int)identity.len,
				   identity.ptr, TNC_IMV_Action_Recommendation_names, rec);
		}
		e->destroy(e);
	}
}

/**
 * Insert a file hash into the database
 */
static bool insert_file_hash(private_attest_db_t *this,
							 pts_meas_algorithms_t algo,
							 chunk_t measurement, int fid,
							 int *hashes_added, int *hashes_updated)
{
	enumerator_t *e;
	uint8_t hex_measurement_buf[2*HASH_SIZE_SHA512 + 1];
	uint8_t *hex_hash_buf;
	chunk_t hex_hash, hex_measurement;
	char *label;
	bool insert = TRUE, update = FALSE;

	label = "could not be created";

	e = this->db->query(this->db,
		"SELECT hash FROM file_hashes "
		"WHERE algo = ? AND file = ? AND version = ?",
		DB_INT, algo, DB_UINT, fid, DB_UINT, this->vid, DB_TEXT);

	if (!e)
	{
		printf("file_hashes query failed\n");
		return FALSE;
	}
	hex_measurement = chunk_to_hex(measurement, hex_measurement_buf, FALSE);

	while (e->enumerate(e, &hex_hash_buf))
	{
		update = TRUE;
		hex_hash = chunk_from_str(hex_hash_buf);

		if (chunk_equals(hex_measurement, hex_hash))
		{
			label = "exists and equals";
			insert = FALSE;
			break;
		}
	}
	e->destroy(e);

	if (insert)
	{
		if (this->db->execute(this->db, NULL,
			"INSERT INTO file_hashes "
			"(file, version, algo, hash) "
			"VALUES (?, ?, ?, ?)",
			DB_UINT, fid, DB_UINT, this->vid,
			DB_INT, algo, DB_TEXT, hex_measurement) != 1)
		{
			printf("file_hash insertion failed\n");
			return FALSE;
		}
		if (update)
		{
			label = "updated";
			(*hashes_updated)++;
		}
		else
		{
			label = "created";
			(*hashes_added)++;
		}
	}
	printf("     %#B - %s\n", &measurement, label);
	return TRUE;
}

/**
 * Add a package version
 */
static bool add_version(private_attest_db_t *this)
{
	int vid, security_old, security, blacklist_old, blacklist;
	time_t t = time(NULL);
	enumerator_t *e;
	bool success;

	security =  this->package_state == OS_PACKAGE_STATE_SECURITY;
	blacklist = this->package_state == OS_PACKAGE_STATE_BLACKLIST;

	e = this->db->query(this->db,
				"SELECT id, security, blacklist FROM versions "
				"WHERE package = ? AND product = ? AND release = ?",
				DB_UINT, this->gid, DB_UINT, this->pid, DB_TEXT, this->version,
				DB_INT, DB_INT, DB_INT, DB_INT);
	if (e)
	{
		if (e->enumerate(e, &vid, &security_old, &blacklist_old))
		{
			this->vid = vid;
		}
		e->destroy(e);
	}
	if (this->vid)
	{
		if (security != security_old || blacklist != blacklist_old)
		{
			/* update security and/or blacklist flag */
			success = this->db->execute(this->db, NULL, "UPDATE versions "
					"SET security = ?, blacklist = ?, time = ? WHERE id = ?",
					DB_INT, security, DB_INT, blacklist, DB_INT, t,
					DB_INT, this->vid) == 1;

			printf("'%s' package %s (%s)%N %s updated in database\n",
				   this->product, this->package, this->version,
				   os_package_state_names, this->package_state,
				   success ? "" : "could not be ");
		}
		else
		{
			success = TRUE;

			printf("'%s' package %s (%s)%N exists in database\n",
				   this->product, this->package, this->version,
				   os_package_state_names, this->package_state);
		}
		return success;
	}

	/* create a new version */
	success = this->db->execute(this->db, NULL,
				"INSERT INTO versions "
				"(package, product, release, security, blacklist, time) "
				"VALUES (?, ?, ?, ?, ?, ?)",
				DB_UINT, this->gid, DB_INT, this->pid, DB_TEXT,
				this->version, DB_INT, security, DB_INT, blacklist,
				DB_INT, t) == 1;

	printf("'%s' package %s (%s)%N %sinserted into database\n",
			this->product, this->package, this->version,
			os_package_state_names, this->package_state,
			success ? "" : "could not be ");

	return success;
}

/**
 * Add hash measurement for a single file or all files in a directory
 */
static bool add_hash(private_attest_db_t *this)
{
	char *pathname, *filename, *label;
	const char *sep;
	pts_file_meas_t *measurements;
	chunk_t measurement;
	hasher_t *hasher = NULL;
	int fid, files_added = 0, hashes_added = 0, hashes_updated = 0;
	enumerator_t *enumerator, *e;

	if (!this->meas_dir)
	{
		this->meas_dir = strdup(this->dir);
	}
	sep = get_separator(this->meas_dir);

	if (this->fid)
	{
		/* build pathname from directory path and relative filename */
		if (asprintf(&pathname, "%s%s%s", this->meas_dir, sep, this->file) == -1)
		{
			return FALSE;
		}
		measurements = pts_file_meas_create_from_path(0, pathname, FALSE,
													  TRUE, this->algo);
		free(pathname);
	}
	else
	{
		measurements = pts_file_meas_create_from_path(0, this->meas_dir, TRUE,
													  TRUE, this->algo);
	}
	if (!measurements)
	{
		printf("file measurement failed\n");
		DESTROY_IF(hasher);
		return FALSE;
	}

	enumerator = measurements->create_enumerator(measurements);
	while (enumerator->enumerate(enumerator, &filename, &measurement))
	{
		if (this->fid)
		{
			/* a single file already exists */
			filename = this->file;
			fid = this->fid;
			label = "exists";
		}
		else
		{
			/* retrieve or create filename */
			label = "could not be created";

			e = this->db->query(this->db,
				"SELECT id FROM files WHERE name = ? AND dir = ?",
				DB_TEXT, filename, DB_INT, this->did, DB_INT);
			if (!e)
			{
				printf("files query failed\n");
				break;
			}
			if (e->enumerate(e, &fid))
			{
				label = "exists";
			}
			else
			{
				if (this->db->execute(this->db, &fid,
					"INSERT INTO files (name, dir) VALUES (?, ?)",
					DB_TEXT, filename, DB_INT, this->did) == 1)
				{
					label = "created";
					files_added++;
				}
			}
			e->destroy(e);
		}
		printf("%4d: %s - %s\n", fid, filename, label);

		/* compute file measurement hash */
		if (!insert_file_hash(this, this->algo, measurement, fid,
							  &hashes_added, &hashes_updated))
		{
			break;
		}
	}
	enumerator->destroy(enumerator);

	printf("%d measurements, added %d new files, %d file hashes, "
		   "updated %d file hashes\n",
			measurements->get_file_count(measurements),
		    files_added, hashes_added, hashes_updated);
	measurements->destroy(measurements);

	return TRUE;
}

METHOD(attest_db_t, add, bool,
	private_attest_db_t *this)
{
	/* insert package version */
	if (this->version_set && this->gid && this->pid)
	{
		if (!add_version(this))
		{
			return FALSE;
		}
	}

	/* add directory or file hash measurement for a given product */
	if (this->did && this->pid)
	{
		return add_hash(this);
	}

	return FALSE;
}

METHOD(attest_db_t, delete, bool,
	private_attest_db_t *this)
{
	bool success;
	int id, count = 0;
	char *name;
	enumerator_t *e;

	/* delete a file measurement hash for a given product */
	if (this->algo && this->pid && this->fid)
	{
		success = this->db->execute(this->db, NULL,
								"DELETE FROM file_hashes AS h "
								"JOIN versions AS v ON h.version = v.id "
								"WHERE h.algo = ? AND v.product = ? AND h.file = ?",
								DB_UINT, this->algo, DB_UINT, this->pid,
								DB_UINT, this->fid) > 0;

		printf("%4d: %s%s%s\n", this->fid, this->dir, get_separator(this->dir),
				this->file);
		printf("%N value for product '%s' %sdeleted from database\n",
				pts_meas_algorithm_names, this->algo, this->product,
				success ? "" : "could not be ");

		return success;
	}

	/* delete product/file entries */
	if (this->pid && (this->fid || this->did))
	{
		success = this->db->execute(this->db, NULL,
							"DELETE FROM product_file "
							"WHERE product = ? AND file = ?",
							DB_UINT, this->pid,
							DB_UINT, this->fid ? this->fid : this->did) > 0;

		printf("product/file pair (%d/%d) %sdeleted from database\n",
				this->pid, this->fid ? this->fid : this->did,
				success ? "" : "could not be ");

		return success;
	}

	if (this->cid)
	{
		success = this->db->execute(this->db, NULL,
								"DELETE FROM components WHERE id = ?",
								DB_UINT, this->cid) > 0;

		printf("component '%s' %sdeleted from database\n", print_cfn(this->cfn),
			   success ? "" : "could not be ");
		return success;
	}

	if (this->fid)
	{
		success = this->db->execute(this->db, NULL,
								"DELETE FROM files WHERE id = ?",
								DB_UINT, this->fid) > 0;

		printf("file '%s%s%s' %sdeleted from database\n", this->dir,
			   get_separator(this->dir), this->file,
			   success ? "" : "could not be ");
		return success;
	}

	if (this->did)
	{
		e = this->db->query(this->db,
				"SELECT id, name FROM files WHERE dir = ? ORDER BY name",
				DB_INT, this->did, DB_INT, DB_TEXT);
		if (e)
		{
			while (e->enumerate(e, &id, &name))
			{
				printf("%4d: %s\n", id, name);
				count++;
			}
			e->destroy(e);

			if (count)
			{
				printf("%d dependent file%s found, "
					   "directory '%s' could not deleted\n",
					   count, (count == 1) ? "" : "s", this->dir);
				return FALSE;
			}
		}
		success = this->db->execute(this->db, NULL,
								"DELETE FROM directories WHERE id = ?",
								DB_UINT, this->did) > 0;
		printf("directory '%s' %sdeleted from database\n", this->dir,
			   success ? "" : "could not be ");
		return success;
	}

	if (this->kid)
	{
		success = this->db->execute(this->db, NULL,
								"DELETE FROM keys WHERE id = ?",
								DB_UINT, this->kid) > 0;

		printf("key %#B %sdeleted from database\n", &this->key,
			   success ? "" : "could not be ");
		return success;
	}
	if (this->pid)
	{
		success = this->db->execute(this->db, NULL,
								"DELETE FROM products WHERE id = ?",
								DB_UINT, this->pid) > 0;

		printf("product '%s' %sdeleted from database\n", this->product,
			   success ? "" : "could not be ");
		return success;
	}

	printf("empty delete command\n");
	return FALSE;
}

METHOD(attest_db_t, destroy, void,
	private_attest_db_t *this)
{
	DESTROY_IF(this->db);
	DESTROY_IF(this->cfn);
	free(this->package);
	free(this->product);
	free(this->version);
	free(this->file);
	free(this->dir);
	free(this->meas_dir);
	free(this->owner);
	free(this->key.ptr);
	free(this);
}

/**
 * Described in header.
 */
attest_db_t *attest_db_create(char *uri)
{
	private_attest_db_t *this;

	INIT(this,
		.public = {
			.set_component = _set_component,
			.set_cid = _set_cid,
			.set_directory = _set_directory,
			.set_did = _set_did,
			.set_file = _set_file,
			.set_fid = _set_fid,
			.set_meas_directory = _set_meas_directory,
			.set_key = _set_key,
			.set_kid = _set_kid,
			.set_package = _set_package,
			.set_gid = _set_gid,
			.set_product = _set_product,
			.set_pid = _set_pid,
			.set_version = _set_version,
			.set_algo = _set_algo,
			.set_relative = _set_relative,
			.set_package_state = _set_package_state,
			.set_sequence = _set_sequence,
			.set_owner = _set_owner,
			.set_utc = _set_utc,
			.list_packages = _list_packages,
			.list_products = _list_products,
			.list_files = _list_files,
			.list_directories = _list_directories,
			.list_components = _list_components,
			.list_devices = _list_devices,
			.list_keys = _list_keys,
			.list_hashes = _list_hashes,
			.list_measurements = _list_measurements,
			.list_sessions = _list_sessions,
			.add = _add,
			.delete = _delete,
			.destroy = _destroy,
		},
		.db = lib->db->create(lib->db, uri),
	);

	if (!this->db)
	{
		fprintf(stderr, "opening database failed.\n");
		destroy(this);
		return NULL;
	}

	return &this->public;
}
