Logo Search packages:      
Sourcecode: mailavenger version File versions  Download package

runprog.C

/* $Id$ */

/*
 *
 * Copyright (C) 2003 David Mazieres (dm@uun.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */


#include "asmtpd.h"
#include "fdlim.h"
#include <grp.h>
#ifdef HAVE_SETUSERCONTEXT
# ifdef HAVE_LOGIN_CAP_H
#  include <login_cap.h>
# endif /* HAVE_LOGIN_CAP_H */
#endif /* HAVE_SETUSERCONTEXT */
#if HAVE_GETSPNAM
# include <shadow.h>
#endif /* HAVE_GETSPNAM */


struct rpstate {
  runprogcb_t cb;
  pid_t pid;
  aios_t aio;
  ref<progout> po;
  bool eof;
  bool timedout;
  timecb_t *tmo;

  rpstate (pid_t pid, int rfd, runprogcb_t cb);
  ~rpstate ();
  void input (str line, int err);
  void reap (int);
  void timeout (int n);
  void maybe_finish ();
  void settmo (int sec) {
    assert (!tmo);
    tmo = delaycb (sec, wrap (this, &rpstate::timeout, 0));
  }
};

str
progout::response (int errcode)
{
  strbuf sb;
  if (output.empty ())
    return NULL;
  for (const str *sp = output.base (); sp + 1 < output.lim (); sp++)
    sb.fmt ("%03d-", errcode) << *sp << "\r\n";
  sb.fmt ("%03d ", errcode) << output.back () << "\r\n";
  return sb;
}

rpstate::rpstate (pid_t pid, int rfd, runprogcb_t cb)
  : cb (cb), pid (pid), aio (aios::alloc (rfd)),
    po (New refcounted<progout>),
    eof (false), timedout (false), tmo (NULL)
{
  chldcb (pid, wrap (this, &rpstate::reap));
  aio->readline (wrap (this, &rpstate::input));
}

rpstate::~rpstate ()
{
  if (pid > 0) {
    chldcb (pid, NULL);
    kill (pid, SIGKILL);
  }
  aio->readcancel ();
  if (tmo)
    timecb_remove (tmo);
}

void
rpstate::input (str line, int err)
{
  if (!line)
    eof = true;
  else if (po->output.size () >= 5120) {
    eof = true;
    aio->abort ();
  }
  else {
    po->output.push_back (line);
    aio->readline (wrap (this, &rpstate::input));
  }
  maybe_finish ();
}

void
rpstate::reap (int status)
{
  pid = -1;
  po->status = status;
  maybe_finish ();
}

void
rpstate::timeout (int n)
{
  tmo = NULL;
  if (n == 0 && pid > 0) {
    kill (pid, SIGTERM);
    tmo = delaycb (5, wrap (this, &rpstate::timeout, 1));
  }
  else if (n == 1 && pid > 0) {
    kill (pid, SIGKILL);
    tmo = delaycb (5, wrap (this, &rpstate::timeout, 2));
  }
  else if (n < 2 && pid <= 0 && !eof)
    tmo = delaycb (5, wrap (this, &rpstate::timeout, 3));
  else {
    timedout = true;
    maybe_finish ();
  }
}

void
rpstate::maybe_finish ()
{
  if ((!eof || po->status == -1) && !timedout)
    return;
  (*cb) (po);
  delete this;
}

rpstate *
runprog (const char *prog, const char **av, int infd,
       bool collect_err, runprogcb_t cb, cbv::ptr postforkcb,
       const char *const *env)
{
  int fds[2];
  if (pipe (fds)) {
    (*cb) (New refcounted<progout> (strerror (errno)));
    return NULL;
  }

  close_on_exec (fds[0]);
  close_on_exec (fds[1]);
  pid_t pid = aspawn (prog, av, infd, fds[1], collect_err ? fds[1] : 2,
                  postforkcb, const_cast<char *const *> (env));
  close (fds[1]);

  if (pid < 0) {
    close (fds[0]);
    (*cb) (New refcounted<progout> (strerror (errno)));
    return NULL;
  }

  return New rpstate (pid, fds[0], cb);
}

void
runprog_cancel (rpstate *rps)
{
  delete rps;
}

void
runprog_timeout (rpstate *rps, int sec)
{
  if (rps)
    rps->settmo (sec);
}

extern "C" {
  char *getusershell(void);
  void setusershell(void);
  void endusershell(void);
}

static bool
validshell (const char *shell)
{
  const char *s;

  setusershell ();
  while ((s = getusershell ()))
    if (!strcmp(s, shell)) {
      endusershell ();
      return true;
    }
  endusershell ();
  return false;
}

