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