/*

** SellaNMS
** $Id: main.cxx,v 2.45 2006/01/24 07:08:09 sella Exp $
** Copyright (c) 2001-2006 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: main.cxx,v 2.45 2006/01/24 07:08:09 sella Exp $";

#include "main.h"
#include "api.h"
#include "configuration/flatfile.h"
#ifndef _GNU_SOURCE
	#define _GNU_SOURCE
#endif
#include "common/nprocs.h"
#include <getopt.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <ctype.h>
#include <dlfcn.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <getopt.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <stl/string>
#include <stl/vector>

#ifdef HAVE_NET_SNMP
   #include <net-snmp/net-snmp-config.h>
   #include <net-snmp/net-snmp-includes.h>
   #include <net-snmp/library/parse.h>
#else
   #include <ucd-snmp/ucd-snmp-config.h>
   #include <ucd-snmp/ucd-snmp-includes.h>
   #include <ucd-snmp/parse.h>
#endif

#ifdef HAVE_SETPROCTITLE
	#include "common/setproctitle.h"
#endif

struct project_t {
	string title;
	string version;
	string daemon;
	string proctitle;
} project;

bool interrupt = false;
bool restart = false;

struct args_t {
	bool test;
	bool usage;
	bool about;
	bool version;
	bool foreground;

	int nice;
	int debug;
	mode_t umask;
	int facility;
	int uid;
	int gid;
	rlim_t core_dump;

	char group[32];
	char user[32];
	char log[255];
	char chroot[255];
	char pid_file[255];
	char stat_file[255];
	char config_file[255];
	char config_path[64];
	char watchdog_file[255];
} args;

ConfigNode *config = NULL;
vector<string> storage_v;
vector<string> daemon_dso_v;
slist<void*> supportLibs;
bool skip_mibs = false;

pthread_mutex_t config_mutex;

void main_sighandler(int sig) {
	static int kill_count = 0;
	static time_t last_interrupt = 0;

	time_t now = time(NULL);
	if (last_interrupt >= now - MAIN_INTERRUPT_WAIT) {
		return; /* Skip duplicate interrupt. */
	} else {
		last_interrupt = now;
	}

	switch (sig) {
		case SIGTSTP: /* Ctrl-Z to trigger SIGHUP when in foreground/debugging. */
		case SIGHUP:
			LMSG("%s rereading configuration...", project.daemon.c_str());
#ifdef HAVE_SETPROCTITLE
		   setproctitle(project.daemon.c_str(), "restarting");
#endif
			interrupt = true;
			restart = true;
			kill_count = 0;

			break;

		case SIGINT: /* Ctrl-C to trigger SIGTERM when in foreground/debugging. */
			if (++kill_count >= 2) { /* 2 Ctrl-C terminates immediately. */
				LMSG("%s forcefully terminated (2x Ctrl-C)", project.daemon.c_str());
				exit(1);
			}
		case SIGTERM:
			LMSG("%s shutting down...", project.daemon.c_str());
#ifdef HAVE_SETPROCTITLE
		   setproctitle(project.proctitle.c_str(), "shutdown");
#endif
			interrupt = true;

			break;

		case SIGUSR1:
			if (++args.debug > 9) {
				args.debug = 0;
			}
			LMSG("debug level now %d", args.debug);
			processDebug();

			break;

		case SIGUSR2:
			LMSG("caught SIGUSR2 - unused");

			break;

		default:
			LMSG("caught unknown signal: %d", sig);
	}
}