struct passwd *
validuser (const char *user, bool reqshell)
{
  struct passwd *pw = getpwnam (user);
  if (!pw)
    pw = getpwnam (mytolower (user));
  if (!pw)
    return NULL;

  if (!pw->pw_uid) {
    maybe_warn (strbuf ("not checking mail for user %s with uid 0;"
                  " should be aliased\n", user));
    return NULL;
  }

  if (reqshell && !validshell (pw->pw_shell)) {
    maybe_warn (strbuf ("not checking mail for user %s with invalid shell;"
                  " should be aliased\n", user));
    return NULL;
  }

  bool expired = false;
#ifdef HAVE_GETSPNAM
  struct spwd *spe = getspnam (pw->pw_name);
  if (spe && spe->sp_expire > 0
      && spe->sp_expire <= (time (NULL) / (24 * 60 * 60)))
    expired = true;
#elif defined (HAVE_PASSWD_PW_EXPIRE)
  if (pw->pw_expire > 0 && pw->pw_expire <= time (NULL))
    expired = true;
#endif /* HAVE_PASSWD_PW_EXPIRE */
  if (expired) {
    maybe_warn (strbuf ("not checking mail for user %s with expired account\n",
                  user));
    return NULL;
  }

  if (pw->pw_dir[0] != '/') {
    maybe_warn (strbuf ("not checking mail for user %s with bad homedir %s\n",
                  user, pw->pw_dir));
    return NULL;
  }

  return pw;
}

#ifndef NGROUPS_MAX
# define NGROUPS_MAX 16
#endif /* !NGROUPS_MAX */

void
become_user (struct passwd *pw, bool grouplist)
{
  if (setsid () == -1)
    warn ("setsid: %m\n");
  bool root = getuid () <= 0;

  if (!pw->pw_uid)
    fatal ("cannot become user %s with uid 0\n", pw->pw_name);
  if (char *p = getenv ("FDLIM_HARD")) {
    int n = atoi (p);
    if (n > fdlim_get (1))
      fdlim_set (n, -1);
  }
  if (char *p = getenv ("FDLIM_SOFT")) {
    int n = atoi (p);
    if (n > fdlim_get (0))
      fdlim_set (n, 0);
  }

#ifdef HAVE_SETUSERCONTEXT
  u_int flags = LOGIN_SETALL;
  if (!grouplist) {
    flags &= ~LOGIN_SETGROUP;
    if (setgid (pw->pw_gid)) {
      if (root)
      fatal ("setgid: %m\n");
      warn ("setgid: %m\n");
    }
  }
  if (root && setusercontext (NULL, pw, pw->pw_uid, flags))
    warn ("setusercontext: %m\n");
#else /* !HAVE_SETUSERCONTEXT */
# if HAVE_SETLOGIN
  if (setlogin (pw->pw_name))
    warn ("setlogin: %m\n");
# endif /* HAVE_SETLOGIN */
# ifdef HAVE_INITGROUPS

  if (grouplist && initgroups (pw->pw_name, pw->pw_gid)) {
    if (root)
      fatal ("initgroups failed\n");
    warn ("initgroups failed\n");
  }

# else /* !HAVE_INITGROUPS */

  if (grouplist) {
    vec<GROUPLIST_T> gl;
    bhash <GROUPLIST_T> cache;
    cache.insert (pw->pw_gid);
#  ifdef HAVE_EGID_IN_GROUPLIST
    gl.push_back (pw->pw_gid);
#  endif /* HAVE_EGID_IN_GROUPLIST */
    setgrent ();
    while (group *gr = getgrent ()) {
      if (cache[gr->gr_gid])
      continue;
      for (char **mp = gr->gr_mem; *mp; mp++)
      if (!strcmp (*mp, pw->pw_name)) {
        cache.insert (gr->gr_gid);
        gl.push_back (gr->gr_gid);
        if (gl.size () > NGROUPS_MAX)
          goto done;
        break;
      }
    }
  done:
    if (setgroups (gl.size (), gl.base ())) {
      if (root)
      fatal ("setgroups: %m\n");
      warn ("setgroups: %m\n");
    }
  }

# endif /* !HAVE_INITGROUPS */

  if (setgid (pw->pw_gid)) {
    if (root)
      fatal ("setgid: %m\n");
    warn ("setgid: %m\n");
  }
  if (setuid (pw->pw_uid))
    warn ("setuid: %m\n");
#endif /* !HAVE_SETUSERCONTEXT */
  if (root && !getuid ())
    fatal ("bacome_user: failed to drop privileges\n");

  /* Who knows what the system libraries are doing?  Wouldn't want to
   * inherit a file descriptor to the shadow file across an exec. */
  endpwent ();
#ifdef HAVE_GETSPNAM
  endspent ();
#endif /* HAVE_GETSPNAM */
  /* More paranoia: what if the child inherits /etc/group fd, then
   * messes us up by changing the file offset at an inopportune
   * time. */
  endgrent ();
}

str
exitstr (int status)
{
  if (!status)
    return "success";
  if (WIFEXITED (status))
    return strbuf ("status %d", WEXITSTATUS (status));
  else if (WIFSIGNALED (status)) {
    strbuf sb;
#ifdef NEED_SYS_SIGNAME_DECL
    sb.fmt ("signal %d", WTERMSIG (status));
#else /* !NEED_SYS_SIGNAME_DECL */
    sb << "SIG" << sys_signame[WTERMSIG (status)];
#endif /* !NEED_SYS_SIGNAME_DECL */
    if (WCOREDUMP (status))
      sb << " (core dumped)";
    return sb;
  }
  else
    return strbuf ("unknown status 0x%x", status);
}

Generated by  Doxygen 1.6.0   Back to index