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