3 * $Id: become.c,v 1.20 1999/05/04 16:17:11 mdw Exp $
5 * Main code for `become'
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of `become'
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.
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.
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.
29 /*----- Revision history --------------------------------------------------*
32 * Revision 1.20 1999/05/04 16:17:11 mdw
33 * Change to header file name for parser. See log for `parse.h' for
36 * Revision 1.19 1998/06/29 13:10:41 mdw
37 * Add some commentary regarding an issue in `sudo' which affects `become';
38 * I'm not fixing it yet because I don't think it's important.
40 * Fixed the PATH lookup code to use the right binary name: `binary' rather
41 * than `todo[0]'. The two only differ when `style' is `l_login', in which
42 * case `binary' has an initial `/' anyway, and the PATH lookup code is
43 * never invoked. The name is used in a buffer-overflow precheck, though,
44 * and auditing is easier if the naming is consistent.
46 * Revision 1.18 1998/06/26 10:32:54 mdw
47 * Cosmetic change: use sizeof(destination) in memcpy.
49 * Revision 1.17 1998/06/18 15:06:59 mdw
50 * Close log before execing program to avoid leaving a socket open.
52 * Revision 1.16 1998/04/23 13:21:04 mdw
53 * Small tweaks. Support no-network configuration option, and rearrange
54 * the help text a little.
56 * Revision 1.15 1998/01/13 11:10:44 mdw
57 * Add `TZ' to the list of variables to be preserved.
59 * Revision 1.14 1998/01/12 16:45:39 mdw
62 * Revision 1.13 1997/09/26 09:14:57 mdw
63 * Merged blowfish branch into trunk.
65 * Revision 1.12 1997/09/25 16:04:48 mdw
66 * Change directory after becoming someone else, instead of before. This
67 * avoids problems with root-squashed NFS mounts.
69 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
70 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
71 * because I prefer Blowfish (without any particularly strong evidence) but
72 * mainly because IDEA is patented and Blowfish isn't.
74 * Revision 1.11 1997/09/24 09:48:45 mdw
75 * Fix (scary) overrun bug in group allocation stuff.
77 * Revision 1.10 1997/09/17 10:14:10 mdw
78 * Fix a typo. Support service names in `--port' option.
80 * Revision 1.9 1997/09/10 10:28:05 mdw
81 * Allow default port to be given as a service name or port number. Handle
82 * groups properly (lots of options here).
84 * Revision 1.8 1997/09/08 13:56:24 mdw
85 * Change criteria for expunging items from the user's PATH: instead of
86 * removing things starting with `.', remove things not starting with `/'.
88 * Revision 1.7 1997/09/08 13:43:20 mdw
89 * Change userid when creating tracefiles rather than fiddling with
90 * `access': it works rather better. Also, insert some stdio buffer
91 * flushing to ensure tracedumps are completely written.
93 * Revision 1.6 1997/09/05 13:47:44 mdw
94 * Make the `-L' (trace-level) option's argument optional, like the long
97 * Revision 1.5 1997/09/05 11:45:19 mdw
98 * Add support for different login styles, and environment variable
99 * manipulation in a safe and useful way.
101 * Revision 1.4 1997/08/20 16:15:13 mdw
102 * Overhaul of environment handling. Fix daft bug in path search code.
104 * Revision 1.3 1997/08/07 16:28:59 mdw
105 * Do something useful when users attempt to become themselves.
107 * Revision 1.2 1997/08/04 10:24:20 mdw
108 * Sources placed under CVS control.
110 * Revision 1.1 1997/07/21 13:47:54 mdw
115 /*----- Header files ------------------------------------------------------*/
117 /* --- ANSI headers --- */
127 /* --- Unix headers --- */
129 #include <sys/types.h>
130 #include <sys/stat.h>
131 #include <sys/socket.h>
132 #include <sys/utsname.h>
134 #include <netinet/in.h>
136 #include <arpa/inet.h>
144 extern char **environ;
146 /* --- Local headers --- */
161 /*----- Type definitions --------------------------------------------------*/
163 /* --- Symbol table entry for an environment variable --- */
165 typedef struct sym_env {
166 sym_base _base; /* Symbol table information */
167 unsigned f; /* Flags word (see below) */
168 char *val; /* Pointer to variable value */
171 /* --- Environment variable flags --- */
177 /* --- Login behaviour types --- */
179 #define l_preserve 0 /* Preserve the environment */
180 #define l_setuser 1 /* Update who I am */
181 #define l_login 2 /* Do a full login */
183 /* --- Group behaviour types --- *
185 * Note that these make a handy bitfield.
188 #ifdef HAVE_SETGROUPS
191 g_unset = 0, /* Nobody's set a preference */
192 g_keep = 1, /* Leave the group memberships */
193 g_replace = 2, /* Replace group memberships */
194 g_merge = (g_keep | g_replace) /* Merge the group memberships */
199 /*----- Static variables --------------------------------------------------*/
201 static sym_table bc__env;
203 /*----- Main code ---------------------------------------------------------*/
205 /* --- @bc__write@ --- *
207 * Arguments: @FILE *fp@ = pointer to a stream to write on
208 * @const char *p@ = pointer to a string
212 * Use: Writes the string to the stream, substituting the program
213 * name (as returned by @quis@) for each occurrence of the
217 static void bc__write(FILE *fp, const char *p)
219 const char *n = quis();
221 size_t nl = strlen(n);
223 /* --- Try to be a little efficient --- *
225 * Gather up non-`$' characters using @strcspn@ and spew them out really
236 fwrite(n, nl, 1, fp);
241 /* --- @bc__setenv@ --- *
243 * Arguments: @sym_env *e@ = pointer to environment variable block
244 * @const char *val@ = value to set
248 * Use: Sets an environment variable block to the right value.
251 static void bc__setenv(sym_env *e, const char *val)
253 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
254 sprintf(e->val, "%s=%s", e->_base.name, val);
257 /* --- @bc__putenv@ --- *
259 * Arguments: @const char *var@ = name of the variable to set, or 0 if
260 * this is embedded in the value string
261 * @const char *val@ = value to set, or 0 if the variable must
263 * @unsigned int fl@ = flags to set
264 * @unsigned int force@ = force overwrite of preserved variables
266 * Returns: Pointer to symbol block, or zero if it was deleted.
268 * Use: Puts an item into the environment.
271 static sym_env *bc__putenv(const char *var, const char *val,
272 unsigned int fl, unsigned int force)
279 /* --- Sort out embedded variable names --- */
282 const char *p = strchr(val, '=');
297 /* --- Find the variable block --- */
300 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
301 if (!f || ~e->f & envFlag_preserve || force) {
307 e = sym_find(&bc__env, var, -1, 0, 0);
308 if (e && (force || ~e->f & envFlag_preserve))
309 sym_remove(&bc__env, e);
313 /* --- Tidy up and return --- */
322 /* --- @bc__addGroups@ --- *
324 * Arguments: @gid_t *g@ = pointer to a group array
325 * @int *png@ = pointer to number of entries in the array
326 * @const gid_t *a@ = pointer to groups to add
327 * @int na@ = number of groups to add
329 * Returns: Zero if it was OK, nonzero if we should stop now.
331 * Use: Adds groups to a groups array.
334 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
339 for (i = 0; i < na; i++) {
341 /* --- Ensure this group isn't already in the list --- */
343 for (j = 0; j < ng; j++) {
348 /* --- See if there's room for more --- */
350 if (ng >= NGROUPS_MAX) {
351 moan("too many groups (system limit exceeded) -- some have been lost");
356 /* --- Add the group --- */
366 /* --- @bc__banner@ --- *
368 * Arguments: @FILE *fp@ = stream to write on
372 * Use: Writes a banner containing copyright information.
375 static void bc__banner(FILE *fp)
377 bc__write(fp, "$ version " VERSION "\n");
380 /* --- @bc__usage@ --- *
382 * Arguments: @FILE *fp@ = stream to write on
386 * Use: Writes a terse reminder of command line syntax.
389 static void bc__usage(FILE *fp)
393 " $ -c <shell-command> <user>\n"
394 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
396 " $ -d [-p <port>] [-f <config-file>]\n"
401 /* --- @bc__help@ --- *
403 * Arguments: @FILE *fp@ = stream to write on
404 * @int suid@ = whether we're running set-uid
408 * Use: Displays a help message for this excellent piece of software.
411 static void bc__help(FILE *fp, int suid)
418 "The `$' program allows you to run a process as another user.\n"
419 "If a command name is given, this is the process executed. If the `-c'\n"
420 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
421 "given, your default login shell is used.\n"
423 "Your user id, the user id you wish to become, the name of the process\n"
424 "you wish to run, and the identity of the current host are looked up to\n"
425 "ensure that you have permission to do this.\n"
427 "Note that logs are kept of all uses of this program.\n"
429 "Options available are:\n"
431 "-h, --help Display this help text\n"
432 "-u, --usage Display a short usage summary\n"
433 "-v, --version Display $'s version number\n"
435 "-e, --preserve-environment Try to preserve the current environment\n"
436 "-s, --su, --set-user Set environment variables to reflect USER\n"
437 "-l, --login Really log in as USER\n"
439 #if DEFAULT_LOGIN_STYLE == l_preserve
440 "preserve-environment"
441 #elif DEFAULT_LOGIN_STYLE == l_setuser
443 #elif DEFAULT_LOGIN_STYLE == l_login
449 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
450 #ifdef HAVE_SETGROUPS
451 "-k, --keep-groups Keep your current set of groups\n"
452 "-m, --merge-groups Merge the lists of groups\n"
453 "-r, --replace-groups Replace the list of groups\n"
456 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
459 "-d, --daemon Start a daemon\n"
460 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
461 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
468 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
471 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
472 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
478 * Arguments: @int argc@ = number of command line arguments
479 * @char *argv[]@ = pointer to the various arguments
481 * Returns: Zero if successful.
483 * Use: Allows a user to change UID.
486 int main(int argc, char *argv[])
488 /* --- Request block setup parameters --- */
490 request rq; /* Request buffer to build */
491 char *cmd = 0; /* Shell command to execute */
492 char *binary = "/bin/sh"; /* Default binary to execute */
493 char **env = environ; /* Default environment to pass */
494 char **todo = 0; /* Pointer to argument list */
495 char *who = 0; /* Who we're meant to become */
496 struct passwd *from_pw = 0; /* User we are right now */
497 struct passwd *to_pw = 0; /* User we want to become */
499 /* --- Become server setup parameters --- */
502 char *conffile = file_RULES; /* Default config file for daemon */
503 int port = 0; /* Default port for daemon */
506 /* --- Miscellanous shared variables --- */
508 unsigned flags = 0; /* Various useful flags */
509 int style = DEFAULT_LOGIN_STYLE; /* Login style */
510 gid_t group = -1; /* Default group to set */
511 int gstyle = g_unset; /* No group style set yet */
513 #ifdef HAVE_SETGROUPS
514 gid_t groups[NGROUPS_MAX]; /* Set of groups */
515 int ngroups; /* Number of groups in the set */
518 /* --- Default argument list executes a shell command --- */
520 static char *shell[] = {
521 "/bin/sh", /* Bourne shell */
522 "-c", /* Read from command line */
523 0, /* Pointer to shell command */
527 /* --- Definitions for the various flags --- */
530 f_daemon = 1, /* Start up in daemon mode */
531 f_duff = 2, /* Fault in arguments */
532 f_shell = 4, /* Run a default shell */
533 f_dummy = 8, /* Don't actually do anything */
534 f_setuid = 16, /* We're running setuid */
535 f_havegroup = 32 /* Set a default group */
538 /* --- Set up the program name --- */
542 if (getuid() != geteuid())
545 /* --- Read the environment into a hashtable --- */
550 sym_createTable(&bc__env);
551 for (p = environ; *p; p++)
552 bc__putenv(0, *p, 0, 0);
555 /* --- Parse some command line arguments --- */
559 static struct option opts[] = {
561 /* --- Asking for help --- */
563 { "help", 0, 0, 'h' },
564 { "usage", 0, 0, 'u' },
565 { "version", 0, 0, 'v' },
567 /* --- Login style options --- */
569 { "preserve-environment", 0, 0, 'e' },
571 { "set-user", 0, 0, 's' },
572 { "login", 0, 0, 'l' },
574 /* --- Group style options --- */
576 { "group", gFlag_argReq, 0, 'g' },
577 #ifdef HAVE_SETGROUPS
578 { "keep-groups", 0, 0, 'k' },
579 { "merge-groups", 0, 0, 'm' },
580 { "replace-groups", 0, 0, 'r' },
583 /* --- Command to run options --- */
585 { "command", gFlag_argReq, 0, 'c' },
587 /* --- Server options --- */
590 { "daemon", 0, 0, 'd' },
591 { "port", gFlag_argReq, 0, 'p' },
592 { "config-file", gFlag_argReq, 0, 'f' },
595 /* --- Tracing options --- */
598 { "impersonate", gFlag_argReq, 0, 'I' },
599 { "trace", gFlag_argOpt, 0, 'T' },
600 { "trace-level", gFlag_argOpt, 0, 'L' },
606 i = mdwopt(argc, argv,
607 "-" /* Return non-options as options */
608 "huv" /* Asking for help */
609 "esl" /* Login style options */
610 #ifdef HAVE_SETGROUPS
611 "g:kmr" /* Group style options */
613 "g:" /* Group (without @setgroups@) */
615 "c:" /* Command to run options */
617 "dp:f:" /* Server options */
620 "I:T::L::" /* Tracing options */
623 opts, 0, 0, gFlag_envVar);
629 /* --- Asking for help --- */
632 bc__help(stdout, flags & f_setuid);
644 /* --- Login style options --- */
656 /* --- Group style options --- */
659 if (isdigit((unsigned char)optarg[0]))
660 group = atoi(optarg);
662 struct group *gr = getgrnam(optarg);
664 die("unknown group `%s'", optarg);
667 flags |= f_havegroup;
680 /* --- Command to run options --- */
686 /* --- Server options --- */
690 if (isdigit((unsigned char)optarg[0]))
691 port = htons(atoi(optarg));
693 struct servent *s = getservbyname(optarg, "udp");
695 die("unknown service name `%s'", optarg);
707 /* --- Pretend to be a different user --- *
709 * There are all sorts of nasty implications for this option. Don't
710 * allow it if we're running setuid. Disable the actual login anyway.
716 if (flags & f_setuid)
717 moan("shan't allow impersonation while running setuid");
720 if (isdigit((unsigned char)optarg[0]))
721 pw = getpwuid(atoi(optarg));
723 pw = getpwnam(optarg);
725 die("can't impersonate unknown user `%s'", optarg);
726 from_pw = userdb_copyUser(pw);
727 rq.from = from_pw->pw_uid;
734 /* --- Tracing support --- *
736 * Be careful not to zap a file I wouldn't normally be allowed to write
745 if (optarg == 0 || strcmp(optarg, "-") == 0)
748 uid_t eu = geteuid(), ru = getuid();
751 if (setreuid(eu, ru))
756 die("couldn't temporarily give up privileges: %s",
760 if ((fp = fopen(optarg, "w")) == 0) {
761 die("couldn't open trace file `%s' for writing: %s",
762 optarg, strerror(errno));
766 if (setreuid(ru, eu))
770 die("couldn't regain privileges: %s", strerror(errno));
772 traceon(fp, TRACE_DFL);
773 trace(TRACE_MISC, "become: tracing enabled");
778 /* --- Setting trace levels --- */
784 unsigned int lvl = 0, l;
785 const char *p = optarg;
787 /* --- Table of tracing facilities --- */
795 static tr lvltbl[] = {
796 { 'm', TRACE_MISC, "miscellaneous messages" },
797 { 's', TRACE_SETUP, "building the request block" },
798 { 'r', TRACE_RULE, "ruleset scanning" },
799 { 'c', TRACE_CHECK, "request checking" },
801 { 'd', TRACE_DAEMON, "server process" },
802 { 'l', TRACE_CLIENT, "client process" },
803 { 'R', TRACE_RAND, "random number generator" },
804 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
806 { 'y', TRACE_YACC, "parsing configuration file" },
807 { 'D', TRACE_DFL, "default tracing options" },
808 { 'A', TRACE_ALL, "all tracing options" },
813 /* --- Output some help if there's no arguemnt --- */
821 for (tp = lvltbl; tp->l; tp++) {
822 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
823 printf("%c -- %s\n", tp->ch, tp->help);
827 "Also, `+' and `-' options are recognised to turn on and off various\n"
828 "tracing options. For example, `A-r' enables everything except ruleset\n"
829 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
841 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
844 if (flags & f_setuid)
847 lvl = sense ? (lvl | l) : (lvl & ~l);
849 moan("unknown trace option `%c'", *p);
855 yydebug = ((lvl & TRACE_YACC) != 0);
860 /* --- Something that wasn't an option --- *
862 * The following nasties are supported:
864 * * NAME=VALUE -- preserve NAME, and give it a VALUE
865 * * NAME= -- preserve NAME, and give it an empty value
866 * * NAME- -- delete NAME
867 * * NAME! -- preserve NAME with existing value
869 * Anything else is either the user name (which is OK) or the start of
870 * the command (in which case I stop and read the rest of the command).
874 size_t sz = strcspn(optarg, "=-!");
877 /* --- None of the above --- */
879 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
889 /* --- Do the appropriate thing --- */
891 switch (optarg[sz]) {
893 bc__putenv(0, optarg, envFlag_preserve, 1);
897 bc__putenv(optarg, 0, 0, 1);
901 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
902 e->f |= envFlag_preserve;
907 /* --- Something I didn't understand has occurred --- */
916 if (flags & f_duff) {
921 /* --- Switch to daemon mode if requested --- */
924 if (flags & f_daemon) {
925 T( trace(TRACE_MISC, "become: daemon mode requested"); )
926 daemon_init(conffile, port);
931 /* --- Open a syslog --- */
933 openlog(quis(), 0, LOG_AUTH);
935 /* --- Pick out the uid --- */
945 if (isdigit((unsigned char)who[0]))
946 pw = getpwuid(atoi(who));
950 die("unknown user `%s'", who);
951 to_pw = userdb_copyUser(pw);
955 /* --- Fill in the easy bits of the request --- */
961 pw = getpwuid(rq.from);
963 die("who are you? (can't find user %li)", (long)rq.from);
964 from_pw = userdb_copyUser(pw);
967 /* --- Find the local host address --- */
973 if ((he = gethostbyname(u.nodename)) == 0)
974 die("who am I? (can't resolve `%s')", u.nodename);
975 memcpy(&rq.host, he->h_addr, sizeof(rq.host));
978 /* --- Fiddle with group ownerships a bit --- */
981 #ifdef HAVE_SETGROUPS
982 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
986 /* --- Set the default login group, if there is one --- */
988 if (~flags & f_havegroup)
989 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
991 #ifndef HAVE_SETGROUPS
993 /* --- Check that it's valid --- */
995 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
996 die("invalid default group");
1000 /* --- Set the default group style --- */
1002 if (gstyle == g_unset)
1003 gstyle = (style == l_login) ? g_replace : g_merge;
1005 /* --- Read in my current set of groups --- */
1007 n_fgr = getgroups(NGROUPS_MAX, from_gr);
1009 /* --- Now read the groups for the target user --- *
1011 * Do this even if I'm using the @g_keep@ style -- I'll use it for
1012 * checking that @group@ is valid.
1020 to_gr[n_tgr++] = to_pw->pw_gid;
1023 while ((gr = getgrent()) != 0) {
1024 if (gr->gr_gid == to_gr[0])
1026 for (p = gr->gr_mem; *p; p++) {
1027 if (strcmp(to_pw->pw_name, *p) == 0) {
1028 to_gr[n_tgr++] = gr->gr_gid;
1029 if (n_tgr >= NGROUPS_MAX)
1040 /* --- Check that @group@ is reasonable --- */
1045 if (group == getgid() || group == from_pw->pw_gid)
1047 for (i = 0; i < n_fgr; i++) {
1048 if (group == from_gr[i])
1051 for (i = 0; i < n_tgr; i++) {
1052 if (group == to_gr[i])
1055 die("invalid default group");
1059 /* --- Phew. Now comes the hard bit --- */
1067 if (gstyle & g_keep) {
1069 ga[i++] = from_pw->pw_gid;
1071 if (gstyle & g_replace)
1072 ga[i++] = to_pw->pw_gid;
1074 /* --- Style authorities will become apoplectic if shown this --- *
1076 * As far as I can see, it's the neatest way of writing it.
1080 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1081 ((gstyle & g_keep) &&
1082 bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
1083 ((gstyle & g_replace) &&
1084 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1089 /* --- Trace the results of all this --- */
1091 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1093 #ifdef HAVE_SETGROUPS
1094 IF_TRACING(TRACE_SETUP, {
1097 for (i = 1; i < ngroups; i++)
1098 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1103 /* --- Shell commands are easy --- */
1110 /* --- A command given on the command line isn't too hard --- */
1112 else if (optind < argc) {
1113 todo = argv + optind;
1122 /* --- An unadorned becoming requires little work --- */
1125 shell[0] = getenv("SHELL");
1127 shell[0] = from_pw->pw_shell;
1133 /* --- An su-like login needs slightly less effort --- */
1136 shell[0] = to_pw->pw_shell;
1142 /* --- A login request needs a little bit of work --- */
1145 const char *p = strrchr(to_pw->pw_shell, '/');
1150 p = to_pw->pw_shell;
1151 shell[0] = xmalloc(strlen(p) + 2);
1153 strcpy(shell[0] + 1, p);
1156 binary = to_pw->pw_shell;
1161 /* --- Mangle the environment --- *
1163 * This keeps getting more complicated all the time. (How true. Now I've
1164 * got all sorts of nasty environment mangling to do.)
1166 * The environment stuff now happens in seven phases:
1168 * 1. Mark very special variables to be preserved. Currently only TERM
1169 * and DISPLAY are treated in this way.
1171 * 2. Set and preserve Become's own environment variables.
1173 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1174 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1176 * 4. If we're preserving the environment or being `su'-like, process the
1177 * PATH variable a little. Otherwise reset it to something
1180 * 5. If we're being `login'-like, expunge all unpreserved variables.
1182 * 6. Expunge any security-critical variables.
1184 * 7. Build a new environment table to pass to child processes.
1188 /* --- Variables to be preserved always --- *
1190 * A user can explicitly expunge a variable in this list, in which case
1191 * we never get to see it here.
1194 static char *preserve[] = {
1195 "TERM", "DISPLAY", "TZ", 0
1198 /* --- Variables to be expunged --- *
1200 * Any environment string which has one of the following as a prefix will
1201 * be expunged from the environment passed to the called process. The
1202 * first line lists variables which have been used to list search paths
1203 * for shared libraries: by manipulating these, an attacker could replace
1204 * a standard library with one of his own. The second line lists other
1205 * well-known dangerous environment variables.
1208 static char *banned[] = {
1209 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1210 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1214 /* --- Other useful variables --- */
1223 /* --- Stage one. Preserve display-specific variables --- */
1225 for (pp = preserve; *pp; pp++) {
1226 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1227 e->f |= envFlag_preserve;
1230 /* --- Stage two. Set Become's own variables --- */
1232 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1234 bc__setenv(e, from_pw->pw_name);
1235 e->f |= envFlag_preserve;
1237 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1239 bc__setenv(e, from_pw->pw_dir);
1240 e->f |= envFlag_preserve;
1242 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1243 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1244 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1245 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1247 /* --- Stage three. Set user identity --- */
1251 static char *maildirs[] = {
1252 "/var/spool/mail", "/var/mail",
1253 "/usr/spool/mail", "/usr/mail",
1259 for (pp = maildirs; *pp; pp++) {
1260 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1261 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1262 bc__putenv("MAIL", b, envFlag_preserve, 0);
1266 } /* Fall through */
1269 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1270 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1271 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1272 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1276 /* --- Stage four. Set the user's PATH properly --- */
1279 /* --- Find an existing path --- *
1281 * If there's no path, or this is a login, then set a default path,
1282 * unless we're meant to preserve the existing one. Whew!
1285 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1287 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1289 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1290 envFlag_preserve, 0);
1293 /* --- Find the string --- */
1295 e->f = envFlag_preserve;
1296 p = strchr(e->val, '=') + 1;
1299 /* --- Write the new version to a dynamically allocated buffer --- */
1301 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1302 strcpy(e->val, "PATH=");
1305 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1320 /* --- Stages five and six. Expunge variables and count numbers --- *
1322 * Folded together, so I only need one pass through the table. Also
1323 * count the number of variables needed at this time.
1328 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1330 /* --- Login style expunges all unpreserved variables --- */
1332 if (style == l_login && ~e->f & envFlag_preserve)
1335 /* --- Otherwise just check the name against the list --- */
1337 for (pp = banned; *pp; pp++) {
1340 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1342 } else if (strcmp(e->_base.name, *pp) == 0)
1350 sym_remove(&bc__env, e);
1353 /* --- Stage seven. Build the new environment block --- */
1355 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1357 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1362 /* --- Trace the command --- */
1364 IF_TRACING(TRACE_SETUP, {
1367 trace(TRACE_SETUP, "setup: from user %s to user %s",
1368 from_pw->pw_name, to_pw->pw_name);
1369 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1370 for (i = 0; todo[i]; i++)
1371 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1372 for (i = 0; env[i]; i++)
1373 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1376 /* --- If necessary, resolve the path to the command --- */
1378 if (!strchr(binary, '/')) {
1382 if ((p = getenv("PATH")) == 0)
1383 p = "/bin:/usr/bin";
1386 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1388 /* --- Check length of string before copying --- */
1390 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1393 /* --- Now build the pathname and check it --- *
1395 * Issue: user can take advantage of these privileges to decide whether
1396 * a program with a given name exists. I'm not sure that's
1397 * particularly significant: it only works on regular files with
1398 * execute permissions, and if you're relying on the names of these
1399 * being secret to keep your security up, then you're doing something
1400 * deeply wrong anyway. On the other hand, it's useful to allow people
1401 * to be able to execute programs and scripts which they wouldn't
1402 * otherwise have access to. [This problem was brought up on
1403 * Bugtraq, as a complaint against sudo.]
1406 sprintf(rq.cmd, "%s/%s", p, binary);
1407 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1408 st.st_mode & 0111 && /* Check it's executable */
1409 S_ISREG(st.st_mode)) /* Check it's a file */
1414 die("couldn't find `%s' in path", todo[0]);
1418 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1420 /* --- Canonicalise the path string, if necessary --- */
1427 /* --- Insert current directory name if path not absolute --- */
1432 if (!getcwd(b, sizeof(b)))
1433 die("couldn't read current directory: %s", strerror(errno));
1438 /* --- Now copy over characters from the path string --- */
1442 /* --- Check for buffer overflows here --- *
1444 * I write at most one byte per iteration so this is OK. Remember to
1445 * allow one for the null byte.
1448 if (p >= b + sizeof(b) - 1)
1449 die("internal error: buffer overflow while canonifying path");
1451 /* --- Reduce multiple slashes to just one --- */
1459 /* --- Handle dots in filenames --- *
1461 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1462 * we've just stuck the current directory on the end of the buffer,
1463 * or we've just put something else on the end.
1466 else if (*q == '.' && p[-1] == '/') {
1468 /* --- A simple `./' just gets removed --- */
1470 if (q[1] == 0 || q[1] == '/') {
1476 /* --- A `../' needs to be peeled back to the previous `/' --- */
1478 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1481 while (p > b && p[-1] != '/')
1494 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1496 /* --- Run the check --- *
1498 * If the user is already what she wants to be, then print a warning.
1499 * Then, if I was just going to spawn a shell, quit, to reduce user
1500 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1501 * checking if we're already root -- root can do anything anyway, and at
1502 * least this way we get some logging done, and offer a more friendly
1506 if (rq.from == rq.to) {
1507 moan("you already are `%s'!", to_pw->pw_name);
1508 if (flags & f_shell) {
1509 moan("(to prevent confusion, I'm not spawning a shell)");
1513 int a = (rq.from == 0) || check(&rq);
1516 "permission %s for %s to become %s to run `%s'",
1517 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1521 die("permission denied");
1524 /* --- Now do the job --- */
1526 T( trace(TRACE_MISC, "become: permission granted"); )
1528 if (flags & f_dummy) {
1529 puts("permission granted");
1533 #ifdef HAVE_SETGROUPS
1534 if (setgroups(ngroups, groups) < 0)
1535 die("couldn't set groups: %s", strerror(errno));
1538 if (setgid(group) < 0)
1539 die("couldn't set default group: %s", strerror(errno));
1540 if (setuid(rq.to) < 0)
1541 die("couldn't set uid: %s", strerror(errno));
1543 /* --- If this was a login, change current directory --- */
1545 if ((flags & f_shell) &&
1547 chdir(to_pw->pw_dir) < 0) {
1548 moan("couldn't change directory to `%s': %s",
1549 to_pw->pw_dir, strerror(errno));
1552 /* --- Finally, call the program --- */
1556 execve(rq.cmd, todo, env);
1557 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1561 /*----- That's all, folks -------------------------------------------------*/