/*-
 * Copyright (c) 1999-2004 Andrey Simonenko
 * 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.
 */

#include "config.h"

#ifndef lint
static const char rcsid[] ATTR_UNUSED =
  "@(#)$Id: ipa.c,v 1.2.2.6 2007/07/20 09:53:41 simon Exp $";
#endif /* !lint */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <sys/file.h>
#include <sys/queue.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <grp.h>
#include <pwd.h>
#include <unistd.h>

#ifdef HAVE_PATHS_H
# include <paths.h>
#endif
#ifndef _PATH_DEVNULL
# ifdef __GNUC__
#  warning "defining _PATH_DEVNULL to \"/dev/null\""
# endif
# define _PATH_DEVNULL "/dev/null"
#endif

#ifdef WITH_PTHREAD
# include <pthread.h>
#endif

#include "ipa_mod.h"

#include "dlapi.h"
#include "confcommon.h"
#include "memfunc.h"
#include "pathnames.h"

#include "ipa_ac.h"
#include "ipa_db.h"
#include "ipa_cmd.h"
#include "ipa_time.h"

#include "ipa_conf.h"
#include "ipa_ctl.h"
#include "ipa_log.h"
#include "ipa_main.h"
#include "ipa_rules.h"
#include "ipa_autorules.h"

#if defined(HAVE_CLOSEFROM) && (STDIN_FILENO > 2 || STDOUT_FILENO > 2 || STDERR_FILENO > 2)
# undef HAVE_CLOSEFROM
#endif

static const char *user_name = NULL;	/* -u <user> */
static const char *group_name = NULL;	/* -g <group> */

static const char *envprogname;

static const char *pid_file_name = IPA_PID_FILE; /* -p <pid-file> */
static int	pid_file_fd;

static int	nofile_max;		/* sysconf(_SC_OPEN_MAX) value. */

static const struct sig_tbl_entry {
	const char *signame;
	int	signo;
} sig_tbl[] = {
	{ "shutdown",		SIGTERM	},
	{ "reconfigure",	SIGHUP	},
	{ "kill",		SIGKILL	},
	{ "test",		0	},
	{ NULL,			0	}
};

#define PID_FILE_PERM	(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) /* 0644 */

static void	exit_err(const char *, ...) ATTR_NORETURN ATTR_FORMAT(printf, 1, 2);
static void	exit_errx(const char *, ...) ATTR_NORETURN ATTR_FORMAT(printf, 1, 2);

/*
 * Close and unlink PID-file.
 */
static void
unlink_pid_file(void)
{
	if (close(pid_file_fd) < 0)
		logmsg(IPA_LOG_ERR, "unlink_pid_file: close(%s)", pid_file_name);
	if (unlink(pid_file_name) < 0)
		logmsg(IPA_LOG_ERR, "unlink_pid_file: unlink(%s)", pid_file_name);
}

/*
 * Output the program name, a message, an error message and exit.
 */
static void
exit_err(const char *format, ...)
{
	va_list ap;
	int errno_save;

	errno_save = errno;
	fflush(stdout);
	fprintf(stderr, "%s: ", envprogname);
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
	if (errno_save != 0)
		fprintf(stderr, ": %s", strerror(errno_save));
	fprintf(stderr, "\n");
	exit(1);
}

/*
 * Output the program name, a message and exit.
 */
static void
exit_errx(const char *format, ...)
{
	va_list ap;

	fflush(stdout);
	fprintf(stderr, "%s: ", envprogname);
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
	fprintf(stderr, "\n");
	exit(1);
}

/*
 * Output version number and settings (-v and -h switches).
 */
static void
show_version(void)
{
	printf(IPA_NAME ", version " PACKAGE_VERSION "\nRuntime settings: "
#ifdef SYM_PREFIX
	"symbol prefix \"" SYM_PREFIX "\", "
#else
	"symbol prefix is not used, "
#endif
#ifdef WITH_PTHREAD
	"thread-safe mode, "
#else
	"single-threaded mode, "
#endif
#ifdef USE_LIBLTDL
	"using libtool's ltdl library, "
#else
	"using dlopen interface, "
#endif
#ifdef WITH_MEMFUNC_DEBUG
	"memfunc debugging is enabled, "
#else
	"memfunc debugging is disabled, "
#endif
	"using shell %s, using null device " _PATH_DEVNULL ".\nSupports:"
#ifdef WITH_AUTORULES
	" autorules"
#endif
#ifdef WITH_RULES
# ifdef WITH_AUTORULES
	","
# endif
	" rules"
#endif
#ifdef WITH_LIMITS
	", limits"
#endif
#ifdef WITH_SUBLIMITS
	", sublimits"
#endif
#ifdef WITH_THRESHOLDS
	", thresholds"
#endif
#ifdef CTL_CHECK_CREDS
	"; checking ipactl's messages credentials is enabled"
#else
	"; checking ipactl's messages credentials is disabled"
#endif
	".\n", shell_path_default);
}

/*
 * Output the help message (-h switch).
 */
