3 * $Id: become.c,v 1.11.2.1 1997/09/26 09:07:58 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.11.2.1 1997/09/26 09:07:58 mdw
33 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
34 * because I prefer Blowfish (without any particularly strong evidence) but
35 * mainly because IDEA is patented and Blowfish isn't.
37 * Revision 1.12 1997/09/25 16:04:48 mdw
38 * Change directory after becoming someone else, instead of before. This
39 * avoids problems with root-squashed NFS mounts.
41 * Revision 1.11 1997/09/24 09:48:45 mdw
42 * Fix (scary) overrun bug in group allocation stuff.
44 * Revision 1.10 1997/09/17 10:14:10 mdw
45 * Fix a typo. Support service names in `--port' option.
47 * Revision 1.9 1997/09/10 10:28:05 mdw
48 * Allow default port to be given as a service name or port number. Handle
49 * groups properly (lots of options here).
51 * Revision 1.8 1997/09/08 13:56:24 mdw
52 * Change criteria for expunging items from the user's PATH: instead of
53 * removing things starting with `.', remove things not starting with `/'.
55 * Revision 1.7 1997/09/08 13:43:20 mdw
56 * Change userid when creating tracefiles rather than fiddling with
57 * `access': it works rather better. Also, insert some stdio buffer
58 * flushing to ensure tracedumps are completely written.
60 * Revision 1.6 1997/09/05 13:47:44 mdw
61 * Make the `-L' (trace-level) option's argument optional, like the long
64 * Revision 1.5 1997/09/05 11:45:19 mdw
65 * Add support for different login styles, and environment variable
66 * manipulation in a safe and useful way.
68 * Revision 1.4 1997/08/20 16:15:13 mdw
69 * Overhaul of environment handling. Fix daft bug in path search code.
71 * Revision 1.3 1997/08/07 16:28:59 mdw
72 * Do something useful when users attempt to become themselves.
74 * Revision 1.2 1997/08/04 10:24:20 mdw
75 * Sources placed under CVS control.
77 * Revision 1.1 1997/07/21 13:47:54 mdw
82 /*----- Header files ------------------------------------------------------*/
84 /* --- ANSI headers --- */
94 /* --- Unix headers --- */
96 #include <sys/types.h>
98 #include <sys/socket.h>
99 #include <sys/utsname.h>
101 #include <netinet/in.h>
103 #include <arpa/inet.h>
111 extern char **environ;
113 /* --- Local headers --- */
128 /*----- Type definitions --------------------------------------------------*/
130 /* --- Symbol table entry for an environment variable --- */
132 typedef struct sym_env {
133 sym_base _base; /* Symbol table information */
134 unsigned f; /* Flags word (see below) */
135 char *val; /* Pointer to variable value */
138 /* --- Environment variable flags --- */
144 /* --- Login behaviour types --- */
147 l_preserve, /* Preserve the environment */
148 l_setuser, /* Update who I am */
149 l_login /* Do a full login */
152 /* --- Group behaviour types --- *
154 * Note that these make a handy bitfield.
157 #ifdef HAVE_SETGROUPS
160 g_unset = 0, /* Nobody's set a preference */
161 g_keep = 1, /* Leave the group memberships */
162 g_replace = 2, /* Replace group memberships */
163 g_merge = (g_keep | g_replace) /* Merge the group memberships */
168 /*----- Static variables --------------------------------------------------*/
170 static sym_table bc__env;
172 /*----- Main code ---------------------------------------------------------*/
174 /* --- @bc__write@ --- *
176 * Arguments: @FILE *fp@ = pointer to a stream to write on
177 * @const char *p@ = pointer to a string
181 * Use: Writes the string to the stream, substituting the program
182 * name (as returned by @quis@) for each occurrence of the
186 static void bc__write(FILE *fp, const char *p)
188 const char *n = quis();
190 size_t nl = strlen(n);
192 /* --- Try to be a little efficient --- *
194 * Gather up non-`$' characters using @strcspn@ and spew them out really
205 fwrite(n, nl, 1, fp);
210 /* --- @bc__setenv@ --- *
212 * Arguments: @sym_env *e@ = pointer to environment variable block
213 * @const char *val@ = value to set
217 * Use: Sets an environment variable block to the right value.
220 static void bc__setenv(sym_env *e, const char *val)
222 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
223 sprintf(e->val, "%s=%s", e->_base.name, val);
226 /* --- @bc__putenv@ --- *
228 * Arguments: @const char *var@ = name of the variable to set, or 0 if
229 * this is embedded in the value string
230 * @const char *val@ = value to set, or 0 if the variable must
232 * @unsigned int fl@ = flags to set
233 * @unsigned int force@ = force overwrite of preserved variables
235 * Returns: Pointer to symbol block, or zero if it was deleted.
237 * Use: Puts an item into the environment.
240 static sym_env *bc__putenv(const char *var, const char *val,
241 unsigned int fl, unsigned int force)
248 /* --- Sort out embedded variable names --- */
251 const char *p = strchr(val, '=');
266 /* --- Find the variable block --- */
269 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
270 if (!f || ~e->f & envFlag_preserve || force) {
276 e = sym_find(&bc__env, var, -1, 0, 0);
277 if (e && (force || ~e->f & envFlag_preserve))
278 sym_remove(&bc__env, e);
282 /* --- Tidy up and return --- */
291 /* --- @bc__addGroups@ --- *
293 * Arguments: @gid_t *g@ = pointer to a group array
294 * @int *png@ = pointer to number of entries in the array
295 * @const gid_t *a@ = pointer to groups to add
296 * @int na@ = number of groups to add
298 * Returns: Zero if it was OK, nonzero if we should stop now.
300 * Use: Adds groups to a groups array.
303 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
308 for (i = 0; i < na; i++) {
310 /* --- Ensure this group isn't already in the list --- */
312 for (j = 0; j < ng; j++) {
317 /* --- See if there's room for more --- */
319 if (ng >= NGROUPS_MAX) {
320 moan("too many groups (system limit exceeded) -- some have been lost");
325 /* --- Add the group --- */
335 /* --- @bc__banner@ --- *
337 * Arguments: @FILE *fp@ = stream to write on
341 * Use: Writes a banner containing copyright information.
344 static void bc__banner(FILE *fp)
346 bc__write(fp, "$ version " VERSION "\n");
349 /* --- @bc__usage@ --- *
351 * Arguments: @FILE *fp@ = stream to write on
355 * Use: Writes a terse reminder of command line syntax.
358 static void bc__usage(FILE *fp)
362 " $ -c <shell-command> <user>\n"
363 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
364 " $ -d [-p <port>] [-f <config-file>]\n");
367 /* --- @bc__help@ --- *
369 * Arguments: @FILE *fp@ = stream to write on
370 * @int suid@ = whether we're running set-uid
374 * Use: Displays a help message for this excellent piece of software.
377 static void bc__help(FILE *fp, int suid)
384 "The `$' program allows you to run a process as another user.\n"
385 "If a command name is given, this is the process executed. If the `-c'\n"
386 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
387 "given, your default login shell is used.\n"
389 "Your user id, the user id you wish to become, the name of the process\n"
390 "you wish to run, and the identity of the current host are looked up to\n"
391 "ensure that you have permission to do this.\n"
393 "Note that logs are kept of all uses of this program.\n"
395 "Options available are:\n"
397 "-h, --help Display this help text\n"
398 "-u, --usage Display a short usage summary\n"
399 "-v, --version Display $'s version number\n"
401 "-e, --preserve-environment Try to preserve the current environment\n"
402 "-s, --su, --set-user Set environment variables to reflect USER\n"
403 "-l, --login Really log in as USER\n"
405 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
406 #ifdef HAVE_SETGROUPS
407 "-k, --keep-groups Keep your current set of groups\n"
408 "-m, --merge-groups Merge the lists of groups\n"
409 "-r, --replace-groups Replace the list of groups\n"
412 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
414 "-d, --daemon Start a daemon\n"
415 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
416 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
421 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
424 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
425 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
431 * Arguments: @int argc@ = number of command line arguments
432 * @char *argv[]@ = pointer to the various arguments
434 * Returns: Zero if successful.
436 * Use: Allows a user to change UID.
439 int main(int argc, char *argv[])
441 /* --- Request block setup parameters --- */
443 request rq; /* Request buffer to build */
444 char *cmd = 0; /* Shell command to execute */
445 char *binary = "/bin/sh"; /* Default binary to execute */
446 char **env = environ; /* Default environment to pass */
447 char **todo = 0; /* Pointer to argument list */
448 char *who = 0; /* Who we're meant to become */
449 struct passwd *from_pw = 0; /* User we are right now */
450 struct passwd *to_pw = 0; /* User we want to become */
452 /* --- Become server setup parameters --- */
454 char *conffile = file_RULES; /* Default config file for daemon */
455 int port = 0; /* Default port for daemon */
457 /* --- Miscellanous shared variables --- */
459 unsigned flags = 0; /* Various useful flags */
460 int style = DEFAULT_LOGIN_STYLE; /* Login style */
461 gid_t group = -1; /* Default group to set */
462 int gstyle = g_unset; /* No group style set yet */
464 #ifdef HAVE_SETGROUPS
465 gid_t groups[NGROUPS_MAX]; /* Set of groups */
466 int ngroups; /* Number of groups in the set */
469 /* --- Default argument list executes a shell command --- */
471 static char *shell[] = {
472 "/bin/sh", /* Bourne shell */
473 "-c", /* Read from command line */
474 0, /* Pointer to shell command */
478 /* --- Definitions for the various flags --- */
481 f_daemon = 1, /* Start up in daemon mode */
482 f_duff = 2, /* Fault in arguments */
483 f_shell = 4, /* Run a default shell */
484 f_dummy = 8, /* Don't actually do anything */
485 f_setuid = 16, /* We're running setuid */
486 f_havegroup = 32 /* Set a default group */
489 /* --- Set up the program name --- */
493 if (getuid() != geteuid())
496 /* --- Read the environment into a hashtable --- */
501 sym_createTable(&bc__env);
502 for (p = environ; *p; p++)
503 bc__putenv(0, *p, 0, 0);
506 /* --- Parse some command line arguments --- */
510 static struct option opts[] = {
512 /* --- Asking for help --- */
514 { "help", 0, 0, 'h' },
515 { "usage", 0, 0, 'u' },
516 { "version", 0, 0, 'v' },
518 /* --- Login style options --- */
520 { "preserve-environment", 0, 0, 'e' },
522 { "set-user", 0, 0, 's' },
523 { "login", 0, 0, 'l' },
525 /* --- Group style options --- */
527 { "group", gFlag_argReq, 0, 'g' },
528 #ifdef HAVE_SETGROUPS
529 { "keep-groups", 0, 0, 'k' },
530 { "merge-groups", 0, 0, 'm' },
531 { "replace-groups", 0, 0, 'r' },
534 /* --- Command to run options --- */
536 { "command", gFlag_argReq, 0, 'c' },
538 /* --- Server options --- */
540 { "daemon", 0, 0, 'd' },
541 { "port", gFlag_argReq, 0, 'p' },
542 { "config-file", gFlag_argReq, 0, 'f' },
544 /* --- Tracing options --- */
547 { "impersonate", gFlag_argReq, 0, 'I' },
548 { "trace", gFlag_argOpt, 0, 'T' },
549 { "trace-level", gFlag_argOpt, 0, 'L' },
555 i = mdwopt(argc, argv,
556 "-" /* Return non-options as options */
557 "huv" /* Asking for help */
558 "esl" /* Login style options */
559 #ifdef HAVE_SETGROUPS
560 "g:kmr" /* Group style options */
562 "g:" /* Group (without @setgroups@) */
564 "c:" /* Command to run options */
565 "dp:f:" /* Server options */
567 "I:T::L::" /* Tracing options */
570 opts, 0, 0, gFlag_envVar);
576 /* --- Asking for help --- */
579 bc__help(stdout, flags & f_setuid);
591 /* --- Login style options --- */
603 /* --- Group style options --- */
606 if (isdigit((unsigned char)optarg[0]))
607 group = atoi(optarg);
609 struct group *gr = getgrnam(optarg);
611 die("unknown group `%s'", optarg);
614 flags |= f_havegroup;
627 /* --- Command to run options --- */
633 /* --- Server options --- */
636 if (isdigit((unsigned char)optarg[0]))
637 port = htons(atoi(optarg));
639 struct servent *s = getservbyname(optarg, "udp");
641 die("unknown service name `%s'", optarg);
652 /* --- Pretend to be a different user --- *
654 * There are all sorts of nasty implications for this option. Don't
655 * allow it if we're running setuid. Disable the actual login anyway.
661 if (flags & f_setuid)
662 moan("shan't allow impersonation while running setuid");
665 if (isdigit((unsigned char)optarg[0]))
666 pw = getpwuid(atoi(optarg));
668 pw = getpwnam(optarg);
670 die("can't impersonate unknown user `%s'", optarg);
671 from_pw = userdb_copyUser(pw);
672 rq.from = from_pw->pw_uid;
679 /* --- Tracing support --- *
681 * Be careful not to zap a file I wouldn't normally be allowed to write
690 if (optarg == 0 || strcmp(optarg, "-") == 0)
693 uid_t eu = geteuid(), ru = getuid();
696 if (setreuid(eu, ru))
701 die("couldn't temporarily give up privileges: %s",
705 if ((fp = fopen(optarg, "w")) == 0) {
706 die("couldn't open trace file `%s' for writing: %s",
707 optarg, strerror(errno));
711 if (setreuid(ru, eu))
715 die("couldn't regain privileges: %s", strerror(errno));
717 traceon(fp, TRACE_DFL);
718 trace(TRACE_MISC, "become: tracing enabled");
723 /* --- Setting trace levels --- */
729 unsigned int lvl = 0, l;
730 const char *p = optarg;
732 /* --- Table of tracing facilities --- */
740 static tr lvltbl[] = {
741 { 'm', TRACE_MISC, "miscellaneous messages" },
742 { 's', TRACE_SETUP, "building the request block" },
743 { 'd', TRACE_DAEMON, "server process" },
744 { 'r', TRACE_RULE, "ruleset scanning" },
745 { 'c', TRACE_CHECK, "request checking" },
746 { 'l', TRACE_CLIENT, "client process" },
747 { 'R', TRACE_RAND, "random number generator" },
748 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
749 { 'y', TRACE_YACC, "parsing configuration file" },
750 { 'D', TRACE_DFL, "default tracing options" },
751 { 'A', TRACE_ALL, "all tracing options" },
756 /* --- Output some help if there's no arguemnt --- */
764 for (tp = lvltbl; tp->l; tp++) {
765 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
766 printf("%c -- %s\n", tp->ch, tp->help);
770 "Also, `+' and `-' options are recognised to turn on and off various\n"
771 "tracing options. For example, `A-r' enables everything except ruleset\n"
772 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
784 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
787 if (flags & f_setuid)
790 lvl = sense ? (lvl | l) : (lvl & ~l);
792 moan("unknown trace option `%c'", *p);
798 yydebug = ((lvl & TRACE_YACC) != 0);
803 /* --- Something that wasn't an option --- *
805 * The following nasties are supported:
807 * * NAME=VALUE -- preserve NAME, and give it a VALUE
808 * * NAME= -- preserve NAME, and give it an empty value
809 * * NAME- -- delete NAME
810 * * NAME! -- preserve NAME with existing value
812 * Anything else is either the user name (which is OK) or the start of
813 * the command (in which case I stop and read the rest of the command).
817 size_t sz = strcspn(optarg, "=-!");
820 /* --- None of the above --- */
822 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
832 /* --- Do the appropriate thing --- */
834 switch (optarg[sz]) {
836 bc__putenv(0, optarg, envFlag_preserve, 1);
840 bc__putenv(optarg, 0, 0, 1);
844 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
845 e->f |= envFlag_preserve;
850 /* --- Something I didn't understand has occurred --- */
859 if (flags & f_duff) {
864 /* --- Switch to daemon mode if requested --- */
866 if (flags & f_daemon) {
867 T( trace(TRACE_MISC, "become: daemon mode requested"); )
868 daemon_init(conffile, port);
872 /* --- Open a syslog --- */
874 openlog(quis(), 0, LOG_AUTH);
876 /* --- Pick out the uid --- */
886 if (isdigit((unsigned char)who[0]))
887 pw = getpwuid(atoi(who));
891 die("unknown user `%s'", who);
892 to_pw = userdb_copyUser(pw);
896 /* --- Fill in the easy bits of the request --- */
902 pw = getpwuid(rq.from);
904 die("who are you? (can't find user %li)", (long)rq.from);
905 from_pw = userdb_copyUser(pw);
908 /* --- Find the local host address --- */
914 if ((he = gethostbyname(u.nodename)) == 0)
915 die("who am I? (can't resolve `%s')", u.nodename);
916 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
919 /* --- Fiddle with group ownerships a bit --- */
922 #ifdef HAVE_SETGROUPS
923 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
927 /* --- Set the default login group, if there is one --- */
929 if (~flags & f_havegroup)
930 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
932 #ifndef HAVE_SETGROUPS
934 /* --- Check that it's valid --- */
936 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
937 die("invalid default group");
941 /* --- Set the default group style --- */
943 if (gstyle == g_unset)
944 gstyle = (style == l_login) ? g_replace : g_merge;
946 /* --- Read in my current set of groups --- */
948 n_fgr = getgroups(NGROUPS_MAX, from_gr);
950 /* --- Now read the groups for the target user --- *
952 * Do this even if I'm using the @g_keep@ style -- I'll use it for
953 * checking that @group@ is valid.
961 to_gr[n_tgr++] = to_pw->pw_gid;
964 while ((gr = getgrent()) != 0) {
965 if (gr->gr_gid == to_gr[0])
967 for (p = gr->gr_mem; *p; p++) {
968 if (strcmp(to_pw->pw_name, *p) == 0) {
969 to_gr[n_tgr++] = gr->gr_gid;
970 if (n_tgr >= NGROUPS_MAX)
981 /* --- Check that @group@ is reasonable --- */
986 if (group == getgid() || group == from_pw->pw_gid)
988 for (i = 0; i < n_fgr; i++) {
989 if (group == from_gr[i])
992 for (i = 0; i < n_tgr; i++) {
993 if (group == to_gr[i])
996 die("invalid default group");
1000 /* --- Phew. Now comes the hard bit --- */
1008 if (gstyle & g_keep) {
1010 ga[i++] = from_pw->pw_gid;
1012 if (gstyle & g_replace)
1013 ga[i++] = to_pw->pw_gid;
1015 /* --- Style authorities will become apoplectic if shown this --- *
1017 * As far as I can see, it's the neatest way of writing it.
1021 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1022 ((gstyle & g_keep) &&
1023 bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
1024 ((gstyle & g_replace) &&
1025 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1030 /* --- Trace the results of all this --- */
1032 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1034 #ifdef HAVE_SETGROUPS
1035 IF_TRACING(TRACE_SETUP, {
1038 for (i = 1; i < ngroups; i++)
1039 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1044 /* --- Shell commands are easy --- */
1051 /* --- A command given on the command line isn't too hard --- */
1053 else if (optind < argc) {
1054 todo = argv + optind;
1063 /* --- An unadorned becoming requires little work --- */
1066 shell[0] = getenv("SHELL");
1068 shell[0] = from_pw->pw_shell;
1074 /* --- An su-like login needs slightly less effort --- */
1077 shell[0] = to_pw->pw_shell;
1083 /* --- A login request needs a little bit of work --- */
1086 const char *p = strrchr(to_pw->pw_shell, '/');
1091 p = to_pw->pw_shell;
1092 shell[0] = xmalloc(strlen(p) + 2);
1094 strcpy(shell[0] + 1, p);
1097 binary = to_pw->pw_shell;
1102 /* --- Mangle the environment --- *
1104 * This keeps getting more complicated all the time. (How true. Now I've
1105 * got all sorts of nasty environment mangling to do.)
1107 * The environment stuff now happens in seven phases:
1109 * 1. Mark very special variables to be preserved. Currently only TERM
1110 * and DISPLAY are treated in this way.
1112 * 2. Set and preserve Become's own environment variables.
1114 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1115 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1117 * 4. If we're preserving the environment or being `su'-like, process the
1118 * PATH variable a little. Otherwise reset it to something
1121 * 5. If we're being `login'-like, expunge all unpreserved variables.
1123 * 6. Expunge any security-critical variables.
1125 * 7. Build a new environment table to pass to child processes.
1129 /* --- Variables to be preserved always --- *
1131 * A user can explicitly expunge a variable in this list, in which case
1132 * we never get to see it here.
1135 static char *preserve[] = {
1136 "TERM", "DISPLAY", 0
1139 /* --- Variables to be expunged --- *
1141 * Any environment string which has one of the following as a prefix will
1142 * be expunged from the environment passed to the called process. The
1143 * first line lists variables which have been used to list search paths
1144 * for shared libraries: by manipulating these, an attacker could replace
1145 * a standard library with one of his own. The second line lists other
1146 * well-known dangerous environment variables.
1149 static char *banned[] = {
1150 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1151 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1155 /* --- Other useful variables --- */
1164 /* --- Stage one. Preserve display-specific variables --- */
1166 for (pp = preserve; *pp; pp++) {
1167 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1168 e->f |= envFlag_preserve;
1171 /* --- Stage two. Set Become's own variables --- */
1173 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1175 bc__setenv(e, from_pw->pw_name);
1176 e->f |= envFlag_preserve;
1178 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1180 bc__setenv(e, from_pw->pw_dir);
1181 e->f |= envFlag_preserve;
1183 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1184 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1185 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1186 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1188 /* --- Stage three. Set user identity --- */
1192 static char *maildirs[] = {
1193 "/var/spool/mail", "/var/mail",
1194 "/usr/spool/mail", "/usr/mail",
1200 for (pp = maildirs; *pp; pp++) {
1201 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1202 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1203 bc__putenv("MAIL", b, envFlag_preserve, 0);
1207 } /* Fall through */
1210 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1211 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1212 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1213 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1217 /* --- Stage four. Set the user's PATH properly --- */
1220 /* --- Find an existing path --- *
1222 * If there's no path, or this is a login, then set a default path,
1223 * unless we're meant to preserve the existing one. Whew!
1226 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1228 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1230 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1231 envFlag_preserve, 0);
1234 /* --- Find the string --- */
1236 e->f = envFlag_preserve;
1237 p = strchr(e->val, '=') + 1;
1240 /* --- Write the new version to a dynamically allocated buffer --- */
1242 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1243 strcpy(e->val, "PATH=");
1246 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1261 /* --- Stages five and six. Expunge variables and count numbers --- *
1263 * Folded together, so I only need one pass through the table. Also
1264 * count the number of variables needed at this time.
1269 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1271 /* --- Login style expunges all unpreserved variables --- */
1273 if (style == l_login && ~e->f & envFlag_preserve)
1276 /* --- Otherwise just check the name against the list --- */
1278 for (pp = banned; *pp; pp++) {
1281 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1283 } else if (strcmp(e->_base.name, *pp) == 0)
1291 sym_remove(&bc__env, e);
1294 /* --- Stage seven. Build the new environment block --- */
1296 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1298 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1303 /* --- Trace the command --- */
1305 IF_TRACING(TRACE_SETUP, {
1308 trace(TRACE_SETUP, "setup: from user %s to user %s",
1309 from_pw->pw_name, to_pw->pw_name);
1310 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1311 for (i = 0; todo[i]; i++)
1312 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1313 for (i = 0; env[i]; i++)
1314 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1317 /* --- If necessary, resolve the path to the command --- */
1319 if (!strchr(binary, '/')) {
1323 if ((p = getenv("PATH")) == 0)
1324 p = "/bin:/usr/bin";
1327 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1329 /* --- Check length of string before copying --- */
1331 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1334 /* --- Now build the pathname and check it --- */
1336 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1337 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1338 st.st_mode & 0111 && /* Check it's executable */
1339 S_ISREG(st.st_mode)) /* Check it's a file */
1344 die("couldn't find `%s' in path", todo[0]);
1348 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1350 /* --- Canonicalise the path string, if necessary --- */
1357 /* --- Insert current directory name if path not absolute --- */
1362 if (!getcwd(b, sizeof(b)))
1363 die("couldn't read current directory: %s", strerror(errno));
1368 /* --- Now copy over characters from the path string --- */
1372 /* --- Check for buffer overflows here --- *
1374 * I write at most one byte per iteration so this is OK. Remember to
1375 * allow one for the null byte.
1378 if (p >= b + sizeof(b) - 1)
1379 die("internal error: buffer overflow while canonifying path");
1381 /* --- Reduce multiple slashes to just one --- */
1389 /* --- Handle dots in filenames --- *
1391 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1392 * we've just stuck the current directory on the end of the buffer,
1393 * or we've just put something else on the end.
1396 else if (*q == '.' && p[-1] == '/') {
1398 /* --- A simple `./' just gets removed --- */
1400 if (q[1] == 0 || q[1] == '/') {
1406 /* --- A `../' needs to be peeled back to the previous `/' --- */
1408 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1411 while (p > b && p[-1] != '/')
1424 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1426 /* --- Run the check --- *
1428 * If the user is already what she wants to be, then print a warning.
1429 * Then, if I was just going to spawn a shell, quit, to reduce user
1430 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1431 * checking if we're already root -- root can do anything anyway, and at
1432 * least this way we get some logging done, and offer a more friendly
1436 if (rq.from == rq.to) {
1437 moan("you already are `%s'!", to_pw->pw_name);
1438 if (flags & f_shell) {
1439 moan("(to prevent confusion, I'm not spawning a shell)");
1443 int a = (rq.from == 0) || check(&rq);
1446 "permission %s for %s to become %s to run `%s'",
1447 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1451 die("permission denied");
1454 /* --- Now do the job --- */
1456 T( trace(TRACE_MISC, "become: permission granted"); )
1458 if (flags & f_dummy) {
1459 puts("permission granted");
1463 #ifdef HAVE_SETGROUPS
1464 if (setgroups(ngroups, groups) < 0)
1465 die("couldn't set groups: %s", strerror(errno));
1468 if (setgid(group) < 0)
1469 die("couldn't set default group: %s", strerror(errno));
1470 if (setuid(rq.to) < 0)
1471 die("couldn't set uid: %s", strerror(errno));
1473 /* --- If this was a login, change current directory --- */
1475 if (flags & f_shell && style == l_login && chdir(to_pw->pw_dir) < 0) {
1476 moan("couldn't change directory to `%s': %s",
1477 to_pw->pw_dir, strerror(errno));
1480 /* --- Finally, call the program --- */
1483 execve(rq.cmd, todo, env);
1484 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1488 /*----- That's all, folks -------------------------------------------------*/