/*
 * Copyright (C) 2011 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 /* for stdndup() */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

#include "tnc.h"

#include <utils/lexparser.h>
#include <utils/debug.h>

#ifdef WIN32
# define DEFAULT_TNC_CONFIG "tnc_config"
#else
# define DEFAULT_TNC_CONFIG "/etc/tnc_config"
#endif

typedef struct private_tnc_t private_tnc_t;

typedef tnccs_manager_t *(*tnc_create_tnccs_manager_t)(void);
typedef imc_manager_t *(*tnc_create_imc_manager_t)(void);
typedef imv_manager_t *(*tnc_create_imv_manager_t)(void);

/**
 * Private additions to tnc_t.
 */
struct private_tnc_t {

	/**
	 * Public members of tnc_t.
	 */
	tnc_t public;

	/**
	 * Number of times we have been initialized
	 */
	refcount_t ref;
};

/**
 * Register plugins if built statically
 */
#ifdef STATIC_PLUGIN_CONSTRUCTORS
#include "plugin_constructors.c"
#endif

/**
 * Single instance of tnc_t.
 */
tnc_t *tnc;

/**
 * Described in header.
 */
void libtnccs_init(void)
{
	private_tnc_t *this;

	if (tnc)
	{	/* already initialized, increase refcount */
		this = (private_tnc_t*)tnc;
		ref_get(&this->ref);
		return;
	}

	INIT(this,
		.public = {
		},
		.ref = 1,
	);
	tnc = &this->public;
	lib->settings->add_fallback(lib->settings, "%s.tnc", "libtnccs", lib->ns);
	lib->settings->add_fallback(lib->settings, "%s.plugins", "libtnccs.plugins",
								lib->ns);
}

/**
 * Described in header.
 */
void libtnccs_deinit(void)
{
	private_tnc_t *this = (private_tnc_t*)tnc;

	if (!this || !ref_put(&this->ref))
	{	/* have more users */
		return;
	}

	free(this);
	tnc = NULL;
}

static bool load_imcvs_from_config(char *filename, bool is_imc)
{
	bool success = FALSE;
	int line_nr DBG_UNUSED = 0;
	chunk_t *src, line;
	char *label;

	if (!filename || !*filename)
	{
		return TRUE;
	}

	label = is_imc ? "IMC" : "IMV";

	DBG1(DBG_TNC, "loading %ss from '%s'", label, filename);
	src = chunk_map(filename, FALSE);
	if (!src)
	{
		DBG1(DBG_TNC, "opening configuration file '%s' failed: %s", filename,
			 strerror(errno));
		return FALSE;
	}

	while (fetchline(src, &line))
	{
		char *name, *path;
		chunk_t token;

		line_nr++;

		/* skip comments or empty lines */
		if (*line.ptr == '#' || !eat_whitespace(&line))
		{
			continue;
		}

		/* determine keyword */
		if (!extract_token(&token, ' ', &line))
		{
			DBG1(DBG_TNC, "line %d: keyword must be followed by a space",
						   line_nr);
			break;
		}

		/* only interested in IMCs or IMVs depending on label */
		if (!match(label, &token))
		{
			continue;
		}

		/* advance to the IMC/IMV name and extract it */
		if (!extract_token(&token, '"', &line) ||
			!extract_token(&token, '"', &line))
		{
			DBG1(DBG_TNC, "line %d: %s name must be set in double quotes",
						   line_nr, label);
			break;
		}

		/* copy the IMC/IMV name */
		name = strndup(token.ptr, token.len);

		/* advance to the IMC/IMV path and extract it */
		if (!eat_whitespace(&line))
		{
			DBG1(DBG_TNC, "line %d: %s path is missing", line_nr, label);
			free(name);
			break;
		}
		if (!extract_token(&token, ' ', &line))
		{
			token = line;
		}

		/* copy the IMC/IMV path */
		path = strndup(token.ptr, token.len);

		/* load and register an IMC/IMV instance */
		if (is_imc)
		{
			success = tnc->imcs->load(tnc->imcs, name, path);
		}
		else
		{
			success = tnc->imvs->load(tnc->imvs, name, path);
		}
		free(name);
		free(path);
		if (!success)
		{
			break;
		}
	}
	chunk_unmap(src);
	return success;
}

/**
 * Described in header.
 */
bool tnc_manager_register(plugin_t *plugin, plugin_feature_t *feature,
						  bool reg, void *data)
{
	bool load_imcvs = FALSE;
	bool is_imc = FALSE;

	if (feature->type == FEATURE_CUSTOM)
	{
		if (streq(feature->arg.custom, "tnccs-manager"))
		{
			if (reg)
			{
				tnc->tnccs = ((tnc_create_tnccs_manager_t)data)();
			}
			else
			{
				tnc->tnccs->destroy(tnc->tnccs);
				tnc->tnccs = NULL;
			}
		}
		else if (streq(feature->arg.custom, "imc-manager"))
		{
			if (reg)
			{
				tnc->imcs = ((tnc_create_imc_manager_t)data)();
				is_imc = TRUE;
				load_imcvs = TRUE;
			}
			else
			{
				tnc->imcs->destroy(tnc->imcs);
				tnc->imcs = NULL;
			}
		}
		else if (streq(feature->arg.custom, "imv-manager"))
		{
			if (reg)
			{
				tnc->imvs = ((tnc_create_imv_manager_t)data)();
				is_imc = FALSE;
				load_imcvs = TRUE;
			}
			else
			{
				tnc->imvs->destroy(tnc->imvs);
				tnc->imvs = NULL;
			}
		}
		else
		{
			return FALSE;
		}

		if (load_imcvs)
		{
			load_imcvs_from_config(
						lib->settings->get_str(lib->settings,
							"%s.tnc.tnc_config", DEFAULT_TNC_CONFIG, lib->ns),
						is_imc);
		}
	}
	return TRUE;
}
