/*
   start-stop-daemon
   Starts, stops, tests and signals daemons

   This is essentially a ground up re-write of Debians
   start-stop-daemon for cleaner code and to integrate into our RC
   system so we can monitor daemons a little.
   */

/* 
 * Copyright 2007 Roy Marples
 * All rights reserved

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* nano seconds */
#define POLL_INTERVAL   20000000
#define WAIT_PIDFILE   500000000
#define START_WAIT     100000000
#define ONE_SECOND    1000000000

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/termios.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#ifdef HAVE_PAM
#include <security/pam_appl.h>

/* We are not supporting authentication conversations */
static struct pam_conv conv = { NULL, NULL};
#endif

#include "builtins.h"
#include "einfo.h"
#include "rc.h"
#include "rc-misc.h"
#include "strlist.h"

typedef struct schedulelist
{
	enum
	{
		schedule_timeout,
		schedule_signal,
		schedule_goto,
		schedule_forever
	} type;
	int value;
	struct schedulelist *gotolist;
	struct schedulelist *next;
} schedulelist_t;
static schedulelist_t *schedule;

static const char *applet;
static char *changeuser;
static char **newenv;

extern char **environ;

static void free_schedulelist (schedulelist_t **list)
{
	schedulelist_t *here;
	schedulelist_t *next;

	for (here = *list; here; here = next) {
		next = here->next;
		free (here);
	}

	*list = NULL;
}

static void cleanup (void)
{
	if (changeuser)
		free (changeuser);

	if (schedule)
		free_schedulelist (&schedule);

	if (newenv)
		rc_strlist_free (newenv);
}

static int parse_signal (const char *sig)
{
	typedef struct signalpair
	{
		const char *name;
		int signal;
	} signalpair_t;

	static const signalpair_t signallist[] = {
		{ "ABRT",	SIGABRT	},
		{ "ALRM",	SIGALRM	},
		{ "FPE",	SIGFPE	},
		{ "HUP",	SIGHUP	},
		{ "ILL",	SIGILL	},
		{ "INT",	SIGINT	},
		{ "KILL",	SIGKILL	},
		{ "PIPE",	SIGPIPE	},
		{ "QUIT",	SIGQUIT	},
		{ "SEGV",	SIGSEGV	},
		{ "TERM",	SIGTERM	},
		{ "USR1",	SIGUSR1	},
		{ "USR2",	SIGUSR2	},
		{ "CHLD",	SIGCHLD	},
		{ "CONT",	SIGCONT	},
		{ "STOP",	SIGSTOP	},
		{ "TSTP",	SIGTSTP	},
		{ "TTIN",	SIGTTIN	},
		{ "TTOU",	SIGTTOU	}
	};

	unsigned int i = 0;
	char *s;

	if (! sig || strlen (sig) == 0)
		return (-1);

	if (sscanf (sig, "%u", &i) == 1) {
		if (i > 0 && i < sizeof (signallist) / sizeof (signallist[0]))
			return (i);
		eerrorx ("%s: `%s' is not a valid signal", applet, sig);
	}

	if (strncmp (sig, "SIG", 3) == 0)
		s = (char *) sig + 3;
	else
		s = NULL;

	for (i = 0; i < sizeof (signallist) / sizeof (signallist[0]); i++)
		if (strcmp (sig, signallist[i].name) == 0 ||
			(s && strcmp (s, signallist[i].name) == 0))
			return (signallist[i].signal);

	eerrorx ("%s: `%s' is not a valid signal", applet, sig);
}

static void parse_schedule_item (schedulelist_t *item, const char *string)
{
	const char *after_hyph;
	int sig;

	if (strcmp (string,"forever") == 0)
		item->type = schedule_forever;
	else if (isdigit (string[0])) {
		item->type = schedule_timeout;
		errno = 0;
		if (sscanf (string, "%d", &item->value) != 1)
			eerrorx ("%s: invalid timeout value in schedule `%s'", applet,
					 string);
	} else if ((after_hyph = string + (string[0] == '-')) &&
			 ((sig = parse_signal (after_hyph)) != -1))
	{
		item->type = schedule_signal;
		item->value = (int) sig;
	}
	else
		eerrorx ("%s: invalid schedule item `%s'", applet, string);
}

