Change userid when creating tracefiles rather than fiddling with
[become] / src / become.c
1 /* -*-c-*-
2 *
3 * $Id: become.c,v 1.7 1997/09/08 13:43:20 mdw Exp $
4 *
5 * Main code for `become'
6 *
7 * (c) 1997 EBI
8 */
9
10 /*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of `become'
13 *
14 * `Become' is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * `Become' is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with `become'; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
29 /*----- Revision history --------------------------------------------------*
30 *
31 * $Log: become.c,v $
32 * Revision 1.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
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
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
46 * Overhaul of environment handling. Fix daft bug in path search code.
47 *
48 * Revision 1.3 1997/08/07 16:28:59 mdw
49 * Do something useful when users attempt to become themselves.
50 *
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
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>
68 #include <time.h>
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
86 extern char **environ;
87
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"
99 #include "sym.h"
100 #include "utils.h"
101 #include "userdb.h"
102
103 /*----- Type definitions --------------------------------------------------*/
104
105 /* --- Symbol table entry for an environment variable --- */
106
107 typedef 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
115 enum {
116 envFlag_preserve = 1
117 };
118
119 /* --- Login behaviour types --- */
120
121 enum {
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
129 static sym_table bc__env;
130
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
145 static 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 *
153 * Gather up non-`$' characters using @strcspn@ and spew them out really
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
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
179 static 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
199 static 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
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
259 static 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
273 static void bc__usage(FILE *fp)
274 {
275 bc__write(fp,
276 "Usage: \n"
277 " $ -c <shell-command> <user>\n"
278 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
279 " $ -d [-p <port>] [-f <config-file>]\n");
280 }
281
282 /* --- @bc__help@ --- *
283 *
284 * Arguments: @FILE *fp@ = stream to write on
285 * @int suid@ = whether we're running set-uid
286 *
287 * Returns: ---
288 *
289 * Use: Displays a help message for this excellent piece of software.
290 */
291
292 static void bc__help(FILE *fp, int suid)
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"
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");
325 #ifdef TRACING
326 bc__write(fp, "\n");
327 if (!suid) {
328 bc__write(fp,
329 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
330 }
331 bc__write(fp,
332 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
333 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
334 #endif
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
347 int main(int argc, char *argv[])
348 {
349 /* --- Request block setup parameters --- */
350
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 */
355 char **todo = 0; /* Pointer to argument list */
356 char *who = 0; /* Who we're meant to become */
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 */
368 int style = DEFAULT_LOGIN_STYLE; /* Login style */
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 */
377 };
378
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 };
388
389 /* --- Set up the program name --- */
390
391 ego(argv[0]);
392 clock();
393 if (getuid() != geteuid())
394 flags |= f_setuid;
395
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
406 /* --- Parse some command line arguments --- */
407
408 for (;;) {
409 int i;
410 static struct option opts[] = {
411
412 /* --- Asking for help --- */
413
414 { "help", 0, 0, 'h' },
415 { "usage", 0, 0, 'u' },
416 { "version", 0, 0, 'v' },
417
418 /* --- Login style options --- */
419
420 { "preserve-environment", 0, 0, 'e' },
421 { "su", 0, 0, 's' },
422 { "set-user", 0, 0, 's' },
423 { "login", 0, 0, 'l' },
424
425 /* --- Command to run options --- */
426
427 { "command", gFlag_argReq, 0, 'c' },
428
429 /* --- Server options --- */
430
431 { "daemon", 0, 0, 'd' },
432 { "port", gFlag_argReq, 0, 'p' },
433 { "config-file", gFlag_argReq, 0, 'f' },
434
435 /* --- Tracing options --- */
436
437 #ifdef TRACING
438 { "impersonate", gFlag_argReq, 0, 'I' },
439 { "trace", gFlag_argOpt, 0, 'T' },
440 { "trace-level", gFlag_argOpt, 0, 'L' },
441 #endif
442
443 { 0, 0, 0, 0 }
444 };
445
446 i = mdwopt(argc, argv,
447 "-" "huv" "esl" "c:" "dp:f:" T("I:T::L::"),
448 opts, 0, 0, gFlag_envVar);
449 if (i < 0)
450 goto done_options;
451
452 switch (i) {
453
454 /* --- Asking for help --- */
455
456 case 'h':
457 bc__help(stdout, flags & f_setuid);
458 exit(0);
459 break;
460 case 'u':
461 bc__usage(stdout);
462 exit(0);
463 break;
464 case 'v':
465 bc__banner(stdout);
466 exit(0);
467 break;
468
469 /* --- Login style options --- */
470
471 case 'e':
472 style = l_preserve;
473 break;
474 case 's':
475 style = l_user;
476 break;
477 case 'l':
478 style = l_login;
479 break;
480
481 /* --- Command to run options --- */
482
483 case 'c':
484 cmd = optarg;
485 break;
486
487 /* --- Server options --- */
488
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;
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
506
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 }
522 break;
523
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 {
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));
550 }
551
552 if ((fp = fopen(optarg, "w")) == 0) {
553 die("couldn't open trace file `%s' for writing: %s",
554 optarg, strerror(errno));
555 }
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));
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
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
696 /* --- Something I didn't understand has occurred --- */
697
698 case '?':
699 flags |= f_duff;
700 break;
701 }
702 }
703
704 done_options:
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) {
713 T( trace(TRACE_MISC, "become: daemon mode requested"); )
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 {
725 struct passwd *pw;
726
727 if (!who) {
728 bc__usage(stderr);
729 exit(1);
730 }
731
732 if (isdigit((unsigned char)who[0]))
733 pw = getpwuid(atoi(who));
734 else
735 pw = getpwnam(who);
736 if (!pw)
737 die("unknown user `%s'", who);
738 to_pw = userdb_copyUser(pw);
739 rq.to = pw->pw_uid;
740 }
741
742 /* --- Fill in the easy bits of the request --- */
743
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 }
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
765 /* --- Shell commands are easy --- */
766
767 if (cmd) {
768 shell[2] = cmd;
769 todo = shell;
770 }
771
772 /* --- A command given on the command line isn't too hard --- */
773
774 else if (optind < argc) {
775 todo = argv + optind;
776 binary = todo[0];
777 }
778
779 else switch (style) {
780
781 /* --- An unadorned becoming requires little work --- */
782
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;
791
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;
818 }
819
820 /* --- Mangle the environment --- *
821 *
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.
844 */
845
846 {
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 */
852
853 static char *preserve[] = {
854 "TERM", "DISPLAY", 0
855 };
856
857 /* --- Variables to be expunged --- *
858 *
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.
865 */
866
867 static char *banned[] = {
868 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
869 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
870 0
871 };
872
873 /* --- Other useful variables --- */
874
875 sym_env *e;
876 char *p, *q, *r;
877 char **pp, **qq;
878 size_t sz;
879 unsigned f;
880 sym_iter i;
881
882 /* --- Stage one. Preserve display-specific variables --- */
883
884 for (pp = preserve; *pp; pp++) {
885 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
886 e->f |= envFlag_preserve;
887 }
888
889 /* --- Stage two. Set Become's own variables --- */
890
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;
900
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);
905
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;
923 }
924 }
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 --- */
936
937 {
938 /* --- Find an existing path --- *
939 *
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!
942 */
943
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);
976 }
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;
986
987 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
988
989 /* --- Login style expunges all unpreserved variables --- */
990
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);
1010 }
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;
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
1035 /* --- If necessary, resolve the path to the command --- */
1036
1037 if (!strchr(binary, '/')) {
1038 char *path, *p;
1039 struct stat st;
1040
1041 if ((p = getenv("PATH")) == 0)
1042 p = "/bin:/usr/bin";
1043 path = xstrdup(p);
1044
1045 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1046
1047 /* --- Check length of string before copying --- */
1048
1049 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1050 continue;
1051
1052 /* --- Now build the pathname and check it --- */
1053
1054 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1055 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1056 st.st_mode & 0111 && /* Check it's executable */
1057 S_ISREG(st.st_mode)) /* Check it's a file */
1058 break;
1059 }
1060
1061 if (!p)
1062 die("couldn't find `%s' in path", todo[0]);
1063 binary = rq.cmd;
1064 free(path);
1065 }
1066 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
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;
1078 q = binary;
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
1090 /* --- Check for buffer overflows here --- *
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)
1097 die("internal error: buffer overflow while canonifying path");
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 }
1142 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1143
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 */
1150
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 {
1158 int a = check(&rq);
1159
1160 syslog(LOG_INFO,
1161 "permission %s for %s to become %s to run `%s'",
1162 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1163 rq.cmd);
1164
1165 if (!a)
1166 die("permission denied");
1167 }
1168
1169 /* --- Now do the job --- */
1170
1171 T( trace(TRACE_MISC, "become: permission granted"); )
1172
1173 if (flags & f_dummy) {
1174 puts("permission granted");
1175 return (0);
1176 } else {
1177 if (setuid(rq.to) == -1)
1178 die("couldn't set uid: %s", strerror(errno));
1179 fflush(0);
1180 execve(rq.cmd, todo, env);
1181 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1182 return (127);
1183 }
1184 }
1185
1186 /*----- That's all, folks -------------------------------------------------*/