/*
 * Copyright (C) 2012 Tobias Brunner
 * Copyright (C) 2008-2009 Martin Willi
 *
 * 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 <sys/types.h>
#include <unistd.h>

#include "nm_service.h"
#include "nm_creds.h"
#include "nm_handler.h"

#include <daemon.h>
#include <processing/jobs/callback_job.h>

typedef struct nm_backend_t nm_backend_t;

/**
 * Data for the NetworkManager backend.
 */
struct nm_backend_t {

	/**
	 * NetworkManager service (VPNPlugin)
	 */
	NMStrongswanPlugin *plugin;

	/**
	 * Glib main loop for a thread, handles DBUS calls
	 */
	GMainLoop *loop;

	/**
	 * credential set registered at the daemon
	 */
	nm_creds_t *creds;

	/**
	 * attribute handler registered at the daemon
	 */
	nm_handler_t *handler;
};

/**
 * Global (but private) instance of the NM backend.
 */
static nm_backend_t *nm_backend = NULL;

/**
 * Terminate the daemon if signaled by NM
 */
static void terminate(void *plugin, void *arg)
{
	kill(getpid(), SIGTERM);
}

/**
 * NM plugin processing routine, creates and handles NMVpnServicePlugin
 */
static job_requeue_t run(nm_backend_t *this)
{
	this->loop = g_main_loop_new(NULL, FALSE);
	g_signal_connect(this->plugin, "quit", G_CALLBACK(terminate), NULL);
	g_main_loop_run(this->loop);
	return JOB_REQUEUE_NONE;
}

/**
 * Cancel the GLib Main Event Loop
 */
CALLBACK(cancel, bool,
	nm_backend_t *this)
{
	if (this->loop)
	{
		if (g_main_loop_is_running(this->loop))
		{
			g_main_loop_quit(this->loop);
		}
		g_main_loop_unref(this->loop);
	}
	return TRUE;
}

/**
 * Deinitialize NetworkManager backend
 */
static void nm_backend_deinit()
{
	nm_backend_t *this = nm_backend;

	if (!this)
	{
		return;
	}
	if (this->plugin)
	{
		g_object_unref(this->plugin);
	}
	lib->credmgr->remove_set(lib->credmgr, &this->creds->set);
	charon->attributes->remove_handler(charon->attributes,
									   &this->handler->handler);
	this->creds->destroy(this->creds);
	this->handler->destroy(this->handler);
	free(this);

	nm_backend = NULL;
}

/**
 * Initialize NetworkManager backend
 */
static bool nm_backend_init()
{
	nm_backend_t *this;

#if !GLIB_CHECK_VERSION(2,36,0)
	g_type_init ();
#endif

#if !GLIB_CHECK_VERSION(2,23,0)
	if (!g_thread_supported())
	{
		g_thread_init(NULL);
	}
#endif

	INIT(this,
		.creds = nm_creds_create(),
		.handler = nm_handler_create(),
	);
	this->plugin = nm_strongswan_plugin_new(this->creds, this->handler);
	nm_backend = this;

	charon->attributes->add_handler(charon->attributes, &this->handler->handler);
	lib->credmgr->add_set(lib->credmgr, &this->creds->set);
	if (!this->plugin)
	{
		DBG1(DBG_CFG, "DBUS binding failed");
		nm_backend_deinit();
		return FALSE;
	}

	lib->processor->queue_job(lib->processor,
		(job_t*)callback_job_create_with_prio((callback_job_cb_t)run, this,
				NULL, cancel, JOB_PRIO_CRITICAL));
	return TRUE;
}

/**
 * Initialize/deinitialize NetworkManager backend
 */
static bool nm_backend_cb(void *plugin,
						  plugin_feature_t *feature, bool reg, void *data)
{
	if (reg)
	{
		return nm_backend_init();
	}
	nm_backend_deinit();
	return TRUE;
}

/*
 * see header file
 */
void nm_backend_register()
{
	static plugin_feature_t features[] = {
		PLUGIN_CALLBACK((plugin_feature_callback_t)nm_backend_cb, NULL),
			PLUGIN_PROVIDE(CUSTOM, "NetworkManager backend"),
				PLUGIN_DEPENDS(CUSTOM, "libcharon"),
				PLUGIN_SDEPEND(PRIVKEY, KEY_RSA),
				PLUGIN_SDEPEND(PRIVKEY, KEY_ECDSA),
				PLUGIN_SDEPEND(CERT_DECODE, CERT_ANY),
				PLUGIN_SDEPEND(CERT_DECODE, CERT_X509),
	};
	lib->plugins->add_static_features(lib->plugins, "nm-backend", features,
									  countof(features), TRUE, NULL, NULL);
}