static void parse_schedule (const char *string, int default_signal)
{
	char buffer[20];
	const char *slash;
	int count = 0;
	schedulelist_t *repeatat = NULL;
	ptrdiff_t len;
	schedulelist_t *next;

	if (string)
		for (slash = string; *slash; slash++)
			if (*slash == '/')
				count++;

	if (schedule)
		free_schedulelist (&schedule);

	schedule = xmalloc (sizeof (schedulelist_t));
	schedule->gotolist = NULL;

	if (count == 0) {
		schedule->type = schedule_signal;
		schedule->value = default_signal;
		schedule->next = xmalloc (sizeof (schedulelist_t));
		next = schedule->next;
		next->type = schedule_timeout;
		next->gotolist = NULL;
		if (string) {
			if (sscanf (string, "%d", &next->value) != 1)
				eerrorx ("%s: invalid timeout value in schedule", applet);
		}
		else
			next->value = 5;
		next->next = NULL;

		return;
	}

	next = schedule;
	while (string != NULL) {
		if ((slash = strchr (string, '/')))
			len = slash - string;
		else
			len = strlen (string);

		if (len >= (ptrdiff_t) sizeof (buffer))
			eerrorx ("%s: invalid schedule item, far too long", applet);

		memcpy (buffer, string, len);
		buffer[len] = 0;
		string = slash ? slash + 1 : NULL;

		parse_schedule_item (next, buffer);
		if (next->type == schedule_forever) {
			if (repeatat)
				eerrorx ("%s: invalid schedule, `forever' appears more than once",
						 applet);

			repeatat = next;
			continue;
		}

		if (string) {
			next->next = xmalloc (sizeof (schedulelist_t));
			next = next->next;
			next->gotolist = NULL;
		}
	}

	if (repeatat) {
		next->next = xmalloc (sizeof (schedulelist_t));
		next = next->next;
		next->type = schedule_goto;
		next->value = 0;
		next->gotolist = repeatat;
	}

	next->next = NULL;
	return;
}

static pid_t get_pid (const char *pidfile, bool quiet)
{
	FILE *fp;
	pid_t pid;

	if (! pidfile)
		return (-1);

	if ((fp = fopen (pidfile, "r")) == NULL) {
		if (! quiet)
			eerror ("%s: fopen `%s': %s", applet, pidfile, strerror (errno));
		return (-1);
	}

	if (fscanf (fp, "%d", &pid) != 1) {
		if (! quiet)
			eerror ("%s: no pid found in `%s'", applet, pidfile);
		fclose (fp);
		return (-1);
	}
	fclose (fp);

	return (pid);
}

/* return number of processed killed, -1 on error */
static int do_stop (const char *exec, const char *cmd,
					const char *pidfile, uid_t uid,int sig,
					bool quiet, bool verbose, bool test)
{
	pid_t *pids; 
	bool killed;
	int nkilled = 0;
	pid_t pid = 0;
	int i;

	if (pidfile) {
		if ((pid = get_pid (pidfile, quiet)) == -1)
			return (quiet ? 0 : -1);
		pids = rc_find_pids (NULL, NULL, 0, pid);
	} else
		pids = rc_find_pids (exec, cmd, uid, pid);

	if (! pids)
		return (0);

	for (i = 0; pids[i]; i++) {
		if (test) {
			if (! quiet)
				einfo ("Would send signal %d to PID %d", sig, pids[i]);
			nkilled++;
			continue;
		}

		if (verbose)
			ebegin ("Sending signal %d to PID %d", sig, pids[i]);
		errno = 0;
		killed = (kill (pids[i], sig) == 0 || errno == ESRCH ? true : false);
		if (verbose)
			eend (killed ? 0 : 1, "%s: failed to send signal %d to PID %d: %s",
				  applet, sig, pids[i], strerror (errno));
		if (! killed) {
			nkilled = -1;
		} else {
			if (nkilled != -1)
				nkilled++;
		}
	}

	free (pids);
	return (nkilled);
}