int main(int argc, char** argv) {
	bool ret_val;
	pid_t pid;

	TRACE_INIT;

	project.title = DGS_PROJECT;
	project.version = DGS_VERSION;
	project.daemon = getName();
	project.proctitle = getProcTitle();

	openlog(NULL, LOG_PID | LOG_PERROR, MAIN_SYSLOG_FACILITY_DEF);

	if (processCommandLine(argc, argv) == FAILURE || args.usage) {
		usage();

		return EXIT_FAILURE;
	}

	if (args.about) {
		about();

		return EXIT_SUCCESS;
	}

	if (args.version) {
		version();

		return EXIT_SUCCESS;
	}

	if (checkEuidRoot() == FAILURE) {
		fprintf(stderr, "%s must be run as root\n", project.daemon.c_str());

		return EXIT_FAILURE;
	}

	if (args.test) {
		if (readConfig() == SUCCESS) {
			fprintf(stderr, "SUCCESS: Configuration contained no syntax errors\n");

			return EXIT_SUCCESS;
		} else {
			fprintf(stderr, "FAILED: Configuration contains syntax errors\n");

			return EXIT_FAILURE;
		}
	}

	if (readConfig() == FAILURE) {
		fprintf(stderr, "configuration file '%s' contains syntax errors\n", args.config_file);

		return EXIT_FAILURE;
	}

	if (processTraceOptions() == FAILURE) {
		fprintf(stderr, "failed to process trace-options\n");

		return EXIT_FAILURE;
	}

	if (processDaemonOptions() == FAILURE) {
		fprintf(stderr, "failed to process command line arguments\n");

		return EXIT_FAILURE;
	}

	if (strlen(args.watchdog_file) > 0) {
		/* write pid in watchdog file. */
		writePidFile(args.watchdog_file, getpid());
	}
   
	if (args.foreground == false) {
		pid = fork();
		if (pid == -1) {
			fprintf(stderr, "failed to fork process\n");

			return EXIT_FAILURE;
		} else if (pid > 0) {
			return EXIT_SUCCESS;
		}
		fclose(stdin); /* Will no longer accept input (ie: Ctrl-C) */
		fclose(stdout); /* Our output to stdout will go unheard. */
		fclose(stderr); /* Our output to stderr will go unheard. */

		LMSG("%s forked to background - pid is %d", project.daemon.c_str(), getpid());
	} else {
		/* Register Ctrl-C and Ctrl-Z for debugging. */
		if (signal(SIGINT, &main_sighandler) == SIG_ERR) {
			fprintf(stderr, "failed to install signal handler for SIGINT.\n");
		}
		if (signal(SIGTSTP, &main_sighandler) == SIG_ERR) {
			fprintf(stderr, "failed to install signal handler for SIGTSTP.\n");
		}
	}

#ifdef HAVE_SETPROCTITLE
   initproctitle(argc, argv);
   setproctitle(project.proctitle.c_str(), "starting");
#endif

	pthread_mutex_init(&config_mutex, NULL);

	/* Install Signal Handlers */
	if (signal(SIGTERM, &main_sighandler) == SIG_ERR) {
		fprintf(stderr, "failed to install signal handler for SIGTERM\n");
	}
	if (signal(SIGHUP, &main_sighandler) == SIG_ERR) {
		fprintf(stderr, "failed to install signal handler for SIGHUP\n");
	}
	if (signal(SIGUSR1, &main_sighandler) == SIG_ERR) {
		fprintf(stderr, "failed to install signal handler for SIGUSR1\n");
	}
	if (signal(SIGUSR2, &main_sighandler) == SIG_ERR) {
		fprintf(stderr, "failed to install signal handler for SIGUSR2\n");
	}

	loadMibs();

	/* Main loop. */
	do {
		interrupt = false;
		restart = false;

#ifdef HAVE_SETPROCTITLE
		setproctitle(project.proctitle.c_str(), "running");
#endif
		LMSG("%s startup complete", project.daemon.c_str());

		dropPriv();
		start();
		grantPriv();

		if (restart) { /* Reread config for HUP */
			if (readConfig() == SUCCESS) {
				if (processTraceOptions() == FAILURE) {
					LMSG("failed to process trace-options");
				}
			} else {
				LMSG("configuration contains syntax errors - continuing with previous configuration");
			}
		}
	} while (restart);

	unloadMibs();

	pthread_mutex_destroy(&config_mutex);

	SAFE_DELETE(config);

	LMSG("%s shutdown complete", project.daemon.c_str());

	snmp_disable_syslog();
	closelog();

	TRACE_FREE;

	return (ret_val ? EXIT_SUCCESS : EXIT_FAILURE);
}

void unloadMibs() {
	if (skip_mibs) {
		return;
	}

	unload_all_mibs();
	snmp_shutdown(project.daemon.c_str());
}

