3 * $Id: become.c,v 1.12 1997/09/25 16:04:48 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.12 1997/09/25 16:04:48 mdw
33 * Change directory after becoming someone else, instead of before. This
34 * avoids problems with root-squashed NFS mounts.
36 * Revision 1.11 1997/09/24 09:48:45 mdw
37 * Fix (scary) overrun bug in group allocation stuff.
39 * Revision 1.10 1997/09/17 10:14:10 mdw
40 * Fix a typo. Support service names in `--port' option.
42 * Revision 1.9 1997/09/10 10:28:05 mdw
43 * Allow default port to be given as a service name or port number. Handle
44 * groups properly (lots of options here).
46 * Revision 1.8 1997/09/08 13:56:24 mdw
47 * Change criteria for expunging items from the user's PATH: instead of
48 * removing things starting with `.', remove things not starting with `/'.
50 * Revision 1.7 1997/09/08 13:43:20 mdw
51 * Change userid when creating tracefiles rather than fiddling with
52 * `access': it works rather better. Also, insert some stdio buffer
53 * flushing to ensure tracedumps are completely written.
55 * Revision 1.6 1997/09/05 13:47:44 mdw
56 * Make the `-L' (trace-level) option's argument optional, like the long
59 * Revision 1.5 1997/09/05 11:45:19 mdw
60 * Add support for different login styles, and environment variable
61 * manipulation in a safe and useful way.
63 * Revision 1.4 1997/08/20 16:15:13 mdw
64 * Overhaul of environment handling. Fix daft bug in path search code.
66 * Revision 1.3 1997/08/07 16:28:59 mdw
67 * Do something useful when users attempt to become themselves.
69 * Revision 1.2 1997/08/04 10:24:20 mdw
70 * Sources placed under CVS control.
72 * Revision 1.1 1997/07/21 13:47:54 mdw
77 /*----- Header files ------------------------------------------------------*/
79 /* --- ANSI headers --- */
89 /* --- Unix headers --- */
91 #include <sys/types.h>
93 #include <sys/socket.h>
94 #include <sys/utsname.h>
96 #include <netinet/in.h>
98 #include <arpa/inet.h>
106 extern char **environ;
108 /* --- Local headers --- */
123 /*----- Type definitions --------------------------------------------------*/
125 /* --- Symbol table entry for an environment variable --- */
127 typedef struct sym_env {
128 sym_base _base; /* Symbol table information */
129 unsigned f; /* Flags word (see below) */
130 char *val; /* Pointer to variable value */
133 /* --- Environment variable flags --- */
139 /* --- Login behaviour types --- */
142 l_preserve, /* Preserve the environment */
143 l_setuser, /* Update who I am */
144 l_login /* Do a full login */
147 /* --- Group behaviour types --- *
149 * Note that these make a handy bitfield.
152 #ifdef HAVE_SETGROUPS
155 g_unset = 0, /* Nobody's set a preference */
156 g_keep = 1, /* Leave the group memberships */
157 g_replace = 2, /* Replace group memberships */
158 g_merge = (g_keep | g_replace) /* Merge the group memberships */
163 /*----- Static variables --------------------------------------------------*/
165 static sym_table bc__env;
167 /*----- Main code ---------------------------------------------------------*/
169 /* --- @bc__write@ --- *
171 * Arguments: @FILE *fp@ = pointer to a stream to write on
172 * @const char *p@ = pointer to a string
176 * Use: Writes the string to the stream, substituting the program
177 * name (as returned by @quis@) for each occurrence of the
181 static void bc__write(FILE *fp, const char *p)
183 const char *n = quis();
185 size_t nl = strlen(n);
187 /* --- Try to be a little efficient --- *
189 * Gather up non-`$' characters using @strcspn@ and spew them out really
200 fwrite(n, nl, 1, fp);
205 /* --- @bc__setenv@ --- *
207 * Arguments: @sym_env *e@ = pointer to environment variable block
208 * @const char *val@ = value to set
212 * Use: Sets an environment variable block to the right value.
215 static void bc__setenv(sym_env *e, const char *val)
217 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
218 sprintf(e->val, "%s=%s", e->_base.name, val);
221 /* --- @bc__putenv@ --- *
223 * Arguments: @const char *var@ = name of the variable to set, or 0 if
224 * this is embedded in the value string
225 * @const char *val@ = value to set, or 0 if the variable must
227 * @unsigned int fl@ = flags to set
228 * @unsigned int force@ = force overwrite of preserved variables
230 * Returns: Pointer to symbol block, or zero if it was deleted.
232 * Use: Puts an item into the environment.
235 static sym_env *bc__putenv(const char *var, const char *val,
236 unsigned int fl, unsigned int force)
243 /* --- Sort out embedded variable names --- */
246 const char *p = strchr(val, '=');
261 /* --- Find the variable block --- */
264 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
265 if (!f || ~e->f & envFlag_preserve || force) {
271 e = sym_find(&bc__env, var, -1, 0, 0);
272 if (e && (force || ~e->f & envFlag_preserve))
273 sym_remove(&bc__env, e);
277 /* --- Tidy up and return --- */
286 /* --- @bc__addGroups@ --- *
288 * Arguments: @gid_t *g@ = pointer to a group array
289 * @int *png@ = pointer to number of entries in the array
290 * @const gid_t *a@ = pointer to groups to add
291 * @int na@ = number of groups to add
293 * Returns: Zero if it was OK, nonzero if we should stop now.
295 * Use: Adds groups to a groups array.
298 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
303 for (i = 0; i < na; i++) {
305 /* --- Ensure this group isn't already in the list --- */
307 for (j = 0; j < ng; j++) {
312 /* --- See if there's room for more --- */
314 if (ng >= NGROUPS_MAX) {
315 moan("too many groups (system limit exceeded) -- some have been lost");
320 /* --- Add the group --- */
330 /* --- @bc__banner@ --- *
332 * Arguments: @FILE *fp@ = stream to write on
336 * Use: Writes a banner containing copyright information.
339 static void bc__banner(FILE *fp)
341 bc__write(fp, "$ version " VERSION "\n");
344 /* --- @bc__usage@ --- *
346 * Arguments: @FILE *fp@ = stream to write on
350 * Use: Writes a terse reminder of command line syntax.
353 static void bc__usage(FILE *fp)
357 " $ -c <shell-command> <user>\n"
358 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
359 " $ -d [-p <port>] [-f <config-file>]\n");
362 /* --- @bc__help@ --- *
364 * Arguments: @FILE *fp@ = stream to write on
365 * @int suid@ = whether we're running set-uid
369 * Use: Displays a help message for this excellent piece of software.
372 static void bc__help(FILE *fp, int suid)
379 "The `$' program allows you to run a process as another user.\n"
380 "If a command name is given, this is the process executed. If the `-c'\n"
381 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
382 "given, your default login shell is used.\n"
384 "Your user id, the user id you wish to become, the name of the process\n"
385 "you wish to run, and the identity of the current host are looked up to\n"
386 "ensure that you have permission to do this.\n"
388 "Note that logs are kept of all uses of this program.\n"
390 "Options available are:\n"
392 "-h, --help Display this help text\n"
393 "-u, --usage Display a short usage summary\n"
394 "-v, --version Display $'s version number\n"
396 "-e, --preserve-environment Try to preserve the current environment\n"
397 "-s, --su, --set-user Set environment variables to reflect USER\n"
398 "-l, --login Really log in as USER\n"
400 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
401 #ifdef HAVE_SETGROUPS
402 "-k, --keep-groups Keep your current set of groups\n"
403 "-m, --merge-groups Merge the lists of groups\n"
404 "-r, --replace-groups Replace the list of groups\n"
407 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
409 "-d, --daemon Start a daemon\n"
410 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
411 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
416 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
419 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
420 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
426 * Arguments: @int argc@ = number of command line arguments
427 * @char *argv[]@ = pointer to the various arguments
429 * Returns: Zero if successful.
431 * Use: Allows a user to change UID.
434 int main(int argc, char *argv[])
436 /* --- Request block setup parameters --- */
438 request rq; /* Request buffer to build */
439 char *cmd = 0; /* Shell command to execute */
440 char *binary = "/bin/sh"; /* Default binary to execute */
441 char **env = environ; /* Default environment to pass */
442 char **todo = 0; /* Pointer to argument list */
443 char *who = 0; /* Who we're meant to become */
444 struct passwd *from_pw = 0; /* User we are right now */
445 struct passwd *to_pw = 0; /* User we want to become */
447 /* --- Become server setup parameters --- */
449 char *conffile = file_RULES; /* Default config file for daemon */
450 int port = 0; /* Default port for daemon */
452 /* --- Miscellanous shared variables --- */
454 unsigned flags = 0; /* Various useful flags */
455 int style = DEFAULT_LOGIN_STYLE; /* Login style */
456 gid_t group = -1; /* Default group to set */
457 int gstyle = g_unset; /* No group style set yet */
459 #ifdef HAVE_SETGROUPS
460 gid_t groups[NGROUPS_MAX]; /* Set of groups */
461 int ngroups; /* Number of groups in the set */
464 /* --- Default argument list executes a shell command --- */
466 static char *shell[] = {
467 "/bin/sh", /* Bourne shell */
468 "-c", /* Read from command line */
469 0, /* Pointer to shell command */
473 /* --- Definitions for the various flags --- */
476 f_daemon = 1, /* Start up in daemon mode */
477 f_duff = 2, /* Fault in arguments */
478 f_shell = 4, /* Run a default shell */
479 f_dummy = 8, /* Don't actually do anything */
480 f_setuid = 16, /* We're running setuid */
481 f_havegroup = 32 /* Set a default group */
484 /* --- Set up the program name --- */
488 if (getuid() != geteuid())
491 /* --- Read the environment into a hashtable --- */
496 sym_createTable(&bc__env);
497 for (p = environ; *p; p++)
498 bc__putenv(0, *p, 0, 0);
501 /* --- Parse some command line arguments --- */
505 static struct option opts[] = {
507 /* --- Asking for help --- */
509 { "help", 0, 0, 'h' },
510 { "usage", 0, 0, 'u' },
511 { "version", 0, 0, 'v' },
513 /* --- Login style options --- */
515 { "preserve-environment", 0, 0, 'e' },
517 { "set-user", 0, 0, 's' },
518 { "login", 0, 0, 'l' },
520 /* --- Group style options --- */
522 { "group", gFlag_argReq, 0, 'g' },
523 #ifdef HAVE_SETGROUPS
524 { "keep-groups", 0, 0, 'k' },
525 { "merge-groups", 0, 0, 'm' },
526 { "replace-groups", 0, 0, 'r' },
529 /* --- Command to run options --- */
531 { "command", gFlag_argReq, 0, 'c' },
533 /* --- Server options --- */
535 { "daemon", 0, 0, 'd' },
536 { "port", gFlag_argReq, 0, 'p' },
537 { "config-file", gFlag_argReq, 0, 'f' },
539 /* --- Tracing options --- */
542 { "impersonate", gFlag_argReq, 0, 'I' },
543 { "trace", gFlag_argOpt, 0, 'T' },
544 { "trace-level", gFlag_argOpt, 0, 'L' },
550 i = mdwopt(argc, argv,
551 "-" /* Return non-options as options */
552 "huv" /* Asking for help */
553 "esl" /* Login style options */
554 #ifdef HAVE_SETGROUPS
555 "g:kmr" /* Group style options */
557 "g:" /* Group (without @setgroups@) */
559 "c:" /* Command to run options */
560 "dp:f:" /* Server options */
562 "I:T::L::" /* Tracing options */
565 opts, 0, 0, gFlag_envVar);
571 /* --- Asking for help --- */
574 bc__help(stdout, flags & f_setuid);
586 /* --- Login style options --- */
598 /* --- Group style options --- */
601 if (isdigit((unsigned char)optarg[0]))
602 group = atoi(optarg);
604 struct group *gr = getgrnam(optarg);
606 die("unknown group `%s'", optarg);
609 flags |= f_havegroup;
622 /* --- Command to run options --- */
628 /* --- Server options --- */
631 if (isdigit((unsigned char)optarg[0]))
632 port = htons(atoi(optarg));
634 struct servent *s = getservbyname(optarg, "udp");
636 die("unknown service name `%s'", optarg);
647 /* --- Pretend to be a different user --- *
649 * There are all sorts of nasty implications for this option. Don't
650 * allow it if we're running setuid. Disable the actual login anyway.
656 if (flags & f_setuid)
657 moan("shan't allow impersonation while running setuid");
660 if (isdigit((unsigned char)optarg[0]))
661 pw = getpwuid(atoi(optarg));
663 pw = getpwnam(optarg);
665 die("can't impersonate unknown user `%s'", optarg);
666 from_pw = userdb_copyUser(pw);
667 rq.from = from_pw->pw_uid;
674 /* --- Tracing support --- *
676 * Be careful not to zap a file I wouldn't normally be allowed to write
685 if (optarg == 0 || strcmp(optarg, "-") == 0)
688 uid_t eu = geteuid(), ru = getuid();
691 if (setreuid(eu, ru))
696 die("couldn't temporarily give up privileges: %s",
700 if ((fp = fopen(optarg, "w")) == 0) {
701 die("couldn't open trace file `%s' for writing: %s",
702 optarg, strerror(errno));
706 if (setreuid(ru, eu))
710 die("couldn't regain privileges: %s", strerror(errno));
712 traceon(fp, TRACE_DFL);
713 trace(TRACE_MISC, "become: tracing enabled");
718 /* --- Setting trace levels --- */
724 unsigned int lvl = 0, l;
725 const char *p = optarg;
727 /* --- Table of tracing facilities --- */
735 static tr lvltbl[] = {
736 { 'm', TRACE_MISC, "miscellaneous messages" },
737 { 's', TRACE_SETUP, "building the request block" },
738 { 'd', TRACE_DAEMON, "server process" },
739 { 'r', TRACE_RULE, "ruleset scanning" },
740 { 'c', TRACE_CHECK, "request checking" },
741 { 'l', TRACE_CLIENT, "client process" },
742 { 'R', TRACE_RAND, "random number generator" },
743 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
744 { 'y', TRACE_YACC, "parsing configuration file" },
745 { 'D', TRACE_DFL, "default tracing options" },
746 { 'A', TRACE_ALL, "all tracing options" },
751 /* --- Output some help if there's no arguemnt --- */
759 for (tp = lvltbl; tp->l; tp++) {
760 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
761 printf("%c -- %s\n", tp->ch, tp->help);
765 "Also, `+' and `-' options are recognised to turn on and off various\n"
766 "tracing options. For example, `A-r' enables everything except ruleset\n"
767 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
779 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
782 if (flags & f_setuid)
785 lvl = sense ? (lvl | l) : (lvl & ~l);
787 moan("unknown trace option `%c'", *p);
793 yydebug = ((lvl & TRACE_YACC) != 0);
798 /* --- Something that wasn't an option --- *
800 * The following nasties are supported:
802 * * NAME=VALUE -- preserve NAME, and give it a VALUE
803 * * NAME= -- preserve NAME, and give it an empty value
804 * * NAME- -- delete NAME
805 * * NAME! -- preserve NAME with existing value
807 * Anything else is either the user name (which is OK) or the start of
808 * the command (in which case I stop and read the rest of the command).
812 size_t sz = strcspn(optarg, "=-!");
815 /* --- None of the above --- */
817 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
827 /* --- Do the appropriate thing --- */
829 switch (optarg[sz]) {
831 bc__putenv(0, optarg, envFlag_preserve, 1);
835 bc__putenv(optarg, 0, 0, 1);
839 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
840 e->f |= envFlag_preserve;
845 /* --- Something I didn't understand has occurred --- */
854 if (flags & f_duff) {
859 /* --- Switch to daemon mode if requested --- */
861 if (flags & f_daemon) {
862 T( trace(TRACE_MISC, "become: daemon mode requested"); )
863 daemon_init(conffile, port);
867 /* --- Open a syslog --- */
869 openlog(quis(), 0, LOG_AUTH);
871 /* --- Pick out the uid --- */
881 if (isdigit((unsigned char)who[0]))
882 pw = getpwuid(atoi(who));
886 die("unknown user `%s'", who);
887 to_pw = userdb_copyUser(pw);
891 /* --- Fill in the easy bits of the request --- */
897 pw = getpwuid(rq.from);
899 die("who are you? (can't find user %li)", (long)rq.from);
900 from_pw = userdb_copyUser(pw);
903 /* --- Find the local host address --- */
909 if ((he = gethostbyname(u.nodename)) == 0)
910 die("who am I? (can't resolve `%s')", u.nodename);
911 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
914 /* --- Fiddle with group ownerships a bit --- */
917 #ifdef HAVE_SETGROUPS
918 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
922 /* --- Set the default login group, if there is one --- */
924 if (~flags & f_havegroup)
925 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
927 #ifndef HAVE_SETGROUPS
929 /* --- Check that it's valid --- */
931 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
932 die("invalid default group");
936 /* --- Set the default group style --- */
938 if (gstyle == g_unset)
939 gstyle = (style == l_login) ? g_replace : g_merge;
941 /* --- Read in my current set of groups --- */
943 n_fgr = getgroups(NGROUPS_MAX, from_gr);
945 /* --- Now read the groups for the target user --- *
947 * Do this even if I'm using the @g_keep@ style -- I'll use it for
948 * checking that @group@ is valid.
956 to_gr[n_tgr++] = to_pw->pw_gid;
959 while ((gr = getgrent()) != 0) {
960 if (gr->gr_gid == to_gr[0])
962 for (p = gr->gr_mem; *p; p++) {
963 if (strcmp(to_pw->pw_name, *p) == 0) {
964 to_gr[n_tgr++] = gr->gr_gid;
965 if (n_tgr >= NGROUPS_MAX)
976 /* --- Check that @group@ is reasonable --- */
981 if (group == getgid() || group == from_pw->pw_gid)
983 for (i = 0; i < n_fgr; i++) {
984 if (group == from_gr[i])
987 for (i = 0; i < n_tgr; i++) {
988 if (group == to_gr[i])
991 die("invalid default group");
995 /* --- Phew. Now comes the hard bit --- */
1003 if (gstyle & g_keep) {
1005 ga[i++] = from_pw->pw_gid;
1007 if (gstyle & g_replace)
1008 ga[i++] = to_pw->pw_gid;
1010 /* --- Style authorities will become apoplectic if shown this --- *
1012 * As far as I can see, it's the neatest way of writing it.
1016 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1017 ((gstyle & g_keep) &&
1018 bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
1019 ((gstyle & g_replace) &&
1020 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1025 /* --- Trace the results of all this --- */
1027 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1029 #ifdef HAVE_SETGROUPS
1030 IF_TRACING(TRACE_SETUP, {
1033 for (i = 1; i < ngroups; i++)
1034 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1039 /* --- Shell commands are easy --- */
1046 /* --- A command given on the command line isn't too hard --- */
1048 else if (optind < argc) {
1049 todo = argv + optind;
1058 /* --- An unadorned becoming requires little work --- */
1061 shell[0] = getenv("SHELL");
1063 shell[0] = from_pw->pw_shell;
1069 /* --- An su-like login needs slightly less effort --- */
1072 shell[0] = to_pw->pw_shell;
1078 /* --- A login request needs a little bit of work --- */
1081 const char *p = strrchr(to_pw->pw_shell, '/');
1086 p = to_pw->pw_shell;
1087 shell[0] = xmalloc(strlen(p) + 2);
1089 strcpy(shell[0] + 1, p);
1092 binary = to_pw->pw_shell;
1097 /* --- Mangle the environment --- *
1099 * This keeps getting more complicated all the time. (How true. Now I've
1100 * got all sorts of nasty environment mangling to do.)
1102 * The environment stuff now happens in seven phases:
1104 * 1. Mark very special variables to be preserved. Currently only TERM
1105 * and DISPLAY are treated in this way.
1107 * 2. Set and preserve Become's own environment variables.
1109 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1110 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1112 * 4. If we're preserving the environment or being `su'-like, process the
1113 * PATH variable a little. Otherwise reset it to something
1116 * 5. If we're being `login'-like, expunge all unpreserved variables.
1118 * 6. Expunge any security-critical variables.
1120 * 7. Build a new environment table to pass to child processes.
1124 /* --- Variables to be preserved always --- *
1126 * A user can explicitly expunge a variable in this list, in which case
1127 * we never get to see it here.
1130 static char *preserve[] = {
1131 "TERM", "DISPLAY", 0
1134 /* --- Variables to be expunged --- *
1136 * Any environment string which has one of the following as a prefix will
1137 * be expunged from the environment passed to the called process. The
1138 * first line lists variables which have been used to list search paths
1139 * for shared libraries: by manipulating these, an attacker could replace
1140 * a standard library with one of his own. The second line lists other
1141 * well-known dangerous environment variables.
1144 static char *banned[] = {
1145 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1146 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1150 /* --- Other useful variables --- */
1159 /* --- Stage one. Preserve display-specific variables --- */
1161 for (pp = preserve; *pp; pp++) {
1162 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1163 e->f |= envFlag_preserve;
1166 /* --- Stage two. Set Become's own variables --- */
1168 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1170 bc__setenv(e, from_pw->pw_name);
1171 e->f |= envFlag_preserve;
1173 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1175 bc__setenv(e, from_pw->pw_dir);
1176 e->f |= envFlag_preserve;
1178 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1179 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1180 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1181 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1183 /* --- Stage three. Set user identity --- */
1187 static char *maildirs[] = {
1188 "/var/spool/mail", "/var/mail",
1189 "/usr/spool/mail", "/usr/mail",
1195 for (pp = maildirs; *pp; pp++) {
1196 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1197 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1198 bc__putenv("MAIL", b, envFlag_preserve, 0);
1202 } /* Fall through */
1205 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1206 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1207 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1208 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1212 /* --- Stage four. Set the user's PATH properly --- */
1215 /* --- Find an existing path --- *
1217 * If there's no path, or this is a login, then set a default path,
1218 * unless we're meant to preserve the existing one. Whew!
1221 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1223 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1225 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1226 envFlag_preserve, 0);
1229 /* --- Find the string --- */
1231 e->f = envFlag_preserve;
1232 p = strchr(e->val, '=') + 1;
1235 /* --- Write the new version to a dynamically allocated buffer --- */
1237 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1238 strcpy(e->val, "PATH=");
1241 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1256 /* --- Stages five and six. Expunge variables and count numbers --- *
1258 * Folded together, so I only need one pass through the table. Also
1259 * count the number of variables needed at this time.
1264 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1266 /* --- Login style expunges all unpreserved variables --- */
1268 if (style == l_login && ~e->f & envFlag_preserve)
1271 /* --- Otherwise just check the name against the list --- */
1273 for (pp = banned; *pp; pp++) {
1276 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1278 } else if (strcmp(e->_base.name, *pp) == 0)
1286 sym_remove(&bc__env, e);
1289 /* --- Stage seven. Build the new environment block --- */
1291 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1293 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1298 /* --- Trace the command --- */
1300 IF_TRACING(TRACE_SETUP, {
1303 trace(TRACE_SETUP, "setup: from user %s to user %s",
1304 from_pw->pw_name, to_pw->pw_name);
1305 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1306 for (i = 0; todo[i]; i++)
1307 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1308 for (i = 0; env[i]; i++)
1309 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1312 /* --- If necessary, resolve the path to the command --- */
1314 if (!strchr(binary, '/')) {
1318 if ((p = getenv("PATH")) == 0)
1319 p = "/bin:/usr/bin";
1322 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1324 /* --- Check length of string before copying --- */
1326 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1329 /* --- Now build the pathname and check it --- */
1331 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1332 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1333 st.st_mode & 0111 && /* Check it's executable */
1334 S_ISREG(st.st_mode)) /* Check it's a file */
1339 die("couldn't find `%s' in path", todo[0]);
1343 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1345 /* --- Canonicalise the path string, if necessary --- */
1352 /* --- Insert current directory name if path not absolute --- */
1357 if (!getcwd(b, sizeof(b)))
1358 die("couldn't read current directory: %s", strerror(errno));
1363 /* --- Now copy over characters from the path string --- */
1367 /* --- Check for buffer overflows here --- *
1369 * I write at most one byte per iteration so this is OK. Remember to
1370 * allow one for the null byte.
1373 if (p >= b + sizeof(b) - 1)
1374 die("internal error: buffer overflow while canonifying path");
1376 /* --- Reduce multiple slashes to just one --- */
1384 /* --- Handle dots in filenames --- *
1386 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1387 * we've just stuck the current directory on the end of the buffer,
1388 * or we've just put something else on the end.
1391 else if (*q == '.' && p[-1] == '/') {
1393 /* --- A simple `./' just gets removed --- */
1395 if (q[1] == 0 || q[1] == '/') {
1401 /* --- A `../' needs to be peeled back to the previous `/' --- */
1403 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1406 while (p > b && p[-1] != '/')
1419 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1421 /* --- Run the check --- *
1423 * If the user is already what she wants to be, then print a warning.
1424 * Then, if I was just going to spawn a shell, quit, to reduce user
1425 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1426 * checking if we're already root -- root can do anything anyway, and at
1427 * least this way we get some logging done, and offer a more friendly
1431 if (rq.from == rq.to) {
1432 moan("you already are `%s'!", to_pw->pw_name);
1433 if (flags & f_shell) {
1434 moan("(to prevent confusion, I'm not spawning a shell)");
1438 int a = (rq.from == 0) || check(&rq);
1441 "permission %s for %s to become %s to run `%s'",
1442 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1446 die("permission denied");
1449 /* --- Now do the job --- */
1451 T( trace(TRACE_MISC, "become: permission granted"); )
1453 if (flags & f_dummy) {
1454 puts("permission granted");
1458 #ifdef HAVE_SETGROUPS
1459 if (setgroups(ngroups, groups) < 0)
1460 die("couldn't set groups: %s", strerror(errno));
1463 if (setgid(group) < 0)
1464 die("couldn't set default group: %s", strerror(errno));
1465 if (setuid(rq.to) < 0)
1466 die("couldn't set uid: %s", strerror(errno));
1468 /* --- If this was a login, change current directory --- */
1470 if (flags & f_shell && style == l_login && chdir(to_pw->pw_dir) < 0) {
1471 moan("couldn't change directory to `%s': %s",
1472 to_pw->pw_dir, strerror(errno));
1475 /* --- Finally, call the program --- */
1478 execve(rq.cmd, todo, env);
1479 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1483 /*----- That's all, folks -------------------------------------------------*/