/* -*-c-*-
*
- * $Id: become.c,v 1.5 1997/09/05 11:45:19 mdw Exp $
+ * $Id: become.c,v 1.19 1998/06/29 13:10:41 mdw Exp $
*
* Main code for `become'
*
- * (c) 1997 EBI
+ * (c) 1998 EBI
*/
/*----- Licensing notice --------------------------------------------------*
/*----- Revision history --------------------------------------------------*
*
* $Log: become.c,v $
- * Revision 1.5 1997/09/05 11:45:19 mdw
+ * Revision 1.19 1998/06/29 13:10:41 mdw
+ * Add some commentary regarding an issue in `sudo' which affects `become';
+ * I'm not fixing it yet because I don't think it's important.
+ *
+ * Fixed the PATH lookup code to use the right binary name: `binary' rather
+ * than `todo[0]'. The two only differ when `style' is `l_login', in which
+ * case `binary' has an initial `/' anyway, and the PATH lookup code is
+ * never invoked. The name is used in a buffer-overflow precheck, though,
+ * and auditing is easier if the naming is consistent.
+ *
+ * Revision 1.18 1998/06/26 10:32:54 mdw
+ * Cosmetic change: use sizeof(destination) in memcpy.
+ *
+ * Revision 1.17 1998/06/18 15:06:59 mdw
+ * Close log before execing program to avoid leaving a socket open.
+ *
+ * Revision 1.16 1998/04/23 13:21:04 mdw
+ * Small tweaks. Support no-network configuration option, and rearrange
+ * the help text a little.
+ *
+ * Revision 1.15 1998/01/13 11:10:44 mdw
+ * Add `TZ' to the list of variables to be preserved.
+ *
+ * Revision 1.14 1998/01/12 16:45:39 mdw
+ * Fix copyright date.
+ *
+ * Revision 1.13 1997/09/26 09:14:57 mdw
+ * Merged blowfish branch into trunk.
+ *
+ * Revision 1.12 1997/09/25 16:04:48 mdw
+ * Change directory after becoming someone else, instead of before. This
+ * avoids problems with root-squashed NFS mounts.
+ *
+ * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
+ * Use the Blowfish encryption algorithm instead of IDEA. This is partly
+ * because I prefer Blowfish (without any particularly strong evidence) but
+ * mainly because IDEA is patented and Blowfish isn't.
+ *
+ * Revision 1.11 1997/09/24 09:48:45 mdw
+ * Fix (scary) overrun bug in group allocation stuff.
+ *
+ * Revision 1.10 1997/09/17 10:14:10 mdw
+ * Fix a typo. Support service names in `--port' option.
+ *
+ * Revision 1.9 1997/09/10 10:28:05 mdw
+ * Allow default port to be given as a service name or port number. Handle
+ * groups properly (lots of options here).
+ *
+ * Revision 1.8 1997/09/08 13:56:24 mdw
+ * Change criteria for expunging items from the user's PATH: instead of
+ * removing things starting with `.', remove things not starting with `/'.
+ *
+ * Revision 1.7 1997/09/08 13:43:20 mdw
+ * Change userid when creating tracefiles rather than fiddling with
+ * `access': it works rather better. Also, insert some stdio buffer
+ * flushing to ensure tracedumps are completely written.
+ *
+ * Revision 1.6 1997/09/05 13:47:44 mdw
+ * Make the `-L' (trace-level) option's argument optional, like the long
+ * version is.
+ *
+ * Revision 1.5 1997/09/05 11:45:19 mdw
* Add support for different login styles, and environment variable
* manipulation in a safe and useful way.
*
#include <ctype.h>
#include <errno.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netdb.h>
+#include <grp.h>
#include <pwd.h>
#include <syslog.h>
#include <unistd.h>
/* --- Login behaviour types --- */
+#define l_preserve 0 /* Preserve the environment */
+#define l_setuser 1 /* Update who I am */
+#define l_login 2 /* Do a full login */
+
+/* --- Group behaviour types --- *
+ *
+ * Note that these make a handy bitfield.
+ */
+
+#ifdef HAVE_SETGROUPS
+
enum {
- l_preserve, /* Preserve the environment */
- l_user, /* Update who I am */
- l_login /* Do a full login */
+ g_unset = 0, /* Nobody's set a preference */
+ g_keep = 1, /* Leave the group memberships */
+ g_replace = 2, /* Replace group memberships */
+ g_merge = (g_keep | g_replace) /* Merge the group memberships */
};
+#endif
+
/*----- Static variables --------------------------------------------------*/
static sym_table bc__env;
if (val) {
e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
- if (~e->f & envFlag_preserve || force) {
+ if (!f || ~e->f & envFlag_preserve || force) {
if (f)
free(e->val);
bc__setenv(e, val);
return (e);
}
+/* --- @bc__addGroups@ --- *
+ *
+ * Arguments: @gid_t *g@ = pointer to a group array
+ * @int *png@ = pointer to number of entries in the array
+ * @const gid_t *a@ = pointer to groups to add
+ * @int na@ = number of groups to add
+ *
+ * Returns: Zero if it was OK, nonzero if we should stop now.
+ *
+ * Use: Adds groups to a groups array.
+ */
+
+static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
+{
+ int i, j;
+ int ng = *png;
+
+ for (i = 0; i < na; i++) {
+
+ /* --- Ensure this group isn't already in the list --- */
+
+ for (j = 0; j < ng; j++) {
+ if (a[i] == g[j])
+ goto next_group;
+ }
+
+ /* --- See if there's room for more --- */
+
+ if (ng >= NGROUPS_MAX) {
+ moan("too many groups (system limit exceeded) -- some have been lost");
+ *png = ng;
+ return (-1);
+ }
+
+ /* --- Add the group --- */
+
+ g[ng++] = a[i];
+ next_group:;
+ }
+
+ *png = ng;
+ return (0);
+}
+
/* --- @bc__banner@ --- *
*
* Arguments: @FILE *fp@ = stream to write on
"Usage: \n"
" $ -c <shell-command> <user>\n"
" $ [<env-var>] <user> [<command> [<arguments>]...]\n"
- " $ -d [-p <port>] [-f <config-file>]\n");
+#ifndef NONETWORK
+ " $ -d [-p <port>] [-f <config-file>]\n"
+#endif
+ );
}
/* --- @bc__help@ --- *
"-e, --preserve-environment Try to preserve the current environment\n"
"-s, --su, --set-user Set environment variables to reflect USER\n"
"-l, --login Really log in as USER\n"
+" [Default is "
+#if DEFAULT_LOGIN_STYLE == l_preserve
+ "preserve-environment"
+#elif DEFAULT_LOGIN_STYLE == l_setuser
+ "set-user"
+#elif DEFAULT_LOGIN_STYLE == l_login
+ "login"
+#else
+ "poorly configured"
+#endif
+"]\n\n"
+"-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
+#ifdef HAVE_SETGROUPS
+"-k, --keep-groups Keep your current set of groups\n"
+"-m, --merge-groups Merge the lists of groups\n"
+"-r, --replace-groups Replace the list of groups\n"
+#endif
"\n"
"-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
+#ifndef NONETWORK
"\n"
"-d, --daemon Start a daemon\n"
"-p PORT, --port=PORT In daemon mode, listen on PORT\n"
-"-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
+"-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
+#endif
+ );
#ifdef TRACING
bc__write(fp, "\n");
if (!suid) {
/* --- Become server setup parameters --- */
+#ifndef NONETWORK
char *conffile = file_RULES; /* Default config file for daemon */
- int port = -1; /* Default port for daemon */
+ int port = 0; /* Default port for daemon */
+#endif
/* --- Miscellanous shared variables --- */
unsigned flags = 0; /* Various useful flags */
int style = DEFAULT_LOGIN_STYLE; /* Login style */
+ gid_t group = -1; /* Default group to set */
+ int gstyle = g_unset; /* No group style set yet */
+
+#ifdef HAVE_SETGROUPS
+ gid_t groups[NGROUPS_MAX]; /* Set of groups */
+ int ngroups; /* Number of groups in the set */
+#endif
/* --- Default argument list executes a shell command --- */
enum {
f_daemon = 1, /* Start up in daemon mode */
f_duff = 2, /* Fault in arguments */
- f_login = 4, /* Execute as a login shell */
+ f_shell = 4, /* Run a default shell */
f_dummy = 8, /* Don't actually do anything */
- f_setuid = 16 /* We're running setuid */
+ f_setuid = 16, /* We're running setuid */
+ f_havegroup = 32 /* Set a default group */
};
/* --- Set up the program name --- */
{ "set-user", 0, 0, 's' },
{ "login", 0, 0, 'l' },
+ /* --- Group style options --- */
+
+ { "group", gFlag_argReq, 0, 'g' },
+#ifdef HAVE_SETGROUPS
+ { "keep-groups", 0, 0, 'k' },
+ { "merge-groups", 0, 0, 'm' },
+ { "replace-groups", 0, 0, 'r' },
+#endif
+
/* --- Command to run options --- */
{ "command", gFlag_argReq, 0, 'c' },
/* --- Server options --- */
+#ifndef NONETWORK
{ "daemon", 0, 0, 'd' },
{ "port", gFlag_argReq, 0, 'p' },
{ "config-file", gFlag_argReq, 0, 'f' },
+#endif
/* --- Tracing options --- */
};
i = mdwopt(argc, argv,
- "-" "huv" "esl" "c:" "dp:f:" T("I:T::L:"),
+ "-" /* Return non-options as options */
+ "huv" /* Asking for help */
+ "esl" /* Login style options */
+#ifdef HAVE_SETGROUPS
+ "g:kmr" /* Group style options */
+#else
+ "g:" /* Group (without @setgroups@) */
+#endif
+ "c:" /* Command to run options */
+#ifndef NONETWORK
+ "dp:f:" /* Server options */
+#endif
+#ifdef TRACING
+ "I:T::L::" /* Tracing options */
+#endif
+ ,
opts, 0, 0, gFlag_envVar);
if (i < 0)
goto done_options;
style = l_preserve;
break;
case 's':
- style = l_user;
+ style = l_setuser;
break;
case 'l':
style = l_login;
break;
+ /* --- Group style options --- */
+
+ case 'g':
+ if (isdigit((unsigned char)optarg[0]))
+ group = atoi(optarg);
+ else {
+ struct group *gr = getgrnam(optarg);
+ if (!gr)
+ die("unknown group `%s'", optarg);
+ group = gr->gr_gid;
+ }
+ flags |= f_havegroup;
+ break;
+
+ case 'k':
+ gstyle = g_keep;
+ break;
+ case 'm':
+ gstyle = g_merge;
+ break;
+ case 'r':
+ gstyle = g_replace;
+ break;
+
/* --- Command to run options --- */
case 'c':
/* --- Server options --- */
+#ifndef NONETWORK
case 'p':
- port = atoi(optarg);
+ if (isdigit((unsigned char)optarg[0]))
+ port = htons(atoi(optarg));
+ else {
+ struct servent *s = getservbyname(optarg, "udp");
+ if (!s)
+ die("unknown service name `%s'", optarg);
+ port = s->s_port;
+ }
break;
case 'd':
flags |= f_daemon;
case 'f':
conffile = optarg;
break;
+#endif
/* --- Pretend to be a different user --- *
*
if (optarg == 0 || strcmp(optarg, "-") == 0)
fp = stdout;
else {
- if ((flags & f_setuid) && access(optarg, W_OK)) {
- die("no write permission for trace file file `%s': %s",
- optarg, strerror(errno));
+ uid_t eu = geteuid(), ru = getuid();
+
+#ifdef HAVE_SETREUID
+ if (setreuid(eu, ru))
+#else
+ if (seteuid(ru))
+#endif
+ {
+ die("couldn't temporarily give up privileges: %s",
+ strerror(errno));
}
+
if ((fp = fopen(optarg, "w")) == 0) {
die("couldn't open trace file `%s' for writing: %s",
optarg, strerror(errno));
}
+
+#ifdef HAVE_SETREUID
+ if (setreuid(ru, eu))
+#else
+ if (seteuid(eu))
+#endif
+ die("couldn't regain privileges: %s", strerror(errno));
}
traceon(fp, TRACE_DFL);
trace(TRACE_MISC, "become: tracing enabled");
static tr lvltbl[] = {
{ 'm', TRACE_MISC, "miscellaneous messages" },
{ 's', TRACE_SETUP, "building the request block" },
- { 'd', TRACE_DAEMON, "server process" },
{ 'r', TRACE_RULE, "ruleset scanning" },
{ 'c', TRACE_CHECK, "request checking" },
+#ifndef NONETWORK
+ { 'd', TRACE_DAEMON, "server process" },
{ 'l', TRACE_CLIENT, "client process" },
{ 'R', TRACE_RAND, "random number generator" },
{ 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
+#endif
{ 'y', TRACE_YACC, "parsing configuration file" },
{ 'D', TRACE_DFL, "default tracing options" },
{ 'A', TRACE_ALL, "all tracing options" },
}
bc__write(stdout,
"\n"
-"Also, `+' and `-' options are recognised to turn on and off vavrious\n"
+"Also, `+' and `-' options are recognised to turn on and off various\n"
"tracing options. For example, `A-r' enables everything except ruleset\n"
"tracing, and `A-D+c' is everything except the defaults, but with request\n"
"check tracing.\n"
/* --- None of the above --- */
if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
- if (who == 0)
+ if (!who) {
who = optarg;
- else {
+ break;
+ } else {
optind--;
goto done_options;
}
/* --- Switch to daemon mode if requested --- */
+#ifndef NONETWORK
if (flags & f_daemon) {
T( trace(TRACE_MISC, "become: daemon mode requested"); )
daemon_init(conffile, port);
exit(0);
}
+#endif
/* --- Open a syslog --- */
uname(&u);
if ((he = gethostbyname(u.nodename)) == 0)
die("who am I? (can't resolve `%s')", u.nodename);
- memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
+ memcpy(&rq.host, he->h_addr, sizeof(rq.host));
+ }
+
+ /* --- Fiddle with group ownerships a bit --- */
+
+ {
+#ifdef HAVE_SETGROUPS
+ gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
+ int n_fgr, n_tgr;
+#endif
+
+ /* --- Set the default login group, if there is one --- */
+
+ if (~flags & f_havegroup)
+ group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
+
+#ifndef HAVE_SETGROUPS
+
+ /* --- Check that it's valid --- */
+
+ if (group != from_pw->pw_gid && group != to_pw->pw_gid)
+ die("invalid default group");
+
+#else
+
+ /* --- Set the default group style --- */
+
+ if (gstyle == g_unset)
+ gstyle = (style == l_login) ? g_replace : g_merge;
+
+ /* --- Read in my current set of groups --- */
+
+ n_fgr = getgroups(NGROUPS_MAX, from_gr);
+
+ /* --- Now read the groups for the target user --- *
+ *
+ * Do this even if I'm using the @g_keep@ style -- I'll use it for
+ * checking that @group@ is valid.
+ */
+
+ {
+ struct group *gr;
+ char **p;
+
+ n_tgr = 0;
+ to_gr[n_tgr++] = to_pw->pw_gid;
+
+ setgrent();
+ while ((gr = getgrent()) != 0) {
+ if (gr->gr_gid == to_gr[0])
+ continue;
+ for (p = gr->gr_mem; *p; p++) {
+ if (strcmp(to_pw->pw_name, *p) == 0) {
+ to_gr[n_tgr++] = gr->gr_gid;
+ if (n_tgr >= NGROUPS_MAX)
+ goto done_groups;
+ break;
+ }
+ }
+ }
+
+ done_groups:
+ endgrent();
+ }
+
+ /* --- Check that @group@ is reasonable --- */
+
+ {
+ int i;
+
+ if (group == getgid() || group == from_pw->pw_gid)
+ goto group_ok;
+ for (i = 0; i < n_fgr; i++) {
+ if (group == from_gr[i])
+ goto group_ok;
+ }
+ for (i = 0; i < n_tgr; i++) {
+ if (group == to_gr[i])
+ goto group_ok;
+ }
+ die("invalid default group");
+ group_ok:;
+ }
+
+ /* --- Phew. Now comes the hard bit --- */
+
+ {
+ gid_t ga[4];
+ int i;
+
+ i = 0;
+ ga[i++] = group;
+ if (gstyle & g_keep) {
+ ga[i++] = getgid();
+ ga[i++] = from_pw->pw_gid;
+ }
+ if (gstyle & g_replace)
+ ga[i++] = to_pw->pw_gid;
+
+ /* --- Style authorities will become apoplectic if shown this --- *
+ *
+ * As far as I can see, it's the neatest way of writing it.
+ */
+
+ ngroups = 0;
+ (void)(bc__addGroups(groups, &ngroups, ga, i) ||
+ ((gstyle & g_keep) &&
+ bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
+ ((gstyle & g_replace) &&
+ bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
+ }
+
+#endif
+
+ /* --- Trace the results of all this --- */
+
+ T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
+
+#ifdef HAVE_SETGROUPS
+ IF_TRACING(TRACE_SETUP, {
+ int i;
+
+ for (i = 1; i < ngroups; i++)
+ trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
+ })
+#endif
}
/* --- Shell commands are easy --- */
binary = todo[0];
}
- else switch (style) {
+ else {
+ flags |= f_shell;
+
+ switch (style) {
- /* --- An unadorned becoming requires little work --- */
+ /* --- An unadorned becoming requires little work --- */
- case l_preserve:
- shell[0] = getenv("SHELL");
- if (!shell[0])
- shell[0] = from_pw->pw_shell;
- shell[1] = 0;
- todo = shell;
- binary = todo[0];
- break;
+ case l_preserve:
+ shell[0] = getenv("SHELL");
+ if (!shell[0])
+ shell[0] = from_pw->pw_shell;
+ shell[1] = 0;
+ todo = shell;
+ binary = todo[0];
+ break;
- /* --- An su-like login needs slightly less effort --- */
+ /* --- An su-like login needs slightly less effort --- */
- case l_user:
- shell[0] = to_pw->pw_shell;
- shell[1] = 0;
- todo = shell;
- binary = todo[0];
- break;
+ case l_setuser:
+ shell[0] = to_pw->pw_shell;
+ shell[1] = 0;
+ todo = shell;
+ binary = todo[0];
+ break;
- /* --- A login request needs a little bit of work --- */
-
- case l_login: {
- const char *p = strrchr(to_pw->pw_shell, '/');
-
- if (p)
- p++;
- else
- p = to_pw->pw_shell;
- shell[0] = xmalloc(strlen(p) + 2);
- shell[0][0] = '-';
- strcpy(shell[0] + 1, p);
- shell[1] = 0;
- todo = shell;
- binary = to_pw->pw_shell;
- chdir(to_pw->pw_dir);
- } break;
+ /* --- A login request needs a little bit of work --- */
+
+ case l_login: {
+ const char *p = strrchr(to_pw->pw_shell, '/');
+
+ if (p)
+ p++;
+ else
+ p = to_pw->pw_shell;
+ shell[0] = xmalloc(strlen(p) + 2);
+ shell[0][0] = '-';
+ strcpy(shell[0] + 1, p);
+ shell[1] = 0;
+ todo = shell;
+ binary = to_pw->pw_shell;
+ } break;
+ }
}
/* --- Mangle the environment --- *
*/
static char *preserve[] = {
- "TERM", "DISPLAY", 0
+ "TERM", "DISPLAY", "TZ", 0
};
-
+
/* --- Variables to be expunged --- *
*
- * Any environment string which has one of the following as a prefix
- * will be expunged from the environment passed to the called process.
- * The first line lists variables which have been used to list search
- * paths for shared libraries: by manipulating these, an attacker could
- * replace a standard library with one of his own. The second line lists
- * other well-known dangerous environment variables.
+ * Any environment string which has one of the following as a prefix will
+ * be expunged from the environment passed to the called process. The
+ * first line lists variables which have been used to list search paths
+ * for shared libraries: by manipulating these, an attacker could replace
+ * a standard library with one of his own. The second line lists other
+ * well-known dangerous environment variables.
*/
static char *banned[] = {
size_t sz;
unsigned f;
sym_iter i;
-
+
/* --- Stage one. Preserve display-specific variables --- */
for (pp = preserve; *pp; pp++) {
}
} /* Fall through */
- case l_user:
+ case l_setuser:
bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
q = e->val + 5;
for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
- if (p[0] == '.')
+ if (p[0] != '/')
continue;
while (*p)
*q++ = *p++;
p = *pp + 1;
if (memcmp(e->_base.name, p, strlen(p)) == 0)
goto expunge;
- } else if (strcmp(e->_base.name, p) == 0)
+ } else if (strcmp(e->_base.name, *pp) == 0)
goto expunge;
}
if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
continue;
- /* --- Now build the pathname and check it --- */
+ /* --- Now build the pathname and check it --- *
+ *
+ * Issue: user can take advantage of these privileges to decide whether
+ * a program with a given name exists. I'm not sure that's
+ * particularly significant: it only works on regular files with
+ * execute permissions, and if you're relying on the names of these
+ * being secret to keep your security up, then you're doing something
+ * deeply wrong anyway. On the other hand, it's useful to allow people
+ * to be able to execute programs and scripts which they wouldn't
+ * otherwise have access to. [This problem was brought up on
+ * Bugtraq, as a complaint against sudo.]
+ */
- sprintf(rq.cmd, "%s/%s", p, todo[0]);
+ sprintf(rq.cmd, "%s/%s", p, binary);
if (stat(rq.cmd, &st) == 0 && /* Check it exists */
st.st_mode & 0111 && /* Check it's executable */
- (st.st_mode & S_IFMT) == S_IFREG) /* Check it's a file */
+ S_ISREG(st.st_mode)) /* Check it's a file */
break;
}
*
* If the user is already what she wants to be, then print a warning.
* Then, if I was just going to spawn a shell, quit, to reduce user
- * confusion. Otherwise, do what was wanted anyway.
+ * confusion. Otherwise, do what was wanted anyway. Also, don't bother
+ * checking if we're already root -- root can do anything anyway, and at
+ * least this way we get some logging done, and offer a more friendly
+ * front-end.
*/
if (rq.from == rq.to) {
moan("you already are `%s'!", to_pw->pw_name);
- if (!cmd && todo == shell) {
+ if (flags & f_shell) {
moan("(to prevent confusion, I'm not spawning a shell)");
exit(0);
}
} else {
- int a = check(&rq);
+ int a = (rq.from == 0) || check(&rq);
syslog(LOG_INFO,
"permission %s for %s to become %s to run `%s'",
if (flags & f_dummy) {
puts("permission granted");
return (0);
- } else {
- if (setuid(rq.to) == -1)
- die("couldn't set uid: %s", strerror(errno));
- execve(rq.cmd, todo, env);
- die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
- return (127);
}
+
+#ifdef HAVE_SETGROUPS
+ if (setgroups(ngroups, groups) < 0)
+ die("couldn't set groups: %s", strerror(errno));
+#endif
+
+ if (setgid(group) < 0)
+ die("couldn't set default group: %s", strerror(errno));
+ if (setuid(rq.to) < 0)
+ die("couldn't set uid: %s", strerror(errno));
+
+ /* --- If this was a login, change current directory --- */
+
+ if ((flags & f_shell) &&
+ style == l_login &&
+ chdir(to_pw->pw_dir) < 0) {
+ moan("couldn't change directory to `%s': %s",
+ to_pw->pw_dir, strerror(errno));
+ }
+
+ /* --- Finally, call the program --- */
+
+ fflush(0);
+ closelog();
+ execve(rq.cmd, todo, env);
+ die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
+ return (127);
}
/*----- That's all, folks -------------------------------------------------*/