/*
 * Copyright (C) 2006-2018 Tobias Brunner
 * Copyright (C) 2005-2014 Martin Willi
 * Copyright (C) 2006 Daniel Roethlisberger
 * Copyright (C) 2005 Jan Hutter
 *
 * 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.
 */

#include <signal.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <errno.h>

/* won't make sense from our logging hook */
#define SD_JOURNAL_SUPPRESS_LOCATION
#include <systemd/sd-daemon.h>
#include <systemd/sd-journal.h>

#include <daemon.h>

#include <library.h>
#include <utils/backtrace.h>
#include <threading/thread.h>
#include <threading/rwlock.h>

/**
 * Default user and group
 */
#ifndef IPSEC_USER
#define IPSEC_USER NULL
#endif

#ifndef IPSEC_GROUP
#define IPSEC_GROUP NULL
#endif

/**
 * hook in library for debugging messages
 */
extern void (*dbg) (debug_t group, level_t level, char *fmt, ...);

/**
 * Logging hook for library logs, using stderr output
 */
static void dbg_stderr(debug_t group, level_t level, char *fmt, ...)
{
	va_list args;

	if (level <= 1)
	{
		va_start(args, fmt);
		fprintf(stderr, "00[%N] ", debug_names, group);
		vfprintf(stderr, fmt, args);
		fprintf(stderr, "\n");
		va_end(args);
	}
}

typedef struct journal_logger_t journal_logger_t;

/**
 * Logger implementation using systemd-journal
 */
struct journal_logger_t {

	/**
	 * Public interface
	 */
	custom_logger_t public;

	/**
	 * Configured loglevels
	 */
	level_t levels[DBG_MAX];

	/**
	 * Lock for levels
	 */
	rwlock_t *lock;
};

METHOD(logger_t, vlog, void,
	journal_logger_t *this, debug_t group, level_t level, int thread,
	ike_sa_t *ike_sa, const char *fmt, va_list args)
{
	char buf[4096], *msg = buf;
	ssize_t len;
	va_list copy;

	va_copy(copy, args);
	len = vsnprintf(msg, sizeof(buf), fmt, copy);
	va_end(copy);

	if (len >= sizeof(buf))
	{
		len++;
		msg = malloc(len);
		va_copy(copy, args);
		len = vsnprintf(msg, len, fmt, copy);
		va_end(copy);
	}
	if (len > 0)
	{
		char unique[64] = "", name[256] = "";
		int priority;

		if (ike_sa)
		{
			snprintf(unique, sizeof(unique), "IKE_SA_UNIQUE_ID=%u",
					 ike_sa->get_unique_id(ike_sa));
			if (ike_sa->get_peer_cfg(ike_sa))
			{
				snprintf(name, sizeof(name), "IKE_SA_NAME=%s",
						 ike_sa->get_name(ike_sa));
			}
		}
		switch (level)
		{
			case LEVEL_AUDIT:
				priority = LOG_NOTICE;
				break;
			case LEVEL_CTRL:
				priority = LOG_INFO;
				break;
			default:
				priority = LOG_DEBUG;
				break;
		}
		sd_journal_send(
			"MESSAGE=%s", msg,
			"MESSAGE_ID=57d2708c-d607-43bd-8c39-66bf%.8x",
				chunk_hash_static(chunk_from_str((char*)fmt)),
			"PRIORITY=%d", priority,
			"GROUP=%N", debug_names, group,
			"LEVEL=%d", level,
			"THREAD=%d", thread,
			unique[0] ? unique : NULL,
			name[0] ? name : NULL,
			NULL);
	}
	if (msg != buf)
	{
		free(msg);
	}
}

METHOD(logger_t, get_level, level_t,
	journal_logger_t *this, debug_t group)
{
	level_t level;

	this->lock->read_lock(this->lock);
	level = this->levels[group];
	this->lock->unlock(this->lock);

	return level;
}

METHOD(custom_logger_t, set_level, void,
	journal_logger_t *this, debug_t group, level_t level)
{
	this->lock->write_lock(this->lock);
	this->levels[group] = level;
	this->lock->unlock(this->lock);
}

METHOD(custom_logger_t, logger_destroy, void,
	journal_logger_t *this)
{
	this->lock->destroy(this->lock);
	free(this);
}

static custom_logger_t *journal_logger_create(const char *name)
{
	journal_logger_t *this;

	INIT(this,
		.public = {
			.logger = {
				.vlog = _vlog,
				.get_level = _get_level,
			},
			.set_level = _set_level,
			.destroy = _logger_destroy,
		},
		.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
	);
	return &this->public;
}

/**
 * Run the daemon and handle unix signals
 */
static int run()
{
	sigset_t set;

	sigemptyset(&set);
	sigaddset(&set, SIGHUP);
	sigaddset(&set, SIGTERM);
	sigprocmask(SIG_BLOCK, &set, NULL);

	sd_notify(0, "READY=1\n");

	while (TRUE)
	{
		int sig;

		sig = sigwaitinfo(&set, NULL);
		if (sig == -1)
		{
			if (errno == EINTR)
			{	/* ignore signals we didn't wait for */
				continue;
			}
			DBG1(DBG_DMN, "waiting for signal failed: %s", strerror(errno));
			return SS_RC_INITIALIZATION_FAILED;
		}
		switch (sig)
		{
			case SIGHUP:
			{
				DBG1(DBG_DMN, "signal of type SIGHUP received. Reloading "
					 "configuration");
				if (lib->settings->load_files(lib->settings, lib->conf, FALSE))
				{
					charon->load_loggers(charon);
					lib->plugins->reload(lib->plugins, NULL);
				}
				else
				{
					DBG1(DBG_DMN, "reloading config failed, keeping old");
				}
				break;
			}
			case SIGTERM:
			{
				DBG1(DBG_DMN, "SIGTERM received, shutting down");
				charon->bus->alert(charon->bus, ALERT_SHUTDOWN_SIGNAL, sig);
				return 0;
			}
		}
	}
}