static void
usage(void)
{
	show_version();
	printf("\
Usage: %s [options]\n\
 Utility for accounting\n\
 Options are:\n\
  -d\t\t\tDo not run in the background (debug mode)\n\
  -f <config-file>\tUse given config-file instead of using default\n\
\t\t\tconfiguration file "IPA_CONF_FILE"\n\
  -t\t\t\tCheck the configuration file (two switches mimic\n\
\t\t\treal configuration regime) and exit\n\
  -c <directory>\tSet the directory "IPA_NAME" should chroot(2) into\n\
\t\t\timmediately, working directory is not changed\n\
  -u <user>\t\tChange UID of the running copy of "IPA_NAME" to <user>\n\
  -g <group>\t\tChange GID of the running copy of "IPA_NAME" to <group>\n\
  -k <signal>\t\tSend signal to the running copy of ipa, <signal>\n\
\t\t\tcan be: reconfigure, shutdown, test or kill\n\
  -i <log-ident>\tUse given log-ident instead of using default \""IPA_LOG_IDENT"\"\n\
  -o <log-file>\t\tUse given log-file instead of using syslog\n\
  -p <pid-file>\t\tUse given pid-file instead of using default\n\
\t\t\tPID-file "IPA_PID_FILE"\n"
"  -x"
#ifdef WITH_RULES
" [-r <rule>"
# ifdef WITH_LIMITS
" [-l <limit>"
#  ifdef WITH_SUBLIMITS
" [-s <sublimit>]"
#  endif
"]"
# endif
# ifdef WITH_THRESHOLDS
" [-t <threshold>]"
# endif
"]\n    section"
# ifdef WITH_ANY_LIMITS
" [subsection]\n\t"
# endif
#else
" section"
#endif /* WITH_RULES */
"\t\tRun commands from the given section and exit\n\
  -h\t\t\tOutput this help message and exit\n\
  -v\t\t\tShow version number and exit\n",
	    envprogname);
}

/*
 * Ignore SIGTTOU, SIGTTIN, SIGTSTP and SIGPIPE and install sigmask
 * to block signals which can be received.  By default SIGCHLD is
 * ignored.
 */
static int
sig_init_1(void)
{
	struct sigaction sa;

	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;

	if (sigaction(SIGTTOU, &sa, (struct sigaction *)NULL) < 0) {
		logmsg(IPA_LOG_ERR, "sig_init_1: sigaction(SIGTTOU)");
		return -1;
	}
	if (sigaction(SIGTTIN, &sa, (struct sigaction *)NULL) < 0) {
		logmsg(IPA_LOG_ERR, "sig_init_1: sigaction(SIGTTIN)");
		return -1;
	}
	if (sigaction(SIGTSTP, &sa, (struct sigaction *)NULL) < 0) {
		logmsg(IPA_LOG_ERR, "sig_init_1: sigaction(SIGTSTP)");
		return -1;
	}

	/*
	 * Need this for ctl socket and for asynchronous running
	 * commands (in case when ipa exits before asynchronous
	 * running commands).
	 */
	if (sigaction(SIGPIPE, &sa, (struct sigaction *)NULL) < 0) {
		logmsg(IPA_LOG_ERR, "sig_init_1: sigaction(SIGPIPE)");
		return -1;
	}

	/* Set sigmask. */
	sigemptyset(&sigmask);
	if (debug)
		sigaddset(&sigmask, SIGINT);
	sigaddset(&sigmask, SIGHUP);
	sigaddset(&sigmask, SIGTERM);

	/*
	 * Signals SIGTERM (and SIGINT) and SIGHUP
	 * before next function are not handled properly.
	 */
	if (Sigprocmask(SIG_SETMASK, &sigmask, (sigset_t *)NULL) < 0) {
		logmsg(IPA_LOG_ERR, "sig_init_1: " SIGPROCMASK_NAME "(SIG_SETMASK)");
		return -1;
	}

	return 0;
}

/*
 * Add SIGCHLD to sigmask, initialize zeromask and set signal handlers.
 */
static int
sig_init_2(void)
{
	struct sigaction sa;

	/* Add SIGCHLD to sigmask. */
	sigaddset(&sigmask, SIGCHLD);
	if (Sigprocmask(SIG_SETMASK, &sigmask, (sigset_t *)NULL) < 0) {
		logmsg(IPA_LOG_ERR, "sig_init_2: "SIGPROCMASK_NAME "(SIG_SETMASK)");
		return -1;
	}

	/* Empty zeromask. */
	sigemptyset(&zeromask);

	/* Install signal handlers. */
	sa.sa_flags = 0;
	sa.sa_mask = sigmask;

	/* Set SIGTERM (and SIGINT) handlers. */
	sa.sa_handler = sig_term;
	if (debug)
		if (sigaction(SIGINT, &sa, (struct sigaction *)NULL) < 0) {
			logmsg(IPA_LOG_ERR, "sig_init_2: sigaction(SIGINT)");
			return -1;
		}
	if (sigaction(SIGTERM, &sa, (struct sigaction *)NULL) < 0) {
		logmsg(IPA_LOG_ERR, "sig_init_2: sigaction(SIGTERM)");
		return -1;
	}

	/* Set SIGHUP handler. */
	sa.sa_handler = sig_hup;
	if (sigaction(SIGHUP, &sa, (struct sigaction *)NULL) < 0) {
		logmsg(IPA_LOG_ERR, "sig_init_2: sigaction(SIGHUP)");
		return -1;
	}

	if (ctl_enable) {
		/*
		 * Set SIGALRM handler, note that this signal is not in
		 * sigmask, since we send and receive it synchronously.
		 */
		sa.sa_handler = sig_alrm;
		if (sigaction(SIGALRM, &sa, (struct sigaction *)NULL) < 0) {
			logmsg(IPA_LOG_ERR, "sig_init_2: sigaction(SIGALRM)");
			return -1;
		}
	}

	/* Set SIGCHLD handler. */
	sa.sa_handler = sig_chld;
	sa.sa_flags = SA_NOCLDSTOP;
	if (sigaction(SIGCHLD, &sa, (struct sigaction *)NULL) < 0) {
		logmsg(IPA_LOG_ERR, "sig_init_2: sigaction(SIGCHLD)");
		return -1;
	}

	return 0;
}

/*
 * Send a signal signo to the running copy of ipa(8).
 * Check if the file pid_file_name is locked and if it is locked,
 * then send a signal to the PID returned in l_pid field of
 * struct flock{}.
 */