static int run_stop_schedule (const char *exec, const char *cmd,
							  const char *pidfile, uid_t uid,
							  bool quiet, bool verbose, bool test)
{
	schedulelist_t *item = schedule;
	int nkilled = 0;
	int tkilled = 0;
	int nrunning = 0;
	long nloops;
	struct timespec ts;

	if (verbose) {
		if (pidfile)
			einfo ("Will stop PID in pidfile `%s'", pidfile);
		if (uid)
			einfo ("Will stop processes owned by UID %d", uid);
		if (exec)
			einfo ("Will stop processes of `%s'", exec);
		if (cmd)
			einfo ("Will stop processes called `%s'", cmd);
	}

	while (item) {
		switch (item->type)	{
			case schedule_goto:
				item = item->gotolist;
				continue;

			case schedule_signal:
				nrunning = 0;
				nkilled = do_stop (exec, cmd, pidfile, uid, item->value,
								   quiet, verbose, test);
				if (nkilled == 0) {
					if (tkilled == 0) {
						if (! quiet)
							eerror ("%s: no matching processes found", applet);
					}
					return (tkilled);
				}
				else if (nkilled == -1)
					return (0);

				tkilled += nkilled;
				break;
			case schedule_timeout:
				if (item->value < 1) {
					item = NULL;
					break;
				}

				nloops = (ONE_SECOND / POLL_INTERVAL) * item->value;
				ts.tv_sec = 0;
				ts.tv_nsec = POLL_INTERVAL;

				while (nloops) {
					if ((nrunning = do_stop (exec, cmd, pidfile,
											 uid, 0, true, false, true)) == 0)
						return (true);

					if (nanosleep (&ts, NULL) == -1) {
						if (errno == EINTR)
							eerror ("%s: caught an interrupt", applet);
						else {
							eerror ("%s: nanosleep: %s", applet, strerror (errno));
							return (0);
						}
					}
					nloops --;
				}
				break;

			default:
				eerror ("%s: invalid schedule item `%d'", applet, item->type);
				return (0);
		}

		if (item)
			item = item->next;
	}

	if (test || (tkilled > 0 && nrunning == 0))
		return (nkilled);

	if (! quiet) {
		if (nrunning == 1)
			eerror ("%s: %d process refused to stop", applet, nrunning);
		else
			eerror ("%s: %d process(es) refused to stop", applet, nrunning);
	}

	return (-nrunning);
}

static void handle_signal (int sig)
{
	int pid;
	int status;
	int serrno = errno;
	char signame[10] = { '\0' };

	switch (sig) {
		case SIGINT:
			if (! signame[0])
				snprintf (signame, sizeof (signame), "SIGINT");
		case SIGTERM:
			if (! signame[0])
				snprintf (signame, sizeof (signame), "SIGTERM");
		case SIGQUIT:
			if (! signame[0])
				snprintf (signame, sizeof (signame), "SIGQUIT");
			eerrorx ("%s: caught %s, aborting", applet, signame);

		case SIGCHLD:
			while (1) {
				if ((pid = waitpid (-1, &status, WNOHANG)) < 0) {
					if (errno != ECHILD)
						eerror ("%s: waitpid: %s", applet, strerror (errno));
					break;
				}
			}
			break;

		default:
			eerror ("%s: caught unknown signal %d", applet, sig);
	}

	/* Restore errno */
	errno = serrno;
}


#include "_usage.h"
#define getoptstring "KN:R:Sbc:d:g:mn:op:s:tu:r:x:1:2:" getoptstring_COMMON
static struct option longopts[] = {
	{ "stop",         0, NULL, 'K'},
	{ "nicelevel",    1, NULL, 'N'},
	{ "retry",        1, NULL, 'R'},
	{ "start",        0, NULL, 'S'},
	{ "startas",      1, NULL, 'a'},
	{ "background",   0, NULL, 'b'},
	{ "chuid",        1, NULL, 'c'},
	{ "chdir",        1, NULL, 'd'},
	{ "group",        1, NULL, 'g'},
	{ "make-pidfile", 0, NULL, 'm'},
	{ "name",         1, NULL, 'n'},
	{ "oknodo",       0, NULL, 'o'},
	{ "pidfile",      1, NULL, 'p'},
	{ "signal",       1, NULL, 's'},
	{ "test",         0, NULL, 't'},
	{ "user",         1, NULL, 'u'},
	{ "chroot",       1, NULL, 'r'},
	{ "exec",         1, NULL, 'x'},
	{ "stdout",       1, NULL, '1'},
	{ "stderr",       1, NULL, '2'},
	longopts_COMMON
};
static const char * const longopts_help[] = {
	"Stop daemon",
	"Set a nicelevel when starting",
	"Retry schedule to use when stopping",
	"Start daemon",
	"deprecated, use --exec",
	"Force daemon to background",
	"deprecated, use --user",
	"Change the PWD",
	"Change the process group",
	"Create a pidfile",
	"Match process name",
	"deprecated",
	"Match pid found in this file",
	"Send a different signal",
	"Test actions, don't do them",
	"Change the process user",
	"Chroot to this directory",
	"Binary to start/stop",
	"Redirect stdout to file",
	"Redirect stderr to file",
	longopts_help_COMMON
};
#include "_usage.c"