/**
 * lookup UID and GID
 */
static bool lookup_uid_gid()
{
	char *name;

	name = lib->settings->get_str(lib->settings, "%s.user", IPSEC_USER,
								  lib->ns);
	if (name && !lib->caps->resolve_uid(lib->caps, name))
	{
		return FALSE;
	}
	name = lib->settings->get_str(lib->settings, "%s.group", IPSEC_GROUP,
								  lib->ns);
	if (name && !lib->caps->resolve_gid(lib->caps, name))
	{
		return FALSE;
	}
	return TRUE;
}

#ifndef DISABLE_SIGNAL_HANDLER
/**
 * Handle SIGSEGV/SIGILL signals raised by threads
 */
static void segv_handler(int signal)
{
	backtrace_t *backtrace;

	DBG1(DBG_DMN, "thread %u received %d", thread_current_id(), signal);
	backtrace = backtrace_create(2);
	backtrace->log(backtrace, NULL, TRUE);
	backtrace->log(backtrace, stderr, TRUE);
	backtrace->destroy(backtrace);

	DBG1(DBG_DMN, "killing ourself, received critical signal");
	abort();
}
#endif /* DISABLE_SIGNAL_HANDLER */

/**
 * Add namespace alias
 */
static void __attribute__ ((constructor))register_namespace()
{
	/* inherit settings from charon */
	library_add_namespace("charon");
}

/**
 * Register journal logger
 */
static void __attribute__ ((constructor))register_logger()
{
	register_custom_logger("journal", journal_logger_create);
}

/**
 * Main function, starts the daemon.
 */
int main(int argc, char *argv[])
{
	struct sigaction action;
	struct utsname utsname;
	int status = SS_RC_INITIALIZATION_FAILED;

	dbg = dbg_stderr;

	if (uname(&utsname) != 0)
	{
		memset(&utsname, 0, sizeof(utsname));
	}

	sd_notifyf(0, "STATUS=Starting charon-systemd, strongSwan %s, %s %s, %s",
			   VERSION, utsname.sysname, utsname.release, utsname.machine);

	atexit(library_deinit);
	if (!library_init(NULL, "charon-systemd"))
	{
		sd_notifyf(0, "STATUS=libstrongswan initialization failed");
		return SS_RC_INITIALIZATION_FAILED;
	}
	if (lib->integrity &&
		!lib->integrity->check_file(lib->integrity, "charon-systemd", argv[0]))
	{
		sd_notifyf(0, "STATUS=integrity check of charon-systemd failed");
		return SS_RC_INITIALIZATION_FAILED;
	}
	if (!libcharon_init())
	{
		sd_notifyf(0, "STATUS=libcharon initialization failed");
		goto error;
	}
	if (!lookup_uid_gid())
	{
		sd_notifyf(0, "STATUS=unknown uid/gid");
		goto error;
	}
	/* we registered the journal logger as custom logger, which gets its
	 * settings from <ns>.customlog.journal, let it fallback to <ns>.journal */
	lib->settings->add_fallback(lib->settings, "%s.customlog.journal",
								"%s.journal", lib->ns);
	/* load the journal logger by default */
	lib->settings->set_default_str(lib->settings, "%s.journal.default", "1",
								   lib->ns);

	charon->load_loggers(charon);

	DBG1(DBG_DMN, "Starting charon-systemd IKE daemon (strongSwan "VERSION", "
		 "%s %s, %s)", utsname.sysname, utsname.release, utsname.machine);

	if (!charon->initialize(charon,
			lib->settings->get_str(lib->settings, "%s.load", PLUGINS, lib->ns)))
	{
		sd_notifyf(0, "STATUS=charon initialization failed");
		goto error;
	}
	lib->plugins->status(lib->plugins, LEVEL_CTRL);

	if (!lib->caps->drop(lib->caps))
	{
		sd_notifyf(0, "STATUS=dropping capabilities failed");
		goto error;
	}

	/* add handler for fatal signals,
	 * INT, TERM and HUP are handled by sigwaitinfo() in run() */
	action.sa_flags = 0;
	sigemptyset(&action.sa_mask);
	sigaddset(&action.sa_mask, SIGINT);
	sigaddset(&action.sa_mask, SIGTERM);
	sigaddset(&action.sa_mask, SIGHUP);

	/* optionally let the external system handle fatal signals */
#ifndef DISABLE_SIGNAL_HANDLER
	action.sa_handler = segv_handler;
	sigaction(SIGSEGV, &action, NULL);
	sigaction(SIGILL, &action, NULL);
	sigaction(SIGBUS, &action, NULL);
#endif /* DISABLE_SIGNAL_HANDLER */

	action.sa_handler = SIG_IGN;
	sigaction(SIGPIPE, &action, NULL);

	pthread_sigmask(SIG_SETMASK, &action.sa_mask, NULL);

	charon->start(charon);

	sd_notifyf(0, "STATUS=charon-systemd running, strongSwan %s, %s %s, %s",
			   VERSION, utsname.sysname, utsname.release, utsname.machine);

	status = run();

error:
	libcharon_deinit();
	return status;
}
