/* strongSwan IPsec starter
 * Copyright (C) 2001-2002 Mathieu Lafon - Arkoon Network Security
 *
 * 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.
 *
 * RCSID $Id$
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

#include <freeswan.h>

#include "../pluto/constants.h"
#include "../pluto/defs.h"
#include "../pluto/log.h"

#include "confread.h"
#include "files.h"
#include "starterwhack.h"
#include "starterstroke.h"
#include "invokepluto.h"
#include "invokecharon.h"
#include "netkey.h"
#include "cmp.h"
#include "interfaces.h"

/**
 * Return codes defined by Linux Standard Base Core Specification 3.1
 * in section 20.2. Init Script Actions
 */
#define LSB_RC_SUCCESS               0   /* success                          */
#define LSB_RC_FAILURE               1   /* generic or unspecified error     */
#define LSB_RC_INVALID_ARGUMENT      2   /* invalid or excess argument(s)    */
#define LSB_RC_NOT_IMPLEMENTED       3   /* unimplemented feature (reload)   */
#define LSB_RC_NOT_ALLOWED           4   /* user had insufficient privilege  */
#define LSB_RC_NOT_INSTALLED         5   /* program is not installed         */
#define LSB_RC_NOT_CONFIGURED        6   /* program is not configured        */
#define LSB_RC_NOT_RUNNING           7   /* program is not running           */

#define FLAG_ACTION_START_PLUTO   0x01
#define FLAG_ACTION_UPDATE        0x02
#define FLAG_ACTION_RELOAD        0x04
#define FLAG_ACTION_QUIT          0x08
#define FLAG_ACTION_LISTEN        0x10
#define FLAG_ACTION_START_CHARON  0x20

static unsigned int _action_ = 0;

static void
fsig(int signal)
{
    switch (signal)
    {
	case SIGCHLD:
	{
	    int status;
	    pid_t pid;
	    char *name = NULL;

	    while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
	    {
		if (pid == starter_pluto_pid())
		    name = " (Pluto)";
		if (pid == starter_charon_pid())
		    name = " (Charon)";
		if (WIFSIGNALED(status))
		    DBG(DBG_CONTROL,
			DBG_log("child %d%s has been killed by sig %d\n",
				pid, name?name:"", WTERMSIG(status))
		       )
		else if (WIFSTOPPED(status))
		    DBG(DBG_CONTROL,
			DBG_log("child %d%s has been stopped by sig %d\n",
				pid, name?name:"", WSTOPSIG(status))
		       )
		else if (WIFEXITED(status))
		    DBG(DBG_CONTROL,
			DBG_log("child %d%s has quit (exit code %d)\n",
				pid, name?name:"", WEXITSTATUS(status))
		       )
		else
		    DBG(DBG_CONTROL,
			DBG_log("child %d%s has quit", pid, name?name:"")
		       )
		if (pid == starter_pluto_pid())
		    starter_pluto_sigchild(pid);
		if (pid == starter_charon_pid())
		    starter_charon_sigchild(pid);
	    }
	}
	break;

	case SIGPIPE:
	    /** ignore **/
	    break;

	case SIGALRM:
	    _action_ |= FLAG_ACTION_START_PLUTO;
	    _action_ |= FLAG_ACTION_START_CHARON;
	    break;

	case SIGHUP:
	    _action_ |= FLAG_ACTION_UPDATE;
	    break;

	case SIGTERM:
	case SIGQUIT:
	case SIGINT:
	    _action_ |= FLAG_ACTION_QUIT;
	    break;

	case SIGUSR1:
	    _action_ |= FLAG_ACTION_RELOAD;
	    _action_ |= FLAG_ACTION_UPDATE;
	    break;

	default:
	    plog("fsig(): unknown signal %d -- investigate", signal);
	    break;
    }
}

static void
usage(char *name)
{
    fprintf(stderr, "Usage: starter [--nofork] [--auto-update <sec>] "
	    "[--debug|--debug-more|--debug-all]\n");
    exit(LSB_RC_INVALID_ARGUMENT);
}

