void exec_logd(const char* fmt, ...) { va_list args; char logline[PATH_MAX]; va_start(args, fmt); vsnprintf(logline, PATH_MAX, fmt, args); va_end(args); exec_log("d", logline); }
static void exec_prepare_fds(int stdout_fd, int stderr_fd) { unsigned long nfiles = 0; register unsigned int i = 0; struct rlimit rlim; int stdin_fd = -1; stdin_fd = open("/dev/null", O_RDONLY); if (stdin_fd < 0) exec_log("error: unable to open /dev/null for stdin: %s", strerror(errno)); else { if (dup2(stdin_fd, STDIN_FILENO) < 0) exec_log("error: unable to dup fd %d to stdin: %s", strerror(errno)); close(stdin_fd); } if (stdout_fd != STDOUT_FILENO) { if (dup2(stdout_fd, STDOUT_FILENO) < 0) exec_log("error: unable to dup fd %d to stdout: %s", strerror(errno)); close(stdout_fd); } if (stderr_fd != STDERR_FILENO) { if (dup2(stderr_fd, STDERR_FILENO) < 0) exec_log("error: unable to dup fd %d to stderr: %s", strerror(errno)); close(stderr_fd); } /* Make sure not to pass on open file descriptors. For stdin, we * dup /dev/null. For stdout and stderr, we dup some pipes, so that * we can capture what the command may write to stdout or stderr. The * stderr output will be logged to the ExecLog. * * First, use getrlimit() to obtain the maximum number of open files * for this process -- then close that number. */ #if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE) # if defined(RLIMIT_NOFILE) if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) { # elif defined(RLIMIT_OFILE) if (getrlimit(RLIMIT_OFILE, &rlim) < 0) { # endif exec_log("getrlimit() error: %s", strerror(errno)); /* Pick some arbitrary high number. */ nfiles = 1024; } else nfiles = rlim.rlim_max; #else /* no RLIMIT_NOFILE or RLIMIT_OFILE */ nfiles = 1024; #endif /* Close the "non-standard" file descriptors. */ for (i = 3; i < nfiles; i++) close(i); return; } static void exec_prepare_pipes(void) { /* Open pipes for stdout and stderr. */ if (pipe(exec_stdout_pipe) < 0) { exec_log("error: unable to open stdout pipe: %s", strerror(errno)); exec_stdout_pipe[0] = -1; exec_stdout_pipe[1] = STDOUT_FILENO; } else { if (fcntl(exec_stdout_pipe[0], F_SETFD, FD_CLOEXEC) < 0) exec_log("error: unable to set cloexec flag on stdout pipe read fd: %s", strerror(errno)); if (fcntl(exec_stdout_pipe[1], F_SETFD, 0) < 0) exec_log("error: unable to set cloexec flag on stdout pipe write fd: %s", strerror(errno)); } if (pipe(exec_stderr_pipe) < 0) { exec_log("error: unable to open stderr pipe: %s", strerror(errno)); exec_stderr_pipe[0] = -1; exec_stderr_pipe[1] = STDERR_FILENO; } else { if (fcntl(exec_stderr_pipe[0], F_SETFD, FD_CLOEXEC) < 0) exec_log("error: unable to set cloexec flag on stderr pipe read fd: %s", strerror(errno)); if (fcntl(exec_stderr_pipe[1], F_SETFD, 0) < 0) exec_log("error: unable to set cloexec flag on stderr pipe write fd: %s", strerror(errno)); } return; } /* Provides a "safe" version of the system(2) call by dropping all special * privileges, currently retained by the daemon, before exec()'ing the * given command. */ static int exec_ssystem(cmd_rec *cmd, config_rec *c, int flags) { pid_t pid; int status; struct sigaction sa_ignore, sa_intr, sa_quit; sigset_t set_chldmask, set_save; /* Prepare signal dispositions. */ sa_ignore.sa_handler = SIG_IGN; sigemptyset(&sa_ignore.sa_mask); sa_ignore.sa_flags = 0; if (sigaction(SIGINT, &sa_ignore, &sa_intr) < 0) return errno; if (sigaction(SIGQUIT, &sa_ignore, &sa_quit) < 0) return errno; sigemptyset(&set_chldmask); sigaddset(&set_chldmask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &set_chldmask, &set_save) < 0) { exec_log("sigprocmask() error: %s", strerror(errno)); return errno; } exec_prepare_pipes(); pid = fork(); if (pid < 0) { exec_log("error: unable to fork: %s", strerror(errno)); status = -1; } else if (pid == 0) { /* Child process */ register unsigned int i = 0; /* Note: there is no need to clean up this temporary pool, as we've * forked. If the exec call succeeds, this child process will exit * normally, and its process space recovered by the OS. If the exec * call fails, we still exit, and the process space is recovered by * the OS. Either way, the memory will be cleaned up without need for * us to do it explicitly (unless one wanted to be pedantic about it, * of course). */ pool *tmp_pool = cmd ? cmd->tmp_pool : make_sub_pool(session.pool); /* Prepare the environment. */ char **env = exec_prepare_environ(tmp_pool, cmd); /* Restore previous signal actions. */ sigaction(SIGINT, &sa_intr, NULL); sigaction(SIGQUIT, &sa_quit, NULL); sigprocmask(SIG_SETMASK, &set_save, NULL); /* Perform any required substitution on the command arguments. */ for (i = 3; i < c->argc; i++) c->argv[i] = exec_subst_var(tmp_pool, c->argv[i], cmd); /* If requested, clear the supplemental group membership of the process. */ if (flags & EXEC_FLAG_CLEAR_GROUPS) { PRIVS_ROOT setgroups(0, NULL); PRIVS_RELINQUISH } if (!(flags & EXEC_FLAG_RUN_AS_ROOT)) { /* Drop all special privileges before exec()'ing the command. This * allows for the user to specify arbitrary input via the given * filename without the admin worrying that some arbitrary command * is being executed that could take advantage of proftpd's retention * of root real user ID. */ PRIVS_ROOT PRIVS_REVOKE } else { /* We were asked to run using root privs. Yuck. */ PRIVS_ROOT } exec_log("preparing to execute '%s' with uid %lu (euid %lu), " "gid %lu (egid %lu)", c->argv[2], (unsigned long) getuid(), (unsigned long) geteuid(), (unsigned long) getgid(), (unsigned long) getegid()); /* Prepare the file descriptors that the process will inherit. */ exec_prepare_fds(exec_stdout_pipe[1], exec_stderr_pipe[1]); errno = 0; execve(c->argv[2], (char **) (c->argv + 2), env); /* Since all previous file descriptors (including those for log files) * have been closed, and root privs have been revoked, there's little * chance of directing a message of execve() failure to proftpd's log * files. execve() only returns if there's an error; the only way we * can signal this to the waiting parent process is to exit with a * non-zero value (the value of errno will do nicely). */ exit(errno); } else {