Fix copyright date.
[become] / src / become.c
CommitLineData
c4f2d992 1/* -*-c-*-
2 *
c758e654 3 * $Id: become.c,v 1.14 1998/01/12 16:45:39 mdw Exp $
c4f2d992 4 *
5 * Main code for `become'
6 *
c758e654 7 * (c) 1998 EBI
c4f2d992 8 */
9
03f996bd 10/*----- Licensing notice --------------------------------------------------*
c4f2d992 11 *
12 * This file is part of `become'
13 *
14 * `Become' is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * `Become' is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
03f996bd 25 * along with `become'; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
c4f2d992 27 */
28
29/*----- Revision history --------------------------------------------------*
30 *
31 * $Log: become.c,v $
c758e654 32 * Revision 1.14 1998/01/12 16:45:39 mdw
33 * Fix copyright date.
34 *
9e5602f0 35 * Revision 1.13 1997/09/26 09:14:57 mdw
36 * Merged blowfish branch into trunk.
37 *
45df69ab 38 * Revision 1.12 1997/09/25 16:04:48 mdw
39 * Change directory after becoming someone else, instead of before. This
40 * avoids problems with root-squashed NFS mounts.
41 *
9e5602f0 42 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
43 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
44 * because I prefer Blowfish (without any particularly strong evidence) but
45 * mainly because IDEA is patented and Blowfish isn't.
46 *
45df69ab 47 * Revision 1.11 1997/09/24 09:48:45 mdw
505f2736 48 * Fix (scary) overrun bug in group allocation stuff.
49 *
50 * Revision 1.10 1997/09/17 10:14:10 mdw
9df0c75d 51 * Fix a typo. Support service names in `--port' option.
52 *
a5089273 53 * Revision 1.9 1997/09/10 10:28:05 mdw
54 * Allow default port to be given as a service name or port number. Handle
55 * groups properly (lots of options here).
56 *
57 * Revision 1.8 1997/09/08 13:56:24 mdw
6a4ec25c 58 * Change criteria for expunging items from the user's PATH: instead of
59 * removing things starting with `.', remove things not starting with `/'.
60 *
61 * Revision 1.7 1997/09/08 13:43:20 mdw
c6885d5f 62 * Change userid when creating tracefiles rather than fiddling with
63 * `access': it works rather better. Also, insert some stdio buffer
64 * flushing to ensure tracedumps are completely written.
65 *
66 * Revision 1.6 1997/09/05 13:47:44 mdw
d6683a48 67 * Make the `-L' (trace-level) option's argument optional, like the long
68 * version is.
69 *
70 * Revision 1.5 1997/09/05 11:45:19 mdw
387501ae 71 * Add support for different login styles, and environment variable
72 * manipulation in a safe and useful way.
73 *
74 * Revision 1.4 1997/08/20 16:15:13 mdw
569b8891 75 * Overhaul of environment handling. Fix daft bug in path search code.
76 *
88e486d5 77 * Revision 1.3 1997/08/07 16:28:59 mdw
78 * Do something useful when users attempt to become themselves.
79 *
03f996bd 80 * Revision 1.2 1997/08/04 10:24:20 mdw
81 * Sources placed under CVS control.
82 *
83 * Revision 1.1 1997/07/21 13:47:54 mdw
c4f2d992 84 * Initial revision
85 *
86 */
87
88/*----- Header files ------------------------------------------------------*/
89
90/* --- ANSI headers --- */
91
92#include <ctype.h>
93#include <errno.h>
a5089273 94#include <limits.h>
c4f2d992 95#include <stdio.h>
96#include <stdlib.h>
97#include <string.h>
03f996bd 98#include <time.h>
c4f2d992 99
100/* --- Unix headers --- */
101
102#include <sys/types.h>
103#include <sys/stat.h>
104#include <sys/socket.h>
105#include <sys/utsname.h>
106
107#include <netinet/in.h>
108
109#include <arpa/inet.h>
110
111#include <netdb.h>
a5089273 112#include <grp.h>
c4f2d992 113#include <pwd.h>
114#include <syslog.h>
115#include <unistd.h>
116
03f996bd 117extern char **environ;
118
c4f2d992 119/* --- Local headers --- */
120
121#include "become.h"
122#include "config.h"
123#include "check.h"
124#include "daemon.h"
125#include "lexer.h"
126#include "mdwopt.h"
127#include "name.h"
128#include "parser.h"
129#include "rule.h"
387501ae 130#include "sym.h"
c4f2d992 131#include "utils.h"
03f996bd 132#include "userdb.h"
c4f2d992 133
387501ae 134/*----- Type definitions --------------------------------------------------*/
135
136/* --- Symbol table entry for an environment variable --- */
137
138typedef struct sym_env {
139 sym_base _base; /* Symbol table information */
140 unsigned f; /* Flags word (see below) */
141 char *val; /* Pointer to variable value */
142} sym_env;
143
144/* --- Environment variable flags --- */
145
146enum {
147 envFlag_preserve = 1
148};
149
150/* --- Login behaviour types --- */
151
152enum {
153 l_preserve, /* Preserve the environment */
a5089273 154 l_setuser, /* Update who I am */
387501ae 155 l_login /* Do a full login */
156};
157
a5089273 158/* --- Group behaviour types --- *
159 *
160 * Note that these make a handy bitfield.
161 */
162
163#ifdef HAVE_SETGROUPS
164
165enum {
166 g_unset = 0, /* Nobody's set a preference */
167 g_keep = 1, /* Leave the group memberships */
168 g_replace = 2, /* Replace group memberships */
169 g_merge = (g_keep | g_replace) /* Merge the group memberships */
170};
171
172#endif
173
387501ae 174/*----- Static variables --------------------------------------------------*/
175
176static sym_table bc__env;
177
c4f2d992 178/*----- Main code ---------------------------------------------------------*/
179
180/* --- @bc__write@ --- *
181 *
182 * Arguments: @FILE *fp@ = pointer to a stream to write on
183 * @const char *p@ = pointer to a string
184 *
185 * Returns: ---
186 *
187 * Use: Writes the string to the stream, substituting the program
188 * name (as returned by @quis@) for each occurrence of the
189 * character `$'.
190 */
191
192static void bc__write(FILE *fp, const char *p)
193{
194 const char *n = quis();
195 size_t l;
196 size_t nl = strlen(n);
197
198 /* --- Try to be a little efficient --- *
199 *
569b8891 200 * Gather up non-`$' characters using @strcspn@ and spew them out really
c4f2d992 201 * quickly.
202 */
203
204 for (;;) {
205 l = strcspn(p, "$");
206 if (l)
207 fwrite(p, l, 1, fp);
208 p += l;
209 if (!*p)
210 break;
211 fwrite(n, nl, 1, fp);
212 p++;
213 }
214}
215
387501ae 216/* --- @bc__setenv@ --- *
217 *
218 * Arguments: @sym_env *e@ = pointer to environment variable block
219 * @const char *val@ = value to set
220 *
221 * Returns: ---
222 *
223 * Use: Sets an environment variable block to the right value.
224 */
225
226static void bc__setenv(sym_env *e, const char *val)
227{
228 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
229 sprintf(e->val, "%s=%s", e->_base.name, val);
230}
231
232/* --- @bc__putenv@ --- *
233 *
234 * Arguments: @const char *var@ = name of the variable to set, or 0 if
235 * this is embedded in the value string
236 * @const char *val@ = value to set, or 0 if the variable must
237 * be removed
238 * @unsigned int fl@ = flags to set
239 * @unsigned int force@ = force overwrite of preserved variables
240 *
241 * Returns: Pointer to symbol block, or zero if it was deleted.
242 *
243 * Use: Puts an item into the environment.
244 */
245
246static sym_env *bc__putenv(const char *var, const char *val,
247 unsigned int fl, unsigned int force)
248{
249 unsigned int f;
250 sym_env *e;
251 char *q = 0;
252 size_t sz;
253
254 /* --- Sort out embedded variable names --- */
255
256 if (!var) {
257 const char *p = strchr(val, '=');
258
259 if (p) {
260 sz = p - val;
261 q = xmalloc(sz + 1);
262 memcpy(q, val, sz);
263 q[sz] = 0;
264 var = q;
265 val = p + 1;
266 } else {
267 var = val;
268 val = 0;
269 }
270 }
271
272 /* --- Find the variable block --- */
273
274 if (val) {
275 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
a5089273 276 if (!f || ~e->f & envFlag_preserve || force) {
387501ae 277 if (f)
278 free(e->val);
279 bc__setenv(e, val);
280 }
281 } else {
282 e = sym_find(&bc__env, var, -1, 0, 0);
283 if (e && (force || ~e->f & envFlag_preserve))
284 sym_remove(&bc__env, e);
285 e = 0;
286 }
287
288 /* --- Tidy up and return --- */
289
290 if (q)
291 free(q);
292 if (e)
293 e->f = fl;
294 return (e);
295}
296
a5089273 297/* --- @bc__addGroups@ --- *
298 *
299 * Arguments: @gid_t *g@ = pointer to a group array
300 * @int *png@ = pointer to number of entries in the array
301 * @const gid_t *a@ = pointer to groups to add
302 * @int na@ = number of groups to add
303 *
304 * Returns: Zero if it was OK, nonzero if we should stop now.
305 *
306 * Use: Adds groups to a groups array.
307 */
308
309static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
310{
311 int i, j;
312 int ng = *png;
313
314 for (i = 0; i < na; i++) {
315
316 /* --- Ensure this group isn't already in the list --- */
317
318 for (j = 0; j < ng; j++) {
319 if (a[i] == g[j])
320 goto next_group;
321 }
322
323 /* --- See if there's room for more --- */
324
505f2736 325 if (ng >= NGROUPS_MAX) {
326 moan("too many groups (system limit exceeded) -- some have been lost");
a5089273 327 *png = ng;
328 return (-1);
329 }
330
331 /* --- Add the group --- */
332
333 g[ng++] = a[i];
334 next_group:;
335 }
336
337 *png = ng;
338 return (0);
339}
340
c4f2d992 341/* --- @bc__banner@ --- *
342 *
343 * Arguments: @FILE *fp@ = stream to write on
344 *
345 * Returns: ---
346 *
347 * Use: Writes a banner containing copyright information.
348 */
349
350static void bc__banner(FILE *fp)
351{
352 bc__write(fp, "$ version " VERSION "\n");
353}
354
355/* --- @bc__usage@ --- *
356 *
357 * Arguments: @FILE *fp@ = stream to write on
358 *
359 * Returns: ---
360 *
361 * Use: Writes a terse reminder of command line syntax.
362 */
363
364static void bc__usage(FILE *fp)
365{
366 bc__write(fp,
367 "Usage: \n"
368 " $ -c <shell-command> <user>\n"
387501ae 369 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
c4f2d992 370 " $ -d [-p <port>] [-f <config-file>]\n");
371}
372
373/* --- @bc__help@ --- *
374 *
375 * Arguments: @FILE *fp@ = stream to write on
569b8891 376 * @int suid@ = whether we're running set-uid
c4f2d992 377 *
378 * Returns: ---
379 *
380 * Use: Displays a help message for this excellent piece of software.
381 */
382
569b8891 383static void bc__help(FILE *fp, int suid)
c4f2d992 384{
385 bc__banner(fp);
386 putc('\n', fp);
387 bc__usage(fp);
388 putc('\n', fp);
389 bc__write(fp,
390"The `$' program allows you to run a process as another user.\n"
391"If a command name is given, this is the process executed. If the `-c'\n"
392"option is used, the process is assumed to be `/bin/sh'. If no command is\n"
393"given, your default login shell is used.\n"
394"\n"
395"Your user id, the user id you wish to become, the name of the process\n"
396"you wish to run, and the identity of the current host are looked up to\n"
397"ensure that you have permission to do this.\n"
398"\n"
399"Note that logs are kept of all uses of this program.\n"
400"\n"
401"Options available are:\n"
402"\n"
387501ae 403"-h, --help Display this help text\n"
404"-u, --usage Display a short usage summary\n"
405"-v, --version Display $'s version number\n"
406"\n"
407"-e, --preserve-environment Try to preserve the current environment\n"
408"-s, --su, --set-user Set environment variables to reflect USER\n"
409"-l, --login Really log in as USER\n"
410"\n"
a5089273 411"-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
412#ifdef HAVE_SETGROUPS
413"-k, --keep-groups Keep your current set of groups\n"
414"-m, --merge-groups Merge the lists of groups\n"
415"-r, --replace-groups Replace the list of groups\n"
416#endif
417"\n"
387501ae 418"-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
419"\n"
420"-d, --daemon Start a daemon\n"
421"-p PORT, --port=PORT In daemon mode, listen on PORT\n"
422"-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
03f996bd 423#ifdef TRACING
387501ae 424 bc__write(fp, "\n");
569b8891 425 if (!suid) {
426 bc__write(fp,
387501ae 427"-I USER, --impersonate=USER Claim to be USER when asking the server\n");
569b8891 428 }
429 bc__write(fp,
387501ae 430"-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
431"-L OPTS, --trace-level=OPTS Set level of tracing information\n");
03f996bd 432#endif
c4f2d992 433}
434
435/* --- @main@ --- *
436 *
437 * Arguments: @int argc@ = number of command line arguments
438 * @char *argv[]@ = pointer to the various arguments
439 *
440 * Returns: Zero if successful.
441 *
442 * Use: Allows a user to change UID.
443 */
444
445int main(int argc, char *argv[])
446{
03f996bd 447 /* --- Request block setup parameters --- */
c4f2d992 448
03f996bd 449 request rq; /* Request buffer to build */
450 char *cmd = 0; /* Shell command to execute */
451 char *binary = "/bin/sh"; /* Default binary to execute */
452 char **env = environ; /* Default environment to pass */
387501ae 453 char **todo = 0; /* Pointer to argument list */
454 char *who = 0; /* Who we're meant to become */
03f996bd 455 struct passwd *from_pw = 0; /* User we are right now */
456 struct passwd *to_pw = 0; /* User we want to become */
457
458 /* --- Become server setup parameters --- */
459
460 char *conffile = file_RULES; /* Default config file for daemon */
9df0c75d 461 int port = 0; /* Default port for daemon */
03f996bd 462
463 /* --- Miscellanous shared variables --- */
464
465 unsigned flags = 0; /* Various useful flags */
387501ae 466 int style = DEFAULT_LOGIN_STYLE; /* Login style */
a5089273 467 gid_t group = -1; /* Default group to set */
468 int gstyle = g_unset; /* No group style set yet */
469
470#ifdef HAVE_SETGROUPS
471 gid_t groups[NGROUPS_MAX]; /* Set of groups */
472 int ngroups; /* Number of groups in the set */
473#endif
03f996bd 474
475 /* --- Default argument list executes a shell command --- */
476
477 static char *shell[] = {
478 "/bin/sh", /* Bourne shell */
479 "-c", /* Read from command line */
480 0, /* Pointer to shell command */
481 0 /* Terminator */
c4f2d992 482 };
483
03f996bd 484 /* --- Definitions for the various flags --- */
485
486 enum {
487 f_daemon = 1, /* Start up in daemon mode */
488 f_duff = 2, /* Fault in arguments */
45df69ab 489 f_shell = 4, /* Run a default shell */
03f996bd 490 f_dummy = 8, /* Don't actually do anything */
a5089273 491 f_setuid = 16, /* We're running setuid */
492 f_havegroup = 32 /* Set a default group */
03f996bd 493 };
c4f2d992 494
495 /* --- Set up the program name --- */
496
497 ego(argv[0]);
03f996bd 498 clock();
499 if (getuid() != geteuid())
500 flags |= f_setuid;
c4f2d992 501
387501ae 502 /* --- Read the environment into a hashtable --- */
503
504 {
505 char **p;
506
507 sym_createTable(&bc__env);
508 for (p = environ; *p; p++)
509 bc__putenv(0, *p, 0, 0);
510 }
511
c4f2d992 512 /* --- Parse some command line arguments --- */
513
514 for (;;) {
515 int i;
03f996bd 516 static struct option opts[] = {
387501ae 517
518 /* --- Asking for help --- */
519
c4f2d992 520 { "help", 0, 0, 'h' },
387501ae 521 { "usage", 0, 0, 'u' },
c4f2d992 522 { "version", 0, 0, 'v' },
387501ae 523
524 /* --- Login style options --- */
525
526 { "preserve-environment", 0, 0, 'e' },
527 { "su", 0, 0, 's' },
528 { "set-user", 0, 0, 's' },
03f996bd 529 { "login", 0, 0, 'l' },
387501ae 530
a5089273 531 /* --- Group style options --- */
532
533 { "group", gFlag_argReq, 0, 'g' },
534#ifdef HAVE_SETGROUPS
535 { "keep-groups", 0, 0, 'k' },
536 { "merge-groups", 0, 0, 'm' },
537 { "replace-groups", 0, 0, 'r' },
538#endif
539
387501ae 540 /* --- Command to run options --- */
541
c4f2d992 542 { "command", gFlag_argReq, 0, 'c' },
387501ae 543
544 /* --- Server options --- */
545
c4f2d992 546 { "daemon", 0, 0, 'd' },
547 { "port", gFlag_argReq, 0, 'p' },
548 { "config-file", gFlag_argReq, 0, 'f' },
387501ae 549
550 /* --- Tracing options --- */
551
03f996bd 552#ifdef TRACING
553 { "impersonate", gFlag_argReq, 0, 'I' },
554 { "trace", gFlag_argOpt, 0, 'T' },
555 { "trace-level", gFlag_argOpt, 0, 'L' },
556#endif
387501ae 557
c4f2d992 558 { 0, 0, 0, 0 }
559 };
560
387501ae 561 i = mdwopt(argc, argv,
a5089273 562 "-" /* Return non-options as options */
563 "huv" /* Asking for help */
564 "esl" /* Login style options */
565#ifdef HAVE_SETGROUPS
566 "g:kmr" /* Group style options */
567#else
568 "g:" /* Group (without @setgroups@) */
569#endif
570 "c:" /* Command to run options */
571 "dp:f:" /* Server options */
572#ifdef TRACING
573 "I:T::L::" /* Tracing options */
574#endif
575 ,
387501ae 576 opts, 0, 0, gFlag_envVar);
c4f2d992 577 if (i < 0)
387501ae 578 goto done_options;
c4f2d992 579
580 switch (i) {
03f996bd 581
387501ae 582 /* --- Asking for help --- */
03f996bd 583
c4f2d992 584 case 'h':
569b8891 585 bc__help(stdout, flags & f_setuid);
586 exit(0);
587 break;
387501ae 588 case 'u':
569b8891 589 bc__usage(stdout);
c4f2d992 590 exit(0);
591 break;
592 case 'v':
593 bc__banner(stdout);
594 exit(0);
595 break;
03f996bd 596
387501ae 597 /* --- Login style options --- */
03f996bd 598
387501ae 599 case 'e':
600 style = l_preserve;
601 break;
602 case 's':
a5089273 603 style = l_setuser;
387501ae 604 break;
03f996bd 605 case 'l':
387501ae 606 style = l_login;
03f996bd 607 break;
387501ae 608
a5089273 609 /* --- Group style options --- */
610
611 case 'g':
612 if (isdigit((unsigned char)optarg[0]))
613 group = atoi(optarg);
614 else {
615 struct group *gr = getgrnam(optarg);
616 if (!gr)
617 die("unknown group `%s'", optarg);
618 group = gr->gr_gid;
619 }
620 flags |= f_havegroup;
621 break;
622
623 case 'k':
624 gstyle = g_keep;
625 break;
626 case 'm':
627 gstyle = g_merge;
628 break;
629 case 'r':
630 gstyle = g_replace;
631 break;
632
387501ae 633 /* --- Command to run options --- */
634
c4f2d992 635 case 'c':
636 cmd = optarg;
637 break;
387501ae 638
639 /* --- Server options --- */
640
c4f2d992 641 case 'p':
a5089273 642 if (isdigit((unsigned char)optarg[0]))
643 port = htons(atoi(optarg));
644 else {
645 struct servent *s = getservbyname(optarg, "udp");
646 if (!s)
647 die("unknown service name `%s'", optarg);
648 port = s->s_port;
649 }
c4f2d992 650 break;
651 case 'd':
652 flags |= f_daemon;
653 break;
654 case 'f':
655 conffile = optarg;
656 break;
03f996bd 657
658 /* --- Pretend to be a different user --- *
659 *
660 * There are all sorts of nasty implications for this option. Don't
661 * allow it if we're running setuid. Disable the actual login anyway.
662 */
663
664#ifdef TRACING
569b8891 665
03f996bd 666 case 'I':
667 if (flags & f_setuid)
668 moan("shan't allow impersonation while running setuid");
669 else {
670 struct passwd *pw;
671 if (isdigit((unsigned char)optarg[0]))
672 pw = getpwuid(atoi(optarg));
673 else
674 pw = getpwnam(optarg);
675 if (!pw)
676 die("can't impersonate unknown user `%s'", optarg);
677 from_pw = userdb_copyUser(pw);
678 rq.from = from_pw->pw_uid;
679 flags |= f_dummy;
680 }
c4f2d992 681 break;
569b8891 682
03f996bd 683#endif
684
685 /* --- Tracing support --- *
686 *
687 * Be careful not to zap a file I wouldn't normally be allowed to write
688 * to!
689 */
690
691#ifdef TRACING
692
693 case 'T': {
694 FILE *fp;
695
696 if (optarg == 0 || strcmp(optarg, "-") == 0)
697 fp = stdout;
698 else {
c6885d5f 699 uid_t eu = geteuid(), ru = getuid();
700
701#ifdef HAVE_SETREUID
702 if (setreuid(eu, ru))
703#else
704 if (seteuid(ru))
705#endif
706 {
707 die("couldn't temporarily give up privileges: %s",
708 strerror(errno));
03f996bd 709 }
c6885d5f 710
03f996bd 711 if ((fp = fopen(optarg, "w")) == 0) {
712 die("couldn't open trace file `%s' for writing: %s",
713 optarg, strerror(errno));
714 }
c6885d5f 715
716#ifdef HAVE_SETREUID
717 if (setreuid(ru, eu))
718#else
719 if (seteuid(eu))
720#endif
721 die("couldn't regain privileges: %s", strerror(errno));
03f996bd 722 }
723 traceon(fp, TRACE_DFL);
724 trace(TRACE_MISC, "become: tracing enabled");
725 } break;
726
727#endif
728
729 /* --- Setting trace levels --- */
730
731#ifdef TRACING
732
733 case 'L': {
734 int sense = 1;
735 unsigned int lvl = 0, l;
736 const char *p = optarg;
737
738 /* --- Table of tracing facilities --- */
739
740 typedef struct tr {
741 char ch;
742 unsigned int l;
743 const char *help;
744 } tr;
745
746 static tr lvltbl[] = {
747 { 'm', TRACE_MISC, "miscellaneous messages" },
748 { 's', TRACE_SETUP, "building the request block" },
749 { 'd', TRACE_DAEMON, "server process" },
750 { 'r', TRACE_RULE, "ruleset scanning" },
751 { 'c', TRACE_CHECK, "request checking" },
752 { 'l', TRACE_CLIENT, "client process" },
753 { 'R', TRACE_RAND, "random number generator" },
754 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
755 { 'y', TRACE_YACC, "parsing configuration file" },
756 { 'D', TRACE_DFL, "default tracing options" },
757 { 'A', TRACE_ALL, "all tracing options" },
758 { 0, 0, 0 }
759 };
760 tr *tp;
761
762 /* --- Output some help if there's no arguemnt --- */
763
764 if (!optarg) {
765 bc__banner(stdout);
766 bc__write(stdout,
767 "\n"
768 "Tracing options:\n"
769 "\n");
770 for (tp = lvltbl; tp->l; tp++) {
771 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
772 printf("%c -- %s\n", tp->ch, tp->help);
773 }
774 bc__write(stdout,
775"\n"
9df0c75d 776"Also, `+' and `-' options are recognised to turn on and off various\n"
03f996bd 777"tracing options. For example, `A-r' enables everything except ruleset\n"
778"tracing, and `A-D+c' is everything except the defaults, but with request\n"
779"check tracing.\n"
780);
781 exit(0);
782 }
783
784 while (*p) {
785 if (*p == '+')
786 sense = 1;
787 else if (*p == '-')
788 sense = 0;
789 else {
790 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
791 ;
792 l = tp->l;
793 if (flags & f_setuid)
794 l &= ~TRACE_PRIV;
795 if (l)
796 lvl = sense ? (lvl | l) : (lvl & ~l);
797 else
798 moan("unknown trace option `%c'", *p);
799 }
800 p++;
801 }
802
803 tracesetlvl(lvl);
804 yydebug = ((lvl & TRACE_YACC) != 0);
805 } break;
806
807#endif
808
387501ae 809 /* --- Something that wasn't an option --- *
810 *
811 * The following nasties are supported:
812 *
813 * * NAME=VALUE -- preserve NAME, and give it a VALUE
814 * * NAME= -- preserve NAME, and give it an empty value
815 * * NAME- -- delete NAME
816 * * NAME! -- preserve NAME with existing value
817 *
818 * Anything else is either the user name (which is OK) or the start of
819 * the command (in which case I stop and read the rest of the command).
820 */
821
822 case 0: {
823 size_t sz = strcspn(optarg, "=-!");
824 sym_env *e;
825
826 /* --- None of the above --- */
827
828 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
9df0c75d 829 if (!who) {
387501ae 830 who = optarg;
9df0c75d 831 break;
832 } else {
387501ae 833 optind--;
834 goto done_options;
835 }
836 }
837
838 /* --- Do the appropriate thing --- */
839
840 switch (optarg[sz]) {
841 case '=':
842 bc__putenv(0, optarg, envFlag_preserve, 1);
843 break;
844 case '-':
845 optarg[sz] = 0;
846 bc__putenv(optarg, 0, 0, 1);
847 break;
848 case '!':
849 optarg[sz] = 0;
850 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
851 e->f |= envFlag_preserve;
852 break;
853 }
854 } break;
855
03f996bd 856 /* --- Something I didn't understand has occurred --- */
857
c4f2d992 858 case '?':
859 flags |= f_duff;
860 break;
861 }
862 }
387501ae 863
864done_options:
c4f2d992 865 if (flags & f_duff) {
866 bc__usage(stderr);
867 exit(1);
868 }
869
870 /* --- Switch to daemon mode if requested --- */
871
872 if (flags & f_daemon) {
03f996bd 873 T( trace(TRACE_MISC, "become: daemon mode requested"); )
c4f2d992 874 daemon_init(conffile, port);
875 exit(0);
876 }
877
878 /* --- Open a syslog --- */
879
880 openlog(quis(), 0, LOG_AUTH);
881
882 /* --- Pick out the uid --- */
883
884 {
c4f2d992 885 struct passwd *pw;
03f996bd 886
387501ae 887 if (!who) {
c4f2d992 888 bc__usage(stderr);
889 exit(1);
890 }
03f996bd 891
387501ae 892 if (isdigit((unsigned char)who[0]))
893 pw = getpwuid(atoi(who));
03f996bd 894 else
387501ae 895 pw = getpwnam(who);
c4f2d992 896 if (!pw)
387501ae 897 die("unknown user `%s'", who);
03f996bd 898 to_pw = userdb_copyUser(pw);
c4f2d992 899 rq.to = pw->pw_uid;
900 }
901
902 /* --- Fill in the easy bits of the request --- */
903
03f996bd 904 if (!from_pw) {
905 struct passwd *pw;
906
907 rq.from = getuid();
908 pw = getpwuid(rq.from);
909 if (!pw)
910 die("who are you? (can't find user %li)", (long)rq.from);
911 from_pw = userdb_copyUser(pw);
912 }
c4f2d992 913
914 /* --- Find the local host address --- */
915
916 {
917 struct utsname u;
918 struct hostent *he;
919 uname(&u);
920 if ((he = gethostbyname(u.nodename)) == 0)
921 die("who am I? (can't resolve `%s')", u.nodename);
922 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
923 }
924
a5089273 925 /* --- Fiddle with group ownerships a bit --- */
926
927 {
928#ifdef HAVE_SETGROUPS
929 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
930 int n_fgr, n_tgr;
931#endif
932
933 /* --- Set the default login group, if there is one --- */
934
935 if (~flags & f_havegroup)
936 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
937
938#ifndef HAVE_SETGROUPS
939
940 /* --- Check that it's valid --- */
941
942 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
943 die("invalid default group");
944
945#else
946
947 /* --- Set the default group style --- */
948
949 if (gstyle == g_unset)
950 gstyle = (style == l_login) ? g_replace : g_merge;
951
952 /* --- Read in my current set of groups --- */
953
954 n_fgr = getgroups(NGROUPS_MAX, from_gr);
955
956 /* --- Now read the groups for the target user --- *
957 *
958 * Do this even if I'm using the @g_keep@ style -- I'll use it for
959 * checking that @group@ is valid.
960 */
961
962 {
963 struct group *gr;
964 char **p;
965
966 n_tgr = 0;
967 to_gr[n_tgr++] = to_pw->pw_gid;
968
969 setgrent();
970 while ((gr = getgrent()) != 0) {
971 if (gr->gr_gid == to_gr[0])
972 continue;
973 for (p = gr->gr_mem; *p; p++) {
974 if (strcmp(to_pw->pw_name, *p) == 0) {
975 to_gr[n_tgr++] = gr->gr_gid;
976 if (n_tgr >= NGROUPS_MAX)
977 goto done_groups;
978 break;
979 }
980 }
981 }
982
983 done_groups:
984 endgrent();
985 }
986
987 /* --- Check that @group@ is reasonable --- */
988
989 {
990 int i;
991
992 if (group == getgid() || group == from_pw->pw_gid)
993 goto group_ok;
994 for (i = 0; i < n_fgr; i++) {
995 if (group == from_gr[i])
996 goto group_ok;
997 }
998 for (i = 0; i < n_tgr; i++) {
999 if (group == to_gr[i])
1000 goto group_ok;
1001 }
1002 die("invalid default group");
1003 group_ok:;
1004 }
1005
1006 /* --- Phew. Now comes the hard bit --- */
1007
1008 {
1009 gid_t ga[4];
1010 int i;
1011
1012 i = 0;
1013 ga[i++] = group;
1014 if (gstyle & g_keep) {
1015 ga[i++] = getgid();
1016 ga[i++] = from_pw->pw_gid;
1017 }
1018 if (gstyle & g_replace)
1019 ga[i++] = to_pw->pw_gid;
1020
1021 /* --- Style authorities will become apoplectic if shown this --- *
1022 *
1023 * As far as I can see, it's the neatest way of writing it.
1024 */
1025
1026 ngroups = 0;
1027 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1028 ((gstyle & g_keep) &&
1029 bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
1030 ((gstyle & g_replace) &&
1031 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1032 }
1033
1034#endif
1035
1036 /* --- Trace the results of all this --- */
1037
1038 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1039
1040#ifdef HAVE_SETGROUPS
1041 IF_TRACING(TRACE_SETUP, {
1042 int i;
1043
1044 for (i = 1; i < ngroups; i++)
1045 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1046 })
1047#endif
1048 }
1049
03f996bd 1050 /* --- Shell commands are easy --- */
c4f2d992 1051
1052 if (cmd) {
1053 shell[2] = cmd;
1054 todo = shell;
03f996bd 1055 }
1056
1057 /* --- A command given on the command line isn't too hard --- */
1058
1059 else if (optind < argc) {
c4f2d992 1060 todo = argv + optind;
03f996bd 1061 binary = todo[0];
1062 }
1063
45df69ab 1064 else {
1065 flags |= f_shell;
03f996bd 1066
45df69ab 1067 switch (style) {
c4f2d992 1068
45df69ab 1069 /* --- An unadorned becoming requires little work --- */
03f996bd 1070
45df69ab 1071 case l_preserve:
1072 shell[0] = getenv("SHELL");
1073 if (!shell[0])
1074 shell[0] = from_pw->pw_shell;
1075 shell[1] = 0;
1076 todo = shell;
1077 binary = todo[0];
1078 break;
387501ae 1079
45df69ab 1080 /* --- An su-like login needs slightly less effort --- */
1081
1082 case l_setuser:
1083 shell[0] = to_pw->pw_shell;
1084 shell[1] = 0;
1085 todo = shell;
1086 binary = todo[0];
1087 break;
1088
1089 /* --- A login request needs a little bit of work --- */
387501ae 1090
45df69ab 1091 case l_login: {
1092 const char *p = strrchr(to_pw->pw_shell, '/');
1093
1094 if (p)
1095 p++;
1096 else
1097 p = to_pw->pw_shell;
1098 shell[0] = xmalloc(strlen(p) + 2);
1099 shell[0][0] = '-';
1100 strcpy(shell[0] + 1, p);
1101 shell[1] = 0;
1102 todo = shell;
1103 binary = to_pw->pw_shell;
1104 } break;
1105 }
03f996bd 1106 }
1107
569b8891 1108 /* --- Mangle the environment --- *
1109 *
387501ae 1110 * This keeps getting more complicated all the time. (How true. Now I've
1111 * got all sorts of nasty environment mangling to do.)
1112 *
1113 * The environment stuff now happens in seven phases:
1114 *
1115 * 1. Mark very special variables to be preserved. Currently only TERM
1116 * and DISPLAY are treated in this way.
1117 *
1118 * 2. Set and preserve Become's own environment variables.
1119 *
1120 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1121 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1122 *
1123 * 4. If we're preserving the environment or being `su'-like, process the
1124 * PATH variable a little. Otherwise reset it to something
1125 * appropriate.
1126 *
1127 * 5. If we're being `login'-like, expunge all unpreserved variables.
1128 *
1129 * 6. Expunge any security-critical variables.
1130 *
1131 * 7. Build a new environment table to pass to child processes.
569b8891 1132 */
1133
1134 {
387501ae 1135 /* --- Variables to be preserved always --- *
1136 *
1137 * A user can explicitly expunge a variable in this list, in which case
1138 * we never get to see it here.
1139 */
569b8891 1140
387501ae 1141 static char *preserve[] = {
1142 "TERM", "DISPLAY", 0
1143 };
c6885d5f 1144
387501ae 1145 /* --- Variables to be expunged --- *
569b8891 1146 *
c6885d5f 1147 * Any environment string which has one of the following as a prefix will
1148 * be expunged from the environment passed to the called process. The
1149 * first line lists variables which have been used to list search paths
1150 * for shared libraries: by manipulating these, an attacker could replace
1151 * a standard library with one of his own. The second line lists other
1152 * well-known dangerous environment variables.
569b8891 1153 */
1154
387501ae 1155 static char *banned[] = {
1156 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1157 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
569b8891 1158 0
1159 };
1160
387501ae 1161 /* --- Other useful variables --- */
03f996bd 1162
387501ae 1163 sym_env *e;
1164 char *p, *q, *r;
1165 char **pp, **qq;
1166 size_t sz;
1167 unsigned f;
1168 sym_iter i;
c6885d5f 1169
387501ae 1170 /* --- Stage one. Preserve display-specific variables --- */
569b8891 1171
387501ae 1172 for (pp = preserve; *pp; pp++) {
1173 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1174 e->f |= envFlag_preserve;
1175 }
569b8891 1176
387501ae 1177 /* --- Stage two. Set Become's own variables --- */
569b8891 1178
387501ae 1179 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1180 if (!f)
1181 bc__setenv(e, from_pw->pw_name);
1182 e->f |= envFlag_preserve;
1183
1184 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1185 if (!f)
1186 bc__setenv(e, from_pw->pw_dir);
1187 e->f |= envFlag_preserve;
569b8891 1188
387501ae 1189 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1190 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1191 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1192 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
569b8891 1193
387501ae 1194 /* --- Stage three. Set user identity --- */
1195
1196 switch (style) {
1197 case l_login: {
1198 static char *maildirs[] = {
1199 "/var/spool/mail", "/var/mail",
1200 "/usr/spool/mail", "/usr/mail",
1201 0
1202 };
1203 struct stat s;
1204 char b[128];
1205
1206 for (pp = maildirs; *pp; pp++) {
1207 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1208 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1209 bc__putenv("MAIL", b, envFlag_preserve, 0);
1210 break;
569b8891 1211 }
569b8891 1212 }
387501ae 1213 } /* Fall through */
1214
a5089273 1215 case l_setuser:
387501ae 1216 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1217 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1218 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1219 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1220 break;
1221 }
1222
1223 /* --- Stage four. Set the user's PATH properly --- */
569b8891 1224
387501ae 1225 {
1226 /* --- Find an existing path --- *
569b8891 1227 *
387501ae 1228 * If there's no path, or this is a login, then set a default path,
1229 * unless we're meant to preserve the existing one. Whew!
569b8891 1230 */
1231
387501ae 1232 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1233
1234 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1235 bc__putenv("PATH",
1236 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1237 envFlag_preserve, 0);
1238 } else {
1239
1240 /* --- Find the string --- */
1241
1242 e->f = envFlag_preserve;
1243 p = strchr(e->val, '=') + 1;
1244 r = e->val;
1245
1246 /* --- Write the new version to a dynamically allocated buffer --- */
1247
1248 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1249 strcpy(e->val, "PATH=");
1250 q = e->val + 5;
1251
1252 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
6a4ec25c 1253 if (p[0] != '/')
387501ae 1254 continue;
1255 while (*p)
1256 *q++ = *p++;
1257 *q++ = ':';
1258 }
1259 q[-1] = 0;
1260
1261 /* --- Done! --- */
1262
1263 free(r);
569b8891 1264 }
387501ae 1265 }
1266
1267 /* --- Stages five and six. Expunge variables and count numbers --- *
1268 *
1269 * Folded together, so I only need one pass through the table. Also
1270 * count the number of variables needed at this time.
1271 */
1272
1273 sz = 0;
569b8891 1274
387501ae 1275 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
569b8891 1276
387501ae 1277 /* --- Login style expunges all unpreserved variables --- */
569b8891 1278
387501ae 1279 if (style == l_login && ~e->f & envFlag_preserve)
1280 goto expunge;
1281
1282 /* --- Otherwise just check the name against the list --- */
1283
1284 for (pp = banned; *pp; pp++) {
1285 if (**pp == '-') {
1286 p = *pp + 1;
1287 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1288 goto expunge;
a5089273 1289 } else if (strcmp(e->_base.name, *pp) == 0)
387501ae 1290 goto expunge;
1291 }
1292
1293 sz++;
1294 continue;
1295
1296 expunge:
1297 sym_remove(&bc__env, e);
569b8891 1298 }
387501ae 1299
1300 /* --- Stage seven. Build the new environment block --- */
1301
1302 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1303
1304 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1305 *qq++ = e->val;
1306 *qq++ = 0;
03f996bd 1307 }
1308
1309 /* --- Trace the command --- */
1310
1311 IF_TRACING(TRACE_SETUP, {
1312 int i;
1313
1314 trace(TRACE_SETUP, "setup: from user %s to user %s",
1315 from_pw->pw_name, to_pw->pw_name);
1316 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1317 for (i = 0; todo[i]; i++)
1318 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1319 for (i = 0; env[i]; i++)
1320 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1321 })
1322
c4f2d992 1323 /* --- If necessary, resolve the path to the command --- */
1324
03f996bd 1325 if (!strchr(binary, '/')) {
1326 char *path, *p;
c4f2d992 1327 struct stat st;
c4f2d992 1328
1329 if ((p = getenv("PATH")) == 0)
1330 p = "/bin:/usr/bin";
03f996bd 1331 path = xstrdup(p);
c4f2d992 1332
569b8891 1333 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
c4f2d992 1334
569b8891 1335 /* --- Check length of string before copying --- */
c4f2d992 1336
03f996bd 1337 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
c4f2d992 1338 continue;
1339
1340 /* --- Now build the pathname and check it --- */
1341
03f996bd 1342 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1343 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
c4f2d992 1344 st.st_mode & 0111 && /* Check it's executable */
c6885d5f 1345 S_ISREG(st.st_mode)) /* Check it's a file */
c4f2d992 1346 break;
1347 }
1348
1349 if (!p)
1350 die("couldn't find `%s' in path", todo[0]);
03f996bd 1351 binary = rq.cmd;
c4f2d992 1352 free(path);
1353 }
03f996bd 1354 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
c4f2d992 1355
1356 /* --- Canonicalise the path string, if necessary --- */
1357
1358 {
1359 char b[CMDLEN_MAX];
1360 char *p;
1361 const char *q;
1362
1363 /* --- Insert current directory name if path not absolute --- */
1364
1365 p = b;
03f996bd 1366 q = binary;
c4f2d992 1367 if (*q != '/') {
1368 if (!getcwd(b, sizeof(b)))
1369 die("couldn't read current directory: %s", strerror(errno));
1370 p += strlen(p);
1371 *p++ = '/';
1372 }
1373
1374 /* --- Now copy over characters from the path string --- */
1375
1376 while (*q) {
1377
569b8891 1378 /* --- Check for buffer overflows here --- *
c4f2d992 1379 *
1380 * I write at most one byte per iteration so this is OK. Remember to
1381 * allow one for the null byte.
1382 */
1383
1384 if (p >= b + sizeof(b) - 1)
03f996bd 1385 die("internal error: buffer overflow while canonifying path");
c4f2d992 1386
1387 /* --- Reduce multiple slashes to just one --- */
1388
1389 if (*q == '/') {
1390 while (*q == '/')
1391 q++;
1392 *p++ = '/';
1393 }
1394
1395 /* --- Handle dots in filenames --- *
1396 *
1397 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1398 * we've just stuck the current directory on the end of the buffer,
1399 * or we've just put something else on the end.
1400 */
1401
1402 else if (*q == '.' && p[-1] == '/') {
1403
1404 /* --- A simple `./' just gets removed --- */
1405
1406 if (q[1] == 0 || q[1] == '/') {
1407 q++;
1408 p--;
1409 continue;
1410 }
1411
1412 /* --- A `../' needs to be peeled back to the previous `/' --- */
1413
1414 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1415 q += 2;
1416 p--;
1417 while (p > b && p[-1] != '/')
1418 p--;
1419 if (p > b)
1420 p--;
1421 continue;
1422 }
1423 } else
1424 *p++ = *q++;
1425 }
1426
1427 *p++ = 0;
1428 strcpy(rq.cmd, b);
1429 }
03f996bd 1430 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
c4f2d992 1431
88e486d5 1432 /* --- Run the check --- *
1433 *
1434 * If the user is already what she wants to be, then print a warning.
1435 * Then, if I was just going to spawn a shell, quit, to reduce user
a5089273 1436 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1437 * checking if we're already root -- root can do anything anyway, and at
1438 * least this way we get some logging done, and offer a more friendly
1439 * front-end.
88e486d5 1440 */
c4f2d992 1441
88e486d5 1442 if (rq.from == rq.to) {
1443 moan("you already are `%s'!", to_pw->pw_name);
45df69ab 1444 if (flags & f_shell) {
88e486d5 1445 moan("(to prevent confusion, I'm not spawning a shell)");
1446 exit(0);
1447 }
1448 } else {
a5089273 1449 int a = (rq.from == 0) || check(&rq);
c4f2d992 1450
1451 syslog(LOG_INFO,
1452 "permission %s for %s to become %s to run `%s'",
03f996bd 1453 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1454 rq.cmd);
c4f2d992 1455
1456 if (!a)
1457 die("permission denied");
1458 }
1459
1460 /* --- Now do the job --- */
1461
03f996bd 1462 T( trace(TRACE_MISC, "become: permission granted"); )
1463
1464 if (flags & f_dummy) {
1465 puts("permission granted");
1466 return (0);
03f996bd 1467 }
a5089273 1468
1469#ifdef HAVE_SETGROUPS
1470 if (setgroups(ngroups, groups) < 0)
1471 die("couldn't set groups: %s", strerror(errno));
1472#endif
1473
1474 if (setgid(group) < 0)
1475 die("couldn't set default group: %s", strerror(errno));
1476 if (setuid(rq.to) < 0)
1477 die("couldn't set uid: %s", strerror(errno));
1478
45df69ab 1479 /* --- If this was a login, change current directory --- */
1480
1481 if (flags & f_shell && style == l_login && chdir(to_pw->pw_dir) < 0) {
1482 moan("couldn't change directory to `%s': %s",
1483 to_pw->pw_dir, strerror(errno));
1484 }
1485
a5089273 1486 /* --- Finally, call the program --- */
1487
1488 fflush(0);
1489 execve(rq.cmd, todo, env);
1490 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1491 return (127);
c4f2d992 1492}
1493
1494/*----- That's all, folks -------------------------------------------------*/