static void
runcopy_sendsig(int signo)
{
	struct flock lock;
	int fd;

	if ( (fd = open(pid_file_name, O_RDONLY)) < 0)
		exit_err("runcopy_sendsig: open(%s, O_RDONLY)", pid_file_name);

	lock.l_start = 0;		/* Zero offset from... */
	lock.l_len = 0;			/* Entire file. */
	lock.l_type = F_WRLCK;		/* Should be valid value. */
	lock.l_whence = SEEK_SET;	/* ... the start of the file. */

	if (fcntl(fd, F_GETLK, &lock) < 0) {
#ifdef __CYGWIN__
		if (errno == ENOSYS)
			exit_errx("On your system the -k option cannot be implemented in a safe way");
#endif
		exit_err("runcopy_sendsig: fcntl(%s, F_GETLK)",
		    pid_file_name);
	}

	if (signo != 0) {
		switch (lock.l_type) {
		case F_UNLCK:
			exit_errx("runcopy_sendsig: file %s is not locked, do not send any signal",
			    pid_file_name);
			/* NOTREACHED */
		case F_RDLCK:
			exit_errx("runcopy_sendsig: file %s is lock, but lock is shared, do not send any signal",
			    pid_file_name);
		}

		printf("Sending signal %d to PID %ld...", signo, (long)lock.l_pid);

		if (kill(lock.l_pid, signo) < 0) {
			printf("\n");
			exit_err("runcopy_sendsig: kill(PID %ld, %d)",
			    (long)lock.l_pid, signo);
		}
		printf(" done\n");
	} else {
		if (lock.l_type == F_UNLCK)
			exit_err("File %s exists, but is not locked",
			    pid_file_name);
		else
			printf("File %s is locked (%s) by PID %ld\n",
			    pid_file_name,
			    lock.l_type == F_WRLCK ? "exclusive" : "shared",
			    (long)lock.l_pid);
	}
}

/*
 * Run in the background.
 */
static int
bg_init(void)
{
	pid_t childpid;

	if ( (childpid = fork()) == (pid_t)-1) {
		logmsg(IPA_LOG_ERR, "bg_init: fork");
		return -1;
	}

	if (childpid != 0)
		exit(0);	/* Parent exits. */

	/* Child continues. */
	setsid();
	return 0;
}

/*
 * Create pipe, if any of pipe's ends is stdin, stdout or stderr,
 * then create another pipe and so on.  Then make the read-end of
 * a pipe as nonblockable, dup2 the write-end of a pipe to fd_write
 * and mark fd_write as nonblockable, also mark the read-end of
 * a pipe as nonblockable and return its value in *fd_read.  Read
 * also comment for fd_init().
 */
static int
pipe_stream(int fd_write, int *fd_read)
{
	u_int i, j;
	int fd0, fd1, fd[6];

	/*
	 * Pipe's ends should not be equal to file descriptor of stdin,
	 * stdout or stderr.  SUSv3 claims that pipe(2) returns lowest
	 * available descriptors, so three (each with two descriptors)
	 * attempts are enough to get desired descriptor.
	 */
	for (i = j = 0; i < 3; j += 2, ++i) {
		if (pipe(fd + j) < 0) {
			logmsg(IPA_LOG_ERR, "pipe_stream: pipe");
			return -1;
		}
		fd0 = fd[j];
		fd1 = fd[j + 1];
		if (fd0 != STDIN_FILENO && fd0 != STDOUT_FILENO && fd0 != STDERR_FILENO &&
		    fd1 != STDIN_FILENO && fd1 != STDOUT_FILENO && fd1 != STDERR_FILENO)
			break;
		if (i == 2) {
			logmsgx(IPA_LOG_ERR, "pipe_stream: something is wrong in algorithm");
			return -1;
		}
	}

	/* Close "incorrectly" opened file descriptors. */
	while (j > 0) {
		j -= 2;
		if (close(fd[j]) < 0) {
			logmsg(IPA_LOG_ERR, "pipe_stream: close(%d)", fd[j]);
			return -1;
		}
		if (close(fd[j + 1]) < 0) {
			logmsg(IPA_LOG_ERR, "pipe_stream: close(%d)", fd[j + 1]);
			return -1;
		}
	}

	if (dup2(fd1, fd_write) < 0) {
		logmsg(IPA_LOG_ERR, "pipe_stream: dup2(%d, %d)", fd1, fd_write);
		return -1;
	}
	if (close(fd1) < 0) {
		logmsg(IPA_LOG_ERR, "pipe_stream: close(%d)", fd1);
		return -1;
	}

	if (set_nonblock(fd0) < 0 || set_nonblock(fd_write) < 0) {
		logmsgx(IPA_LOG_ERR, "pipe_stream: set_nonblock failed");
		return -1;
	}

	*fd_read = fd0;

	return 0;
}

/*
 * Close not used file descriptors and create pipes for standard
 * streams stdout and stderr (if needed).  If this function returns
 * -1, then function which called fd_init() should not call log_stdxxx()
 * functions since syslog() or log file could use one of STDxxx_FILENO
 * descriptors.  Also any log message should not be sent before this
 * function invocation.
 */
