/*- * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PATHS_H # include #endif #ifndef _PATH_DEVNULL # ifdef __GNUC__ # warning "defining _PATH_DEVNULL to \"/dev/null\"" # endif # define _PATH_DEVNULL "/dev/null" #endif #ifdef WITH_PTHREAD # include #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 */ static const char *group_name = NULL; /* -g */ static const char *envprogname; static const char *pid_file_name = IPA_PID_FILE; /* -p */ 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 \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 \tSet the directory "IPA_NAME" should chroot(2) into\n\ \t\t\timmediately, working directory is not changed\n\ -u \t\tChange UID of the running copy of "IPA_NAME" to \n\ -g \t\tChange GID of the running copy of "IPA_NAME" to \n\ -k \t\tSend signal to the running copy of ipa, \n\ \t\t\tcan be: reconfigure, shutdown, test or kill\n\ -i \tUse given log-ident instead of using default \""IPA_LOG_IDENT"\"\n\ -o \t\tUse given log-file instead of using syslog\n\ -p \t\tUse given pid-file instead of using default\n\ \t\t\tPID-file "IPA_PID_FILE"\n" " -x" #ifdef WITH_RULES " [-r " # ifdef WITH_LIMITS " [-l " # ifdef WITH_SUBLIMITS " [-s ]" # endif "]" # endif # ifdef WITH_THRESHOLDS " [-t ]" # 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 and -g 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 */ 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 */ 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 . */ 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; }