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