Change criteria for expunging items from the user's PATH: instead of
[become] / src / become.c
CommitLineData
c4f2d992 1/* -*-c-*-
2 *
6a4ec25c 3 * $Id: become.c,v 1.8 1997/09/08 13:56:24 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 $
6a4ec25c 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
c6885d5f 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
d6683a48 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
387501ae 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
569b8891 50 * Overhaul of environment handling. Fix daft bug in path search code.
51 *
88e486d5 52 * Revision 1.3 1997/08/07 16:28:59 mdw
53 * Do something useful when users attempt to become themselves.
54 *
03f996bd 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
c4f2d992 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>
03f996bd 72#include <time.h>
c4f2d992 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
03f996bd 90extern char **environ;
91
c4f2d992 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"
387501ae 103#include "sym.h"
c4f2d992 104#include "utils.h"
03f996bd 105#include "userdb.h"
c4f2d992 106
387501ae 107/*----- Type definitions --------------------------------------------------*/
108
109/* --- Symbol table entry for an environment variable --- */
110
111typedef 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
119enum {
120 envFlag_preserve = 1
121};
122
123/* --- Login behaviour types --- */
124
125enum {
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
133static sym_table bc__env;
134
c4f2d992 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
149static 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 *
569b8891 157 * Gather up non-`$' characters using @strcspn@ and spew them out really
c4f2d992 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
387501ae 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
183static 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
203static 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
c4f2d992 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
263static 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
277static void bc__usage(FILE *fp)
278{
279 bc__write(fp,
280 "Usage: \n"
281 " $ -c <shell-command> <user>\n"
387501ae 282 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
c4f2d992 283 " $ -d [-p <port>] [-f <config-file>]\n");
284}
285
286/* --- @bc__help@ --- *
287 *
288 * Arguments: @FILE *fp@ = stream to write on
569b8891 289 * @int suid@ = whether we're running set-uid
c4f2d992 290 *
291 * Returns: ---
292 *
293 * Use: Displays a help message for this excellent piece of software.
294 */
295
569b8891 296static void bc__help(FILE *fp, int suid)
c4f2d992 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"
387501ae 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");
03f996bd 329#ifdef TRACING
387501ae 330 bc__write(fp, "\n");
569b8891 331 if (!suid) {
332 bc__write(fp,
387501ae 333"-I USER, --impersonate=USER Claim to be USER when asking the server\n");
569b8891 334 }
335 bc__write(fp,
387501ae 336"-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
337"-L OPTS, --trace-level=OPTS Set level of tracing information\n");
03f996bd 338#endif
c4f2d992 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
351int main(int argc, char *argv[])
352{
03f996bd 353 /* --- Request block setup parameters --- */
c4f2d992 354
03f996bd 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 */
387501ae 359 char **todo = 0; /* Pointer to argument list */
360 char *who = 0; /* Who we're meant to become */
03f996bd 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 */
387501ae 372 int style = DEFAULT_LOGIN_STYLE; /* Login style */
03f996bd 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 */
c4f2d992 381 };
382
03f996bd 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 };
c4f2d992 392
393 /* --- Set up the program name --- */
394
395 ego(argv[0]);
03f996bd 396 clock();
397 if (getuid() != geteuid())
398 flags |= f_setuid;
c4f2d992 399
387501ae 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
c4f2d992 410 /* --- Parse some command line arguments --- */
411
412 for (;;) {
413 int i;
03f996bd 414 static struct option opts[] = {
387501ae 415
416 /* --- Asking for help --- */
417
c4f2d992 418 { "help", 0, 0, 'h' },
387501ae 419 { "usage", 0, 0, 'u' },
c4f2d992 420 { "version", 0, 0, 'v' },
387501ae 421
422 /* --- Login style options --- */
423
424 { "preserve-environment", 0, 0, 'e' },
425 { "su", 0, 0, 's' },
426 { "set-user", 0, 0, 's' },
03f996bd 427 { "login", 0, 0, 'l' },
387501ae 428
429 /* --- Command to run options --- */
430
c4f2d992 431 { "command", gFlag_argReq, 0, 'c' },
387501ae 432
433 /* --- Server options --- */
434
c4f2d992 435 { "daemon", 0, 0, 'd' },
436 { "port", gFlag_argReq, 0, 'p' },
437 { "config-file", gFlag_argReq, 0, 'f' },
387501ae 438
439 /* --- Tracing options --- */
440
03f996bd 441#ifdef TRACING
442 { "impersonate", gFlag_argReq, 0, 'I' },
443 { "trace", gFlag_argOpt, 0, 'T' },
444 { "trace-level", gFlag_argOpt, 0, 'L' },
445#endif
387501ae 446
c4f2d992 447 { 0, 0, 0, 0 }
448 };
449
387501ae 450 i = mdwopt(argc, argv,
d6683a48 451 "-" "huv" "esl" "c:" "dp:f:" T("I:T::L::"),
387501ae 452 opts, 0, 0, gFlag_envVar);
c4f2d992 453 if (i < 0)
387501ae 454 goto done_options;
c4f2d992 455
456 switch (i) {
03f996bd 457
387501ae 458 /* --- Asking for help --- */
03f996bd 459
c4f2d992 460 case 'h':
569b8891 461 bc__help(stdout, flags & f_setuid);
462 exit(0);
463 break;
387501ae 464 case 'u':
569b8891 465 bc__usage(stdout);
c4f2d992 466 exit(0);
467 break;
468 case 'v':
469 bc__banner(stdout);
470 exit(0);
471 break;
03f996bd 472
387501ae 473 /* --- Login style options --- */
03f996bd 474
387501ae 475 case 'e':
476 style = l_preserve;
477 break;
478 case 's':
479 style = l_user;
480 break;
03f996bd 481 case 'l':
387501ae 482 style = l_login;
03f996bd 483 break;
387501ae 484
485 /* --- Command to run options --- */
486
c4f2d992 487 case 'c':
488 cmd = optarg;
489 break;
387501ae 490
491 /* --- Server options --- */
492
c4f2d992 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;
03f996bd 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
569b8891 510
03f996bd 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 }
c4f2d992 526 break;
569b8891 527
03f996bd 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 {
c6885d5f 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));
03f996bd 554 }
c6885d5f 555
03f996bd 556 if ((fp = fopen(optarg, "w")) == 0) {
557 die("couldn't open trace file `%s' for writing: %s",
558 optarg, strerror(errno));
559 }
c6885d5f 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));
03f996bd 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
387501ae 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
03f996bd 700 /* --- Something I didn't understand has occurred --- */
701
c4f2d992 702 case '?':
703 flags |= f_duff;
704 break;
705 }
706 }
387501ae 707
708done_options:
c4f2d992 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) {
03f996bd 717 T( trace(TRACE_MISC, "become: daemon mode requested"); )
c4f2d992 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 {
c4f2d992 729 struct passwd *pw;
03f996bd 730
387501ae 731 if (!who) {
c4f2d992 732 bc__usage(stderr);
733 exit(1);
734 }
03f996bd 735
387501ae 736 if (isdigit((unsigned char)who[0]))
737 pw = getpwuid(atoi(who));
03f996bd 738 else
387501ae 739 pw = getpwnam(who);
c4f2d992 740 if (!pw)
387501ae 741 die("unknown user `%s'", who);
03f996bd 742 to_pw = userdb_copyUser(pw);
c4f2d992 743 rq.to = pw->pw_uid;
744 }
745
746 /* --- Fill in the easy bits of the request --- */
747
03f996bd 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 }
c4f2d992 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
03f996bd 769 /* --- Shell commands are easy --- */
c4f2d992 770
771 if (cmd) {
772 shell[2] = cmd;
773 todo = shell;
03f996bd 774 }
775
776 /* --- A command given on the command line isn't too hard --- */
777
778 else if (optind < argc) {
c4f2d992 779 todo = argv + optind;
03f996bd 780 binary = todo[0];
781 }
782
387501ae 783 else switch (style) {
03f996bd 784
387501ae 785 /* --- An unadorned becoming requires little work --- */
c4f2d992 786
387501ae 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;
03f996bd 795
387501ae 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;
03f996bd 822 }
823
569b8891 824 /* --- Mangle the environment --- *
825 *
387501ae 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.
569b8891 848 */
849
850 {
387501ae 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 */
569b8891 856
387501ae 857 static char *preserve[] = {
858 "TERM", "DISPLAY", 0
859 };
c6885d5f 860
387501ae 861 /* --- Variables to be expunged --- *
569b8891 862 *
c6885d5f 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.
569b8891 869 */
870
387501ae 871 static char *banned[] = {
872 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
873 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
569b8891 874 0
875 };
876
387501ae 877 /* --- Other useful variables --- */
03f996bd 878
387501ae 879 sym_env *e;
880 char *p, *q, *r;
881 char **pp, **qq;
882 size_t sz;
883 unsigned f;
884 sym_iter i;
c6885d5f 885
387501ae 886 /* --- Stage one. Preserve display-specific variables --- */
569b8891 887
387501ae 888 for (pp = preserve; *pp; pp++) {
889 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
890 e->f |= envFlag_preserve;
891 }
569b8891 892
387501ae 893 /* --- Stage two. Set Become's own variables --- */
569b8891 894
387501ae 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;
569b8891 904
387501ae 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);
569b8891 909
387501ae 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;
569b8891 927 }
569b8891 928 }
387501ae 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 --- */
569b8891 940
387501ae 941 {
942 /* --- Find an existing path --- *
569b8891 943 *
387501ae 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!
569b8891 946 */
947
387501ae 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, ":")) {
6a4ec25c 969 if (p[0] != '/')
387501ae 970 continue;
971 while (*p)
972 *q++ = *p++;
973 *q++ = ':';
974 }
975 q[-1] = 0;
976
977 /* --- Done! --- */
978
979 free(r);
569b8891 980 }
387501ae 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;
569b8891 990
387501ae 991 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
569b8891 992
387501ae 993 /* --- Login style expunges all unpreserved variables --- */
569b8891 994
387501ae 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);
569b8891 1014 }
387501ae 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;
03f996bd 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
c4f2d992 1039 /* --- If necessary, resolve the path to the command --- */
1040
03f996bd 1041 if (!strchr(binary, '/')) {
1042 char *path, *p;
c4f2d992 1043 struct stat st;
c4f2d992 1044
1045 if ((p = getenv("PATH")) == 0)
1046 p = "/bin:/usr/bin";
03f996bd 1047 path = xstrdup(p);
c4f2d992 1048
569b8891 1049 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
c4f2d992 1050
569b8891 1051 /* --- Check length of string before copying --- */
c4f2d992 1052
03f996bd 1053 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
c4f2d992 1054 continue;
1055
1056 /* --- Now build the pathname and check it --- */
1057
03f996bd 1058 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1059 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
c4f2d992 1060 st.st_mode & 0111 && /* Check it's executable */
c6885d5f 1061 S_ISREG(st.st_mode)) /* Check it's a file */
c4f2d992 1062 break;
1063 }
1064
1065 if (!p)
1066 die("couldn't find `%s' in path", todo[0]);
03f996bd 1067 binary = rq.cmd;
c4f2d992 1068 free(path);
1069 }
03f996bd 1070 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
c4f2d992 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;
03f996bd 1082 q = binary;
c4f2d992 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
569b8891 1094 /* --- Check for buffer overflows here --- *
c4f2d992 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)
03f996bd 1101 die("internal error: buffer overflow while canonifying path");
c4f2d992 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 }
03f996bd 1146 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
c4f2d992 1147
88e486d5 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 */
c4f2d992 1154
88e486d5 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 {
c4f2d992 1162 int a = check(&rq);
c4f2d992 1163
1164 syslog(LOG_INFO,
1165 "permission %s for %s to become %s to run `%s'",
03f996bd 1166 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1167 rq.cmd);
c4f2d992 1168
1169 if (!a)
1170 die("permission denied");
1171 }
1172
1173 /* --- Now do the job --- */
1174
03f996bd 1175 T( trace(TRACE_MISC, "become: permission granted"); )
1176
1177 if (flags & f_dummy) {
1178 puts("permission granted");
1179 return (0);
1180 } else {
88e486d5 1181 if (setuid(rq.to) == -1)
03f996bd 1182 die("couldn't set uid: %s", strerror(errno));
c6885d5f 1183 fflush(0);
03f996bd 1184 execve(rq.cmd, todo, env);
1185 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1186 return (127);
1187 }
c4f2d992 1188}
1189
1190/*----- That's all, folks -------------------------------------------------*/