void loadMibs() {
	string str;
	vector<string> *v = NULL;
	vector<string>::iterator it;

	if (skip_mibs) {
		return;
	}

	/* Setup the SNMP library */
	LMSG("snmp mibs loading...");

	if (config->isNode("system snmp-mib debug")) {
		snmp_set_do_debugging(1);
	}

	if (config->assignValues("system snmp-mib", v)) {
		/* Add additional MIB directories. */
		for (it = v->begin(); it != v->end(); ++it) {
			if (it->compare("debug")) {
				str.append(*it + ":");
			}
		}
		delete v;
	}

	setenv("MIBDIRS", str.c_str(), 0); /* setenv("MIBS", ":", 0); setenv("MIBFILES", ":", 0); */
#ifdef HAVE_NET_SNMP
	netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_MIBDIRS, str.c_str());
#endif

	init_snmp(project.daemon.c_str());

	if (config->isNode("system snmp-mib debug")) {
		LMSG("calling to init_mib()");
	}
	init_mib(); /* Setup internal MIB structures */

	if (config->isNode("system snmp-mib debug")) {
		LMSG("calling to read_all_mibs()");
	}
	read_all_mibs(); /* Actually reads in the MIBs at this point. */
	snmp_set_do_debugging(0);

	LMSG("snmp mibs loaded");
}

RESULT version(void) {
	fprintf(stdout, "%s v%s (%s)\n\n", project.title.c_str(), project.version.c_str(), project.daemon.c_str());
	fprintf(stdout, "Copyright (c) 2000-2006, Digital Genesis Software.  All rights reserved.\n");
	fprintf(stdout, "This program is released under the GNU Public License, version 2.\n\n");

	return SUCCESS;
}

RESULT about(void) {
	fprintf(stdout, "%s v%s (%s)\n\n", project.title.c_str(), project.version.c_str(), project.daemon.c_str());
	fprintf(stdout, "Credits:\n");
	fprintf(stdout, "James M. Sella/DGS - Design, implementation and documentation.\n");
	fprintf(stdout, "William T. Sella/DGS - Design, implementation and documentation.\n\n");

	return SUCCESS;
}

RESULT usage() {
	fprintf(stdout, "\n");
	fprintf(stdout, "Usage: %s [OPTIONS]\n", project.daemon.c_str());
	fprintf(stdout, "\n");
	fprintf(stdout, "  Informational\n");
	fprintf(stdout, "    -h, --help          Display this help screen\n");
	fprintf(stdout, "    -v, --version       Display version information\n");
	fprintf(stdout, "    -a, --about         Display credit information\n");
	fprintf(stdout, "\n");
	fprintf(stdout, "  Configuration\n");
	fprintf(stdout, "    -c, --config=FILE   Read configuration from FILE\n");
	fprintf(stdout, "    -C, --confpath=PATH Use PATH for configuration information.\n");
	fprintf(stdout, "    -t, --test          Test configuration and exit\n");
	fprintf(stdout, "\n");
	fprintf(stdout, "  System\n");
	fprintf(stdout, "    -f, --foreground    Execute daemon in foreground (no not fork)\n");
	fprintf(stdout, "    -u, --user=USER     Run as specified user\n");
	fprintf(stdout, "    -g, --group=GROUP   Run as specified group\n");
	fprintf(stdout, "    -n, --nice=INT      Set niceness to INTeger [-20,19]\n");
	fprintf(stdout, "    -m, --umask=MASK    Set umask to MASK\n");
	fprintf(stdout, "    -x, --coredump=UINT Set coredump size to UINT [0, ULONG_MAX]\n");
	fprintf(stdout, "    -r, --chroot=DIR    Change root to DIRectory\n");
	fprintf(stdout, "    -p, --pidfile=FILE  Write process id to FILE\n");
	fprintf(stdout, "    -s, --statfile=FILE Write status information to FILE\n");
	fprintf(stdout, "\n");
	fprintf(stdout, "  Output\n");
	fprintf(stdout, "    -F, --facility=FAC  Override facility with FACility\n");
	fprintf(stdout, "    -l, --logfile=FILE  Output logging information to FILE\n");
	fprintf(stdout, "    -d, --debug=INT     Set the debug level to INTeger [0,9]\n");
	fprintf(stdout, "\n");
	fprintf(stdout, "To report bugs, email sellanms@digitalgenesis.com.");
	fprintf(stdout, "\n");
	
	return SUCCESS;
}

