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