static int
fd_init(void)
{
	long val;
	int fd;

	errno = 0;
	val = sysconf(_SC_OPEN_MAX);
	if (val < 0) {
		if (errno != 0) {
			logmsg(IPA_LOG_ERR, "fd_init: sysconf(_SC_OPEN_MAX)");
			return -1;
		}
#ifndef HAVE_CLOSEFROM
		logmsgx(IPA_LOG_ERR, "fd_init: infinite limit for _SC_OPEN_MAX is not supported");
		return -1;
#endif
	}
	nofile_max = (int)val;

#ifdef HAVE_CLOSEFROM
	if (!debug) {
		(void)close(STDIN_FILENO);
		(void)close(STDERR_FILENO);
	}
	(void)close(STDOUT_FILENO);
	for (fd = 3; fd < pid_file_fd; ++fd)
		(void)close(fd);
	(void)closefrom(fd + 1);
#else
# if SIZEOF_LONG > SIZEOF_INT
	if (val > INT_MAX) {
		logmsgx(IPA_LOG_ERR, "fd_init: too big value %ld for int type from sysconf(_SC_OPEN_MAX)",
		    val);
		return -1;
	}
# endif
	for (fd = 0; fd < (int)val; ++fd)
		if (fd != pid_file_fd &&
		    !(debug && (fd == STDIN_FILENO || fd == STDERR_FILENO)))
			(void)close(fd);
#endif /* HAVE_CLOSEFROM */

	if (!debug) {
		if ( (fd = open(_PATH_DEVNULL, O_RDWR)) < 0) {
			logmsg(IPA_LOG_ERR, "fd_init: open(%s, O_RDWR)", _PATH_DEVNULL);
			return -1;
		}
		if (fd != STDIN_FILENO) {
			/* stdin -> /dev/null */
			if (dup2(fd, STDIN_FILENO) < 0) {
				logmsg(IPA_LOG_ERR, "fd_init: dup2(%d, %d)", fd, STDIN_FILENO);
				return -1;
			}
			if (close(fd) < 0) {
				logmsg(IPA_LOG_ERR, "fd_init: close(%d)", fd);
				return -1;
			}
		}
		if (pipe_stream(STDERR_FILENO, &stderr_read) < 0) {
			logmsgx(IPA_LOG_ERR, "fd_init: pipe_stream for STDERR_FILENO failed");
			return -1;
		}
	}

	if (pipe_stream(STDOUT_FILENO, &stdout_read) < 0) {
		logmsgx(IPA_LOG_ERR, "fd_init: pipe_stream for STDOUT_FILENO failed");
		return -1;
	}

	return 0;
}

/*
 * Create/open PID-file and check if it is already locked, since it is
 * better to create PID-file before writing something to the log, we need
 * to choose file descriptor for PID-file not equal to stdxxx.
 */
static void
create_pid_file(void)
{
	struct flock lock;
	int fd[3];
	u_int i;

	/*
	 * pid_file_fd should not be equal to file descriptor of stdin,
	 * stdout or stderr.  SUSv3 claims that open(2) returns lowest
	 * available descriptor, so four attempts are enough to
	 * get desired descriptor.
	 */
	for (i = 0; i < 4; ++i) {
		if ( (pid_file_fd = open(pid_file_name, O_RDWR|O_CREAT, PID_FILE_PERM)) < 0)
			exit_err("create_pid_file: cannot create or open PID-file: open(%s, O_RDWR|O_CREAT, 0%03o)",
			    pid_file_name, PID_FILE_PERM);
		if (pid_file_fd == STDIN_FILENO ||
		    pid_file_fd == STDOUT_FILENO ||
		    pid_file_fd == STDERR_FILENO) {
			if (i == 3)
				exit_err("create_pid_file: something is wrong in algorithm");
			fd[i] = pid_file_fd;
		} else
			break;
	}

	/* Close "incorrectly" opened file descriptors. */
	if (i > 0)
		do {
			if (close(fd[--i]) < 0)
				exit_err("create_pid_file: close(%d)", fd[i]);
		} while (i > 0);

	lock.l_start = 0;		/* Zero offset from... */
	lock.l_len = 0;			/* Entire file. */
	lock.l_type = F_WRLCK;		/* Should be a valid value. */
	lock.l_whence = SEEK_SET;	/* ... the start of the file. */

	if (fcntl(pid_file_fd, F_GETLK, &lock) < 0) {
#ifdef __CYGWIN__
		if (errno == ENOSYS)
			return;
#endif
		exit_err("create_pid_file: fcntl(%s, F_GETLK)",
		    pid_file_name);
	}

	switch (lock.l_type) {
	case F_UNLCK:
		break;
	case F_WRLCK:
	case F_RDLCK:
		exit_errx("create_pid_file: file %s is already locked (%s) by PID %ld",
		    pid_file_name, lock.l_type == F_WRLCK ? "exclusive" : "shared",
		    (long)lock.l_pid);
		/* NOTREACHED */
	default:
		exit_errx("create_pid_file: unknown lock type %d for file %s",
		    lock.l_type, pid_file_name);
	}
}

/*
 * Lock the file pid_file_name to prevent multiple copies of itself
 * from running and store PID in this file.
 */
static int
lock_pid_file(void)
{
	struct flock lock;
	ssize_t	n;
	int len;
	char num_str[22];

	lock.l_start = 0;		/* Zero offset from... */
	lock.l_len = 0;			/* Entire file. */
	lock.l_type = F_WRLCK;		/* Write lock. */
	lock.l_whence = SEEK_SET;	/* ... the start of the file. */

	if (fcntl(pid_file_fd, F_SETLK, &lock) < 0) {
		switch (errno) {
		case EACCES:
		case EAGAIN:
			logmsg(IPA_LOG_ERR, "lock_pid_file: cannot acquire exclusive lock on file %s",
			    pid_file_name);
			break;
		default:
			logmsg(IPA_LOG_ERR, "lock_pid_file: fcntl(%s, F_SETLK)",
			    pid_file_name);
		}
		return -1;
	}

	if (ftruncate(pid_file_fd, (off_t)0) < 0) {
		logmsg(IPA_LOG_ERR, "lock_pid_file: ftruncate(%s, 0)",
		    pid_file_name);
		goto failed;
	}

	if ( (len = snprintf(num_str, sizeof num_str, "%ld\n", (long)getpid())) < 0) {
		logmsg(IPA_LOG_ERR, "lock_pid_file: snprintf");
		goto failed;
	}
	if (len >= sizeof num_str) {
		logmsgx(IPA_LOG_ERR, "lock_pid_file: not enough space in snprintf");
		goto failed;
	}

	if ( (n = write(pid_file_fd, num_str, len)) < 0) {
		logmsg(IPA_LOG_ERR, "lock_pid_file: write(%s)",
		    pid_file_name);
		goto failed;
	}
	if (n != len) {
		logmsgx(IPA_LOG_ERR, "lock_pid_file: write(%s): short write, %ld of %lu bytes",
		    pid_file_name, (long)n, (u_long)len);
		goto failed;
	}

	return 0;

failed:
	unlink_pid_file();
	return -1;
}

