Change userid when creating tracefiles rather than fiddling with
[become] / src / become.c
CommitLineData
c4f2d992 1/* -*-c-*-
2 *
c6885d5f 3 * $Id: become.c,v 1.7 1997/09/08 13:43:20 mdw Exp $
c4f2d992 4 *
5 * Main code for `become'
6 *
7 * (c) 1997 EBI
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 $
c6885d5f 32 * Revision 1.7 1997/09/08 13:43:20 mdw
33 * Change userid when creating tracefiles rather than fiddling with
34 * `access': it works rather better. Also, insert some stdio buffer
35 * flushing to ensure tracedumps are completely written.
36 *
37 * Revision 1.6 1997/09/05 13:47:44 mdw
d6683a48 38 * Make the `-L' (trace-level) option's argument optional, like the long
39 * version is.
40 *
41 * Revision 1.5 1997/09/05 11:45:19 mdw
387501ae 42 * Add support for different login styles, and environment variable
43 * manipulation in a safe and useful way.
44 *
45 * Revision 1.4 1997/08/20 16:15:13 mdw
569b8891 46 * Overhaul of environment handling. Fix daft bug in path search code.
47 *
88e486d5 48 * Revision 1.3 1997/08/07 16:28:59 mdw
49 * Do something useful when users attempt to become themselves.
50 *
03f996bd 51 * Revision 1.2 1997/08/04 10:24:20 mdw
52 * Sources placed under CVS control.
53 *
54 * Revision 1.1 1997/07/21 13:47:54 mdw
c4f2d992 55 * Initial revision
56 *
57 */
58
59/*----- Header files ------------------------------------------------------*/
60
61/* --- ANSI headers --- */
62
63#include <ctype.h>
64#include <errno.h>
65#include <stdio.h>
66#include <stdlib.h>
67#include <string.h>
03f996bd 68#include <time.h>
c4f2d992 69
70/* --- Unix headers --- */
71
72#include <sys/types.h>
73#include <sys/stat.h>
74#include <sys/socket.h>
75#include <sys/utsname.h>
76
77#include <netinet/in.h>
78
79#include <arpa/inet.h>
80
81#include <netdb.h>
82#include <pwd.h>
83#include <syslog.h>
84#include <unistd.h>
85
03f996bd 86extern char **environ;
87
c4f2d992 88/* --- Local headers --- */
89
90#include "become.h"
91#include "config.h"
92#include "check.h"
93#include "daemon.h"
94#include "lexer.h"
95#include "mdwopt.h"
96#include "name.h"
97#include "parser.h"
98#include "rule.h"
387501ae 99#include "sym.h"
c4f2d992 100#include "utils.h"
03f996bd 101#include "userdb.h"
c4f2d992 102
387501ae 103/*----- Type definitions --------------------------------------------------*/
104
105/* --- Symbol table entry for an environment variable --- */
106
107typedef struct sym_env {
108 sym_base _base; /* Symbol table information */
109 unsigned f; /* Flags word (see below) */
110 char *val; /* Pointer to variable value */
111} sym_env;
112
113/* --- Environment variable flags --- */
114
115enum {
116 envFlag_preserve = 1
117};
118
119/* --- Login behaviour types --- */
120
121enum {
122 l_preserve, /* Preserve the environment */
123 l_user, /* Update who I am */
124 l_login /* Do a full login */
125};
126
127/*----- Static variables --------------------------------------------------*/
128
129static sym_table bc__env;
130
c4f2d992 131/*----- Main code ---------------------------------------------------------*/
132
133/* --- @bc__write@ --- *
134 *
135 * Arguments: @FILE *fp@ = pointer to a stream to write on
136 * @const char *p@ = pointer to a string
137 *
138 * Returns: ---
139 *
140 * Use: Writes the string to the stream, substituting the program
141 * name (as returned by @quis@) for each occurrence of the
142 * character `$'.
143 */
144
145static void bc__write(FILE *fp, const char *p)
146{
147 const char *n = quis();
148 size_t l;
149 size_t nl = strlen(n);
150
151 /* --- Try to be a little efficient --- *
152 *
569b8891 153 * Gather up non-`$' characters using @strcspn@ and spew them out really
c4f2d992 154 * quickly.
155 */
156
157 for (;;) {
158 l = strcspn(p, "$");
159 if (l)
160 fwrite(p, l, 1, fp);
161 p += l;
162 if (!*p)
163 break;
164 fwrite(n, nl, 1, fp);
165 p++;
166 }
167}
168
387501ae 169/* --- @bc__setenv@ --- *
170 *
171 * Arguments: @sym_env *e@ = pointer to environment variable block
172 * @const char *val@ = value to set
173 *
174 * Returns: ---
175 *
176 * Use: Sets an environment variable block to the right value.
177 */
178
179static void bc__setenv(sym_env *e, const char *val)
180{
181 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
182 sprintf(e->val, "%s=%s", e->_base.name, val);
183}
184
185/* --- @bc__putenv@ --- *
186 *
187 * Arguments: @const char *var@ = name of the variable to set, or 0 if
188 * this is embedded in the value string
189 * @const char *val@ = value to set, or 0 if the variable must
190 * be removed
191 * @unsigned int fl@ = flags to set
192 * @unsigned int force@ = force overwrite of preserved variables
193 *
194 * Returns: Pointer to symbol block, or zero if it was deleted.
195 *
196 * Use: Puts an item into the environment.
197 */
198
199static sym_env *bc__putenv(const char *var, const char *val,
200 unsigned int fl, unsigned int force)
201{
202 unsigned int f;
203 sym_env *e;
204 char *q = 0;
205 size_t sz;
206
207 /* --- Sort out embedded variable names --- */
208
209 if (!var) {
210 const char *p = strchr(val, '=');
211
212 if (p) {
213 sz = p - val;
214 q = xmalloc(sz + 1);
215 memcpy(q, val, sz);
216 q[sz] = 0;
217 var = q;
218 val = p + 1;
219 } else {
220 var = val;
221 val = 0;
222 }
223 }
224
225 /* --- Find the variable block --- */
226
227 if (val) {
228 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
229 if (~e->f & envFlag_preserve || force) {
230 if (f)
231 free(e->val);
232 bc__setenv(e, val);
233 }
234 } else {
235 e = sym_find(&bc__env, var, -1, 0, 0);
236 if (e && (force || ~e->f & envFlag_preserve))
237 sym_remove(&bc__env, e);
238 e = 0;
239 }
240
241 /* --- Tidy up and return --- */
242
243 if (q)
244 free(q);
245 if (e)
246 e->f = fl;
247 return (e);
248}
249
c4f2d992 250/* --- @bc__banner@ --- *
251 *
252 * Arguments: @FILE *fp@ = stream to write on
253 *
254 * Returns: ---
255 *
256 * Use: Writes a banner containing copyright information.
257 */
258
259static void bc__banner(FILE *fp)
260{
261 bc__write(fp, "$ version " VERSION "\n");
262}
263
264/* --- @bc__usage@ --- *
265 *
266 * Arguments: @FILE *fp@ = stream to write on
267 *
268 * Returns: ---
269 *
270 * Use: Writes a terse reminder of command line syntax.
271 */
272
273static void bc__usage(FILE *fp)
274{
275 bc__write(fp,
276 "Usage: \n"
277 " $ -c <shell-command> <user>\n"
387501ae 278 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
c4f2d992 279 " $ -d [-p <port>] [-f <config-file>]\n");
280}
281
282/* --- @bc__help@ --- *
283 *
284 * Arguments: @FILE *fp@ = stream to write on
569b8891 285 * @int suid@ = whether we're running set-uid
c4f2d992 286 *
287 * Returns: ---
288 *
289 * Use: Displays a help message for this excellent piece of software.
290 */
291
569b8891 292static void bc__help(FILE *fp, int suid)
c4f2d992 293{
294 bc__banner(fp);
295 putc('\n', fp);
296 bc__usage(fp);
297 putc('\n', fp);
298 bc__write(fp,
299"The `$' program allows you to run a process as another user.\n"
300"If a command name is given, this is the process executed. If the `-c'\n"
301"option is used, the process is assumed to be `/bin/sh'. If no command is\n"
302"given, your default login shell is used.\n"
303"\n"
304"Your user id, the user id you wish to become, the name of the process\n"
305"you wish to run, and the identity of the current host are looked up to\n"
306"ensure that you have permission to do this.\n"
307"\n"
308"Note that logs are kept of all uses of this program.\n"
309"\n"
310"Options available are:\n"
311"\n"
387501ae 312"-h, --help Display this help text\n"
313"-u, --usage Display a short usage summary\n"
314"-v, --version Display $'s version number\n"
315"\n"
316"-e, --preserve-environment Try to preserve the current environment\n"
317"-s, --su, --set-user Set environment variables to reflect USER\n"
318"-l, --login Really log in as USER\n"
319"\n"
320"-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
321"\n"
322"-d, --daemon Start a daemon\n"
323"-p PORT, --port=PORT In daemon mode, listen on PORT\n"
324"-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
03f996bd 325#ifdef TRACING
387501ae 326 bc__write(fp, "\n");
569b8891 327 if (!suid) {
328 bc__write(fp,
387501ae 329"-I USER, --impersonate=USER Claim to be USER when asking the server\n");
569b8891 330 }
331 bc__write(fp,
387501ae 332"-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
333"-L OPTS, --trace-level=OPTS Set level of tracing information\n");
03f996bd 334#endif
c4f2d992 335}
336
337/* --- @main@ --- *
338 *
339 * Arguments: @int argc@ = number of command line arguments
340 * @char *argv[]@ = pointer to the various arguments
341 *
342 * Returns: Zero if successful.
343 *
344 * Use: Allows a user to change UID.
345 */
346
347int main(int argc, char *argv[])
348{
03f996bd 349 /* --- Request block setup parameters --- */
c4f2d992 350
03f996bd 351 request rq; /* Request buffer to build */
352 char *cmd = 0; /* Shell command to execute */
353 char *binary = "/bin/sh"; /* Default binary to execute */
354 char **env = environ; /* Default environment to pass */
387501ae 355 char **todo = 0; /* Pointer to argument list */
356 char *who = 0; /* Who we're meant to become */
03f996bd 357 struct passwd *from_pw = 0; /* User we are right now */
358 struct passwd *to_pw = 0; /* User we want to become */
359
360 /* --- Become server setup parameters --- */
361
362 char *conffile = file_RULES; /* Default config file for daemon */
363 int port = -1; /* Default port for daemon */
364
365 /* --- Miscellanous shared variables --- */
366
367 unsigned flags = 0; /* Various useful flags */
387501ae 368 int style = DEFAULT_LOGIN_STYLE; /* Login style */
03f996bd 369
370 /* --- Default argument list executes a shell command --- */
371
372 static char *shell[] = {
373 "/bin/sh", /* Bourne shell */
374 "-c", /* Read from command line */
375 0, /* Pointer to shell command */
376 0 /* Terminator */
c4f2d992 377 };
378
03f996bd 379 /* --- Definitions for the various flags --- */
380
381 enum {
382 f_daemon = 1, /* Start up in daemon mode */
383 f_duff = 2, /* Fault in arguments */
384 f_login = 4, /* Execute as a login shell */
385 f_dummy = 8, /* Don't actually do anything */
386 f_setuid = 16 /* We're running setuid */
387 };
c4f2d992 388
389 /* --- Set up the program name --- */
390
391 ego(argv[0]);
03f996bd 392 clock();
393 if (getuid() != geteuid())
394 flags |= f_setuid;
c4f2d992 395
387501ae 396 /* --- Read the environment into a hashtable --- */
397
398 {
399 char **p;
400
401 sym_createTable(&bc__env);
402 for (p = environ; *p; p++)
403 bc__putenv(0, *p, 0, 0);
404 }
405
c4f2d992 406 /* --- Parse some command line arguments --- */
407
408 for (;;) {
409 int i;
03f996bd 410 static struct option opts[] = {
387501ae 411
412 /* --- Asking for help --- */
413
c4f2d992 414 { "help", 0, 0, 'h' },
387501ae 415 { "usage", 0, 0, 'u' },
c4f2d992 416 { "version", 0, 0, 'v' },
387501ae 417
418 /* --- Login style options --- */
419
420 { "preserve-environment", 0, 0, 'e' },
421 { "su", 0, 0, 's' },
422 { "set-user", 0, 0, 's' },
03f996bd 423 { "login", 0, 0, 'l' },
387501ae 424
425 /* --- Command to run options --- */
426
c4f2d992 427 { "command", gFlag_argReq, 0, 'c' },
387501ae 428
429 /* --- Server options --- */
430
c4f2d992 431 { "daemon", 0, 0, 'd' },
432 { "port", gFlag_argReq, 0, 'p' },
433 { "config-file", gFlag_argReq, 0, 'f' },
387501ae 434
435 /* --- Tracing options --- */
436
03f996bd 437#ifdef TRACING
438 { "impersonate", gFlag_argReq, 0, 'I' },
439 { "trace", gFlag_argOpt, 0, 'T' },
440 { "trace-level", gFlag_argOpt, 0, 'L' },
441#endif
387501ae 442
c4f2d992 443 { 0, 0, 0, 0 }
444 };
445
387501ae 446 i = mdwopt(argc, argv,
d6683a48 447 "-" "huv" "esl" "c:" "dp:f:" T("I:T::L::"),
387501ae 448 opts, 0, 0, gFlag_envVar);
c4f2d992 449 if (i < 0)
387501ae 450 goto done_options;
c4f2d992 451
452 switch (i) {
03f996bd 453
387501ae 454 /* --- Asking for help --- */
03f996bd 455
c4f2d992 456 case 'h':
569b8891 457 bc__help(stdout, flags & f_setuid);
458 exit(0);
459 break;
387501ae 460 case 'u':
569b8891 461 bc__usage(stdout);
c4f2d992 462 exit(0);
463 break;
464 case 'v':
465 bc__banner(stdout);
466 exit(0);
467 break;
03f996bd 468
387501ae 469 /* --- Login style options --- */
03f996bd 470
387501ae 471 case 'e':
472 style = l_preserve;
473 break;
474 case 's':
475 style = l_user;
476 break;
03f996bd 477 case 'l':
387501ae 478 style = l_login;
03f996bd 479 break;
387501ae 480
481 /* --- Command to run options --- */
482
c4f2d992 483 case 'c':
484 cmd = optarg;
485 break;
387501ae 486
487 /* --- Server options --- */
488
c4f2d992 489 case 'p':
490 port = atoi(optarg);
491 break;
492 case 'd':
493 flags |= f_daemon;
494 break;
495 case 'f':
496 conffile = optarg;
497 break;
03f996bd 498
499 /* --- Pretend to be a different user --- *
500 *
501 * There are all sorts of nasty implications for this option. Don't
502 * allow it if we're running setuid. Disable the actual login anyway.
503 */
504
505#ifdef TRACING
569b8891 506
03f996bd 507 case 'I':
508 if (flags & f_setuid)
509 moan("shan't allow impersonation while running setuid");
510 else {
511 struct passwd *pw;
512 if (isdigit((unsigned char)optarg[0]))
513 pw = getpwuid(atoi(optarg));
514 else
515 pw = getpwnam(optarg);
516 if (!pw)
517 die("can't impersonate unknown user `%s'", optarg);
518 from_pw = userdb_copyUser(pw);
519 rq.from = from_pw->pw_uid;
520 flags |= f_dummy;
521 }
c4f2d992 522 break;
569b8891 523
03f996bd 524#endif
525
526 /* --- Tracing support --- *
527 *
528 * Be careful not to zap a file I wouldn't normally be allowed to write
529 * to!
530 */
531
532#ifdef TRACING
533
534 case 'T': {
535 FILE *fp;
536
537 if (optarg == 0 || strcmp(optarg, "-") == 0)
538 fp = stdout;
539 else {
c6885d5f 540 uid_t eu = geteuid(), ru = getuid();
541
542#ifdef HAVE_SETREUID
543 if (setreuid(eu, ru))
544#else
545 if (seteuid(ru))
546#endif
547 {
548 die("couldn't temporarily give up privileges: %s",
549 strerror(errno));
03f996bd 550 }
c6885d5f 551
03f996bd 552 if ((fp = fopen(optarg, "w")) == 0) {
553 die("couldn't open trace file `%s' for writing: %s",
554 optarg, strerror(errno));
555 }
c6885d5f 556
557#ifdef HAVE_SETREUID
558 if (setreuid(ru, eu))
559#else
560 if (seteuid(eu))
561#endif
562 die("couldn't regain privileges: %s", strerror(errno));
03f996bd 563 }
564 traceon(fp, TRACE_DFL);
565 trace(TRACE_MISC, "become: tracing enabled");
566 } break;
567
568#endif
569
570 /* --- Setting trace levels --- */
571
572#ifdef TRACING
573
574 case 'L': {
575 int sense = 1;
576 unsigned int lvl = 0, l;
577 const char *p = optarg;
578
579 /* --- Table of tracing facilities --- */
580
581 typedef struct tr {
582 char ch;
583 unsigned int l;
584 const char *help;
585 } tr;
586
587 static tr lvltbl[] = {
588 { 'm', TRACE_MISC, "miscellaneous messages" },
589 { 's', TRACE_SETUP, "building the request block" },
590 { 'd', TRACE_DAEMON, "server process" },
591 { 'r', TRACE_RULE, "ruleset scanning" },
592 { 'c', TRACE_CHECK, "request checking" },
593 { 'l', TRACE_CLIENT, "client process" },
594 { 'R', TRACE_RAND, "random number generator" },
595 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
596 { 'y', TRACE_YACC, "parsing configuration file" },
597 { 'D', TRACE_DFL, "default tracing options" },
598 { 'A', TRACE_ALL, "all tracing options" },
599 { 0, 0, 0 }
600 };
601 tr *tp;
602
603 /* --- Output some help if there's no arguemnt --- */
604
605 if (!optarg) {
606 bc__banner(stdout);
607 bc__write(stdout,
608 "\n"
609 "Tracing options:\n"
610 "\n");
611 for (tp = lvltbl; tp->l; tp++) {
612 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
613 printf("%c -- %s\n", tp->ch, tp->help);
614 }
615 bc__write(stdout,
616"\n"
617"Also, `+' and `-' options are recognised to turn on and off vavrious\n"
618"tracing options. For example, `A-r' enables everything except ruleset\n"
619"tracing, and `A-D+c' is everything except the defaults, but with request\n"
620"check tracing.\n"
621);
622 exit(0);
623 }
624
625 while (*p) {
626 if (*p == '+')
627 sense = 1;
628 else if (*p == '-')
629 sense = 0;
630 else {
631 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
632 ;
633 l = tp->l;
634 if (flags & f_setuid)
635 l &= ~TRACE_PRIV;
636 if (l)
637 lvl = sense ? (lvl | l) : (lvl & ~l);
638 else
639 moan("unknown trace option `%c'", *p);
640 }
641 p++;
642 }
643
644 tracesetlvl(lvl);
645 yydebug = ((lvl & TRACE_YACC) != 0);
646 } break;
647
648#endif
649
387501ae 650 /* --- Something that wasn't an option --- *
651 *
652 * The following nasties are supported:
653 *
654 * * NAME=VALUE -- preserve NAME, and give it a VALUE
655 * * NAME= -- preserve NAME, and give it an empty value
656 * * NAME- -- delete NAME
657 * * NAME! -- preserve NAME with existing value
658 *
659 * Anything else is either the user name (which is OK) or the start of
660 * the command (in which case I stop and read the rest of the command).
661 */
662
663 case 0: {
664 size_t sz = strcspn(optarg, "=-!");
665 sym_env *e;
666
667 /* --- None of the above --- */
668
669 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
670 if (who == 0)
671 who = optarg;
672 else {
673 optind--;
674 goto done_options;
675 }
676 }
677
678 /* --- Do the appropriate thing --- */
679
680 switch (optarg[sz]) {
681 case '=':
682 bc__putenv(0, optarg, envFlag_preserve, 1);
683 break;
684 case '-':
685 optarg[sz] = 0;
686 bc__putenv(optarg, 0, 0, 1);
687 break;
688 case '!':
689 optarg[sz] = 0;
690 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
691 e->f |= envFlag_preserve;
692 break;
693 }
694 } break;
695
03f996bd 696 /* --- Something I didn't understand has occurred --- */
697
c4f2d992 698 case '?':
699 flags |= f_duff;
700 break;
701 }
702 }
387501ae 703
704done_options:
c4f2d992 705 if (flags & f_duff) {
706 bc__usage(stderr);
707 exit(1);
708 }
709
710 /* --- Switch to daemon mode if requested --- */
711
712 if (flags & f_daemon) {
03f996bd 713 T( trace(TRACE_MISC, "become: daemon mode requested"); )
c4f2d992 714 daemon_init(conffile, port);
715 exit(0);
716 }
717
718 /* --- Open a syslog --- */
719
720 openlog(quis(), 0, LOG_AUTH);
721
722 /* --- Pick out the uid --- */
723
724 {
c4f2d992 725 struct passwd *pw;
03f996bd 726
387501ae 727 if (!who) {
c4f2d992 728 bc__usage(stderr);
729 exit(1);
730 }
03f996bd 731
387501ae 732 if (isdigit((unsigned char)who[0]))
733 pw = getpwuid(atoi(who));
03f996bd 734 else
387501ae 735 pw = getpwnam(who);
c4f2d992 736 if (!pw)
387501ae 737 die("unknown user `%s'", who);
03f996bd 738 to_pw = userdb_copyUser(pw);
c4f2d992 739 rq.to = pw->pw_uid;
740 }
741
742 /* --- Fill in the easy bits of the request --- */
743
03f996bd 744 if (!from_pw) {
745 struct passwd *pw;
746
747 rq.from = getuid();
748 pw = getpwuid(rq.from);
749 if (!pw)
750 die("who are you? (can't find user %li)", (long)rq.from);
751 from_pw = userdb_copyUser(pw);
752 }
c4f2d992 753
754 /* --- Find the local host address --- */
755
756 {
757 struct utsname u;
758 struct hostent *he;
759 uname(&u);
760 if ((he = gethostbyname(u.nodename)) == 0)
761 die("who am I? (can't resolve `%s')", u.nodename);
762 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
763 }
764
03f996bd 765 /* --- Shell commands are easy --- */
c4f2d992 766
767 if (cmd) {
768 shell[2] = cmd;
769 todo = shell;
03f996bd 770 }
771
772 /* --- A command given on the command line isn't too hard --- */
773
774 else if (optind < argc) {
c4f2d992 775 todo = argv + optind;
03f996bd 776 binary = todo[0];
777 }
778
387501ae 779 else switch (style) {
03f996bd 780
387501ae 781 /* --- An unadorned becoming requires little work --- */
c4f2d992 782
387501ae 783 case l_preserve:
784 shell[0] = getenv("SHELL");
785 if (!shell[0])
786 shell[0] = from_pw->pw_shell;
787 shell[1] = 0;
788 todo = shell;
789 binary = todo[0];
790 break;
03f996bd 791
387501ae 792 /* --- An su-like login needs slightly less effort --- */
793
794 case l_user:
795 shell[0] = to_pw->pw_shell;
796 shell[1] = 0;
797 todo = shell;
798 binary = todo[0];
799 break;
800
801 /* --- A login request needs a little bit of work --- */
802
803 case l_login: {
804 const char *p = strrchr(to_pw->pw_shell, '/');
805
806 if (p)
807 p++;
808 else
809 p = to_pw->pw_shell;
810 shell[0] = xmalloc(strlen(p) + 2);
811 shell[0][0] = '-';
812 strcpy(shell[0] + 1, p);
813 shell[1] = 0;
814 todo = shell;
815 binary = to_pw->pw_shell;
816 chdir(to_pw->pw_dir);
817 } break;
03f996bd 818 }
819
569b8891 820 /* --- Mangle the environment --- *
821 *
387501ae 822 * This keeps getting more complicated all the time. (How true. Now I've
823 * got all sorts of nasty environment mangling to do.)
824 *
825 * The environment stuff now happens in seven phases:
826 *
827 * 1. Mark very special variables to be preserved. Currently only TERM
828 * and DISPLAY are treated in this way.
829 *
830 * 2. Set and preserve Become's own environment variables.
831 *
832 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
833 * SHELL and MAIL) if we're being `su'-like or `login'-like.
834 *
835 * 4. If we're preserving the environment or being `su'-like, process the
836 * PATH variable a little. Otherwise reset it to something
837 * appropriate.
838 *
839 * 5. If we're being `login'-like, expunge all unpreserved variables.
840 *
841 * 6. Expunge any security-critical variables.
842 *
843 * 7. Build a new environment table to pass to child processes.
569b8891 844 */
845
846 {
387501ae 847 /* --- Variables to be preserved always --- *
848 *
849 * A user can explicitly expunge a variable in this list, in which case
850 * we never get to see it here.
851 */
569b8891 852
387501ae 853 static char *preserve[] = {
854 "TERM", "DISPLAY", 0
855 };
c6885d5f 856
387501ae 857 /* --- Variables to be expunged --- *
569b8891 858 *
c6885d5f 859 * Any environment string which has one of the following as a prefix will
860 * be expunged from the environment passed to the called process. The
861 * first line lists variables which have been used to list search paths
862 * for shared libraries: by manipulating these, an attacker could replace
863 * a standard library with one of his own. The second line lists other
864 * well-known dangerous environment variables.
569b8891 865 */
866
387501ae 867 static char *banned[] = {
868 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
869 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
569b8891 870 0
871 };
872
387501ae 873 /* --- Other useful variables --- */
03f996bd 874
387501ae 875 sym_env *e;
876 char *p, *q, *r;
877 char **pp, **qq;
878 size_t sz;
879 unsigned f;
880 sym_iter i;
c6885d5f 881
387501ae 882 /* --- Stage one. Preserve display-specific variables --- */
569b8891 883
387501ae 884 for (pp = preserve; *pp; pp++) {
885 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
886 e->f |= envFlag_preserve;
887 }
569b8891 888
387501ae 889 /* --- Stage two. Set Become's own variables --- */
569b8891 890
387501ae 891 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
892 if (!f)
893 bc__setenv(e, from_pw->pw_name);
894 e->f |= envFlag_preserve;
895
896 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
897 if (!f)
898 bc__setenv(e, from_pw->pw_dir);
899 e->f |= envFlag_preserve;
569b8891 900
387501ae 901 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
902 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
903 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
904 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
569b8891 905
387501ae 906 /* --- Stage three. Set user identity --- */
907
908 switch (style) {
909 case l_login: {
910 static char *maildirs[] = {
911 "/var/spool/mail", "/var/mail",
912 "/usr/spool/mail", "/usr/mail",
913 0
914 };
915 struct stat s;
916 char b[128];
917
918 for (pp = maildirs; *pp; pp++) {
919 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
920 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
921 bc__putenv("MAIL", b, envFlag_preserve, 0);
922 break;
569b8891 923 }
569b8891 924 }
387501ae 925 } /* Fall through */
926
927 case l_user:
928 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
929 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
930 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
931 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
932 break;
933 }
934
935 /* --- Stage four. Set the user's PATH properly --- */
569b8891 936
387501ae 937 {
938 /* --- Find an existing path --- *
569b8891 939 *
387501ae 940 * If there's no path, or this is a login, then set a default path,
941 * unless we're meant to preserve the existing one. Whew!
569b8891 942 */
943
387501ae 944 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
945
946 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
947 bc__putenv("PATH",
948 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
949 envFlag_preserve, 0);
950 } else {
951
952 /* --- Find the string --- */
953
954 e->f = envFlag_preserve;
955 p = strchr(e->val, '=') + 1;
956 r = e->val;
957
958 /* --- Write the new version to a dynamically allocated buffer --- */
959
960 e->val = xmalloc(4 + 1 + strlen(p) + 1);
961 strcpy(e->val, "PATH=");
962 q = e->val + 5;
963
964 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
965 if (p[0] == '.')
966 continue;
967 while (*p)
968 *q++ = *p++;
969 *q++ = ':';
970 }
971 q[-1] = 0;
972
973 /* --- Done! --- */
974
975 free(r);
569b8891 976 }
387501ae 977 }
978
979 /* --- Stages five and six. Expunge variables and count numbers --- *
980 *
981 * Folded together, so I only need one pass through the table. Also
982 * count the number of variables needed at this time.
983 */
984
985 sz = 0;
569b8891 986
387501ae 987 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
569b8891 988
387501ae 989 /* --- Login style expunges all unpreserved variables --- */
569b8891 990
387501ae 991 if (style == l_login && ~e->f & envFlag_preserve)
992 goto expunge;
993
994 /* --- Otherwise just check the name against the list --- */
995
996 for (pp = banned; *pp; pp++) {
997 if (**pp == '-') {
998 p = *pp + 1;
999 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1000 goto expunge;
1001 } else if (strcmp(e->_base.name, p) == 0)
1002 goto expunge;
1003 }
1004
1005 sz++;
1006 continue;
1007
1008 expunge:
1009 sym_remove(&bc__env, e);
569b8891 1010 }
387501ae 1011
1012 /* --- Stage seven. Build the new environment block --- */
1013
1014 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1015
1016 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1017 *qq++ = e->val;
1018 *qq++ = 0;
03f996bd 1019 }
1020
1021 /* --- Trace the command --- */
1022
1023 IF_TRACING(TRACE_SETUP, {
1024 int i;
1025
1026 trace(TRACE_SETUP, "setup: from user %s to user %s",
1027 from_pw->pw_name, to_pw->pw_name);
1028 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1029 for (i = 0; todo[i]; i++)
1030 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1031 for (i = 0; env[i]; i++)
1032 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1033 })
1034
c4f2d992 1035 /* --- If necessary, resolve the path to the command --- */
1036
03f996bd 1037 if (!strchr(binary, '/')) {
1038 char *path, *p;
c4f2d992 1039 struct stat st;
c4f2d992 1040
1041 if ((p = getenv("PATH")) == 0)
1042 p = "/bin:/usr/bin";
03f996bd 1043 path = xstrdup(p);
c4f2d992 1044
569b8891 1045 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
c4f2d992 1046
569b8891 1047 /* --- Check length of string before copying --- */
c4f2d992 1048
03f996bd 1049 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
c4f2d992 1050 continue;
1051
1052 /* --- Now build the pathname and check it --- */
1053
03f996bd 1054 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1055 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
c4f2d992 1056 st.st_mode & 0111 && /* Check it's executable */
c6885d5f 1057 S_ISREG(st.st_mode)) /* Check it's a file */
c4f2d992 1058 break;
1059 }
1060
1061 if (!p)
1062 die("couldn't find `%s' in path", todo[0]);
03f996bd 1063 binary = rq.cmd;
c4f2d992 1064 free(path);
1065 }
03f996bd 1066 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
c4f2d992 1067
1068 /* --- Canonicalise the path string, if necessary --- */
1069
1070 {
1071 char b[CMDLEN_MAX];
1072 char *p;
1073 const char *q;
1074
1075 /* --- Insert current directory name if path not absolute --- */
1076
1077 p = b;
03f996bd 1078 q = binary;
c4f2d992 1079 if (*q != '/') {
1080 if (!getcwd(b, sizeof(b)))
1081 die("couldn't read current directory: %s", strerror(errno));
1082 p += strlen(p);
1083 *p++ = '/';
1084 }
1085
1086 /* --- Now copy over characters from the path string --- */
1087
1088 while (*q) {
1089
569b8891 1090 /* --- Check for buffer overflows here --- *
c4f2d992 1091 *
1092 * I write at most one byte per iteration so this is OK. Remember to
1093 * allow one for the null byte.
1094 */
1095
1096 if (p >= b + sizeof(b) - 1)
03f996bd 1097 die("internal error: buffer overflow while canonifying path");
c4f2d992 1098
1099 /* --- Reduce multiple slashes to just one --- */
1100
1101 if (*q == '/') {
1102 while (*q == '/')
1103 q++;
1104 *p++ = '/';
1105 }
1106
1107 /* --- Handle dots in filenames --- *
1108 *
1109 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1110 * we've just stuck the current directory on the end of the buffer,
1111 * or we've just put something else on the end.
1112 */
1113
1114 else if (*q == '.' && p[-1] == '/') {
1115
1116 /* --- A simple `./' just gets removed --- */
1117
1118 if (q[1] == 0 || q[1] == '/') {
1119 q++;
1120 p--;
1121 continue;
1122 }
1123
1124 /* --- A `../' needs to be peeled back to the previous `/' --- */
1125
1126 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1127 q += 2;
1128 p--;
1129 while (p > b && p[-1] != '/')
1130 p--;
1131 if (p > b)
1132 p--;
1133 continue;
1134 }
1135 } else
1136 *p++ = *q++;
1137 }
1138
1139 *p++ = 0;
1140 strcpy(rq.cmd, b);
1141 }
03f996bd 1142 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
c4f2d992 1143
88e486d5 1144 /* --- Run the check --- *
1145 *
1146 * If the user is already what she wants to be, then print a warning.
1147 * Then, if I was just going to spawn a shell, quit, to reduce user
1148 * confusion. Otherwise, do what was wanted anyway.
1149 */
c4f2d992 1150
88e486d5 1151 if (rq.from == rq.to) {
1152 moan("you already are `%s'!", to_pw->pw_name);
1153 if (!cmd && todo == shell) {
1154 moan("(to prevent confusion, I'm not spawning a shell)");
1155 exit(0);
1156 }
1157 } else {
c4f2d992 1158 int a = check(&rq);
c4f2d992 1159
1160 syslog(LOG_INFO,
1161 "permission %s for %s to become %s to run `%s'",
03f996bd 1162 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1163 rq.cmd);
c4f2d992 1164
1165 if (!a)
1166 die("permission denied");
1167 }
1168
1169 /* --- Now do the job --- */
1170
03f996bd 1171 T( trace(TRACE_MISC, "become: permission granted"); )
1172
1173 if (flags & f_dummy) {
1174 puts("permission granted");
1175 return (0);
1176 } else {
88e486d5 1177 if (setuid(rq.to) == -1)
03f996bd 1178 die("couldn't set uid: %s", strerror(errno));
c6885d5f 1179 fflush(0);
03f996bd 1180 execve(rq.cmd, todo, env);
1181 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1182 return (127);
1183 }
c4f2d992 1184}
1185
1186/*----- That's all, folks -------------------------------------------------*/