/* $Id: child.c,v 1.27 2006/06/01 04:34:52 dm Exp $ */
/*
*
* Copyright (C) 2004 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 "local.h"
#include <ctype.h>
#include <pwd.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 */
#include <sys/signal.h>
#include <sys/wait.h>
char *getusershell(void);
void setusershell(void);
void endusershell(void);
static int
validshell (const char *shell)
{
const char *s;
setusershell ();
while ((s = getusershell ()))
if (!strcmp (s, shell)) {
endusershell ();
return 1;
}
endusershell ();
return 0;
}
struct passwd *
validuser (const char *user)
{
char *avdir;
struct stat sb;
int expired = 0;
#ifdef HAVE_GETSPNAM
struct spwd *spe;
#endif /* HAVE_GETSPNAM */
struct passwd *pw = getpwnam (user);
if (!pw || !pw->pw_uid || !validshell (pw->pw_shell) || pw->pw_dir[0] != '/')
return NULL;
#ifdef HAVE_GETSPNAM
spe = getspnam (pw->pw_name);
if (spe && spe->sp_expire > 0
&& spe->sp_expire <= (time (NULL) / (24 * 60 * 60)))
expired = 1;
#elif defined (HAVE_PASSWD_PW_EXPIRE)
if (pw->pw_expire > 0 && pw->pw_expire <= time (NULL))
expired = 1;
#endif /* HAVE_PASSWD_PW_EXPIRE */
if (expired)
return NULL;
avdir = xmalloc (strlen (pw->pw_dir) + sizeof ("/.avenger"));
strcpy (avdir, pw->pw_dir);
strcat (avdir, "/.avenger");
if (lstat (avdir, &sb)) {
if (tmperr (errno)) {
perror (avdir);
exit (EX_OSERR);
}
pw = NULL;
}
else if (!S_ISDIR (sb.st_mode))
pw = NULL;
free (avdir);
return pw;
}
#ifndef NGROUPS_MAX
# define NGROUPS_MAX 16
#endif /* !NGROUPS_MAX */
int
become_user (struct passwd *pw, int grouplist, int cd)
{
int root = getuid () <= 0;
u_int flags __attribute__ ((unused));
if (setsid () == -1)
perror ("setsid");
if (!grouplist && setgroups (0, NULL)) {
GETGROUPS_T gid = getgid ();
if (setgroups (1, &gid) && root) {
perror ("fatal: setgroups");
return (EX_OSERR);
}
}
#ifdef HAVE_SETUSERCONTEXT
flags = LOGIN_SETALL;
flags &= ~LOGIN_SETUMASK;
if (!grouplist) {
flags &= ~LOGIN_SETGROUP;
if (setgid (pw->pw_gid)) {
perror ("setgid");
if (root)
return (EX_OSERR);
}
}
if (root && setusercontext (NULL, pw, pw->pw_uid, flags)) {
perror ("setusercontext");
return (EX_OSERR);
}
#else /* !HAVE_SETUSERCONTEXT */
# if HAVE_SETLOGIN
if (setlogin (pw->pw_name))
perror ("setlogin");
# endif /* HAVE_SETLOGIN */
# ifdef HAVE_INITGROUPS
if (grouplist && initgroups (pw->pw_name, pw->pw_gid)) {
fprintf (stderr, "initgroups failed\n");
if (root)
return (EX_OSERR);
}
# else /* !HAVE_INITGROUPS */
if (grouplist) {
GETGROUPS_T gl[NGROUPS_MAX];
int gn = 0;
struct group *gr;
# ifdef HAVE_EGID_IN_GROUPLIST
gl[gn++] = pw->pw_gid;
# endif /* HAVE_EGID_IN_GROUPLIST */
setgrent ();
while (gn < NGROUPS_MAX && (gr = getgrent ())) {
int i;
char **mp;
if (gr->gr_gid == pw->pw_gid)
continue;
for (i = 0; i < gn; i++)
if (gr->gr_gid == gl[i])
goto next;
for (mp = gr->gr_mem; *mp; mp++)
if (!strcmp (*mp, pw->pw_name)) {
gl[gn++] = gr->gr_gid;
goto next;
}
next:;
}
if (setgroups (gn, gl)) {
perror ("setgroups");
if (root)
return (EX_OSERR);
}
}
# endif /* !HAVE_INITGROUPS */
if (setgid (pw->pw_gid)) {
perror ("setgid");
if (root)
return (EX_OSERR);
}
if (setuid (pw->pw_uid))
perror ("setuid");
#endif /* !HAVE_SETUSERCONTEXT */
if (root && pw->pw_uid && !getuid ()) {
fprintf (stderr, "bacome_user: failed to drop privileges\n");
return (EX_OSERR);
}
mysetenv ("HOME", pw->pw_dir, -1);
if (cd && chdir (pw->pw_dir)) {
perror (pw->pw_dir);
return tmperr (errno) ? EX_OSERR : EX_CONFIG;
}
/* 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 */
return 0;
}
static int
linefromprog (char **resp, const char *prog, int mfd)
{
pid_t pid = 0;
int status = -1;
int fds[2];
static char buf[1025];
int n, nn;
*resp = NULL;
while (*prog && isspace (*prog))
prog++;
if (!*prog)
return 0;
if (pipe (fds)) {
perror ("pipe");
return EX_OSERR;
}
if (lseek (mfd, 0, SEEK_SET) == -1 || (pid = fork ()) == -1) {
perror (pid ? "fork" : "lseek");
close (fds[0]);
close (fds[1]);
return EX_OSERR;
}
if (!pid) {
close (fds[0]);
errno = 0;
if (mfd && dup2 (mfd, 0)) {
perror ("dup2 (mfd)");
_exit (EX_OSERR);
}
else if (fds[1] != 1 && dup2 (fds[1], 1) != 1) {
perror ("dup2 (pipe)");
_exit (EX_OSERR);
}
execl ("/bin/sh", "/bin/sh", "-c", prog, (char *) NULL);
perror ("/bin/sh");
_exit (EX_OSFILE);
}
close (fds[1]);
n = 0;
while ((nn = read (fds[0], buf + n, sizeof (buf) - n)))
if (nn < 0 || (size_t) (n += nn) >= sizeof (buf)) {
close (fds[0]);
waitpid (pid, &status, 0);
fprintf (stderr, "command '%s' produced too much output\n", prog);
return EX_SOFTWARE;
}
close (fds[0]);
waitpid (pid, &status, 0);
if (!status) {
if (n > 0 && memchr (buf, '\n', n) == buf + n - 1) {
buf[n - 1] = '\0';
*resp = buf;
}
else
fprintf (stderr, "command '%s' didn't output exactly one line\n", prog);
}
if (WIFEXITED (status)) {
status = WEXITSTATUS (status);
if (status < 64
|| (status > 68 && status != 99 && status != 100
&& status != 111 && status != 112))
status = 0;
return status;
}
if (WIFSIGNALED (status))
fprintf (stderr, "command '%s' exited with signal %d\n",
prog, WTERMSIG (status));
return EX_TEMPFAIL;
}
static int
do_line (char *line, size_t len, FILE *parent, int mfd)
{
char *in;
int n;
if (len > 0)
line[--len] = '\0';
switch (line[0]) {
case '\n':
case '\0':
case '#':
return 0;
case '&':
fprintf (parent, "%s\n", line);
return 0;
case '<':
if (line[1] == '!') {
n = linefromprog (&in, line + 2, mfd);
if (in)
fprintf (parent, "<%s\n", in);
return n;
}
else
fprintf (parent, "<%s\n", line + 1);
return 0;
case '!':
n = linefromprog (&in, line + 1, mfd);
if (!in || !*in)
return n;
if (in[0] != '.' && in[0] != '/')
fprintf (stderr, "invalid mailbox name '%s' from '%s'\n", in, line);
line = in;
len = strlen (line);
/* cascade */
case '.':
case '/':
if (len < 2) {
fprintf (stderr, "invalid line '%s' in .avenger/local* file\n", line);
return 0;
}
if (line[len - 1] == '/') {
line[len - 1] = '\0';
return deliver_maildir (line, mfd, 1);
}
return deliver_mbox (line, mfd, 1);
case '|':
{
pid_t pid = 0;
int status = -1;
while (*++line && isspace (*line))
;
if (!*line)
return 0;
if (lseek (mfd, 0, SEEK_SET) == -1 || (pid = fork ()) == -1) {
perror (pid ? "fork" : "lseek");
return EX_OSERR;
}
if (!pid) {
if (dup2 (mfd, 0)) {
perror ("dup2");
_exit (EX_OSERR);
}
execl ("/bin/sh", "/bin/sh", "-c", line, (char *) NULL);
perror ("/bin/sh");
_exit (EX_OSFILE);
}
if (waitpid (pid, &status, 0) != pid) {
perror ("waitpid");
return EX_OSFILE;
}
if (WIFEXITED (status)) {
status = WEXITSTATUS (status);
if (status > 0 && status < 64)
fprintf (stderr, "command '%s' exited with status %d\n",
line, status);
return status;
}
if (WIFSIGNALED (status))
fprintf (stderr, "command '%s' exited with signal %d\n",
line, WTERMSIG (status));
return EX_TEMPFAIL;
}
default:
fprintf (stderr, "invalid line '%s' in .avenger/local* file\n", line);
return 0;
}
}
static int
open_local (const char *extra, char **path)
{
char buf[300];
const char *p;
if (!extra) {
int fd = open (".avenger/local", O_RDONLY);
if (fd < 0)
fd = open ("/dev/null", O_RDONLY);
if (fd < 0)
errno = EAGAIN;
return fd;
}
p = extra + strlen (extra);
snprintf (buf, sizeof (buf), ".avenger/local%c%s",
opt_separator, extra);
for (;;) {
int fd = open (buf, O_RDONLY);
if (fd >= 0) {
if (!*p)
mysetenv ("PREFIX", extra, -1);
else {
int i;
char sufn[80];
mysetenv ("PREFIX", extra, p - extra - 1);
mysetenv ("SUFFIX", p, -1);
for (i = 1; (p = strchr (p, opt_separator)); i++) {
snprintf (sufn, sizeof (sufn), "SUFFIX%d", i);
mysetenv (sufn, ++p, -1);
}
}
if (path)
*path = xstrdup (buf);
return fd;
}
if (p <= extra || tmperr (errno))
return -1;
do {
p--;
} while (p > extra && p[-1] != opt_separator);
snprintf (buf, sizeof (buf), ".avenger/local%c%.*sdefault",
opt_separator, (int) (p - extra), extra);
}
}
static void
nothing (int sig)
{
}
int
child (struct passwd *pw, int pfd, int mfd)
{
FILE *parent = fdopen (pfd, "w");
FILE *local;
int err;
int fd;
struct lnbuf buf;
int gotone = 0;
char *localpath = NULL; /* XXX - initialize to work around gcc4 */
if (getenv ("FORKDEBUG")) {
signal (SIGCONT, nothing);
fprintf (stderr, "%s: pid %d waiting for debugger\n", progname, getpid ());
pause ();
}
if (!parent) {
perror ("fdopen");
return EX_OSERR;
}
setvbuf (parent, NULL, _IOLBF, 0);
if ((err = become_user (pw, 1, 1)))
return err;
fd = open_local (opt_extra, &localpath);
if (fd < 0) {
if (tmperr (errno))
return EX_OSERR;
else if (errno == ENOENT)
return EX_NOUSER;
else
return EX_CONFIG;
}
local = fdopen (fd, "r");
if (!local) {
perror ("fdopen");
close (fd);
return EX_OSERR;
}
bzero (&buf, sizeof (buf));
while ((err = readln (&buf, local, 8192))) {
switch (err) {
case LNBUF_OK:
if (!gotone && buf.buf[0] == '#' && buf.buf[1] == '!') {
struct stat sb;
if (fstat (fd, &sb) || !(sb.st_mode & 0111)) {
fprintf (stderr, "%s starts '#!' but not executable\n", localpath);
return EX_TEMPFAIL;
}
if (lseek (mfd, 0, SEEK_SET) == -1) {
perror ("lseek");
return EX_OSERR;
}
if (mfd != 0 && (dup2 (mfd, 0) == -1 || close (mfd))) {
perror ("dup2");
return EX_OSERR;
}
fclose (parent);
execl (localpath, localpath, (char *) NULL);
perror (localpath);
return EX_TEMPFAIL;
}
gotone = 1;
err = do_line (buf.buf, buf.size, parent, mfd);
if (err)
return err;
break;
case LNBUF_EOFNL:
fprintf (stderr, "ignoring incomplete line in %s\n", localpath);
break;
case LNBUF_NOMEM:
fprintf (stderr, "out of memory reading line from %s\n", localpath);
return EX_OSERR;
case LNBUF_TOOBIG:
fprintf (stderr, "line too long in %s file\n", localpath);
return EX_CONFIG;
case LNBUF_IOERR:
fprintf (stderr, "error reading %s file\n", localpath);
return EX_OSERR;
}
}
if (gotone)
return 0;
else {
char *dl = xmalloc (strlen (opt_default) + 2);
sprintf (dl, "%s\n", opt_default);
err = do_line (dl, strlen (dl), parent, mfd);
free (dl);
return err;
}
}
syntax highlighted by Code2HTML, v. 0.9.1