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