int start_stop_daemon (int argc, char **argv)
{
	int devnull_fd = -1;
#ifdef TIOCNOTTY
	int tty_fd = -1;
#endif

#ifdef HAVE_PAM
	pam_handle_t *pamh = NULL;
	int pamr;
#endif

	int opt;
	bool start = false;
	bool stop = false;
	bool oknodo = false;
	bool test = false;
	bool quiet;
	bool verbose = false;
	char *exec = NULL;
	char *cmd = NULL;
	char *pidfile = NULL;
	int sig = SIGTERM;
	int nicelevel = 0;
	bool background = false;
	bool makepidfile = false;
	uid_t uid = 0;
	gid_t gid = 0;
	char *ch_root = NULL;
	char *ch_dir = NULL;
	int tid = 0;
	char *redirect_stderr = NULL;
	char *redirect_stdout = NULL;
	int stdout_fd;
	int stderr_fd;
	pid_t pid;
	int i;
	char *svcname = getenv ("SVCNAME");
	char *env;

	applet = basename_c (argv[0]);
	atexit (cleanup);

	signal (SIGINT, handle_signal);
	signal (SIGQUIT, handle_signal);
	signal (SIGTERM, handle_signal);

	if ((env = getenv ("SSD_NICELEVEL")))
		if (sscanf (env, "%d", &nicelevel) != 1)
			eerror ("%s: invalid nice level `%s' (SSD_NICELEVEL)", applet, env);

	while ((opt = getopt_long (argc, argv, getoptstring, longopts,
							   (int *) 0)) != -1)
		switch (opt) {
			case 'K':  /* --stop */
				stop = true;
				break;

			case 'N':  /* --nice */
				if (sscanf (optarg, "%d", &nicelevel) != 1)
					eerrorx ("%s: invalid nice level `%s'", applet, optarg);
				break;

			case 'R':  /* --retry <schedule>|<timeout> */
				parse_schedule (optarg, sig);
				break;

			case 'S':  /* --start */
				start = true;
				break;

			case 'b':  /* --background */
				background = true;
				break;

			case 'u':  /* --user <username>|<uid> */
			case 'c':  /* --chuid <username>|<uid> */
				{
					char *p = optarg;
					char *cu = strsep (&p, ":");
					struct passwd *pw = NULL;

					changeuser = xstrdup (cu);
					if (sscanf (cu, "%d", &tid) != 1)
						pw = getpwnam (cu);
					else
						pw = getpwuid (tid);

					if (! pw)
						eerrorx ("%s: user `%s' not found", applet, cu);
					uid = pw->pw_uid;
					if (! gid)
						gid = pw->pw_gid;

					if (p) {
						struct group *gr = NULL;
						char *cg = strsep (&p, ":");

						if (sscanf (cg, "%d", &tid) != 1)
							gr = getgrnam (cg);
						else
							gr = getgrgid (tid);

						if (! gr)
							eerrorx ("%s: group `%s' not found", applet, cg);
						gid = gr->gr_gid;
					}
				}
				break;

			case 'd':  /* --chdir /new/dir */
				ch_dir = optarg;
				break;

			case 'g':  /* --group <group>|<gid> */
				{
					struct group *gr = getgrnam (optarg);

					if (sscanf (optarg, "%d", &tid) != 1)
						gr = getgrnam (optarg);
					else
						gr = getgrgid (tid);

					if (! gr)
						eerrorx ("%s: group `%s' not found", applet, optarg);
					gid = gr->gr_gid;
				}
				break;

			case 'm':  /* --make-pidfile */
				makepidfile = true;
				break;

			case 'n':  /* --name <process-name> */
				cmd = optarg;
				break;

			case 'o':  /* --oknodo */
				oknodo = true;
				break;

			case 'p':  /* --pidfile <pid-file> */
				pidfile = optarg;
				break;

			case 's':  /* --signal <signal> */
				sig = parse_signal (optarg);
				break;

			case 't':  /* --test */
				test = true;
				break;

			case 'r':  /* --chroot /new/root */
				ch_root = optarg;
				break;

			case 'a':
			case 'x':  /* --exec <executable> */
				exec = optarg;
				break;

			case '1':   /* --stdout /path/to/stdout.lgfile */
				redirect_stdout = optarg;
				break;

			case '2':  /* --stderr /path/to/stderr.logfile */
				redirect_stderr = optarg;
				break;

			case_RC_COMMON_GETOPT
		}

	quiet = rc_yesno (getenv ("EINFO_QUIET"));
	verbose = rc_yesno (getenv ("EINFO_VERBOSE"));

	/* Allow start-stop-daemon --signal HUP --exec /usr/sbin/dnsmasq
	 * instead of forcing --stop --oknodo as well */
	if (! start && ! stop)
		if (sig != SIGINT &&
			sig != SIGTERM &&
			sig != SIGQUIT &&
			sig != SIGKILL)
		{
			oknodo = true;
			stop = true;
		}

	if (start == stop)
		eerrorx ("%s: need one of --start or --stop", applet);

	if (start && ! exec)
		eerrorx ("%s: --start needs --exec", applet);

	if (stop && ! exec && ! pidfile && ! cmd && ! uid)
		eerrorx ("%s: --stop needs --exec, --pidfile, --name or --user", applet);

	if (makepidfile && ! pidfile)
		eerrorx ("%s: --make-pidfile is only relevant with --pidfile", applet);

	if (background && ! start)
		eerrorx ("%s: --background is only relevant with --start", applet);

	if ((redirect_stdout || redirect_stderr) && ! background)
		eerrorx ("%s: --stdout and --stderr are only relevant with --background",
				 applet);

	argc -= optind;
	argv += optind;

	/* Validate that the binary exists if we are starting */
	if (exec && start) {
		char *tmp;
		if (ch_root)
			tmp = rc_strcatpaths (ch_root, exec, (char *) NULL);
		else
			tmp = exec;
		if (! exists (tmp)) {
			eerror ("%s: %s does not exist", applet, tmp);
			if (ch_root)
				free (tmp);
			exit (EXIT_FAILURE);
		}
		if (ch_root)
			free (tmp);
	}

	if (stop) {
		int result;

		if (! schedule) {
			if (test || oknodo)
				parse_schedule ("0", sig);
			else
				parse_schedule (NULL, sig);
		}

		result = run_stop_schedule (exec, cmd, pidfile, uid, quiet, verbose, test);
		if (test || oknodo)
			return (result > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
		if (result < 1)
			exit (result == 0 ? EXIT_SUCCESS : EXIT_FAILURE);

		if (pidfile && exists (pidfile))
			unlink (pidfile);

		if (svcname)
			rc_service_daemon_set (svcname, exec, cmd, pidfile, false);

		exit (EXIT_SUCCESS);
	}

	if (do_stop (exec, cmd, pidfile, uid, 0, true, false, true) > 0)
		eerrorx ("%s: %s is already running", applet, exec);

	if (test) {
		if (quiet)
			exit (EXIT_SUCCESS);

		einfon ("Would start %s", exec);
		while (argc-- > 0)
			printf("%s ", *argv++);
		printf ("\n");
		eindent ();
		if (uid != 0)
			einfo ("as user id %d", uid);
		if (gid != 0)
			einfo ("as group id %d", gid);
		if (ch_root)
			einfo ("in root `%s'", ch_root);
		if (ch_dir)
			einfo ("in dir `%s'", ch_dir);
		if (nicelevel != 0)
			einfo ("with a priority of %d", nicelevel);
		eoutdent ();
		exit (EXIT_SUCCESS);
	}

	/* Ensure this is unset, so if the daemon does /etc/init.d/foo
	   Then we filter the environment accordingly */
	unsetenv ("RC_SOFTLEVEL");

	if (verbose) {
		ebegin ("Detaching to start `%s'", exec);
		eindent ();
	}

	if (background)
		signal (SIGCHLD, handle_signal);

	*--argv = exec;
	if ((pid = fork ()) == -1)
		eerrorx ("%s: fork: %s", applet, strerror (errno));

	/* Child process - lets go! */
	if (pid == 0) {
		pid_t mypid = getpid ();

#ifdef TIOCNOTTY
		tty_fd = open("/dev/tty", O_RDWR);
#endif

		devnull_fd = open("/dev/null", O_RDWR);

		if (nicelevel) {
			if (setpriority (PRIO_PROCESS, mypid, nicelevel) == -1)
				eerrorx ("%s: setpritory %d: %s", applet, nicelevel,
						 strerror(errno));
		}

		if (ch_root && chroot (ch_root) < 0)
			eerrorx ("%s: chroot `%s': %s", applet, ch_root, strerror (errno));

		if (ch_dir && chdir (ch_dir) < 0)
			eerrorx ("%s: chdir `%s': %s", applet, ch_dir, strerror (errno));

		if (makepidfile && pidfile) {
			FILE *fp = fopen (pidfile, "w");
			if (! fp)
				eerrorx ("%s: fopen `%s': %s", applet, pidfile, strerror
						 (errno));
			fprintf (fp, "%d\n", mypid);
			fclose (fp);
		}

#ifdef HAVE_PAM
		if (changeuser != NULL)
			pamr = pam_start ("start-stop-daemon", changeuser, &conv, &pamh);
		else
			pamr = pam_start ("start-stop-daemon", "nobody", &conv, &pamh);

		if (pamr == PAM_SUCCESS)
			pamr = pam_authenticate (pamh, PAM_SILENT);
		if (pamr == PAM_SUCCESS)
			pamr = pam_acct_mgmt (pamh, PAM_SILENT);
		if (pamr == PAM_SUCCESS)
			pamr = pam_open_session (pamh, PAM_SILENT);
		if (pamr != PAM_SUCCESS)
			eerrorx ("%s: pam error: %s", applet, pam_strerror(pamh, pamr));
#endif

		if (gid && setgid (gid))
			eerrorx ("%s: unable to set groupid to %d", applet, gid);
		if (changeuser && initgroups (changeuser, gid))
			eerrorx ("%s: initgroups (%s, %d)", applet, changeuser, gid);
		if (uid && setuid (uid))
			eerrorx ("%s: unable to set userid to %d", applet, uid);
		else {
			struct passwd *passwd = getpwuid (uid);
			if (passwd) {
				unsetenv ("HOME");
				if (passwd->pw_dir)
					setenv ("HOME", passwd->pw_dir, 1);
				unsetenv ("USER");
				if (passwd->pw_name)
					setenv ("USER", passwd->pw_name, 1);
			}
		}

		/* Close any fd's to the passwd database */
		endpwent ();

#ifdef TIOCNOTTY
		ioctl(tty_fd, TIOCNOTTY, 0);
		close(tty_fd);
#endif

		/* Clean the environment of any RC_ variables */
		STRLIST_FOREACH (environ, env, i) {
			if (strncmp (env, "RC_", 3) == 0 ||
				strncmp (env, "SSD_NICELEVEL=", strlen ("SSD_NICELEVEL=")) == 0)
				continue;

			/* For the path, remove the rcscript bin dir from it */
			if (strncmp (env, "PATH=", 5) == 0) {
				char *path = xstrdup (env);
				char *newpath = NULL;
				char *p = path;
				char *token;
				char *np;
				int l;
				int t;

				p += 5;
				while ((token = strsep (&p, ":"))) {
					if (strcmp (token, RC_LIBDIR "/bin") == 0 ||
						strcmp (token, RC_LIBDIR "/sbin") == 0)
						continue;
					
					t = strlen (token);
					if (newpath) {
						l = strlen (newpath);
						newpath = xrealloc (newpath, sizeof (char) * (l + t + 2));
						np = newpath + l;
						*np++ = ':';
						memcpy (np, token, sizeof (char) * strlen (token));
						np += t;
						*np = '\0';	
					} else {
						l = strlen ("PATH=") + t + 1;
						newpath = xmalloc (sizeof (char) * l);
						snprintf (newpath, l, "PATH=%s", token);
					}
				}
				rc_strlist_add (&newenv, newpath);
				free (path);
				free (newpath);
			} else
				rc_strlist_add (&newenv, env);
		}

		umask (022);

		stdout_fd = devnull_fd;
		stderr_fd = devnull_fd;
		if (redirect_stdout) {
			if ((stdout_fd = open (redirect_stdout, O_WRONLY | O_CREAT | O_APPEND,
								   S_IRUSR | S_IWUSR)) == -1)
				eerrorx ("%s: unable to open the logfile for stdout `%s': %s",
						 applet, redirect_stdout, strerror (errno));
		}
		if (redirect_stderr) {
			if ((stderr_fd = open (redirect_stderr, O_WRONLY | O_CREAT | O_APPEND,
								   S_IRUSR | S_IWUSR)) == -1)
				eerrorx ("%s: unable to open the logfile for stderr `%s': %s",
						 applet, redirect_stderr, strerror (errno));
		}

		if (background) {
			/* Hmmm, some daemons may need stdin? */
			dup2 (devnull_fd, STDIN_FILENO);
			dup2 (stdout_fd, STDOUT_FILENO);
			dup2 (stderr_fd, STDERR_FILENO);
		}

		for (i = getdtablesize () - 1; i >= 3; --i)
			close(i);

		setsid ();

		execve (exec, argv, newenv);
#ifdef HAVE_PAM
		if (pamr == PAM_SUCCESS)
			pam_close_session (pamh, PAM_SILENT);
#endif
		eerrorx ("%s: failed to exec `%s': %s", applet, exec, strerror (errno));
	}

	/* Parent process */
	if (! background) {
		/* As we're not backgrounding the process, wait for our pid to return */
		int status = 0;
		int savepid = pid;

		errno = 0;
		do {
			pid = waitpid (savepid, &status, 0);
			if (pid < 1) {
				eerror ("waitpid %d: %s", savepid, strerror (errno));
				return (-1);
			}
		} while (! WIFEXITED (status) && ! WIFSIGNALED (status));

		if (! WIFEXITED (status) || WEXITSTATUS (status) != 0) {
			if (! quiet)
				eerrorx ("%s: failed to start `%s'", applet, exec);
			exit (EXIT_FAILURE);
		}

		pid = savepid;
	}

	/* Wait a little bit and check that process is still running
	   We do this as some badly written daemons fork and then barf */
	if (START_WAIT > 0) {
		struct timespec ts;
		int nloops = START_WAIT / POLL_INTERVAL;
		int nloopsp = WAIT_PIDFILE / POLL_INTERVAL;
		bool alive = false;
		
		ts.tv_sec = 0;
		ts.tv_nsec = POLL_INTERVAL;

		while (nloops) {
			if (nanosleep (&ts, NULL) == -1) {
				if (errno == EINTR)
					eerror ("%s: caught an interrupt", applet);
				else {
					eerror ("%s: nanosleep: %s", applet, strerror (errno));
					return (0);
				}
			}

			/* We wait for a specific amount of time for a pidfile to be
			 * created. Once everything is in place we then wait some more
			 * to ensure that the daemon really is running and won't abort due
			 * to a config error. */
			if (! background && pidfile && nloopsp)
				nloopsp --;
			else
				nloops --;

			/* This is knarly.
			   If we backgrounded then we know the exact pid.
			   Otherwise if we have a pidfile then it *may* know the exact pid.
			   Failing that, we'll have to query processes.
			   We sleep first as some programs like ntp like to fork, and write
			   their pidfile a LONG time later. */
			if (background) {
				if (kill (pid, 0) == 0)
					alive = true;
			} else {
				if (pidfile) {
					/* The pidfile may not have been written yet - give it some time */
					if (get_pid (pidfile, true) == -1) {
						if (! nloopsp)
							eerrorx ("%s: did not create a valid pid in `%s'",
									 applet, pidfile);
						alive = true;
					} else
						nloopsp = 0;
				}
				if (do_stop (exec, cmd, pidfile, uid, 0, true, false, true) > 0)
					alive = true;
			}

			if (! alive)
				eerrorx ("%s: %s died", applet, exec);
		}
	}

	if (svcname)
		rc_service_daemon_set (svcname, exec, cmd, pidfile, true);

	exit (EXIT_SUCCESS);
}