RESULT checkEuidRoot() {
	if (geteuid() != 0) {
		return FAILURE;
	}
	return SUCCESS;
}

RESULT readConfig() {
	RESULT result;

	pthread_mutex_lock(&config_mutex);

	ConfigNode *tmp, *node = NULL;
	Configuration *config_mod = (Configuration*) new Flatfile();

	if ((result = config_mod->read(args.config_file)) == SUCCESS) {
		if (config_mod->get("", &(node)) == SUCCESS && node != NULL) {
			tmp = config;
			config = node;

			SAFE_DELETE(tmp);
		} else {
			result = FAILURE;
		}
	}

	SAFE_DELETE(config_mod);

	pthread_mutex_unlock(&config_mutex);

	return result;
}


RESULT processCommandLine(int argc, char **argv) {
	RESULT result = SUCCESS;

	extern char *optarg;

	int c;
	
	int longindex = 0;
	struct option longopts[] =
	{
		{"help",				0, 0, 'h'},
		{"version",			0, 0, 'v'},
		{"about",			0, 0, 'a'},
		{"config",			1, 0, 'c'},
		{"confpath",		1, 0, 'C'},
		{"test",				0, 0, 't'},
		{"foreground",		0, 0, 'f'},
		{"user",				1, 0, 'u'},
		{"group",			1, 0, 'g'},
		{"nice",				1, 0, 'n'},
		{"coredump",		1, 0, 'x'},
		{"umask",			1, 0, 'm'},
		{"chroot",			1, 0, 'r'},
		{"pidfile",			1, 0, 'p'},
		{"statfile",		1, 0, 's'},
		{"facility",		1, 0, 'F'},
		{"logfile",			1, 0, 'l'},
		{"debug",			1, 0, 'd'},
		{"watchdog-pid",	1, 0, 'W'},
		{0,					0, 0, 0}
	};

	memset(&args, 0, sizeof(args));

	/* Set defaults */
	strncpy(args.config_file, getDefaultConfigFile(), sizeof(args.config_file));
	strncpy(args.config_path, getDefaultConfigPath(), sizeof(args.config_path));
	strncpy(args.pid_file, getDefaultPidFile(), sizeof(args.pid_file));
	strncpy(args.stat_file, getDefaultStatFile(), sizeof(args.stat_file));
	args.nice = getDefaultNiceness();
	args.facility = MAIN_SYSLOG_FACILITY_DEF;
	args.core_dump = RLIM_INFINITY;

	while ((c = getopt_long(argc, argv, "hvac:C:tfu:g:n:x:m:r:p:s:l:d:W:", longopts, &longindex)) != -1) {
		switch (c) {
			case 'h':
				args.usage = true;
				break;

			case 'v':
				args.version = true;
				break;

			case 'a':
				args.about = true;
				break;

			case 'c':
				strncpy(args.config_file, optarg, sizeof(args.config_file));
				break;

			case 'C':
				strncpy(args.config_path, optarg, sizeof(args.config_path));
				break;

			case 't':
				args.test = true;
				break;

			case 'f':
				args.foreground = true;
				break;

			case 'n':
				args.nice = atol(optarg);
				break;

			case 'x':
				args.core_dump =  strtoul(optarg, NULL, 10);
				break;

			case 'm':
				args.umask = atol(optarg);
				break;

			case 'r':
				strncpy(args.chroot, optarg, sizeof(args.chroot));
				break;

			case 'p':
				strncpy(args.pid_file, optarg, sizeof(args.pid_file));
				break;

			case 's':
				strncpy(args.stat_file, optarg, sizeof(args.stat_file));
				break;

			case 'u':
				strncpy(args.user, optarg, sizeof(args.user));
				break;

			case 'g':
				strncpy(args.group, optarg, sizeof(args.group));
				break;

			case 'F':
				if (!strcasecmp(optarg, "user")) {
					args.facility = LOG_USER;
				} else if (!strcasecmp(optarg, "daemon")) {
					args.facility = LOG_DAEMON;
				} else if (!strcasecmp(optarg, "local0")) {
					args.facility = LOG_LOCAL0;
				} else if (!strcasecmp(optarg, "local1")) {
					args.facility = LOG_LOCAL1;
				} else if (!strcasecmp(optarg, "local2")) {
					args.facility = LOG_LOCAL2;
				} else if (!strcasecmp(optarg, "local3")) {
					args.facility = LOG_LOCAL3;
				} else if (!strcasecmp(optarg, "local4")) {
					args.facility = LOG_LOCAL4;
				} else if (!strcasecmp(optarg, "local5")) {
					args.facility = LOG_LOCAL5;
				} else if (!strcasecmp(optarg, "local6")) {
					args.facility = LOG_LOCAL6;
				} else if (!strcasecmp(optarg, "local7")) {
					args.facility = LOG_LOCAL7;
				} else if (!strcasecmp(optarg, "auth") || !strcasecmp(optarg, "security")) {
					args.facility = LOG_AUTH;
				} else if (!strcasecmp(optarg, "authpriv")) {
					args.facility = LOG_AUTHPRIV;
				} else if (!strcasecmp(optarg, "cron")) {
					args.facility = LOG_CRON;
				} else if (!strcasecmp(optarg, "ftp")) {
					args.facility = LOG_FTP;
				} else if (!strcasecmp(optarg, "kern")) {
					args.facility = LOG_KERN;
				} else if (!strcasecmp(optarg, "lpr")) {
					args.facility = LOG_LPR;
				} else if (!strcasecmp(optarg, "mail")) {
					args.facility = LOG_MAIL;
				} else if (!strcasecmp(optarg, "news")) {
					args.facility = LOG_NEWS;
				} else if (!strcasecmp(optarg, "uucp")) {
					args.facility = LOG_UUCP;
				} else if (strlen(optarg) <= 2 && isdigit(optarg[0])) {
					args.facility = atol(optarg);
					if (args.facility < 0 || args.facility > 23) {
						args.facility = MAIN_SYSLOG_FACILITY_DEF;
					}
				}
				break;

			case 'l':
				strncpy(args.log, optarg, sizeof(args.log));
				break;

			case 'd':
				args.debug = atol(optarg);
				break;

			case 'W':
				strncpy(args.watchdog_file, optarg, sizeof(args.watchdog_file));
				break;

			default:
				result = FAILURE;
		}
	}

	return result;
}

