3 * $Id: become.c,v 1.16 1998/04/23 13:21:04 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.16 1998/04/23 13:21:04 mdw
33 * Small tweaks. Support no-network configuration option, and rearrange
34 * the help text a little.
36 * Revision 1.15 1998/01/13 11:10:44 mdw
37 * Add `TZ' to the list of variables to be preserved.
39 * Revision 1.14 1998/01/12 16:45:39 mdw
42 * Revision 1.13 1997/09/26 09:14:57 mdw
43 * Merged blowfish branch into trunk.
45 * Revision 1.12 1997/09/25 16:04:48 mdw
46 * Change directory after becoming someone else, instead of before. This
47 * avoids problems with root-squashed NFS mounts.
49 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
50 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
51 * because I prefer Blowfish (without any particularly strong evidence) but
52 * mainly because IDEA is patented and Blowfish isn't.
54 * Revision 1.11 1997/09/24 09:48:45 mdw
55 * Fix (scary) overrun bug in group allocation stuff.
57 * Revision 1.10 1997/09/17 10:14:10 mdw
58 * Fix a typo. Support service names in `--port' option.
60 * Revision 1.9 1997/09/10 10:28:05 mdw
61 * Allow default port to be given as a service name or port number. Handle
62 * groups properly (lots of options here).
64 * Revision 1.8 1997/09/08 13:56:24 mdw
65 * Change criteria for expunging items from the user's PATH: instead of
66 * removing things starting with `.', remove things not starting with `/'.
68 * Revision 1.7 1997/09/08 13:43:20 mdw
69 * Change userid when creating tracefiles rather than fiddling with
70 * `access': it works rather better. Also, insert some stdio buffer
71 * flushing to ensure tracedumps are completely written.
73 * Revision 1.6 1997/09/05 13:47:44 mdw
74 * Make the `-L' (trace-level) option's argument optional, like the long
77 * Revision 1.5 1997/09/05 11:45:19 mdw
78 * Add support for different login styles, and environment variable
79 * manipulation in a safe and useful way.
81 * Revision 1.4 1997/08/20 16:15:13 mdw
82 * Overhaul of environment handling. Fix daft bug in path search code.
84 * Revision 1.3 1997/08/07 16:28:59 mdw
85 * Do something useful when users attempt to become themselves.
87 * Revision 1.2 1997/08/04 10:24:20 mdw
88 * Sources placed under CVS control.
90 * Revision 1.1 1997/07/21 13:47:54 mdw
95 /*----- Header files ------------------------------------------------------*/
97 /* --- ANSI headers --- */
107 /* --- Unix headers --- */
109 #include <sys/types.h>
110 #include <sys/stat.h>
111 #include <sys/socket.h>
112 #include <sys/utsname.h>
114 #include <netinet/in.h>
116 #include <arpa/inet.h>
124 extern char **environ;
126 /* --- Local headers --- */
141 /*----- Type definitions --------------------------------------------------*/
143 /* --- Symbol table entry for an environment variable --- */
145 typedef struct sym_env {
146 sym_base _base; /* Symbol table information */
147 unsigned f; /* Flags word (see below) */
148 char *val; /* Pointer to variable value */
151 /* --- Environment variable flags --- */
157 /* --- Login behaviour types --- */
159 #define l_preserve 0 /* Preserve the environment */
160 #define l_setuser 1 /* Update who I am */
161 #define l_login 2 /* Do a full login */
163 /* --- Group behaviour types --- *
165 * Note that these make a handy bitfield.
168 #ifdef HAVE_SETGROUPS
171 g_unset = 0, /* Nobody's set a preference */
172 g_keep = 1, /* Leave the group memberships */
173 g_replace = 2, /* Replace group memberships */
174 g_merge = (g_keep | g_replace) /* Merge the group memberships */
179 /*----- Static variables --------------------------------------------------*/
181 static sym_table bc__env;
183 /*----- Main code ---------------------------------------------------------*/
185 /* --- @bc__write@ --- *
187 * Arguments: @FILE *fp@ = pointer to a stream to write on
188 * @const char *p@ = pointer to a string
192 * Use: Writes the string to the stream, substituting the program
193 * name (as returned by @quis@) for each occurrence of the
197 static void bc__write(FILE *fp, const char *p)
199 const char *n = quis();
201 size_t nl = strlen(n);
203 /* --- Try to be a little efficient --- *
205 * Gather up non-`$' characters using @strcspn@ and spew them out really
216 fwrite(n, nl, 1, fp);
221 /* --- @bc__setenv@ --- *
223 * Arguments: @sym_env *e@ = pointer to environment variable block
224 * @const char *val@ = value to set
228 * Use: Sets an environment variable block to the right value.
231 static void bc__setenv(sym_env *e, const char *val)
233 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
234 sprintf(e->val, "%s=%s", e->_base.name, val);
237 /* --- @bc__putenv@ --- *
239 * Arguments: @const char *var@ = name of the variable to set, or 0 if
240 * this is embedded in the value string
241 * @const char *val@ = value to set, or 0 if the variable must
243 * @unsigned int fl@ = flags to set
244 * @unsigned int force@ = force overwrite of preserved variables
246 * Returns: Pointer to symbol block, or zero if it was deleted.
248 * Use: Puts an item into the environment.
251 static sym_env *bc__putenv(const char *var, const char *val,
252 unsigned int fl, unsigned int force)
259 /* --- Sort out embedded variable names --- */
262 const char *p = strchr(val, '=');
277 /* --- Find the variable block --- */
280 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
281 if (!f || ~e->f & envFlag_preserve || force) {
287 e = sym_find(&bc__env, var, -1, 0, 0);
288 if (e && (force || ~e->f & envFlag_preserve))
289 sym_remove(&bc__env, e);
293 /* --- Tidy up and return --- */
302 /* --- @bc__addGroups@ --- *
304 * Arguments: @gid_t *g@ = pointer to a group array
305 * @int *png@ = pointer to number of entries in the array
306 * @const gid_t *a@ = pointer to groups to add
307 * @int na@ = number of groups to add
309 * Returns: Zero if it was OK, nonzero if we should stop now.
311 * Use: Adds groups to a groups array.
314 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
319 for (i = 0; i < na; i++) {
321 /* --- Ensure this group isn't already in the list --- */
323 for (j = 0; j < ng; j++) {
328 /* --- See if there's room for more --- */
330 if (ng >= NGROUPS_MAX) {
331 moan("too many groups (system limit exceeded) -- some have been lost");
336 /* --- Add the group --- */
346 /* --- @bc__banner@ --- *
348 * Arguments: @FILE *fp@ = stream to write on
352 * Use: Writes a banner containing copyright information.
355 static void bc__banner(FILE *fp)
357 bc__write(fp, "$ version " VERSION "\n");
360 /* --- @bc__usage@ --- *
362 * Arguments: @FILE *fp@ = stream to write on
366 * Use: Writes a terse reminder of command line syntax.
369 static void bc__usage(FILE *fp)
373 " $ -c <shell-command> <user>\n"
374 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
376 " $ -d [-p <port>] [-f <config-file>]\n"
381 /* --- @bc__help@ --- *
383 * Arguments: @FILE *fp@ = stream to write on
384 * @int suid@ = whether we're running set-uid
388 * Use: Displays a help message for this excellent piece of software.
391 static void bc__help(FILE *fp, int suid)
398 "The `$' program allows you to run a process as another user.\n"
399 "If a command name is given, this is the process executed. If the `-c'\n"
400 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
401 "given, your default login shell is used.\n"
403 "Your user id, the user id you wish to become, the name of the process\n"
404 "you wish to run, and the identity of the current host are looked up to\n"
405 "ensure that you have permission to do this.\n"
407 "Note that logs are kept of all uses of this program.\n"
409 "Options available are:\n"
411 "-h, --help Display this help text\n"
412 "-u, --usage Display a short usage summary\n"
413 "-v, --version Display $'s version number\n"
415 "-e, --preserve-environment Try to preserve the current environment\n"
416 "-s, --su, --set-user Set environment variables to reflect USER\n"
417 "-l, --login Really log in as USER\n"
419 #if DEFAULT_LOGIN_STYLE == l_preserve
420 "preserve-environment"
421 #elif DEFAULT_LOGIN_STYLE == l_setuser
423 #elif DEFAULT_LOGIN_STYLE == l_login
429 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
430 #ifdef HAVE_SETGROUPS
431 "-k, --keep-groups Keep your current set of groups\n"
432 "-m, --merge-groups Merge the lists of groups\n"
433 "-r, --replace-groups Replace the list of groups\n"
436 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
439 "-d, --daemon Start a daemon\n"
440 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
441 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
448 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
451 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
452 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
458 * Arguments: @int argc@ = number of command line arguments
459 * @char *argv[]@ = pointer to the various arguments
461 * Returns: Zero if successful.
463 * Use: Allows a user to change UID.
466 int main(int argc, char *argv[])
468 /* --- Request block setup parameters --- */
470 request rq; /* Request buffer to build */
471 char *cmd = 0; /* Shell command to execute */
472 char *binary = "/bin/sh"; /* Default binary to execute */
473 char **env = environ; /* Default environment to pass */
474 char **todo = 0; /* Pointer to argument list */
475 char *who = 0; /* Who we're meant to become */
476 struct passwd *from_pw = 0; /* User we are right now */
477 struct passwd *to_pw = 0; /* User we want to become */
479 /* --- Become server setup parameters --- */
482 char *conffile = file_RULES; /* Default config file for daemon */
483 int port = 0; /* Default port for daemon */
486 /* --- Miscellanous shared variables --- */
488 unsigned flags = 0; /* Various useful flags */
489 int style = DEFAULT_LOGIN_STYLE; /* Login style */
490 gid_t group = -1; /* Default group to set */
491 int gstyle = g_unset; /* No group style set yet */
493 #ifdef HAVE_SETGROUPS
494 gid_t groups[NGROUPS_MAX]; /* Set of groups */
495 int ngroups; /* Number of groups in the set */
498 /* --- Default argument list executes a shell command --- */
500 static char *shell[] = {
501 "/bin/sh", /* Bourne shell */
502 "-c", /* Read from command line */
503 0, /* Pointer to shell command */
507 /* --- Definitions for the various flags --- */
510 f_daemon = 1, /* Start up in daemon mode */
511 f_duff = 2, /* Fault in arguments */
512 f_shell = 4, /* Run a default shell */
513 f_dummy = 8, /* Don't actually do anything */
514 f_setuid = 16, /* We're running setuid */
515 f_havegroup = 32 /* Set a default group */
518 /* --- Set up the program name --- */
522 if (getuid() != geteuid())
525 /* --- Read the environment into a hashtable --- */
530 sym_createTable(&bc__env);
531 for (p = environ; *p; p++)
532 bc__putenv(0, *p, 0, 0);
535 /* --- Parse some command line arguments --- */
539 static struct option opts[] = {
541 /* --- Asking for help --- */
543 { "help", 0, 0, 'h' },
544 { "usage", 0, 0, 'u' },
545 { "version", 0, 0, 'v' },
547 /* --- Login style options --- */
549 { "preserve-environment", 0, 0, 'e' },
551 { "set-user", 0, 0, 's' },
552 { "login", 0, 0, 'l' },
554 /* --- Group style options --- */
556 { "group", gFlag_argReq, 0, 'g' },
557 #ifdef HAVE_SETGROUPS
558 { "keep-groups", 0, 0, 'k' },
559 { "merge-groups", 0, 0, 'm' },
560 { "replace-groups", 0, 0, 'r' },
563 /* --- Command to run options --- */
565 { "command", gFlag_argReq, 0, 'c' },
567 /* --- Server options --- */
570 { "daemon", 0, 0, 'd' },
571 { "port", gFlag_argReq, 0, 'p' },
572 { "config-file", gFlag_argReq, 0, 'f' },
575 /* --- Tracing options --- */
578 { "impersonate", gFlag_argReq, 0, 'I' },
579 { "trace", gFlag_argOpt, 0, 'T' },
580 { "trace-level", gFlag_argOpt, 0, 'L' },
586 i = mdwopt(argc, argv,
587 "-" /* Return non-options as options */
588 "huv" /* Asking for help */
589 "esl" /* Login style options */
590 #ifdef HAVE_SETGROUPS
591 "g:kmr" /* Group style options */
593 "g:" /* Group (without @setgroups@) */
595 "c:" /* Command to run options */
597 "dp:f:" /* Server options */
600 "I:T::L::" /* Tracing options */
603 opts, 0, 0, gFlag_envVar);
609 /* --- Asking for help --- */
612 bc__help(stdout, flags & f_setuid);
624 /* --- Login style options --- */
636 /* --- Group style options --- */
639 if (isdigit((unsigned char)optarg[0]))
640 group = atoi(optarg);
642 struct group *gr = getgrnam(optarg);
644 die("unknown group `%s'", optarg);
647 flags |= f_havegroup;
660 /* --- Command to run options --- */
666 /* --- Server options --- */
670 if (isdigit((unsigned char)optarg[0]))
671 port = htons(atoi(optarg));
673 struct servent *s = getservbyname(optarg, "udp");
675 die("unknown service name `%s'", optarg);
687 /* --- Pretend to be a different user --- *
689 * There are all sorts of nasty implications for this option. Don't
690 * allow it if we're running setuid. Disable the actual login anyway.
696 if (flags & f_setuid)
697 moan("shan't allow impersonation while running setuid");
700 if (isdigit((unsigned char)optarg[0]))
701 pw = getpwuid(atoi(optarg));
703 pw = getpwnam(optarg);
705 die("can't impersonate unknown user `%s'", optarg);
706 from_pw = userdb_copyUser(pw);
707 rq.from = from_pw->pw_uid;
714 /* --- Tracing support --- *
716 * Be careful not to zap a file I wouldn't normally be allowed to write
725 if (optarg == 0 || strcmp(optarg, "-") == 0)
728 uid_t eu = geteuid(), ru = getuid();
731 if (setreuid(eu, ru))
736 die("couldn't temporarily give up privileges: %s",
740 if ((fp = fopen(optarg, "w")) == 0) {
741 die("couldn't open trace file `%s' for writing: %s",
742 optarg, strerror(errno));
746 if (setreuid(ru, eu))
750 die("couldn't regain privileges: %s", strerror(errno));
752 traceon(fp, TRACE_DFL);
753 trace(TRACE_MISC, "become: tracing enabled");
758 /* --- Setting trace levels --- */
764 unsigned int lvl = 0, l;
765 const char *p = optarg;
767 /* --- Table of tracing facilities --- */
775 static tr lvltbl[] = {
776 { 'm', TRACE_MISC, "miscellaneous messages" },
777 { 's', TRACE_SETUP, "building the request block" },
778 { 'r', TRACE_RULE, "ruleset scanning" },
779 { 'c', TRACE_CHECK, "request checking" },
781 { 'd', TRACE_DAEMON, "server process" },
782 { 'l', TRACE_CLIENT, "client process" },
783 { 'R', TRACE_RAND, "random number generator" },
784 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
786 { 'y', TRACE_YACC, "parsing configuration file" },
787 { 'D', TRACE_DFL, "default tracing options" },
788 { 'A', TRACE_ALL, "all tracing options" },
793 /* --- Output some help if there's no arguemnt --- */
801 for (tp = lvltbl; tp->l; tp++) {
802 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
803 printf("%c -- %s\n", tp->ch, tp->help);
807 "Also, `+' and `-' options are recognised to turn on and off various\n"
808 "tracing options. For example, `A-r' enables everything except ruleset\n"
809 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
821 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
824 if (flags & f_setuid)
827 lvl = sense ? (lvl | l) : (lvl & ~l);
829 moan("unknown trace option `%c'", *p);
835 yydebug = ((lvl & TRACE_YACC) != 0);
840 /* --- Something that wasn't an option --- *
842 * The following nasties are supported:
844 * * NAME=VALUE -- preserve NAME, and give it a VALUE
845 * * NAME= -- preserve NAME, and give it an empty value
846 * * NAME- -- delete NAME
847 * * NAME! -- preserve NAME with existing value
849 * Anything else is either the user name (which is OK) or the start of
850 * the command (in which case I stop and read the rest of the command).
854 size_t sz = strcspn(optarg, "=-!");
857 /* --- None of the above --- */
859 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
869 /* --- Do the appropriate thing --- */
871 switch (optarg[sz]) {
873 bc__putenv(0, optarg, envFlag_preserve, 1);
877 bc__putenv(optarg, 0, 0, 1);
881 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
882 e->f |= envFlag_preserve;
887 /* --- Something I didn't understand has occurred --- */
896 if (flags & f_duff) {
901 /* --- Switch to daemon mode if requested --- */
904 if (flags & f_daemon) {
905 T( trace(TRACE_MISC, "become: daemon mode requested"); )
906 daemon_init(conffile, port);
911 /* --- Open a syslog --- */
913 openlog(quis(), 0, LOG_AUTH);
915 /* --- Pick out the uid --- */
925 if (isdigit((unsigned char)who[0]))
926 pw = getpwuid(atoi(who));
930 die("unknown user `%s'", who);
931 to_pw = userdb_copyUser(pw);
935 /* --- Fill in the easy bits of the request --- */
941 pw = getpwuid(rq.from);
943 die("who are you? (can't find user %li)", (long)rq.from);
944 from_pw = userdb_copyUser(pw);
947 /* --- Find the local host address --- */
953 if ((he = gethostbyname(u.nodename)) == 0)
954 die("who am I? (can't resolve `%s')", u.nodename);
955 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
958 /* --- Fiddle with group ownerships a bit --- */
961 #ifdef HAVE_SETGROUPS
962 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
966 /* --- Set the default login group, if there is one --- */
968 if (~flags & f_havegroup)
969 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
971 #ifndef HAVE_SETGROUPS
973 /* --- Check that it's valid --- */
975 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
976 die("invalid default group");
980 /* --- Set the default group style --- */
982 if (gstyle == g_unset)
983 gstyle = (style == l_login) ? g_replace : g_merge;
985 /* --- Read in my current set of groups --- */
987 n_fgr = getgroups(NGROUPS_MAX, from_gr);
989 /* --- Now read the groups for the target user --- *
991 * Do this even if I'm using the @g_keep@ style -- I'll use it for
992 * checking that @group@ is valid.
1000 to_gr[n_tgr++] = to_pw->pw_gid;
1003 while ((gr = getgrent()) != 0) {
1004 if (gr->gr_gid == to_gr[0])
1006 for (p = gr->gr_mem; *p; p++) {
1007 if (strcmp(to_pw->pw_name, *p) == 0) {
1008 to_gr[n_tgr++] = gr->gr_gid;
1009 if (n_tgr >= NGROUPS_MAX)
1020 /* --- Check that @group@ is reasonable --- */
1025 if (group == getgid() || group == from_pw->pw_gid)
1027 for (i = 0; i < n_fgr; i++) {
1028 if (group == from_gr[i])
1031 for (i = 0; i < n_tgr; i++) {
1032 if (group == to_gr[i])
1035 die("invalid default group");
1039 /* --- Phew. Now comes the hard bit --- */
1047 if (gstyle & g_keep) {
1049 ga[i++] = from_pw->pw_gid;
1051 if (gstyle & g_replace)
1052 ga[i++] = to_pw->pw_gid;
1054 /* --- Style authorities will become apoplectic if shown this --- *
1056 * As far as I can see, it's the neatest way of writing it.
1060 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1061 ((gstyle & g_keep) &&
1062 bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
1063 ((gstyle & g_replace) &&
1064 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1069 /* --- Trace the results of all this --- */
1071 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1073 #ifdef HAVE_SETGROUPS
1074 IF_TRACING(TRACE_SETUP, {
1077 for (i = 1; i < ngroups; i++)
1078 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1083 /* --- Shell commands are easy --- */
1090 /* --- A command given on the command line isn't too hard --- */
1092 else if (optind < argc) {
1093 todo = argv + optind;
1102 /* --- An unadorned becoming requires little work --- */
1105 shell[0] = getenv("SHELL");
1107 shell[0] = from_pw->pw_shell;
1113 /* --- An su-like login needs slightly less effort --- */
1116 shell[0] = to_pw->pw_shell;
1122 /* --- A login request needs a little bit of work --- */
1125 const char *p = strrchr(to_pw->pw_shell, '/');
1130 p = to_pw->pw_shell;
1131 shell[0] = xmalloc(strlen(p) + 2);
1133 strcpy(shell[0] + 1, p);
1136 binary = to_pw->pw_shell;
1141 /* --- Mangle the environment --- *
1143 * This keeps getting more complicated all the time. (How true. Now I've
1144 * got all sorts of nasty environment mangling to do.)
1146 * The environment stuff now happens in seven phases:
1148 * 1. Mark very special variables to be preserved. Currently only TERM
1149 * and DISPLAY are treated in this way.
1151 * 2. Set and preserve Become's own environment variables.
1153 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1154 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1156 * 4. If we're preserving the environment or being `su'-like, process the
1157 * PATH variable a little. Otherwise reset it to something
1160 * 5. If we're being `login'-like, expunge all unpreserved variables.
1162 * 6. Expunge any security-critical variables.
1164 * 7. Build a new environment table to pass to child processes.
1168 /* --- Variables to be preserved always --- *
1170 * A user can explicitly expunge a variable in this list, in which case
1171 * we never get to see it here.
1174 static char *preserve[] = {
1175 "TERM", "DISPLAY", "TZ", 0
1178 /* --- Variables to be expunged --- *
1180 * Any environment string which has one of the following as a prefix will
1181 * be expunged from the environment passed to the called process. The
1182 * first line lists variables which have been used to list search paths
1183 * for shared libraries: by manipulating these, an attacker could replace
1184 * a standard library with one of his own. The second line lists other
1185 * well-known dangerous environment variables.
1188 static char *banned[] = {
1189 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1190 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1194 /* --- Other useful variables --- */
1203 /* --- Stage one. Preserve display-specific variables --- */
1205 for (pp = preserve; *pp; pp++) {
1206 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1207 e->f |= envFlag_preserve;
1210 /* --- Stage two. Set Become's own variables --- */
1212 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1214 bc__setenv(e, from_pw->pw_name);
1215 e->f |= envFlag_preserve;
1217 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1219 bc__setenv(e, from_pw->pw_dir);
1220 e->f |= envFlag_preserve;
1222 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1223 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1224 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1225 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1227 /* --- Stage three. Set user identity --- */
1231 static char *maildirs[] = {
1232 "/var/spool/mail", "/var/mail",
1233 "/usr/spool/mail", "/usr/mail",
1239 for (pp = maildirs; *pp; pp++) {
1240 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1241 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1242 bc__putenv("MAIL", b, envFlag_preserve, 0);
1246 } /* Fall through */
1249 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1250 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1251 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1252 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1256 /* --- Stage four. Set the user's PATH properly --- */
1259 /* --- Find an existing path --- *
1261 * If there's no path, or this is a login, then set a default path,
1262 * unless we're meant to preserve the existing one. Whew!
1265 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1267 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1269 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1270 envFlag_preserve, 0);
1273 /* --- Find the string --- */
1275 e->f = envFlag_preserve;
1276 p = strchr(e->val, '=') + 1;
1279 /* --- Write the new version to a dynamically allocated buffer --- */
1281 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1282 strcpy(e->val, "PATH=");
1285 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1300 /* --- Stages five and six. Expunge variables and count numbers --- *
1302 * Folded together, so I only need one pass through the table. Also
1303 * count the number of variables needed at this time.
1308 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1310 /* --- Login style expunges all unpreserved variables --- */
1312 if (style == l_login && ~e->f & envFlag_preserve)
1315 /* --- Otherwise just check the name against the list --- */
1317 for (pp = banned; *pp; pp++) {
1320 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1322 } else if (strcmp(e->_base.name, *pp) == 0)
1330 sym_remove(&bc__env, e);
1333 /* --- Stage seven. Build the new environment block --- */
1335 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1337 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1342 /* --- Trace the command --- */
1344 IF_TRACING(TRACE_SETUP, {
1347 trace(TRACE_SETUP, "setup: from user %s to user %s",
1348 from_pw->pw_name, to_pw->pw_name);
1349 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1350 for (i = 0; todo[i]; i++)
1351 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1352 for (i = 0; env[i]; i++)
1353 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1356 /* --- If necessary, resolve the path to the command --- */
1358 if (!strchr(binary, '/')) {
1362 if ((p = getenv("PATH")) == 0)
1363 p = "/bin:/usr/bin";
1366 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1368 /* --- Check length of string before copying --- */
1370 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1373 /* --- Now build the pathname and check it --- */
1375 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1376 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1377 st.st_mode & 0111 && /* Check it's executable */
1378 S_ISREG(st.st_mode)) /* Check it's a file */
1383 die("couldn't find `%s' in path", todo[0]);
1387 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1389 /* --- Canonicalise the path string, if necessary --- */
1396 /* --- Insert current directory name if path not absolute --- */
1401 if (!getcwd(b, sizeof(b)))
1402 die("couldn't read current directory: %s", strerror(errno));
1407 /* --- Now copy over characters from the path string --- */
1411 /* --- Check for buffer overflows here --- *
1413 * I write at most one byte per iteration so this is OK. Remember to
1414 * allow one for the null byte.
1417 if (p >= b + sizeof(b) - 1)
1418 die("internal error: buffer overflow while canonifying path");
1420 /* --- Reduce multiple slashes to just one --- */
1428 /* --- Handle dots in filenames --- *
1430 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1431 * we've just stuck the current directory on the end of the buffer,
1432 * or we've just put something else on the end.
1435 else if (*q == '.' && p[-1] == '/') {
1437 /* --- A simple `./' just gets removed --- */
1439 if (q[1] == 0 || q[1] == '/') {
1445 /* --- A `../' needs to be peeled back to the previous `/' --- */
1447 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1450 while (p > b && p[-1] != '/')
1463 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1465 /* --- Run the check --- *
1467 * If the user is already what she wants to be, then print a warning.
1468 * Then, if I was just going to spawn a shell, quit, to reduce user
1469 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1470 * checking if we're already root -- root can do anything anyway, and at
1471 * least this way we get some logging done, and offer a more friendly
1475 if (rq.from == rq.to) {
1476 moan("you already are `%s'!", to_pw->pw_name);
1477 if (flags & f_shell) {
1478 moan("(to prevent confusion, I'm not spawning a shell)");
1482 int a = (rq.from == 0) || check(&rq);
1485 "permission %s for %s to become %s to run `%s'",
1486 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1490 die("permission denied");
1493 /* --- Now do the job --- */
1495 T( trace(TRACE_MISC, "become: permission granted"); )
1497 if (flags & f_dummy) {
1498 puts("permission granted");
1502 #ifdef HAVE_SETGROUPS
1503 if (setgroups(ngroups, groups) < 0)
1504 die("couldn't set groups: %s", strerror(errno));
1507 if (setgid(group) < 0)
1508 die("couldn't set default group: %s", strerror(errno));
1509 if (setuid(rq.to) < 0)
1510 die("couldn't set uid: %s", strerror(errno));
1512 /* --- If this was a login, change current directory --- */
1514 if ((flags & f_shell) &&
1516 chdir(to_pw->pw_dir) < 0) {
1517 moan("couldn't change directory to `%s': %s",
1518 to_pw->pw_dir, strerror(errno));
1521 /* --- Finally, call the program --- */
1524 execve(rq.cmd, todo, env);
1525 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1529 /*----- That's all, folks -------------------------------------------------*/