#ifdef WITH_RULES

# define X_OPTSTRING_RULE "r:"

# ifdef WITH_LIMITS
#  define X_OPTSTRING_LIMIT "l:"
# else
#  define X_OPTSTRING_LIMIT ""
# endif

# ifdef WITH_SUBLIMITS
#  define X_OPTSTRING_SUBLIMIT "s:"
# else
#  define X_OPTSTRING_SUBLIMIT ""
# endif

# ifdef WITH_THRESHOLDS
#  define X_OPTSTRING_THRESHOLD "t:"
# else
#  define X_OPTSTRING_THRESHOLD ""
# endif

# define X_OPTSTRING ":" X_OPTSTRING_RULE X_OPTSTRING_LIMIT X_OPTSTRING_SUBLIMIT X_OPTSTRING_THRESHOLD

#else /* !WITH_RULES */

# define X_OPTSTRING ""

#endif /* WITH_RULES */

/*
 * Implement "-x ..." option.
 */
static int
run_opt_commands(int argc, char *argv[])
{
	int	opt;
	u_int	section_code;
	const char *section;
	const struct cmd_list *cmdl;
	enum {
		SECT_STARTUP =		   0,
		SECT_SHUTDOWN,		/* 1 */
#if defined(WITH_RULES) && defined(WITH_LIMITS)
		SECT_REACH,		/* 2 */
		SECT_IF_REACHED,	/* 3 */
		SECT_IF_NOT_REACHED,	/* 4 */
#endif
		SECT_NOTSET		/* 5 */
	};
#ifdef WITH_RULES
	const char *rule_name = NULL;
	const struct rule *rule;
	const struct cmds_rule *cmds_rule;
# ifdef WITH_LIMITS
	u_int	subsection_code;
	const char *subsection = NULL;
	const char *limit_name = NULL;
	const struct limit *limit;
	const struct cmds_limit *cmds_limit;
# endif
# ifdef WITH_SUBLIMITS
	const char *sublimit_name = NULL;
	const struct sublimit *sublimit;
# endif
# ifdef WITH_THRESHOLDS
	const char *threshold_name = NULL;
	const struct threshold *threshold;
# endif
#endif /* WITH_RULES */

	xvlogmsgx = vlogmsgx_stderr;

	while ( (opt = getopt(argc, argv, X_OPTSTRING)) != -1)
		switch (opt) {
#ifdef WITH_RULES
		case 'r':
			rule_name = optarg;
			break;
# ifdef WITH_LIMITS
		case 'l':
			limit_name = optarg;
			break;
# endif
# ifdef WITH_SUBLIMITS
		case 's':
			sublimit_name = optarg;
			break;
# endif
# ifdef WITH_THRESHOLDS
		case 't':
			threshold_name = optarg;
			break;
# endif
		case ':':
			exit_errx("option -%c in -x requires an argument", optopt);
			/* NOTREACHED */
#endif /* WITH_RULES */
		case '?':
			exit_errx("illegal option -%c in -x", optopt);
			/* NOTREACHED */
		default:
			exit_errx("unexpected option -%c in -x", optopt);
		}

	if (optind == argc)
		exit_errx("cannot find a section name in the command line");

	section = argv[optind++];

#ifdef WITH_RULES

# if defined(WITH_LIMITS) && defined(WITH_THRESHOLDS)
	if (limit_name != NULL && threshold_name != NULL)
		exit_errx("-l and -t options cannot be used together");
# endif

# if defined(WITH_SUBLIMITS) && defined(WITH_THRESHOLDS)
	if (sublimit_name != NULL && threshold_name != NULL)
		exit_errx("-s and -t options cannot be used together");
# endif

	if (rule_name != NULL)
		if ( (rule = rule_by_name(rule_name)) == NULL)
			exit_errx("cannot find rule %s in the configuration file",
			    rule_name);

# ifdef WITH_LIMITS
	if (limit_name != NULL) {
		if (rule_name == NULL)
			exit_errx("the -l option should be used with the -r option");
		if ( (limit = limit_by_name(rule, limit_name)) == NULL)
			exit_errx("cannot find limit %s for rule %s in the configuration file",
			    limit_name, rule_name);
	}
# endif /* WITH_LIMITS */

# ifdef WITH_SUBLIMITS
	if (sublimit_name != NULL) {
		if (limit_name == NULL)
			exit_errx("the -s option should be used with the -l option");
		STAILQ_FOREACH(sublimit, &limit->sublimits, link)
			if (strcmp(sublimit->sublimit_name, sublimit_name) == 0)
				break;
		if (sublimit == NULL)
			exit_errx("cannot find sublimit %s for limit %s in rule %s in the configuration file",
			    sublimit_name, limit_name, rule_name);
	}
# endif /* WITH_SUBLIMITS */

# ifdef WITH_THRESHOLDS
	if (threshold_name != NULL) {
		if (rule_name == NULL)
			exit_errx("the -t option should be used with the -r option");
		if ( (threshold = threshold_by_name(rule, threshold_name)) == NULL)
			exit_errx("cannot find threshold %s for rule %s in the configuration file",
			    threshold_name, rule_name);
	}
# endif /* WITH_THRESHOLDS */

#endif /* WITH_RULES */

	if (strcmp(section, "startup") == 0)
		section_code = SECT_STARTUP;
	else if (strcmp(section, "shutdown") == 0)
		section_code = SECT_SHUTDOWN;
	else
		section_code = SECT_NOTSET;

#ifdef WITH_RULES

# ifdef WITH_LIMITS
	subsection_code = SECT_NOTSET;
# endif
	if (optind != argc) {
# ifdef WITH_LIMITS
		if (optind + 1 != argc)
			exit_errx("too many sections for given options");
		subsection = argv[optind];
		if (limit_name != NULL) {
			if (strcmp(subsection, "if_reached") == 0)
				subsection_code = SECT_IF_REACHED;
			else if (strcmp(subsection, "if_not_reached") == 0)
				subsection_code = SECT_IF_NOT_REACHED;
			else
				exit_errx("unknown subsection \"%s\" for given options", subsection);
		}
#  ifdef WITH_THRESHOLDS
		else if (threshold_name != NULL)
			exit_errx("too many sections for given options");
#  endif
# else
		exit_errx("too many sections for given options");
# endif /* WITH_LIMITS */
	}

# ifdef WITH_LIMITS
	if (section_code == SECT_NOTSET)
		if (strcmp(section, "reach") == 0)
			section_code = SECT_REACH;
# endif

# ifdef WITH_SUBLIMITS
	if (sublimit_name != NULL) {
		switch (section_code) {
		case SECT_STARTUP:
			cmds_limit = &sublimit->rc[RC_STARTUP];
			break;
		case SECT_SHUTDOWN:
			cmds_limit = &sublimit->rc[RC_SHUTDOWN];
			break;
		case SECT_REACH:
			if (subsection != NULL)
				exit_errx("too many sections for given options");
			return exec_cmd_list_cons(&sublimit->reach, "rule %s { limit %s { sublimit %s { reach {}}}}",
			    rule_name, limit_name, sublimit_name);
		default: /* SECT_NOTSET */
			exit_errx("unknown section \"%s\" for given options", section);
		}
		switch (subsection_code) {
		case SECT_IF_REACHED:
			cmdl = &cmds_limit->cmdl_if_reached;
			break;
		case SECT_IF_NOT_REACHED:
			cmdl = &cmds_limit->cmdl_if_not_reached;
			break;
		default: /* SECT_NOTSET */
			return exec_cmd_list_cons(&cmds_limit->cmdl, "rule %s { limit %s { sublimit %s { %s {}}}}",
			    rule_name, limit_name, sublimit_name, section);
		}
		return exec_cmd_list_cons(cmdl, "rule %s { limit %s { sublimit %s { %s { %s {}}}}}",
		    rule_name, limit_name, sublimit_name, section, subsection);
	}
# endif /* WITH_SUBLIMITS */

# ifdef WITH_LIMITS
	if (limit_name != NULL) {
		switch (section_code) {
		case SECT_STARTUP:
			cmds_limit = &limit->rc[RC_STARTUP];
			break;
		case SECT_SHUTDOWN:
			cmds_limit = &limit->rc[RC_SHUTDOWN];
			break;
		default:
			if (subsection != NULL)
				exit_errx("too many sections for given options");
			if (section_code == SECT_REACH)
				cmdl = &limit->reach;
			else if (strcmp(section, "expire") == 0)
				cmdl = &limit->expire.cmdl;
			else if (strcmp(section, "restart") == 0)
				cmdl = &limit->restart.cmdl;
			else
				exit_errx("unknown section \"%s\" for given options", section);
			return exec_cmd_list_cons(cmdl, "rule %s { limit %s { %s {}}}",
			    rule_name, limit_name, section);
		}
		switch (subsection_code) {
		case SECT_IF_REACHED:
			cmdl = &cmds_limit->cmdl_if_reached;
			break;
		case SECT_IF_NOT_REACHED:
			cmdl = &cmds_limit->cmdl_if_not_reached;
			break;
		default: /* SECT_NOTSET */
			return exec_cmd_list_cons(&cmds_limit->cmdl, "rule %s { limit %s { %s {}}}",
			    rule_name, limit_name, section);
		}
		return exec_cmd_list_cons(cmdl, "rule %s { limit %s { %s { %s {}}}}",
		    rule_name, limit_name, section, subsection);
	}
# endif /* WITH_LIMITS */

# ifdef WITH_THRESHOLDS
	if (threshold_name != NULL) {
		switch (section_code) {
		case SECT_STARTUP:
			cmdl = &threshold->rc[RC_STARTUP];
			break;
		case SECT_SHUTDOWN:
			cmdl = &threshold->rc[RC_SHUTDOWN];
			break;
		default:
			if (strcmp(section, "below_threshold") == 0)
				cmdl = &threshold->below_threshold;
			else if (strcmp(section, "equal_threshold") == 0)
				cmdl = &threshold->equal_threshold;
			else if (strcmp(section, "above_threshold") == 0)
				cmdl = &threshold->above_threshold;
			else
				exit_errx("unknown section \"%s\" for given options", section);
		}
		return exec_cmd_list_cons(cmdl, "rule %s { threshold %s { %s {}}}",
		    rule_name, threshold_name, section);
	}
# endif /* WITH_THRESHOLDS */

	if (rule_name != NULL) {
		switch (section_code) {
		case SECT_STARTUP:
			cmds_rule = &rule->rc[RC_STARTUP];
			break;
		case SECT_SHUTDOWN:
			cmds_rule = &rule->rc[RC_SHUTDOWN];
			break;
		default:
			exit_errx("unknown section \"%s\" for given options", section);
		}
# ifdef WITH_LIMITS
		if (subsection != NULL) {
			if (strcmp(subsection, "if_any_reached") == 0)
				cmdl = &cmds_rule->cmdl_if_any_reached;
			else if (strcmp(subsection, "if_any_not_reached") == 0)
				cmdl = &cmds_rule->cmdl_if_any_not_reached;
			else if (strcmp(subsection, "if_all_reached") == 0)
				cmdl = &cmds_rule->cmdl_if_all_reached;
			else if (strcmp(subsection, "if_all_not_reached") == 0)
				cmdl = &cmds_rule->cmdl_if_all_not_reached;
			else
				exit_errx("unknown subsection \"%s\" for given options", subsection);
			return exec_cmd_list_cons(cmdl, "rule %s { %s { %s {}}}",
			    rule_name, section, subsection);
		}
# endif /* WITH_LIMITS */
		return exec_cmd_list_cons(&cmds_rule->cmdl, "rule %s { %s {}}",
		    rule_name, section);
	}

#endif /* WITH_RULES */

	if (optind != argc)
		exit_errx("too many sections for given options");

	switch (section_code) {
	case SECT_STARTUP:
		cmdl = &cmds_startup;
		break;
	case SECT_SHUTDOWN:
		cmdl = &cmds_shutdown;
		break;
	default:
		exit_errx("unknown section \"%s\" for given options", section);
	}
	return exec_cmd_list_cons(cmdl, "%s {}", section);
}