RESULT processTraceOptions() {
	ConfigNode node;
	const char *path = args.config_path;

	if (args.debug > 0) {
		processDebug();

		return SUCCESS;
	}

	if (!config->assignNode(path, node)) {
		fprintf(stderr, "module configuration not found at '%s'\n", path);

		return FAILURE;
	}

	TRACE_UNSET(TRACE_ALL);

	if (node.isNode("trace-options all")) { /* Everything except for 'internal' and 'dump'. */
		TRACE_SET(TRACE_CONFIG | TRACE_SQL | TRACE_POLICY_MAP | TRACE_POLICY_SQL | TRACE_POLICY_IN | TRACE_POLICY_OUT | TRACE_THREAD | TRACE_TIMER | TRACE_DEBUG);
	} else if (node.isNode("trace-options developer")) { /* Same as 'all', plus 'internal' and 'dump'. Very chatty. */
		TRACE_SET(TRACE_ALL);
	} else {
		if (node.isNode("trace-options config")) TRACE_SET(TRACE_CONFIG);
		if (node.isNode("trace-options sql")) TRACE_SET(TRACE_SQL);
		if (node.isNode("trace-options policy-map")) TRACE_SET(TRACE_POLICY_MAP);
		if (node.isNode("trace-options policy-sql")) TRACE_SET(TRACE_POLICY_SQL);
		if (node.isNode("trace-options policy")) TRACE_SET(TRACE_POLICY_IN | TRACE_POLICY_OUT);
		if (node.isNode("trace-options policy-in")) TRACE_SET(TRACE_POLICY_IN);
		if (node.isNode("trace-options policy-out")) TRACE_SET(TRACE_POLICY_OUT);
		if (node.isNode("trace-options thread")) TRACE_SET(TRACE_THREAD);
		if (node.isNode("trace-options timer")) TRACE_SET(TRACE_TIMER);
		if (node.isNode("trace-options debug")) TRACE_SET(TRACE_DEBUG);
		if (node.isNode("trace-options internal")) TRACE_SET(TRACE_INTERNAL);
		if (node.isNode("trace-options dump")) TRACE_SET(TRACE_DUMP);
	}

	/* Handle exceptions. */
	if (node.isNode("trace-options-except")) {
		if (node.isNode("trace-options-except config")) TRACE_UNSET(TRACE_CONFIG);
		if (node.isNode("trace-options-except sql")) TRACE_UNSET(TRACE_SQL);
		if (node.isNode("trace-options-except policy-map")) TRACE_UNSET(TRACE_POLICY_MAP);
		if (node.isNode("trace-options-except policy-sql")) TRACE_UNSET(TRACE_POLICY_SQL);
		if (node.isNode("trace-options-except policy")) TRACE_UNSET(TRACE_POLICY_IN | TRACE_POLICY_OUT);
		if (node.isNode("trace-options-except policy-in")) TRACE_UNSET(TRACE_POLICY_IN);
		if (node.isNode("trace-options-except policy-out")) TRACE_UNSET(TRACE_POLICY_OUT);
		if (node.isNode("trace-options-except thread")) TRACE_UNSET(TRACE_THREAD);
		if (node.isNode("trace-options-except timer")) TRACE_UNSET(TRACE_TIMER);
		if (node.isNode("trace-options-except debug")) TRACE_UNSET(TRACE_DEBUG);
		if (node.isNode("trace-options-except internal")) TRACE_UNSET(TRACE_INTERNAL);
		if (node.isNode("trace-options-except dump")) TRACE_UNSET(TRACE_DUMP);
	}

	return SUCCESS;
}

