/* $Id: runprog.C,v 1.9 2005/09/14 18:04:46 dm Exp $ */ /* * * 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 #ifdef HAVE_SETUSERCONTEXT # ifdef HAVE_LOGIN_CAP_H # include # endif /* HAVE_LOGIN_CAP_H */ #endif /* HAVE_SETUSERCONTEXT */ #if HAVE_GETSPNAM # include #endif /* HAVE_GETSPNAM */ struct rpstate { runprogcb_t cb; pid_t pid; aios_t aio; ref 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), 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 (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 (env)); close (fds[1]); if (pid < 0) { close (fds[0]); (*cb) (New refcounted (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 gl; bhash 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); }