/*
 * Implement -u <user> and -g <group> options.
 */
static void
change_user(void)
{
	char	*endptr;
	uid_t	uid;
	gid_t	gid;
	const struct passwd *pwd;
	const struct group *grp;

	xvlogmsgx = vlogmsgx_stderr;

	if (group_name != NULL) {
		/* -g <group> */
		errno = 0;
		if ( (grp = getgrnam(group_name)) == NULL) {
			if (errno != 0)
				exit_err("getgrnam(%s)", group_name);
			if (isdigit(*group_name) == 0)
				exit_errx("cannot find group %s", group_name);
			errno = 0;
			gid = (gid_t)strtoul(group_name, &endptr, 10);
			if (errno != 0)
				exit_err("change_user: strtoul");
			if (group_name == endptr || *endptr != '\0')
				exit_errx("cannot find group %s", group_name);
		} else
			gid = grp->gr_gid;
		if (setgroups(1, &gid) < 0)
			exit_err("change_user: setgroups(1, [%lu])", (u_long)gid);
	}

	if (user_name != NULL) {
		/* -u <user> */
		errno = 0;
		if ( (pwd = getpwnam(user_name)) == NULL) {
			if (errno != 0)
				exit_err("getpwnam(%s)", user_name);
			if (isdigit(*user_name) == 0)
				exit_errx("cannot find user %s", user_name);
			errno = 0;
			uid = (uid_t)strtoul(user_name, &endptr, 10);
			if (errno != 0)
				exit_err("change_user: strtoul");
			if (user_name == endptr || *endptr != '\0')
				exit_errx("cannot find user %s", user_name);
			if (group_name == NULL) {
				errno = 0;
				if ( (pwd = getpwuid(uid)) == NULL) {
					if (errno != 0)
						exit_err("getpwuid(%lu)",
						    (u_long)uid);
					else
						exit_errx("cannot find user with UID %lu and -g option was not given",
						    (u_long)uid);
				}
			}
		} else
			uid = pwd->pw_uid;
		if (pwd != NULL && group_name == NULL) {
			gid = pwd->pw_gid;
			if (setsuppgids(pwd->pw_name, gid) < 0)
				exit_errx("change_user: cannot set all groups for user %s", pwd->pw_name);
		}
	}

	if (setgid(gid) < 0)
		exit_err("change_user: setgid(%lu)", (u_long)gid);

	if (user_name != NULL)
		if (setuid(uid) < 0)
			exit_err("change_user: setuid(%lu)", (u_long)uid);

	/* To be sure, that passwd and group files are closed. */
	endpwent();
	endgrent();
}

