/*-
* 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