3 * $Id: become.c,v 1.14 1998/01/12 16:45:39 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.14 1998/01/12 16:45:39 mdw
35 * Revision 1.13 1997/09/26 09:14:57 mdw
36 * Merged blowfish branch into trunk.
38 * Revision 1.12 1997/09/25 16:04:48 mdw
39 * Change directory after becoming someone else, instead of before. This
40 * avoids problems with root-squashed NFS mounts.
42 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
43 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
44 * because I prefer Blowfish (without any particularly strong evidence) but
45 * mainly because IDEA is patented and Blowfish isn't.
47 * Revision 1.11 1997/09/24 09:48:45 mdw
48 * Fix (scary) overrun bug in group allocation stuff.
50 * Revision 1.10 1997/09/17 10:14:10 mdw
51 * Fix a typo. Support service names in `--port' option.
53 * Revision 1.9 1997/09/10 10:28:05 mdw
54 * Allow default port to be given as a service name or port number. Handle
55 * groups properly (lots of options here).
57 * Revision 1.8 1997/09/08 13:56:24 mdw
58 * Change criteria for expunging items from the user's PATH: instead of
59 * removing things starting with `.', remove things not starting with `/'.
61 * Revision 1.7 1997/09/08 13:43:20 mdw
62 * Change userid when creating tracefiles rather than fiddling with
63 * `access': it works rather better. Also, insert some stdio buffer
64 * flushing to ensure tracedumps are completely written.
66 * Revision 1.6 1997/09/05 13:47:44 mdw
67 * Make the `-L' (trace-level) option's argument optional, like the long
70 * Revision 1.5 1997/09/05 11:45:19 mdw
71 * Add support for different login styles, and environment variable
72 * manipulation in a safe and useful way.
74 * Revision 1.4 1997/08/20 16:15:13 mdw
75 * Overhaul of environment handling. Fix daft bug in path search code.
77 * Revision 1.3 1997/08/07 16:28:59 mdw
78 * Do something useful when users attempt to become themselves.
80 * Revision 1.2 1997/08/04 10:24:20 mdw
81 * Sources placed under CVS control.
83 * Revision 1.1 1997/07/21 13:47:54 mdw
88 /*----- Header files ------------------------------------------------------*/
90 /* --- ANSI headers --- */
100 /* --- Unix headers --- */
102 #include <sys/types.h>
103 #include <sys/stat.h>
104 #include <sys/socket.h>
105 #include <sys/utsname.h>
107 #include <netinet/in.h>
109 #include <arpa/inet.h>
117 extern char **environ;
119 /* --- Local headers --- */
134 /*----- Type definitions --------------------------------------------------*/
136 /* --- Symbol table entry for an environment variable --- */
138 typedef struct sym_env {
139 sym_base _base; /* Symbol table information */
140 unsigned f; /* Flags word (see below) */
141 char *val; /* Pointer to variable value */
144 /* --- Environment variable flags --- */
150 /* --- Login behaviour types --- */
153 l_preserve, /* Preserve the environment */
154 l_setuser, /* Update who I am */
155 l_login /* Do a full login */
158 /* --- Group behaviour types --- *
160 * Note that these make a handy bitfield.
163 #ifdef HAVE_SETGROUPS
166 g_unset = 0, /* Nobody's set a preference */
167 g_keep = 1, /* Leave the group memberships */
168 g_replace = 2, /* Replace group memberships */
169 g_merge = (g_keep | g_replace) /* Merge the group memberships */
174 /*----- Static variables --------------------------------------------------*/
176 static sym_table bc__env;
178 /*----- Main code ---------------------------------------------------------*/
180 /* --- @bc__write@ --- *
182 * Arguments: @FILE *fp@ = pointer to a stream to write on
183 * @const char *p@ = pointer to a string
187 * Use: Writes the string to the stream, substituting the program
188 * name (as returned by @quis@) for each occurrence of the
192 static void bc__write(FILE *fp, const char *p)
194 const char *n = quis();
196 size_t nl = strlen(n);
198 /* --- Try to be a little efficient --- *
200 * Gather up non-`$' characters using @strcspn@ and spew them out really
211 fwrite(n, nl, 1, fp);
216 /* --- @bc__setenv@ --- *
218 * Arguments: @sym_env *e@ = pointer to environment variable block
219 * @const char *val@ = value to set
223 * Use: Sets an environment variable block to the right value.
226 static void bc__setenv(sym_env *e, const char *val)
228 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
229 sprintf(e->val, "%s=%s", e->_base.name, val);
232 /* --- @bc__putenv@ --- *
234 * Arguments: @const char *var@ = name of the variable to set, or 0 if
235 * this is embedded in the value string
236 * @const char *val@ = value to set, or 0 if the variable must
238 * @unsigned int fl@ = flags to set
239 * @unsigned int force@ = force overwrite of preserved variables
241 * Returns: Pointer to symbol block, or zero if it was deleted.
243 * Use: Puts an item into the environment.
246 static sym_env *bc__putenv(const char *var, const char *val,
247 unsigned int fl, unsigned int force)
254 /* --- Sort out embedded variable names --- */
257 const char *p = strchr(val, '=');
272 /* --- Find the variable block --- */
275 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
276 if (!f || ~e->f & envFlag_preserve || force) {
282 e = sym_find(&bc__env, var, -1, 0, 0);
283 if (e && (force || ~e->f & envFlag_preserve))
284 sym_remove(&bc__env, e);
288 /* --- Tidy up and return --- */
297 /* --- @bc__addGroups@ --- *
299 * Arguments: @gid_t *g@ = pointer to a group array
300 * @int *png@ = pointer to number of entries in the array
301 * @const gid_t *a@ = pointer to groups to add
302 * @int na@ = number of groups to add
304 * Returns: Zero if it was OK, nonzero if we should stop now.
306 * Use: Adds groups to a groups array.
309 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
314 for (i = 0; i < na; i++) {
316 /* --- Ensure this group isn't already in the list --- */
318 for (j = 0; j < ng; j++) {
323 /* --- See if there's room for more --- */
325 if (ng >= NGROUPS_MAX) {
326 moan("too many groups (system limit exceeded) -- some have been lost");
331 /* --- Add the group --- */
341 /* --- @bc__banner@ --- *
343 * Arguments: @FILE *fp@ = stream to write on
347 * Use: Writes a banner containing copyright information.
350 static void bc__banner(FILE *fp)
352 bc__write(fp, "$ version " VERSION "\n");
355 /* --- @bc__usage@ --- *
357 * Arguments: @FILE *fp@ = stream to write on
361 * Use: Writes a terse reminder of command line syntax.
364 static void bc__usage(FILE *fp)
368 " $ -c <shell-command> <user>\n"
369 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
370 " $ -d [-p <port>] [-f <config-file>]\n");
373 /* --- @bc__help@ --- *
375 * Arguments: @FILE *fp@ = stream to write on
376 * @int suid@ = whether we're running set-uid
380 * Use: Displays a help message for this excellent piece of software.
383 static void bc__help(FILE *fp, int suid)
390 "The `$' program allows you to run a process as another user.\n"
391 "If a command name is given, this is the process executed. If the `-c'\n"
392 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
393 "given, your default login shell is used.\n"
395 "Your user id, the user id you wish to become, the name of the process\n"
396 "you wish to run, and the identity of the current host are looked up to\n"
397 "ensure that you have permission to do this.\n"
399 "Note that logs are kept of all uses of this program.\n"
401 "Options available are:\n"
403 "-h, --help Display this help text\n"
404 "-u, --usage Display a short usage summary\n"
405 "-v, --version Display $'s version number\n"
407 "-e, --preserve-environment Try to preserve the current environment\n"
408 "-s, --su, --set-user Set environment variables to reflect USER\n"
409 "-l, --login Really log in as USER\n"
411 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
412 #ifdef HAVE_SETGROUPS
413 "-k, --keep-groups Keep your current set of groups\n"
414 "-m, --merge-groups Merge the lists of groups\n"
415 "-r, --replace-groups Replace the list of groups\n"
418 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
420 "-d, --daemon Start a daemon\n"
421 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
422 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
427 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
430 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
431 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
437 * Arguments: @int argc@ = number of command line arguments
438 * @char *argv[]@ = pointer to the various arguments
440 * Returns: Zero if successful.
442 * Use: Allows a user to change UID.
445 int main(int argc, char *argv[])
447 /* --- Request block setup parameters --- */
449 request rq; /* Request buffer to build */
450 char *cmd = 0; /* Shell command to execute */
451 char *binary = "/bin/sh"; /* Default binary to execute */
452 char **env = environ; /* Default environment to pass */
453 char **todo = 0; /* Pointer to argument list */
454 char *who = 0; /* Who we're meant to become */
455 struct passwd *from_pw = 0; /* User we are right now */
456 struct passwd *to_pw = 0; /* User we want to become */
458 /* --- Become server setup parameters --- */
460 char *conffile = file_RULES; /* Default config file for daemon */
461 int port = 0; /* Default port for daemon */
463 /* --- Miscellanous shared variables --- */
465 unsigned flags = 0; /* Various useful flags */
466 int style = DEFAULT_LOGIN_STYLE; /* Login style */
467 gid_t group = -1; /* Default group to set */
468 int gstyle = g_unset; /* No group style set yet */
470 #ifdef HAVE_SETGROUPS
471 gid_t groups[NGROUPS_MAX]; /* Set of groups */
472 int ngroups; /* Number of groups in the set */
475 /* --- Default argument list executes a shell command --- */
477 static char *shell[] = {
478 "/bin/sh", /* Bourne shell */
479 "-c", /* Read from command line */
480 0, /* Pointer to shell command */
484 /* --- Definitions for the various flags --- */
487 f_daemon = 1, /* Start up in daemon mode */
488 f_duff = 2, /* Fault in arguments */
489 f_shell = 4, /* Run a default shell */
490 f_dummy = 8, /* Don't actually do anything */
491 f_setuid = 16, /* We're running setuid */
492 f_havegroup = 32 /* Set a default group */
495 /* --- Set up the program name --- */
499 if (getuid() != geteuid())
502 /* --- Read the environment into a hashtable --- */
507 sym_createTable(&bc__env);
508 for (p = environ; *p; p++)
509 bc__putenv(0, *p, 0, 0);
512 /* --- Parse some command line arguments --- */
516 static struct option opts[] = {
518 /* --- Asking for help --- */
520 { "help", 0, 0, 'h' },
521 { "usage", 0, 0, 'u' },
522 { "version", 0, 0, 'v' },
524 /* --- Login style options --- */
526 { "preserve-environment", 0, 0, 'e' },
528 { "set-user", 0, 0, 's' },
529 { "login", 0, 0, 'l' },
531 /* --- Group style options --- */
533 { "group", gFlag_argReq, 0, 'g' },
534 #ifdef HAVE_SETGROUPS
535 { "keep-groups", 0, 0, 'k' },
536 { "merge-groups", 0, 0, 'm' },
537 { "replace-groups", 0, 0, 'r' },
540 /* --- Command to run options --- */
542 { "command", gFlag_argReq, 0, 'c' },
544 /* --- Server options --- */
546 { "daemon", 0, 0, 'd' },
547 { "port", gFlag_argReq, 0, 'p' },
548 { "config-file", gFlag_argReq, 0, 'f' },
550 /* --- Tracing options --- */
553 { "impersonate", gFlag_argReq, 0, 'I' },
554 { "trace", gFlag_argOpt, 0, 'T' },
555 { "trace-level", gFlag_argOpt, 0, 'L' },
561 i = mdwopt(argc, argv,
562 "-" /* Return non-options as options */
563 "huv" /* Asking for help */
564 "esl" /* Login style options */
565 #ifdef HAVE_SETGROUPS
566 "g:kmr" /* Group style options */
568 "g:" /* Group (without @setgroups@) */
570 "c:" /* Command to run options */
571 "dp:f:" /* Server options */
573 "I:T::L::" /* Tracing options */
576 opts, 0, 0, gFlag_envVar);
582 /* --- Asking for help --- */
585 bc__help(stdout, flags & f_setuid);
597 /* --- Login style options --- */
609 /* --- Group style options --- */
612 if (isdigit((unsigned char)optarg[0]))
613 group = atoi(optarg);
615 struct group *gr = getgrnam(optarg);
617 die("unknown group `%s'", optarg);
620 flags |= f_havegroup;
633 /* --- Command to run options --- */
639 /* --- Server options --- */
642 if (isdigit((unsigned char)optarg[0]))
643 port = htons(atoi(optarg));
645 struct servent *s = getservbyname(optarg, "udp");
647 die("unknown service name `%s'", optarg);
658 /* --- Pretend to be a different user --- *
660 * There are all sorts of nasty implications for this option. Don't
661 * allow it if we're running setuid. Disable the actual login anyway.
667 if (flags & f_setuid)
668 moan("shan't allow impersonation while running setuid");
671 if (isdigit((unsigned char)optarg[0]))
672 pw = getpwuid(atoi(optarg));
674 pw = getpwnam(optarg);
676 die("can't impersonate unknown user `%s'", optarg);
677 from_pw = userdb_copyUser(pw);
678 rq.from = from_pw->pw_uid;
685 /* --- Tracing support --- *
687 * Be careful not to zap a file I wouldn't normally be allowed to write
696 if (optarg == 0 || strcmp(optarg, "-") == 0)
699 uid_t eu = geteuid(), ru = getuid();
702 if (setreuid(eu, ru))
707 die("couldn't temporarily give up privileges: %s",
711 if ((fp = fopen(optarg, "w")) == 0) {
712 die("couldn't open trace file `%s' for writing: %s",
713 optarg, strerror(errno));
717 if (setreuid(ru, eu))
721 die("couldn't regain privileges: %s", strerror(errno));
723 traceon(fp, TRACE_DFL);
724 trace(TRACE_MISC, "become: tracing enabled");
729 /* --- Setting trace levels --- */
735 unsigned int lvl = 0, l;
736 const char *p = optarg;
738 /* --- Table of tracing facilities --- */
746 static tr lvltbl[] = {
747 { 'm', TRACE_MISC, "miscellaneous messages" },
748 { 's', TRACE_SETUP, "building the request block" },
749 { 'd', TRACE_DAEMON, "server process" },
750 { 'r', TRACE_RULE, "ruleset scanning" },
751 { 'c', TRACE_CHECK, "request checking" },
752 { 'l', TRACE_CLIENT, "client process" },
753 { 'R', TRACE_RAND, "random number generator" },
754 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
755 { 'y', TRACE_YACC, "parsing configuration file" },
756 { 'D', TRACE_DFL, "default tracing options" },
757 { 'A', TRACE_ALL, "all tracing options" },
762 /* --- Output some help if there's no arguemnt --- */
770 for (tp = lvltbl; tp->l; tp++) {
771 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
772 printf("%c -- %s\n", tp->ch, tp->help);
776 "Also, `+' and `-' options are recognised to turn on and off various\n"
777 "tracing options. For example, `A-r' enables everything except ruleset\n"
778 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
790 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
793 if (flags & f_setuid)
796 lvl = sense ? (lvl | l) : (lvl & ~l);
798 moan("unknown trace option `%c'", *p);
804 yydebug = ((lvl & TRACE_YACC) != 0);
809 /* --- Something that wasn't an option --- *
811 * The following nasties are supported:
813 * * NAME=VALUE -- preserve NAME, and give it a VALUE
814 * * NAME= -- preserve NAME, and give it an empty value
815 * * NAME- -- delete NAME
816 * * NAME! -- preserve NAME with existing value
818 * Anything else is either the user name (which is OK) or the start of
819 * the command (in which case I stop and read the rest of the command).
823 size_t sz = strcspn(optarg, "=-!");
826 /* --- None of the above --- */
828 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
838 /* --- Do the appropriate thing --- */
840 switch (optarg[sz]) {
842 bc__putenv(0, optarg, envFlag_preserve, 1);
846 bc__putenv(optarg, 0, 0, 1);
850 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
851 e->f |= envFlag_preserve;
856 /* --- Something I didn't understand has occurred --- */
865 if (flags & f_duff) {
870 /* --- Switch to daemon mode if requested --- */
872 if (flags & f_daemon) {
873 T( trace(TRACE_MISC, "become: daemon mode requested"); )
874 daemon_init(conffile, port);
878 /* --- Open a syslog --- */
880 openlog(quis(), 0, LOG_AUTH);
882 /* --- Pick out the uid --- */
892 if (isdigit((unsigned char)who[0]))
893 pw = getpwuid(atoi(who));
897 die("unknown user `%s'", who);
898 to_pw = userdb_copyUser(pw);
902 /* --- Fill in the easy bits of the request --- */
908 pw = getpwuid(rq.from);
910 die("who are you? (can't find user %li)", (long)rq.from);
911 from_pw = userdb_copyUser(pw);
914 /* --- Find the local host address --- */
920 if ((he = gethostbyname(u.nodename)) == 0)
921 die("who am I? (can't resolve `%s')", u.nodename);
922 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
925 /* --- Fiddle with group ownerships a bit --- */
928 #ifdef HAVE_SETGROUPS
929 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
933 /* --- Set the default login group, if there is one --- */
935 if (~flags & f_havegroup)
936 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
938 #ifndef HAVE_SETGROUPS
940 /* --- Check that it's valid --- */
942 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
943 die("invalid default group");
947 /* --- Set the default group style --- */
949 if (gstyle == g_unset)
950 gstyle = (style == l_login) ? g_replace : g_merge;
952 /* --- Read in my current set of groups --- */
954 n_fgr = getgroups(NGROUPS_MAX, from_gr);
956 /* --- Now read the groups for the target user --- *
958 * Do this even if I'm using the @g_keep@ style -- I'll use it for
959 * checking that @group@ is valid.
967 to_gr[n_tgr++] = to_pw->pw_gid;
970 while ((gr = getgrent()) != 0) {
971 if (gr->gr_gid == to_gr[0])
973 for (p = gr->gr_mem; *p; p++) {
974 if (strcmp(to_pw->pw_name, *p) == 0) {
975 to_gr[n_tgr++] = gr->gr_gid;
976 if (n_tgr >= NGROUPS_MAX)
987 /* --- Check that @group@ is reasonable --- */
992 if (group == getgid() || group == from_pw->pw_gid)
994 for (i = 0; i < n_fgr; i++) {
995 if (group == from_gr[i])
998 for (i = 0; i < n_tgr; i++) {
999 if (group == to_gr[i])
1002 die("invalid default group");
1006 /* --- Phew. Now comes the hard bit --- */
1014 if (gstyle & g_keep) {
1016 ga[i++] = from_pw->pw_gid;
1018 if (gstyle & g_replace)
1019 ga[i++] = to_pw->pw_gid;
1021 /* --- Style authorities will become apoplectic if shown this --- *
1023 * As far as I can see, it's the neatest way of writing it.
1027 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1028 ((gstyle & g_keep) &&
1029 bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
1030 ((gstyle & g_replace) &&
1031 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1036 /* --- Trace the results of all this --- */
1038 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1040 #ifdef HAVE_SETGROUPS
1041 IF_TRACING(TRACE_SETUP, {
1044 for (i = 1; i < ngroups; i++)
1045 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1050 /* --- Shell commands are easy --- */
1057 /* --- A command given on the command line isn't too hard --- */
1059 else if (optind < argc) {
1060 todo = argv + optind;
1069 /* --- An unadorned becoming requires little work --- */
1072 shell[0] = getenv("SHELL");
1074 shell[0] = from_pw->pw_shell;
1080 /* --- An su-like login needs slightly less effort --- */
1083 shell[0] = to_pw->pw_shell;
1089 /* --- A login request needs a little bit of work --- */
1092 const char *p = strrchr(to_pw->pw_shell, '/');
1097 p = to_pw->pw_shell;
1098 shell[0] = xmalloc(strlen(p) + 2);
1100 strcpy(shell[0] + 1, p);
1103 binary = to_pw->pw_shell;
1108 /* --- Mangle the environment --- *
1110 * This keeps getting more complicated all the time. (How true. Now I've
1111 * got all sorts of nasty environment mangling to do.)
1113 * The environment stuff now happens in seven phases:
1115 * 1. Mark very special variables to be preserved. Currently only TERM
1116 * and DISPLAY are treated in this way.
1118 * 2. Set and preserve Become's own environment variables.
1120 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1121 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1123 * 4. If we're preserving the environment or being `su'-like, process the
1124 * PATH variable a little. Otherwise reset it to something
1127 * 5. If we're being `login'-like, expunge all unpreserved variables.
1129 * 6. Expunge any security-critical variables.
1131 * 7. Build a new environment table to pass to child processes.
1135 /* --- Variables to be preserved always --- *
1137 * A user can explicitly expunge a variable in this list, in which case
1138 * we never get to see it here.
1141 static char *preserve[] = {
1142 "TERM", "DISPLAY", 0
1145 /* --- Variables to be expunged --- *
1147 * Any environment string which has one of the following as a prefix will
1148 * be expunged from the environment passed to the called process. The
1149 * first line lists variables which have been used to list search paths
1150 * for shared libraries: by manipulating these, an attacker could replace
1151 * a standard library with one of his own. The second line lists other
1152 * well-known dangerous environment variables.
1155 static char *banned[] = {
1156 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1157 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1161 /* --- Other useful variables --- */
1170 /* --- Stage one. Preserve display-specific variables --- */
1172 for (pp = preserve; *pp; pp++) {
1173 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1174 e->f |= envFlag_preserve;
1177 /* --- Stage two. Set Become's own variables --- */
1179 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1181 bc__setenv(e, from_pw->pw_name);
1182 e->f |= envFlag_preserve;
1184 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1186 bc__setenv(e, from_pw->pw_dir);
1187 e->f |= envFlag_preserve;
1189 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1190 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1191 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1192 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1194 /* --- Stage three. Set user identity --- */
1198 static char *maildirs[] = {
1199 "/var/spool/mail", "/var/mail",
1200 "/usr/spool/mail", "/usr/mail",
1206 for (pp = maildirs; *pp; pp++) {
1207 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1208 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1209 bc__putenv("MAIL", b, envFlag_preserve, 0);
1213 } /* Fall through */
1216 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1217 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1218 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1219 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1223 /* --- Stage four. Set the user's PATH properly --- */
1226 /* --- Find an existing path --- *
1228 * If there's no path, or this is a login, then set a default path,
1229 * unless we're meant to preserve the existing one. Whew!
1232 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1234 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1236 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1237 envFlag_preserve, 0);
1240 /* --- Find the string --- */
1242 e->f = envFlag_preserve;
1243 p = strchr(e->val, '=') + 1;
1246 /* --- Write the new version to a dynamically allocated buffer --- */
1248 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1249 strcpy(e->val, "PATH=");
1252 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1267 /* --- Stages five and six. Expunge variables and count numbers --- *
1269 * Folded together, so I only need one pass through the table. Also
1270 * count the number of variables needed at this time.
1275 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1277 /* --- Login style expunges all unpreserved variables --- */
1279 if (style == l_login && ~e->f & envFlag_preserve)
1282 /* --- Otherwise just check the name against the list --- */
1284 for (pp = banned; *pp; pp++) {
1287 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1289 } else if (strcmp(e->_base.name, *pp) == 0)
1297 sym_remove(&bc__env, e);
1300 /* --- Stage seven. Build the new environment block --- */
1302 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1304 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1309 /* --- Trace the command --- */
1311 IF_TRACING(TRACE_SETUP, {
1314 trace(TRACE_SETUP, "setup: from user %s to user %s",
1315 from_pw->pw_name, to_pw->pw_name);
1316 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1317 for (i = 0; todo[i]; i++)
1318 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1319 for (i = 0; env[i]; i++)
1320 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1323 /* --- If necessary, resolve the path to the command --- */
1325 if (!strchr(binary, '/')) {
1329 if ((p = getenv("PATH")) == 0)
1330 p = "/bin:/usr/bin";
1333 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1335 /* --- Check length of string before copying --- */
1337 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1340 /* --- Now build the pathname and check it --- */
1342 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1343 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1344 st.st_mode & 0111 && /* Check it's executable */
1345 S_ISREG(st.st_mode)) /* Check it's a file */
1350 die("couldn't find `%s' in path", todo[0]);
1354 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1356 /* --- Canonicalise the path string, if necessary --- */
1363 /* --- Insert current directory name if path not absolute --- */
1368 if (!getcwd(b, sizeof(b)))
1369 die("couldn't read current directory: %s", strerror(errno));
1374 /* --- Now copy over characters from the path string --- */
1378 /* --- Check for buffer overflows here --- *
1380 * I write at most one byte per iteration so this is OK. Remember to
1381 * allow one for the null byte.
1384 if (p >= b + sizeof(b) - 1)
1385 die("internal error: buffer overflow while canonifying path");
1387 /* --- Reduce multiple slashes to just one --- */
1395 /* --- Handle dots in filenames --- *
1397 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1398 * we've just stuck the current directory on the end of the buffer,
1399 * or we've just put something else on the end.
1402 else if (*q == '.' && p[-1] == '/') {
1404 /* --- A simple `./' just gets removed --- */
1406 if (q[1] == 0 || q[1] == '/') {
1412 /* --- A `../' needs to be peeled back to the previous `/' --- */
1414 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1417 while (p > b && p[-1] != '/')
1430 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1432 /* --- Run the check --- *
1434 * If the user is already what she wants to be, then print a warning.
1435 * Then, if I was just going to spawn a shell, quit, to reduce user
1436 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1437 * checking if we're already root -- root can do anything anyway, and at
1438 * least this way we get some logging done, and offer a more friendly
1442 if (rq.from == rq.to) {
1443 moan("you already are `%s'!", to_pw->pw_name);
1444 if (flags & f_shell) {
1445 moan("(to prevent confusion, I'm not spawning a shell)");
1449 int a = (rq.from == 0) || check(&rq);
1452 "permission %s for %s to become %s to run `%s'",
1453 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1457 die("permission denied");
1460 /* --- Now do the job --- */
1462 T( trace(TRACE_MISC, "become: permission granted"); )
1464 if (flags & f_dummy) {
1465 puts("permission granted");
1469 #ifdef HAVE_SETGROUPS
1470 if (setgroups(ngroups, groups) < 0)
1471 die("couldn't set groups: %s", strerror(errno));
1474 if (setgid(group) < 0)
1475 die("couldn't set default group: %s", strerror(errno));
1476 if (setuid(rq.to) < 0)
1477 die("couldn't set uid: %s", strerror(errno));
1479 /* --- If this was a login, change current directory --- */
1481 if (flags & f_shell && style == l_login && chdir(to_pw->pw_dir) < 0) {
1482 moan("couldn't change directory to `%s': %s",
1483 to_pw->pw_dir, strerror(errno));
1486 /* --- Finally, call the program --- */
1489 execve(rq.cmd, todo, env);
1490 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1494 /*----- That's all, folks -------------------------------------------------*/