int
main(int argc, char *argv[])
{
	int	opt;			/* Current option. */
	int	ret;			/* Return code. */
	int	action;			/* Return code from ipa_main. */
	int	t_flag = 0;		/* Non-zero if -t. */
	int	x_flag = 0;		/* Non-zero if -x. */
	const char *k_optarg = NULL;	/* -k <signal>. */
	const struct sig_tbl_entry *sig_tbl_ptr;

	/* Save the program name. */
	if ( (envprogname = strrchr(argv[0], '/')) != NULL)
		++envprogname;
	else
		envprogname = argv[0];

	opterr = 0;
	while ( (opt = getopt(argc, argv, ":c:df:g:hi:k:o:p:tu:vx")) != -1)
		switch (opt) {
		case ':':
			exit_errx("option requires an argument -- %c", optopt);
			/* NOTREACHED */
		case '?':
			exit_errx("illegal option -- %c", optopt);
			/* NOTREACHED */
		case 'c':
			if (*optarg != '/')
				exit_errx("path in the -c option should be absolute");
			if (chroot(optarg) < 0)
				exit_err("main: chroot(%s)", optarg);
			break;
		case 'd':
			debug = 1;
			break;
		case 'f':
			ipa_conf_file = optarg;
			break;
		case 'g':
			group_name = optarg;
			break;
		case 'h':
			usage();
			return 0;
		case 'i':
			log_ident = optarg;
			break;
		case 'k':
			k_optarg = optarg;
			break;
		case 'o':
			log_file = optarg;
			if (*log_file != '/')
				exit_errx("path in the -o option should be absolute");
			break;
		case 'p':
			pid_file_name = optarg;
			if (*pid_file_name != '/')
				exit_errx("path in the -p option should be absolute");
			break;
		case 't':
			if (t_flag)
				mimic_real_config = 1;
			else
				t_flag = 1;
			break;
		case 'u':
			user_name = optarg;
			break;
		case 'v':
			show_version();
			return 0;
		case 'x':
			x_flag = 1;
			goto getopt_done;
		default:
			exit_errx("unexpected option -- %c", optopt);
		}

	if (optind < argc)
		exit_errx("non-switch argument \"%s\"", argv[optind]);

getopt_done:
	if (user_name != NULL || group_name != NULL)
		change_user();

	if (k_optarg != NULL) {
		for (sig_tbl_ptr = sig_tbl; sig_tbl_ptr->signame != NULL; ++sig_tbl_ptr)
			if (strcmp(k_optarg, sig_tbl_ptr->signame) == 0) {
				runcopy_sendsig(sig_tbl_ptr->signo);
				return 0;
			}
		exit_errx("illegal argument \"%s\" for the -k option", k_optarg);
	}

#ifdef HAVE_DL_INIT
	if (dl_init() != 0)
		exit_errx("main: dl_init failed");
#endif

	setlocale(LC_ALL, "");

	memfunc_pre_init();

	myuid = getuid();
	mygid = getgid();

	if (t_flag) {
		if (configure(TEST_PARSING) < 0)
			return 1;
		show_config();
		return 0;
	}

	if (x_flag) {
		if (configure(CMD_PARSING) < 0)
			return 1;
		return run_opt_commands(argc, argv) < 0 ? 1 : 0;
	}

	create_pid_file();

	tzset();

	init_log();
	open_log();

	if (fd_init() < 0) {
		logmsgx(IPA_LOG_ERR, "main: fd_init failed");
		return 1;
	}

	logmsgx(IPA_LOG_INFO, "-----------------------------------------------------");
	logmsgx(IPA_LOG_INFO, IPA_NAME ": version " PACKAGE_VERSION " started by UID %lu, GID %lu",
	    (u_long)myuid, (u_long)mygid);
#ifdef WITH_PTHREAD
	logmsgx(IPA_LOG_INFO, "thread-safe mode");
#endif
#ifdef WITH_MEMFUNC_DEBUG
	logmsgx(IPA_LOG_INFO, "memfunc_debug is on");
#endif

	ret = 1;

	if (!debug) {
		if (bg_init() < 0) {
			logmsgx(IPA_LOG_ERR, "cannot run in the background");
			goto done_exit;
		}
		logmsgx(IPA_LOG_INFO, "started in background mode");
	} else
		logmsgx(IPA_LOG_INFO, "started in foreground mode");

	if (lock_pid_file() < 0) {
		logmsgx(IPA_LOG_ERR, "main: lock_pid_file failed");
		goto done_exit;
	}

	logmsgx(IPA_LOG_INFO, "maximum number of open descriptors is %d%s",
	    nofile_max, nofile_max >= 0 ? "" : " (no limit)");

	if (configure(CONFIG_PARSING) < 0) {
		logmsgx(IPA_LOG_ERR, "main: configure failed");
		goto done_exit_unlink;
	}

	/* Startup. */
	if (sig_init_1() < 0) {
		logmsgx(IPA_LOG_ERR, "main: sig_init_1 failed");
		goto done_deinit;
	}

	logmsgx(IPA_LOG_INFO, "startuping... all signals are blocked");

	if (init_all() < 0) {
		logmsgx(IPA_LOG_ERR, "main: init_all failed");
		goto done_deinit;
	}

	if (run_cmds(RC_STARTUP) < 0) {
		logmsgx(IPA_LOG_ERR, "main: run_cmds(RC_STARTUP) failed");
		goto done_deinit;
	}

	if (sig_init_2() < 0) {
		logmsgx(IPA_LOG_ERR, "main: sig_init_2 failed");
		goto done_shutdown;
	}

	/* Working... */
	for (;;) {
		action = ipa_main();
		if (action == -1) {
			/* Some error occurred. */
			logmsgx(IPA_LOG_ERR, "cannot perform accounting");
			break;
		}

		if (action == 0) {
			/* Normal exit. */
			ret = 0;
			break;
		}

		/* Reconfiguration. */
		logmsgx(IPA_LOG_INFO, "reconfiguring... all signals are ignored");
		if (reconfigure() < 0)
			goto done_exit;

		/* Now init all and call ipa_main() with new configuration. */
		if (init_all() < 0) {
			logmsgx(IPA_LOG_ERR, "main: init_all failed");
			break;
		}
	}

done_shutdown:
	/* Shutdowning. */
	logmsgx(IPA_LOG_INFO, "shutdowning... all signals are ignored");
	if (run_cmds(RC_SHUTDOWN) < 0) {
		logmsgx(IPA_LOG_ERR, "main: run_cmds(RC_SHUTDOWN) failed");
		ret = 1;
	}

done_deinit:
	/* Deiniting. */
	if (deinit_all() < 0) {
		logmsgx(IPA_LOG_ERR, "main: deinit_all failed");
		ret = 1;
	}
	if (free_all() < 0) {
		logmsgx(IPA_LOG_ERR, "main: free_all failed");
		ret = 1;
	}

done_exit_unlink:
	/* Unlink PID file, since it is locked by this process. */
	unlink_pid_file();

done_exit:
	/* Exiting. */
#ifdef HAVE_DL_EXIT
	if (dl_exit() != 0) {
		logmsgx(IPA_LOG_ERR, "main: dl_exit failed");
		ret = 1;
	}
#endif
	log_stdall();

	logmsgx(IPA_LOG_INFO, IPA_NAME ": version " PACKAGE_VERSION " exited, return code is %d",
	    ret);

	close_log();

	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1