int main (int argc, char **argv)
{
    starter_config_t *cfg = NULL;
    starter_config_t *new_cfg;
    starter_conn_t *conn, *conn2;
    starter_ca_t *ca, *ca2;

    struct stat stb;

    char *err = NULL;
    int i;
    int id = 1;
    struct timeval tv;
    unsigned long auto_update = 0;
    time_t last_reload;
    bool no_fork = FALSE;

    /* global variables defined in log.h */
    log_to_stderr = TRUE;
    base_debugging = DBG_NONE;

    /* parse command line */
    for (i = 1; i < argc; i++)
    {
	if (streq(argv[i], "--debug"))
	{
	    base_debugging |= DBG_CONTROL;
	}
	else if (streq(argv[i], "--debug-more"))
	{
	    base_debugging |= DBG_CONTROLMORE;
	}
	else if (streq(argv[i], "--debug-all"))
	{
	    base_debugging |= DBG_ALL;
	}
	else if (streq(argv[i], "--nofork"))
	{
	    no_fork = TRUE;
	}
	else if (streq(argv[i], "--auto-update") && i+1 < argc)
	{
	    auto_update = atoi(argv[++i]);
	    if (!auto_update)
		usage(argv[0]);
	}
	else
	{
	    usage(argv[0]);
	}
    }

    /* Init */
    init_log("ipsec_starter");
    cur_debugging = base_debugging;

    signal(SIGHUP,  fsig);
    signal(SIGCHLD, fsig);
    signal(SIGPIPE, fsig);
    signal(SIGINT,  fsig);
    signal(SIGTERM, fsig);
    signal(SIGQUIT, fsig);
    signal(SIGALRM, fsig);
    signal(SIGUSR1, fsig);

    plog("Starting strongSwan %s IPsec [starter]...", ipsec_version_code());

    /* verify that we can start */
    if (getuid() != 0)
    {
	plog("permission denied (must be superuser)");
	exit(LSB_RC_NOT_ALLOWED);
    }

    if (stat(PLUTO_PID_FILE, &stb) == 0)
    {
	plog("pluto is already running (%s exists) -- skipping pluto start", PLUTO_PID_FILE);
    }
    else
    {
	_action_ |= FLAG_ACTION_START_PLUTO;
    }
    if (stat(CHARON_PID_FILE, &stb) == 0)
    {
	plog("charon is already running (%s exists) -- skipping charon start", CHARON_PID_FILE);
    }
    else
    {
	_action_ |= FLAG_ACTION_START_CHARON;
    }
    if (stat(DEV_RANDOM, &stb) != 0)
    {
	plog("unable to start strongSwan IPsec -- no %s!", DEV_RANDOM);
	exit(LSB_RC_FAILURE);
    }

    if (stat(DEV_URANDOM, &stb)!= 0)
    {
	plog("unable to start strongSwan IPsec -- no %s!", DEV_URANDOM);
	exit(LSB_RC_FAILURE);
    }

    cfg = confread_load(CONFIG_FILE);
    if (cfg == NULL || cfg->err > 0)
    {
	plog("unable to start strongSwan -- fatal errors in config");
	if (cfg)
	{
	    confread_free(cfg);
	}
	exit(LSB_RC_INVALID_ARGUMENT);
    }

    /* determine if we have a native netkey IPsec stack */
    if (!starter_netkey_init())
    {
	plog("no netkey IPSec stack detected");
	exit(LSB_RC_FAILURE);
    }

    last_reload = time(NULL);

    if (stat(STARTER_PID_FILE, &stb) == 0)
    {
	plog("starter is already running (%s exists) -- no fork done", STARTER_PID_FILE);
	exit(LSB_RC_SUCCESS);
    }

    /* fork if we're not debugging stuff */
    if (!no_fork)
    {
	log_to_stderr = FALSE;

	switch (fork())
	{
	    case 0:
	    {
		int fnull = open("/dev/null", O_RDWR);

		if (fnull >= 0)
		{
		    dup2(fnull, STDIN_FILENO);
		    dup2(fnull, STDOUT_FILENO);
		    dup2(fnull, STDERR_FILENO);
		    close(fnull);
		}
		setsid(); 
	    }
	    break;
	    case -1:
		plog("can't fork: %s", strerror(errno));
		break;
	    default:
		exit(LSB_RC_SUCCESS);
	}
    }

    /* save pid file in /var/run/starter.pid */
    {
	FILE *fd = fopen(STARTER_PID_FILE, "w");

	if (fd)
	{
	    fprintf(fd, "%u\n", getpid());
	    fclose(fd);
	}
    }

    for (;;)
    {
	/*
	 * Stop pluto/charon (if started) and exit
	 */
	if (_action_ & FLAG_ACTION_QUIT)
	{
	    if (starter_pluto_pid())
		starter_stop_pluto();
	    if (starter_charon_pid())
		starter_stop_charon();
	    starter_netkey_cleanup();
	    confread_free(cfg);
	    unlink(STARTER_PID_FILE);
	    unlink(INFO_FILE);
#ifdef LEAK_DETECTIVE
	    report_leaks();
#endif /* LEAK_DETECTIVE */
	    close_log();
	    plog("ipsec starter stopped");
	    exit(LSB_RC_SUCCESS);
	}

	/*
	 * Delete all connections. Will be added below
	 */
	if (_action_ & FLAG_ACTION_RELOAD)
	{
	    if (starter_pluto_pid() || starter_charon_pid())
	    {
		for (conn = cfg->conn_first; conn; conn = conn->next)
		{
		    if (conn->state == STATE_ADDED)
		    {
			if (starter_charon_pid())
			{
			    starter_stroke_del_conn(conn);
			}
			if (starter_pluto_pid())
			{
			    starter_whack_del_conn(conn);
			}
			conn->state = STATE_TO_ADD;
		    }
		}
		for (ca = cfg->ca_first; ca; ca = ca->next)
		{
		    if (ca->state == STATE_ADDED)
		    {
			if (starter_charon_pid())
			{
			    starter_stroke_del_ca(ca);
			}
			if (starter_pluto_pid())
			{
			    starter_whack_del_ca(ca);
			}
			ca->state = STATE_TO_ADD;
		    }
		}
	    }
	    _action_ &= ~FLAG_ACTION_RELOAD;
	}

	/*
	 * Update configuration
	 */
	if (_action_ & FLAG_ACTION_UPDATE)
	{
	    err = NULL;
	    DBG(DBG_CONTROL,
		DBG_log("Reloading config...")
	       );
	    new_cfg = confread_load(CONFIG_FILE);

	    if (new_cfg->err + new_cfg->non_fatal_err == 0)
	    {
		/* Switch to new config. New conn will be loaded below */
		if (!starter_cmp_defaultroute(&new_cfg->defaultroute
				   , &cfg->defaultroute))
		{
		    _action_ |= FLAG_ACTION_LISTEN;
		}

		if (!starter_cmp_pluto(cfg, new_cfg)) 
		{
		    plog("Pluto has changed");
		    if (starter_pluto_pid())
			starter_stop_pluto();
		    _action_ &= ~FLAG_ACTION_LISTEN;
		    _action_ |= FLAG_ACTION_START_PLUTO;
		}
		else
		{
		    /* Only reload conn and ca sections if pluto is not killed */

		    /* Look for new connections that are already loaded */
		    for (conn = cfg->conn_first; conn; conn = conn->next)
		    {
			if (conn->state == STATE_ADDED)
			{
			    for (conn2 = new_cfg->conn_first; conn2; conn2 = conn2->next)
			    {
				if (conn2->state == STATE_TO_ADD && starter_cmp_conn(conn, conn2))
				{
				    conn->state = STATE_REPLACED;
				    conn2->state = STATE_ADDED;
				    conn2->id = conn->id;
				    break;
				}
			    }
			}
		    }

		    /* Remove conn sections that have become unused */
		    for (conn = cfg->conn_first; conn; conn = conn->next)
		    {
			if (conn->state == STATE_ADDED)
			{
			    if (starter_charon_pid())
			    {
				starter_stroke_del_conn(conn);
			    }
			    if (starter_pluto_pid())
			    {
				starter_whack_del_conn(conn);
			    }
			}
		    }

		    /* Look for new ca sections that are already loaded */
		    for (ca = cfg->ca_first; ca; ca = ca->next)
		    {
			if (ca->state == STATE_ADDED)
			{
			    for (ca2 = new_cfg->ca_first; ca2; ca2 = ca2->next)
			    {
				if (ca2->state == STATE_TO_ADD && starter_cmp_ca(ca, ca2))
				{
				    ca->state = STATE_REPLACED;
				    ca2->state = STATE_ADDED;
				    break;
				}
			    }
			}
		    }

		    /* Remove ca sections that have become unused */
		    for (ca = cfg->ca_first; ca; ca = ca->next)
		    {
			if (ca->state == STATE_ADDED)
			{
			    if (starter_charon_pid())
			    {
				starter_stroke_del_ca(ca);
			    }
			    if (starter_pluto_pid())
			    {
				starter_whack_del_ca(ca);
			    }
			}
		    }
		}
		confread_free(cfg);
		cfg = new_cfg;
	    }
	    else
	    {
		plog("can't reload config file due to errors -- keeping old one");
		confread_free(new_cfg);
	    }
	    _action_ &= ~FLAG_ACTION_UPDATE;
	    last_reload = time(NULL);
	}

	/*
	 * Start pluto
	 */
	if (_action_ & FLAG_ACTION_START_PLUTO)
	{
	    if (cfg->setup.plutostart && !starter_pluto_pid())
	    {
		DBG(DBG_CONTROL,
		    DBG_log("Attempting to start pluto...")
		   );

		if (starter_start_pluto(cfg, no_fork) == 0)
		{
		    starter_whack_listen();
		}
		else
		{
		    /* schedule next try */
		    alarm(PLUTO_RESTART_DELAY);
		}
	    }
	    _action_ &= ~FLAG_ACTION_START_PLUTO;

	    for (ca = cfg->ca_first; ca; ca = ca->next)
	    {
		if (ca->state == STATE_ADDED)
		    ca->state = STATE_TO_ADD;
	    }

	    for (conn = cfg->conn_first; conn; conn = conn->next)
	    {
		if (conn->state == STATE_ADDED)
		    conn->state = STATE_TO_ADD;
	    }
	}
	
	/*
	 * Start charon
	 */
	if (_action_ & FLAG_ACTION_START_CHARON)
	{
	    if (cfg->setup.charonstart && !starter_charon_pid())
	    {
		DBG(DBG_CONTROL,
		    DBG_log("Attempting to start charon...")
		   );
		if (starter_start_charon(cfg, no_fork))
		{
		    /* schedule next try */
		    alarm(PLUTO_RESTART_DELAY);
		}
		starter_stroke_configure(cfg);
	    }
	    _action_ &= ~FLAG_ACTION_START_CHARON;
	}

	/*
	 * Tell pluto to reread its interfaces
	 */
	if (_action_ & FLAG_ACTION_LISTEN)
	{
	    if (starter_pluto_pid())
	    {
		starter_whack_listen();
		_action_ &= ~FLAG_ACTION_LISTEN;
	    }
	}

	/*
	 * Add stale conn and ca sections
	 */
	if (starter_pluto_pid() || starter_charon_pid())
	{
	    for (ca = cfg->ca_first; ca; ca = ca->next)
	    {
		if (ca->state == STATE_TO_ADD)
		{
		    if (starter_charon_pid())
		    {
			starter_stroke_add_ca(ca);
		    }
		    if (starter_pluto_pid())
		    {
			starter_whack_add_ca(ca);
		    }
		    ca->state = STATE_ADDED;
		}
	    }

	    for (conn = cfg->conn_first; conn; conn = conn->next)
	    {
		if (conn->state == STATE_TO_ADD)
		{
		    if (conn->id == 0)
		    {
			/* affect new unique id */
			conn->id = id++;
		    }
		    if (starter_charon_pid())
		    {
			starter_stroke_add_conn(cfg, conn);
		    }
		    if (starter_pluto_pid())
		    {
			starter_whack_add_conn(conn);
		    }
		    conn->state = STATE_ADDED;

		    if (conn->startup == STARTUP_START)
		    {
			if (conn->keyexchange == KEY_EXCHANGE_IKEV2)
			{
			    if (starter_charon_pid())
			    {
				starter_stroke_initiate_conn(conn);
			    }
			}
			else
			{
			    if (starter_pluto_pid())
			    {
				starter_whack_initiate_conn(conn);
			    }
			}
		    }
		    else if (conn->startup == STARTUP_ROUTE)
		    {
			if (conn->keyexchange == KEY_EXCHANGE_IKEV2)
			{
			    if (starter_charon_pid())
			    {
				starter_stroke_route_conn(conn);
			    }
			}
			else
			{
			    if (starter_pluto_pid())
			    {
				starter_whack_route_conn(conn);
			    }
			}
		    }
		}
	    }
	}

	/*
	 * If auto_update activated, when to stop select
	 */
	if (auto_update)
	{
	    time_t now = time(NULL);

	    tv.tv_sec = (now < last_reload + auto_update)
		    ? (last_reload + auto_update-now) : 0;
	    tv.tv_usec = 0;
	}

	/*
	 * Wait for something to happen
	 */
	if (select(0, NULL, NULL, NULL, auto_update ? &tv : NULL) == 0)
	{
	    /* timeout -> auto_update */
	    _action_ |= FLAG_ACTION_UPDATE;
	}
    }
    exit(LSB_RC_SUCCESS);
}

