chiark / gitweb /
6f5c236319babccf866b508b74f656981f17319f
[become] / src / become.c
1 /* -*-c-*-
2  *
3  * $Id: become.c,v 1.4 1997/08/20 16:15:13 mdw Exp $
4  *
5  * Main code for `become'
6  *
7  * (c) 1997 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.4  1997/08/20 16:15:13  mdw
33  * Overhaul of environment handling.  Fix daft bug in path search code.
34  *
35  * Revision 1.3  1997/08/07 16:28:59  mdw
36  * Do something useful when users attempt to become themselves.
37  *
38  * Revision 1.2  1997/08/04 10:24:20  mdw
39  * Sources placed under CVS control.
40  *
41  * Revision 1.1  1997/07/21  13:47:54  mdw
42  * Initial revision
43  *
44  */
45
46 /*----- Header files ------------------------------------------------------*/
47
48 /* --- ANSI headers --- */
49
50 #include <ctype.h>
51 #include <errno.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <time.h>
56
57 /* --- Unix headers --- */
58
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #include <sys/socket.h>
62 #include <sys/utsname.h>
63
64 #include <netinet/in.h>
65
66 #include <arpa/inet.h>
67
68 #include <netdb.h>
69 #include <pwd.h>
70 #include <syslog.h>
71 #include <unistd.h>
72
73 extern char **environ;
74
75 /* --- Local headers --- */
76
77 #include "become.h"
78 #include "config.h"
79 #include "check.h"
80 #include "daemon.h"
81 #include "lexer.h"
82 #include "mdwopt.h"
83 #include "name.h"
84 #include "parser.h"
85 #include "rule.h"
86 #include "utils.h"
87 #include "userdb.h"
88
89 /*----- Main code ---------------------------------------------------------*/
90
91 /* --- @bc__write@ --- *
92  *
93  * Arguments:   @FILE *fp@ = pointer to a stream to write on
94  *              @const char *p@ = pointer to a string
95  *
96  * Returns:     ---
97  *
98  * Use:         Writes the string to the stream, substituting the program
99  *              name (as returned by @quis@) for each occurrence of the
100  *              character `$'.
101  */
102
103 static void bc__write(FILE *fp, const char *p)
104 {
105   const char *n = quis();
106   size_t l;
107   size_t nl = strlen(n);
108
109   /* --- Try to be a little efficient --- *
110    *
111    * Gather up non-`$' characters using @strcspn@ and spew them out really
112    * quickly.
113    */
114
115   for (;;) {
116     l = strcspn(p, "$");
117     if (l)
118       fwrite(p, l, 1, fp);
119     p += l;
120     if (!*p)
121       break;
122     fwrite(n, nl, 1, fp);
123     p++;
124   }
125 }
126
127 /* --- @bc__banner@ --- *
128  *
129  * Arguments:   @FILE *fp@ = stream to write on
130  *
131  * Returns:     ---
132  *
133  * Use:         Writes a banner containing copyright information.
134  */
135
136 static void bc__banner(FILE *fp)
137 {
138   bc__write(fp, "$ version " VERSION "\n");
139 }
140
141 /* --- @bc__usage@ --- *
142  *
143  * Arguments:   @FILE *fp@ = stream to write on
144  *
145  * Returns:     ---
146  *
147  * Use:         Writes a terse reminder of command line syntax.
148  */
149
150 static void bc__usage(FILE *fp)
151 {
152   bc__write(fp,
153             "Usage: \n"
154             "   $ -c <shell-command> <user>\n"
155             "   $ <user> [<command> [<arguments>]...]\n"
156             "   $ -d [-p <port>] [-f <config-file>]\n");
157 }
158
159 /* --- @bc__makeEnv@ --- *
160  *
161  * Arguments:   @const char *var@ = name of an environment variable
162  *              @const char *value@ = value of the variable
163  *
164  * Returns:     A pointer to an allocated block assigning the value to the
165  *              name.
166  *
167  * Use:         Constructs environment mappings.
168  */
169
170 static char *bc__makeEnv(const char *var, const char *value)
171 {
172   char *p = xmalloc(strlen(var) + strlen(value) + 2);
173   sprintf(p, "%s=%s", var, value);
174   return (p);
175 }
176
177 /* --- @bc__help@ --- *
178  *
179  * Arguments:   @FILE *fp@ = stream to write on
180  *              @int suid@ = whether we're running set-uid
181  *
182  * Returns:     ---
183  *
184  * Use:         Displays a help message for this excellent piece of software.
185  */
186
187 static void bc__help(FILE *fp, int suid)
188 {
189   bc__banner(fp);
190   putc('\n', fp);
191   bc__usage(fp);
192   putc('\n', fp);
193   bc__write(fp,
194 "The `$' program allows you to run a process as another user.\n"
195 "If a command name is given, this is the process executed.  If the `-c'\n"
196 "option is used, the process is assumed to be `/bin/sh'.  If no command is\n"
197 "given, your default login shell is used.\n"
198 "\n"
199 "Your user id, the user id you wish to become, the name of the process\n"
200 "you wish to run, and the identity of the current host are looked up to\n"
201 "ensure that you have permission to do this.\n"
202 "\n"
203 "Note that logs are kept of all uses of this program.\n"
204 "\n"
205 "Options available are:\n"
206 "\n"
207 "-h, --help             Display this help text\n"
208 "-v, --version          Display the version number of this copy of $\n"
209 "-l, --login            Really log in as the user\n"
210 "-c, --command=CMD      Run the (Bourne) shell command CMD\n"
211 "-d, --daemon           Start up a daemon, to accept requests from clients\n"
212 "-p, --port=PORT                In daemon mode, listen on PORT\n"
213 "-f, --config-file=FILE In daemon mode, read config from FILE\n");
214 #ifdef TRACING
215   if (!suid) {
216     bc__write(fp,
217 "--impersonate=USER     Claim to be USER when asking the server\n");
218   }
219   bc__write(fp,
220 "--trace=FILE           Dump trace information to FILE (boring)\n"
221 "--trace-level=OPTS     Set level of tracing information\n");
222 #endif
223 }
224
225 /* --- @main@ --- *
226  *
227  * Arguments:   @int argc@ = number of command line arguments
228  *              @char *argv[]@ = pointer to the various arguments
229  *
230  * Returns:     Zero if successful.
231  *
232  * Use:         Allows a user to change UID.
233  */
234
235 int main(int argc, char *argv[])
236 {
237   /* --- Request block setup parameters --- */
238
239   request rq;                           /* Request buffer to build */
240   char *cmd = 0;                        /* Shell command to execute */
241   char *binary = "/bin/sh";             /* Default binary to execute */
242   char **env = environ;                 /* Default environment to pass */
243   char **todo;                          /* Pointer to argument list */
244   struct passwd *from_pw = 0;           /* User we are right now */
245   struct passwd *to_pw = 0;             /* User we want to become */
246
247   /* --- Become server setup parameters --- */
248
249   char *conffile = file_RULES;          /* Default config file for daemon */
250   int port = -1;                        /* Default port for daemon */
251
252   /* --- Miscellanous shared variables --- */
253
254   unsigned flags = 0;                   /* Various useful flags */
255
256   /* --- Default argument list executes a shell command --- */
257
258   static char *shell[] = {
259     "/bin/sh",                          /* Bourne shell */
260     "-c",                               /* Read from command line */
261     0,                                  /* Pointer to shell command */
262     0                                   /* Terminator */
263   };
264
265   /* --- Definitions for the various flags --- */
266
267   enum {
268     f_daemon = 1,                       /* Start up in daemon mode */
269     f_duff = 2,                         /* Fault in arguments */
270     f_login = 4,                        /* Execute as a login shell */
271     f_dummy = 8,                        /* Don't actually do anything */
272     f_setuid = 16                       /* We're running setuid */
273   };
274
275   /* --- Set up the program name --- */
276
277   ego(argv[0]);
278   clock();
279   if (getuid() != geteuid())
280     flags |= f_setuid;
281
282   /* --- Parse some command line arguments --- */
283
284   for (;;) {
285     int i;
286     static struct option opts[] = {
287       { "help",         0,              0,      'h' },
288       { "usage",        0,              0,      'U' },
289       { "version",      0,              0,      'v' },
290       { "login",        0,              0,      'l' },
291       { "command",      gFlag_argReq,   0,      'c' },
292       { "daemon",       0,              0,      'd' },
293       { "port",         gFlag_argReq,   0,      'p' },
294       { "config-file",  gFlag_argReq,   0,      'f' },
295 #ifdef TRACING
296       { "impersonate",  gFlag_argReq,   0,      'I' },
297       { "trace",        gFlag_argOpt,   0,      'T' },
298       { "trace-level",  gFlag_argOpt,   0,      'L' },
299 #endif
300       { 0,              0,              0,      0 }
301     };
302
303     i = mdwopt(argc, argv, "+hvlc:p:df:", opts, 0, 0, 0);
304     if (i < 0)
305       break;
306
307     switch (i) {
308
309       /* --- Standard help and version options --- */
310
311       case 'h':
312         bc__help(stdout, flags & f_setuid);
313         exit(0);
314         break;
315       case 'U':
316         bc__usage(stdout);
317         exit(0);
318         break;
319       case 'v':
320         bc__banner(stdout);
321         exit(0);
322         break;
323
324       /* --- Various simple options --- */
325
326       case 'l':
327         flags |= f_login;
328         break;
329       case 'c':
330         cmd = optarg;
331         break;
332       case 'p':
333         port = atoi(optarg);
334         break;
335       case 'd':
336         flags |= f_daemon;
337         break;
338       case 'f':
339         conffile = optarg;
340         break;
341
342       /* --- Pretend to be a different user --- *
343        *
344        * There are all sorts of nasty implications for this option.  Don't
345        * allow it if we're running setuid.  Disable the actual login anyway.
346        */
347
348 #ifdef TRACING
349
350       case 'I':
351         if (flags & f_setuid)
352           moan("shan't allow impersonation while running setuid");
353         else {
354           struct passwd *pw;
355           if (isdigit((unsigned char)optarg[0]))
356             pw = getpwuid(atoi(optarg));
357           else
358             pw = getpwnam(optarg);
359           if (!pw)
360             die("can't impersonate unknown user `%s'", optarg);
361           from_pw = userdb_copyUser(pw);
362           rq.from = from_pw->pw_uid;
363           flags |= f_dummy;
364         }
365         break;
366
367 #endif
368
369       /* --- Tracing support --- *
370        *
371        * Be careful not to zap a file I wouldn't normally be allowed to write
372        * to!
373        */
374
375 #ifdef TRACING
376
377       case 'T': {
378         FILE *fp;
379
380         if (optarg == 0 || strcmp(optarg, "-") == 0)
381           fp = stdout;
382         else {
383           if ((flags & f_setuid) && access(optarg, W_OK)) {
384             die("no write permission for trace file file `%s': %s",
385                 optarg, strerror(errno));
386           }
387           if ((fp = fopen(optarg, "w")) == 0) {
388             die("couldn't open trace file `%s' for writing: %s",
389                 optarg, strerror(errno));
390           }
391         }
392         traceon(fp, TRACE_DFL);
393         trace(TRACE_MISC, "become: tracing enabled");
394       } break;
395
396 #endif
397
398       /* --- Setting trace levels --- */
399
400 #ifdef TRACING
401
402       case 'L': {
403         int sense = 1;
404         unsigned int lvl = 0, l;
405         const char *p = optarg;
406
407         /* --- Table of tracing facilities --- */
408
409         typedef struct tr {
410           char ch;
411           unsigned int l;
412           const char *help;
413         } tr;
414
415         static tr lvltbl[] = {
416           { 'm', TRACE_MISC,    "miscellaneous messages" },
417           { 's', TRACE_SETUP,   "building the request block" },
418           { 'd', TRACE_DAEMON,  "server process" },
419           { 'r', TRACE_RULE,    "ruleset scanning" },
420           { 'c', TRACE_CHECK,   "request checking" },
421           { 'l', TRACE_CLIENT,  "client process" },
422           { 'R', TRACE_RAND,    "random number generator" },
423           { 'C', TRACE_CRYPTO,  "cryptographic processing of requests" },
424           { 'y', TRACE_YACC,    "parsing configuration file" },
425           { 'D', TRACE_DFL,     "default tracing options" },
426           { 'A', TRACE_ALL,     "all tracing options" },
427           { 0,   0,             0 }
428         };
429         tr *tp;
430
431         /* --- Output some help if there's no arguemnt --- */
432
433         if (!optarg) {
434           bc__banner(stdout);
435           bc__write(stdout,
436                     "\n"
437                     "Tracing options:\n"
438                     "\n");
439           for (tp = lvltbl; tp->l; tp++) {
440             if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
441               printf("%c -- %s\n", tp->ch, tp->help);
442           }
443           bc__write(stdout,
444 "\n"
445 "Also, `+' and `-' options are recognised to turn on and off vavrious\n"
446 "tracing options.  For example, `A-r' enables everything except ruleset\n"
447 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
448 "check tracing.\n"
449 );
450           exit(0);
451         }
452
453         while (*p) {
454           if (*p == '+')
455             sense = 1;
456           else if (*p == '-')
457             sense = 0;
458           else {
459             for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
460               ;
461             l = tp->l;
462             if (flags & f_setuid)
463               l &= ~TRACE_PRIV;
464             if (l)
465               lvl = sense ? (lvl | l) : (lvl & ~l);
466             else
467               moan("unknown trace option `%c'", *p);
468           }
469           p++;
470         }
471
472         tracesetlvl(lvl);
473         yydebug = ((lvl & TRACE_YACC) != 0);
474       } break;
475
476 #endif
477
478       /* --- Something I didn't understand has occurred --- */
479
480       case '?':
481         flags |= f_duff;
482         break;
483     }
484   }
485   if (flags & f_duff) {
486     bc__usage(stderr);
487     exit(1);
488   }
489
490   /* --- Switch to daemon mode if requested --- */
491
492   if (flags & f_daemon) {
493     T( trace(TRACE_MISC, "become: daemon mode requested"); )
494     daemon_init(conffile, port);
495     exit(0);
496   }
497
498   /* --- Open a syslog --- */
499
500   openlog(quis(), 0, LOG_AUTH);
501
502   /* --- Pick out the uid --- */
503
504   {
505     const char *u;
506     struct passwd *pw;
507
508     if (optind >= argc) {
509       bc__usage(stderr);
510       exit(1);
511     }
512     u = argv[optind++];
513
514     if (isdigit((unsigned char)u[0]))
515       pw = getpwuid(atoi(u));
516     else
517       pw = getpwnam(u);
518     if (!pw)
519       die("unknown user `%s'", u);
520     to_pw = userdb_copyUser(pw);
521     rq.to = pw->pw_uid;
522   }
523
524   /* --- Fill in the easy bits of the request --- */
525
526   if (!from_pw) {
527     struct passwd *pw;
528
529     rq.from = getuid();
530     pw = getpwuid(rq.from);
531     if (!pw)
532       die("who are you? (can't find user %li)", (long)rq.from);
533     from_pw = userdb_copyUser(pw);
534   }
535
536   /* --- Find the local host address --- */
537
538   {
539     struct utsname u;
540     struct hostent *he;
541     uname(&u);
542     if ((he = gethostbyname(u.nodename)) == 0)
543       die("who am I? (can't resolve `%s')", u.nodename);
544     memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
545   }
546
547   /* --- Shell commands are easy --- */
548
549   if (cmd) {
550     shell[2] = cmd;
551     todo = shell;
552   }
553
554   /* --- A command given on the command line isn't too hard --- */
555
556   else if (optind < argc) {
557     todo = argv + optind;
558     binary = todo[0];
559   }
560
561   /* --- A login request needs a little bit of work --- */
562
563   else if (flags & f_login) {
564     const char *p = strrchr(to_pw->pw_shell, '/');
565     if (p)
566       p++;
567     else
568       p = to_pw->pw_shell;
569     shell[0] = xmalloc(strlen(p) + 2);
570     shell[0][0] = '-';
571     strcpy(shell[0] + 1, p);
572     shell[1] = 0;
573     todo = shell;
574     binary = to_pw->pw_shell;
575   }
576
577   /* --- An unadorned becoming requires little work --- */
578
579   else {
580     shell[0] = getenv("SHELL");
581     if (!shell[0])
582       shell[0] = from_pw->pw_shell;
583     shell[1] = 0;
584     todo = shell;
585     binary = todo[0];
586   }
587
588   /* --- Mangle the environment --- *
589    *
590    * This keeps getting more complicated all the time.
591    */
592
593   {
594     size_t i, j, b;
595     int pass;
596
597     /* --- Expunge some environment variables --- *
598      *
599      * Any environment string which has one of the following as a prefix
600      * will be expunged from the environment passed to the called process.
601      * The first line lists variables which have been used to list search
602      * paths for shared libraries: by manipulating these, an attacker could
603      * replace a standard library with one of his own.  The second line lists
604      * other well-known dangerous environment variables.
605      */
606
607     static const char *banned[] = {
608       "LD_", "SHLIB_PATH=", "LIBPATH=", "_RLD_",
609       "IFS=", "ENV=", "BASH_ENV=", "KRB_CONF=",
610       0
611     };
612
613     /* --- Do this in two passes --- *
614      *
615      * The first pass works out how big the environment block needs to be.
616      * The second actually fills it.
617      */
618
619     for (pass = 0; pass < 2; pass++) {
620       i = j = 0;
621
622       if (flags & f_login) {
623
624         /* --- This is a login request --- *
625          *
626          * Erase the existing environment and build a new one.
627          */
628
629         if (!pass)
630           i += 4;
631         else {
632           env[i++] = "PATH=/usr/bin:/bin";
633           env[i++] = bc__makeEnv("USER", to_pw->pw_name);
634           env[i++] = bc__makeEnv("LOGNAME", to_pw->pw_name);
635           env[i++] = bc__makeEnv("HOME", to_pw->pw_dir);
636         }
637       } else {
638
639         /* --- Normal request --- *
640          *
641          * Remove dangerous variables from the list.
642          */
643
644         for (j = 0; environ[j]; j++) {
645           for (b = 0; banned[b]; b++) {
646             if (memcmp(environ[j], banned[b], strlen(banned[b])) == 0)
647               goto skip_var;
648           }
649           if (pass)
650             env[i] = environ[j];
651           i++;
652         skip_var:;
653         }
654       }
655
656       /* --- Now add our own variables --- *
657        *
658        * The following are supplied only to help people construct startup
659        * scripts.  Anyone who relies on them being accurate for
660        * authentication purposes will get exactly what they deserve.
661        */
662
663       if (!pass)
664         i += 4;
665       else {
666         env[i++] = bc__makeEnv("BECOME_OLDUSER", from_pw->pw_name);
667         env[i++] = bc__makeEnv("BECOME_OLDHOME", from_pw->pw_dir);
668         env[i++] = bc__makeEnv("BECOME_USER", to_pw->pw_name);
669         env[i++] = bc__makeEnv("BECOME_HOME", to_pw->pw_dir);
670       }
671
672       /* --- Allocate memory after the first pass is complete --- */
673
674       if (pass)
675         env[i] = 0;
676       i++;
677
678       if (!pass)
679         env = xmalloc(i * sizeof(env[0]));
680     }
681   }
682
683   /* --- Trace the command --- */
684
685   IF_TRACING(TRACE_SETUP, {
686     int i;
687
688     trace(TRACE_SETUP, "setup: from user %s to user %s",
689           from_pw->pw_name, to_pw->pw_name);
690     trace(TRACE_SETUP, "setup: binary == `%s'", binary);
691     for (i = 0; todo[i]; i++)
692       trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
693     for (i = 0; env[i]; i++)
694       trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
695   })
696
697   /* --- If necessary, resolve the path to the command --- */
698
699   if (!strchr(binary, '/')) {
700     char *path, *p;
701     struct stat st;
702
703     if ((p = getenv("PATH")) == 0)
704       p = "/bin:/usr/bin";
705     path = xstrdup(p);
706
707     for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
708
709       /* --- Check length of string before copying --- */
710
711       if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
712         continue;
713
714       /* --- Now build the pathname and check it --- */
715
716       sprintf(rq.cmd, "%s/%s", p, todo[0]);
717       if (stat(rq.cmd, &st) == 0 &&     /* Check it exists */
718           st.st_mode & 0111 &&          /* Check it's executable */
719           (st.st_mode & S_IFMT) == S_IFREG) /* Check it's a file */
720         break;
721     }
722
723     if (!p)
724       die("couldn't find `%s' in path", todo[0]);
725     binary = rq.cmd;
726     free(path);
727   }
728   T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
729
730   /* --- Canonicalise the path string, if necessary --- */
731
732   {
733     char b[CMDLEN_MAX];
734     char *p;
735     const char *q;
736
737     /* --- Insert current directory name if path not absolute --- */
738
739     p = b;
740     q = binary;
741     if (*q != '/') {
742       if (!getcwd(b, sizeof(b)))
743         die("couldn't read current directory: %s", strerror(errno));
744       p += strlen(p);
745       *p++ = '/';
746     }
747
748     /* --- Now copy over characters from the path string --- */
749
750     while (*q) {
751
752       /* --- Check for buffer overflows here --- *
753        *
754        * I write at most one byte per iteration so this is OK.  Remember to
755        * allow one for the null byte.
756        */
757
758       if (p >= b + sizeof(b) - 1)
759         die("internal error: buffer overflow while canonifying path");
760
761       /* --- Reduce multiple slashes to just one --- */
762
763       if (*q == '/') {
764         while (*q == '/')
765           q++;
766         *p++ = '/';
767       }
768
769       /* --- Handle dots in filenames --- *
770        *
771        * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
772        * we've just stuck the current directory on the end of the buffer,
773        * or we've just put something else on the end.
774        */
775
776       else if (*q == '.' && p[-1] == '/') {
777
778         /* --- A simple `./' just gets removed --- */
779
780         if (q[1] == 0 || q[1] == '/') {
781           q++;
782           p--;
783           continue;
784         }
785
786         /* --- A `../' needs to be peeled back to the previous `/' --- */
787
788         if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
789           q += 2;
790           p--;
791           while (p > b && p[-1] != '/')
792             p--;
793           if (p > b)
794             p--;
795           continue;
796         }
797       } else
798         *p++ = *q++;
799     }
800
801     *p++ = 0;
802     strcpy(rq.cmd, b);
803   }
804   T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
805
806   /* --- Run the check --- *
807    *
808    * If the user is already what she wants to be, then print a warning.
809    * Then, if I was just going to spawn a shell, quit, to reduce user
810    * confusion.  Otherwise, do what was wanted anyway.
811    */
812
813   if (rq.from == rq.to) {
814     moan("you already are `%s'!", to_pw->pw_name);
815     if (!cmd && todo == shell) {
816       moan("(to prevent confusion, I'm not spawning a shell)");
817       exit(0);
818     }
819   } else {
820     int a = check(&rq);
821
822     syslog(LOG_INFO,
823            "permission %s for %s to become %s to run `%s'",
824            a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
825            rq.cmd);
826
827     if (!a)
828       die("permission denied");
829   }
830
831   /* --- Now do the job --- */
832
833   T( trace(TRACE_MISC, "become: permission granted"); )
834
835   if (flags & f_dummy) {
836     puts("permission granted");
837     return (0);
838   } else {
839     if (setuid(rq.to) == -1)
840       die("couldn't set uid: %s", strerror(errno));
841     execve(rq.cmd, todo, env);
842     die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
843     return (127);
844   }
845 }
846
847 /*----- That's all, folks -------------------------------------------------*/