/*
 * Copyright (C) 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 <unistd.h>

#include "swid_gen.h"

#include <bio/bio_writer.h>
#include <utils/process.h>

#define SWID_GENERATOR	"/usr/local/bin/swid_generator"

typedef struct private_swid_gen_t private_swid_gen_t;

/**
 * Private data of a swid_gen_t object.
 *
 */
struct private_swid_gen_t {

	/**
	 * Public swid_gen_t interface.
	 */
	swid_gen_t public;

	/**
	 * Path of the SWID generator command
	 */
	char *generator;

	/**
	 * Entity name of the tagCreator
	 */
	char *entity;

	/**
	 * Regid of the tagCreator
	 */
	char *regid;

};

METHOD(swid_gen_t, generate_tag, char*,
	private_swid_gen_t *this, char *sw_id, char *package, char *version,
	bool full, bool pretty)
{
	process_t *process;
	char *tag = NULL;
	size_t tag_buf_len = 8192;
	char tag_buf[tag_buf_len], *argv[12] = {};
	bio_writer_t *writer;
	chunk_t swid_tag;
	FILE *file;
	int i = 0, out;

	argv[i++] = this->generator;
	argv[i++] = "swid";
	argv[i++] = "--entity-name";
	argv[i++] = this->entity;
	argv[i++] = "--regid";
	argv[i++] = this->regid;

	if (full || !package || !version)
	{
		argv[i++] = "--software-id";
		argv[i++] = sw_id;
		if (full)
		{
			argv[i++] = "--full";
		}
	}
	else
	{
		argv[i++] = "--name";
		argv[i++] = package;
		argv[i++] = "--version-string";
		argv[i++] = version;
	}
	if (pretty)
	{
		argv[i++] = "--pretty";
	}

	process = process_start(argv, NULL, NULL, &out, NULL, TRUE);
	if (!process)
	{
		DBG1(DBG_IMC, "failed to run swid_generator command");
		return NULL;
	}
	file = fdopen(out, "r");
	if (file)
	{
		writer = bio_writer_create(tag_buf_len);
		while (TRUE)
		{
			if (!fgets(tag_buf, tag_buf_len, file))
			{
				break;
			}
			writer->write_data(writer, chunk_create(tag_buf, strlen(tag_buf)));
		}
		fclose(file);

		swid_tag = writer->extract_buf(writer);
		writer->destroy(writer);

		if (swid_tag.len > 0)
		{
			tag = swid_tag.ptr;
			tag[swid_tag.len - 1] = '\0';
		}
		else
		{
			chunk_free(&swid_tag);
		}
	}
	else
	{
		close(out);
	}
	process->wait(process, NULL);
	return tag;
}

typedef struct {
	/** public enumerator interface */
	enumerator_t public;
	/** swid_generator output stream */
	FILE *file;
	/** generate software identifier only */
	bool sw_id_only;
} swid_gen_enumerator_t;

METHOD(enumerator_t, enumerate, bool,
	swid_gen_enumerator_t *this, va_list args)
{
	chunk_t *out;

	VA_ARGS_VGET(args, out);

	if (this->sw_id_only)
	{
		char line[BUF_LEN];
		size_t len;

		if (!fgets(line, sizeof(line), this->file))
		{
			return FALSE;
		}
		len = strlen(line);

		if (len == 0)
		{
			return FALSE;
		}

		/* remove trailing newline if present */
		if (line[len - 1] == '\n')
		{
			len--;
		}
		DBG3(DBG_IMC, "  %.*s", len, line);
		*out = chunk_clone(chunk_create(line, len));
	}
	else
	{
		bool last_newline = TRUE;
		size_t len, line_len = 8192;
		char line[line_len];
		bio_writer_t *writer;
		chunk_t swid_tag;

		writer = bio_writer_create(line_len);
		while (TRUE)
		{
			if (!fgets(line, line_len, this->file))
			{
				break;
			}
			len = strlen(line);

			if (last_newline && line[0] == '\n')
			{
				break;
			}
			else
			{
				last_newline = (line[len-1] == '\n');
				writer->write_data(writer, chunk_create(line, len));
			}
		}
		swid_tag = writer->extract_buf(writer);
		writer->destroy(writer);

		if (swid_tag.len <= 1)
		{
			chunk_free(&swid_tag);
			return FALSE;
		}

		/* remove trailing newline if present */
		if (swid_tag.ptr[swid_tag.len - 1] == '\n')
		{
			swid_tag.len--;
		}
		DBG3(DBG_IMC, "  %.*s", swid_tag.len, swid_tag.ptr);
		*out = swid_tag;
	}

	return TRUE;
}

METHOD(enumerator_t, enumerator_destroy, void,
	swid_gen_enumerator_t *this)
{
	pclose(this->file);
	free(this);
}

METHOD(swid_gen_t, create_tag_enumerator, enumerator_t*,
	private_swid_gen_t *this, bool sw_id_only, bool full, bool pretty)
{
	swid_gen_enumerator_t *enumerator;
	char command[BUF_LEN];
	char doc_separator[] = "'\n\n'";
	FILE *file;

	/* Assemble the SWID generator command */
	if (sw_id_only)
	{
		snprintf(command, BUF_LEN, "%s software-id --regid %s ",
				 this->generator, this->regid);
	}
	else
	{
		snprintf(command, BUF_LEN, "%s swid --entity-name \"%s\" --regid %s "
				 "--doc-separator %s%s%s", this->generator, this->entity,
				 this->regid, doc_separator, pretty ? " --pretty" : "",
											 full   ? " --full"   : "");
	}

	/* Open a pipe stream for reading the SWID generator output */
	file = popen(command, "r");
	if (!file)
	{
		DBG1(DBG_IMC, "failed to run swid_generator command");
		return NULL;
	}

	INIT(enumerator,
		.public = {
			.enumerate = enumerator_enumerate_default,
			.venumerate = _enumerate,
			.destroy = _enumerator_destroy,
		},
		.sw_id_only = sw_id_only,
		.file = file,
	);

	return &enumerator->public;
}

METHOD(swid_gen_t, destroy, void,
	private_swid_gen_t *this)
{
	free(this->generator);
	free(this->entity);
	free(this->regid);
	free(this);
}

/**
 * See header
 */
swid_gen_t *swid_gen_create(void)
{
	private_swid_gen_t *this;
	char *entity, *regid, *generator;

	entity = lib->settings->get_str(lib->settings,
				"libimcv.swid_gen.tag_creator.name", "strongSwan Project");
	regid  = lib->settings->get_str(lib->settings,
				"libimcv.swid_gen.tag_creator.regid", "strongswan.org");
	generator = lib->settings->get_str(lib->settings,
				"libimcv.swid_gen.command", SWID_GENERATOR);

	INIT(this,
		.public = {
			.generate_tag = _generate_tag,
			.create_tag_enumerator = _create_tag_enumerator,
			.destroy = _destroy,
		},
		.generator = strdup(generator),
		.entity = strdup(entity),
		.regid = strdup(regid),
	);

	return &this->public;
}
