Beispiel #1
0
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);
}
Beispiel #2
0
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 {