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