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 -------------------------------------------------*/