RESULT processDebug() {
	switch (args.debug) {
		default:
		case 0:
			TRACE_SET(DEBUGLEVEL_0);
			break;
		case 1:
			TRACE_SET(DEBUGLEVEL_1);
			break;
		case 2:
			TRACE_SET(DEBUGLEVEL_2);
			break;
		case 3:
			TRACE_SET(DEBUGLEVEL_3);
			break;
		case 4:
			TRACE_SET(DEBUGLEVEL_4);
			break;
		case 5:
			TRACE_SET(DEBUGLEVEL_5);
			break;
		case 6:
			TRACE_SET(DEBUGLEVEL_6);
			break;
		case 7:
			TRACE_SET(DEBUGLEVEL_7);
			break;
		case 8:
			TRACE_SET(DEBUGLEVEL_8);
			break;
		case 9:
			TRACE_SET(DEBUGLEVEL_9);
			break;
	}

	return SUCCESS;
}

RESULT processDaemonOptions() {
	RESULT result = SUCCESS;
	FILE *fp;
	int fd;
	long val;
	struct rlimit rl;
	struct passwd *ent = NULL;
	struct group *gent = NULL;

	/* Open connection to syslog */
	val = LOG_PID | ((args.foreground) ? LOG_PERROR : 0);
	closelog();
	snmp_enable_syslog();
	openlog(project.daemon.c_str(), val, args.facility);

	/* Announce that we're starting up.. */
	LMSG("%s (%s v%s) starting up...", project.daemon.c_str(), project.title.c_str(), project.version.c_str());
	LMSG("%lu CPUs detected", get_nprocs());

	if (args.nice != 0) {
		LMSG("setting niceness of %d", args.nice);
		setpriority(PRIO_PROCESS, 0, args.nice);
	}

	/* If euid is root, and a user was specified. */
	if (!geteuid() && strlen(args.user) > 0) {
		if (!(ent = getpwnam(args.user))) {
			result = FAILURE;
			LMSG("user '%s' not found in passwd file", args.user);
		} else {
			args.uid = ent->pw_uid;
			if (strlen(args.group) > 0 && (gent = getgrnam(args.group)) != NULL) {
				args.gid = gent->gr_gid;
			} else if ((gent = getgrgid(ent->pw_gid)) != NULL) {
				if (strlen(args.group) > 0) {
					LMSG("group '%s' not found in the group file", args.group);
				}

				strncpy(args.group, gent->gr_name, sizeof(args.group));
				args.gid = gent->gr_gid;
			} else {
				LMSG("group '%s' not found in the group file", args.group);
				strncpy(args.group, "unknown", sizeof(args.group));
				args.gid = ent->pw_gid;
			}

			/* Capture the uid and gid */
			if (result == SUCCESS) {
				LMSG("setting user to '%s' (uid: %lu) and group to '%s' (gid: %lu)", args.user, args.uid, args.group, args.gid);
			}
		}
	}

	/* Lock ourselves into a chroot jail, if requested. */
	if (strlen(args.chroot) > 0) {
		if (chdir(args.chroot) == -1 || chroot(args.chroot) == -1) {
			result = FAILURE;
			LMSG("failed to change root to '%s'", args.chroot);
		} else {
			LMSG("setting root directory of '%s'", args.chroot);
		}
	}

	/* Create a statfile */
	if (args.stat_file) {
		if (!(fp = fopen(args.stat_file, "w"))) {
			result = FAILURE;
			LMSG("failed to open statfile '%s'", args.stat_file);
		} else {
			if (ent != NULL && geteuid() == 0) {
				fd = fileno(fp);
				fchown(fd, geteuid(), ent->pw_gid);
				fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
			}
			fclose(fp);
		}
	}

	/* Set our umask */
	if (args.umask != 0) {
		umask((mode_t) args.umask);
		LMSG("setting umask to %04lu", args.umask);
	}

	/* Set core dump size - */
	rl.rlim_cur = rl.rlim_max = args.core_dump;
	setrlimit(RLIMIT_CORE, &rl);

	return result;
}

