/* $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 <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);
}
syntax highlighted by Code2HTML, v. 0.9.1