RESULT writePidFile(const char* pid_file, pid_t pid) {
	RESULT result = SUCCESS;
	FILE *fp;
	struct stat stat_buf;

	if (lstat(pid_file, &stat_buf) == 0) {
		if (!S_ISREG(stat_buf.st_mode)) {
			LMSG("existing pid file %s is not a regular file", pid_file);

			return FAILURE;
		} else if (stat_buf.st_size > 32) {
			LMSG("existing pid file %s is larger than expected (suspicious)", pid_file);

			return FAILURE;
		}
	}

	if (!(fp = fopen(pid_file, "w"))) {
		result = FAILURE;

		LMSG("failed to open pidfile '%s'", pid_file);
	} else {
		if (fprintf(fp, "%d", pid) < 1) {
			result = FAILURE;

			LMSG("failed write to pidfile '%s'", pid_file);
		}
		fflush(fp);
		fclose(fp);
	}

	return result;
}

RESULT grantPriv() {
	/* If uid and gid are not root (0) */
	if (getuid() != 0 && (args.uid == 0 || args.gid == 0)) {
		LMSG("non-root user attempting to set UID or GID to 0 (root).");

		return FAILURE;
	}

	TRACE(TRACE_INTERNAL, "setting owner to uid 0 gid 0");

	if (setegid(0)) {
		LMSG("failed to set GID to %d", args.gid);

		return FAILURE;
	} else if (seteuid(0)) {
		LMSG("failed to set UID to %d", args.uid);

		return FAILURE;
	}

	return SUCCESS;
}

RESULT dropPriv() {
	/* If uid and gid are not root (0) */
	if (getuid() != 0 && (args.uid == 0 || args.gid == 0)) {
		LMSG("non-root user attempting to set UID %d and GID %d.", args.uid, args.gid);

		return FAILURE;
	}

	TRACE(TRACE_INTERNAL, "setting owner to uid %d gid %d", args.uid, args.gid);

	if (setegid(args.gid)) {
		LMSG("failed to set GID to %d", args.gid);

		return FAILURE;
	} else if (seteuid(args.uid)) {
		LMSG("failed to set UID to %d", args.uid);

		return FAILURE;
	}

	return SUCCESS;
}

char* getConfigPath() {
	return args.config_path;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim:
** vim: noet ts=3 sw=3
*/
