3 * $Id: become.c,v 1.15 1998/01/13 11:10:44 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.15 1998/01/13 11:10:44 mdw
33 * Add `TZ' to the list of variables to be preserved.
35 * Revision 1.14 1998/01/12 16:45:39 mdw
38 * Revision 1.13 1997/09/26 09:14:57 mdw
39 * Merged blowfish branch into trunk.
41 * Revision 1.12 1997/09/25 16:04:48 mdw
42 * Change directory after becoming someone else, instead of before. This
43 * avoids problems with root-squashed NFS mounts.
45 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
46 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
47 * because I prefer Blowfish (without any particularly strong evidence) but
48 * mainly because IDEA is patented and Blowfish isn't.
50 * Revision 1.11 1997/09/24 09:48:45 mdw
51 * Fix (scary) overrun bug in group allocation stuff.
53 * Revision 1.10 1997/09/17 10:14:10 mdw
54 * Fix a typo. Support service names in `--port' option.
56 * Revision 1.9 1997/09/10 10:28:05 mdw
57 * Allow default port to be given as a service name or port number. Handle
58 * groups properly (lots of options here).
60 * Revision 1.8 1997/09/08 13:56:24 mdw
61 * Change criteria for expunging items from the user's PATH: instead of
62 * removing things starting with `.', remove things not starting with `/'.
64 * Revision 1.7 1997/09/08 13:43:20 mdw
65 * Change userid when creating tracefiles rather than fiddling with
66 * `access': it works rather better. Also, insert some stdio buffer
67 * flushing to ensure tracedumps are completely written.
69 * Revision 1.6 1997/09/05 13:47:44 mdw
70 * Make the `-L' (trace-level) option's argument optional, like the long
73 * Revision 1.5 1997/09/05 11:45:19 mdw
74 * Add support for different login styles, and environment variable
75 * manipulation in a safe and useful way.
77 * Revision 1.4 1997/08/20 16:15:13 mdw
78 * Overhaul of environment handling. Fix daft bug in path search code.
80 * Revision 1.3 1997/08/07 16:28:59 mdw
81 * Do something useful when users attempt to become themselves.
83 * Revision 1.2 1997/08/04 10:24:20 mdw
84 * Sources placed under CVS control.
86 * Revision 1.1 1997/07/21 13:47:54 mdw
91 /*----- Header files ------------------------------------------------------*/
93 /* --- ANSI headers --- */
103 /* --- Unix headers --- */
105 #include <sys/types.h>
106 #include <sys/stat.h>
107 #include <sys/socket.h>
108 #include <sys/utsname.h>
110 #include <netinet/in.h>
112 #include <arpa/inet.h>
120 extern char **environ;
122 /* --- Local headers --- */
137 /*----- Type definitions --------------------------------------------------*/
139 /* --- Symbol table entry for an environment variable --- */
141 typedef struct sym_env {
142 sym_base _base; /* Symbol table information */
143 unsigned f; /* Flags word (see below) */
144 char *val; /* Pointer to variable value */
147 /* --- Environment variable flags --- */
153 /* --- Login behaviour types --- */
156 l_preserve, /* Preserve the environment */
157 l_setuser, /* Update who I am */
158 l_login /* Do a full login */
161 /* --- Group behaviour types --- *
163 * Note that these make a handy bitfield.
166 #ifdef HAVE_SETGROUPS
169 g_unset = 0, /* Nobody's set a preference */
170 g_keep = 1, /* Leave the group memberships */
171 g_replace = 2, /* Replace group memberships */
172 g_merge = (g_keep | g_replace) /* Merge the group memberships */
177 /*----- Static variables --------------------------------------------------*/
179 static sym_table bc__env;
181 /*----- Main code ---------------------------------------------------------*/
183 /* --- @bc__write@ --- *
185 * Arguments: @FILE *fp@ = pointer to a stream to write on
186 * @const char *p@ = pointer to a string
190 * Use: Writes the string to the stream, substituting the program
191 * name (as returned by @quis@) for each occurrence of the
195 static void bc__write(FILE *fp, const char *p)
197 const char *n = quis();
199 size_t nl = strlen(n);
201 /* --- Try to be a little efficient --- *
203 * Gather up non-`$' characters using @strcspn@ and spew them out really
214 fwrite(n, nl, 1, fp);
219 /* --- @bc__setenv@ --- *
221 * Arguments: @sym_env *e@ = pointer to environment variable block
222 * @const char *val@ = value to set
226 * Use: Sets an environment variable block to the right value.
229 static void bc__setenv(sym_env *e, const char *val)
231 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
232 sprintf(e->val, "%s=%s", e->_base.name, val);
235 /* --- @bc__putenv@ --- *
237 * Arguments: @const char *var@ = name of the variable to set, or 0 if
238 * this is embedded in the value string
239 * @const char *val@ = value to set, or 0 if the variable must
241 * @unsigned int fl@ = flags to set
242 * @unsigned int force@ = force overwrite of preserved variables
244 * Returns: Pointer to symbol block, or zero if it was deleted.
246 * Use: Puts an item into the environment.
249 static sym_env *bc__putenv(const char *var, const char *val,
250 unsigned int fl, unsigned int force)
257 /* --- Sort out embedded variable names --- */
260 const char *p = strchr(val, '=');
275 /* --- Find the variable block --- */
278 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
279 if (!f || ~e->f & envFlag_preserve || force) {
285 e = sym_find(&bc__env, var, -1, 0, 0);
286 if (e && (force || ~e->f & envFlag_preserve))
287 sym_remove(&bc__env, e);
291 /* --- Tidy up and return --- */
300 /* --- @bc__addGroups@ --- *
302 * Arguments: @gid_t *g@ = pointer to a group array
303 * @int *png@ = pointer to number of entries in the array
304 * @const gid_t *a@ = pointer to groups to add
305 * @int na@ = number of groups to add
307 * Returns: Zero if it was OK, nonzero if we should stop now.
309 * Use: Adds groups to a groups array.
312 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
317 for (i = 0; i < na; i++) {
319 /* --- Ensure this group isn't already in the list --- */
321 for (j = 0; j < ng; j++) {
326 /* --- See if there's room for more --- */
328 if (ng >= NGROUPS_MAX) {
329 moan("too many groups (system limit exceeded) -- some have been lost");
334 /* --- Add the group --- */
344 /* --- @bc__banner@ --- *
346 * Arguments: @FILE *fp@ = stream to write on
350 * Use: Writes a banner containing copyright information.
353 static void bc__banner(FILE *fp)
355 bc__write(fp, "$ version " VERSION "\n");
358 /* --- @bc__usage@ --- *
360 * Arguments: @FILE *fp@ = stream to write on
364 * Use: Writes a terse reminder of command line syntax.
367 static void bc__usage(FILE *fp)
371 " $ -c <shell-command> <user>\n"
372 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
373 " $ -d [-p <port>] [-f <config-file>]\n");
376 /* --- @bc__help@ --- *
378 * Arguments: @FILE *fp@ = stream to write on
379 * @int suid@ = whether we're running set-uid
383 * Use: Displays a help message for this excellent piece of software.
386 static void bc__help(FILE *fp, int suid)
393 "The `$' program allows you to run a process as another user.\n"
394 "If a command name is given, this is the process executed. If the `-c'\n"
395 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
396 "given, your default login shell is used.\n"
398 "Your user id, the user id you wish to become, the name of the process\n"
399 "you wish to run, and the identity of the current host are looked up to\n"
400 "ensure that you have permission to do this.\n"
402 "Note that logs are kept of all uses of this program.\n"
404 "Options available are:\n"
406 "-h, --help Display this help text\n"
407 "-u, --usage Display a short usage summary\n"
408 "-v, --version Display $'s version number\n"
410 "-e, --preserve-environment Try to preserve the current environment\n"
411 "-s, --su, --set-user Set environment variables to reflect USER\n"
412 "-l, --login Really log in as USER\n"
414 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
415 #ifdef HAVE_SETGROUPS
416 "-k, --keep-groups Keep your current set of groups\n"
417 "-m, --merge-groups Merge the lists of groups\n"
418 "-r, --replace-groups Replace the list of groups\n"
421 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
423 "-d, --daemon Start a daemon\n"
424 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
425 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
430 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
433 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
434 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
440 * Arguments: @int argc@ = number of command line arguments
441 * @char *argv[]@ = pointer to the various arguments
443 * Returns: Zero if successful.
445 * Use: Allows a user to change UID.
448 int main(int argc, char *argv[])
450 /* --- Request block setup parameters --- */
452 request rq; /* Request buffer to build */
453 char *cmd = 0; /* Shell command to execute */
454 char *binary = "/bin/sh"; /* Default binary to execute */
455 char **env = environ; /* Default environment to pass */
456 char **todo = 0; /* Pointer to argument list */
457 char *who = 0; /* Who we're meant to become */
458 struct passwd *from_pw = 0; /* User we are right now */
459 struct passwd *to_pw = 0; /* User we want to become */
461 /* --- Become server setup parameters --- */
463 char *conffile = file_RULES; /* Default config file for daemon */
464 int port = 0; /* Default port for daemon */
466 /* --- Miscellanous shared variables --- */
468 unsigned flags = 0; /* Various useful flags */
469 int style = DEFAULT_LOGIN_STYLE; /* Login style */
470 gid_t group = -1; /* Default group to set */
471 int gstyle = g_unset; /* No group style set yet */
473 #ifdef HAVE_SETGROUPS
474 gid_t groups[NGROUPS_MAX]; /* Set of groups */
475 int ngroups; /* Number of groups in the set */
478 /* --- Default argument list executes a shell command --- */
480 static char *shell[] = {
481 "/bin/sh", /* Bourne shell */
482 "-c", /* Read from command line */
483 0, /* Pointer to shell command */
487 /* --- Definitions for the various flags --- */
490 f_daemon = 1, /* Start up in daemon mode */
491 f_duff = 2, /* Fault in arguments */
492 f_shell = 4, /* Run a default shell */
493 f_dummy = 8, /* Don't actually do anything */
494 f_setuid = 16, /* We're running setuid */
495 f_havegroup = 32 /* Set a default group */
498 /* --- Set up the program name --- */
502 if (getuid() != geteuid())
505 /* --- Read the environment into a hashtable --- */
510 sym_createTable(&bc__env);
511 for (p = environ; *p; p++)
512 bc__putenv(0, *p, 0, 0);
515 /* --- Parse some command line arguments --- */
519 static struct option opts[] = {
521 /* --- Asking for help --- */
523 { "help", 0, 0, 'h' },
524 { "usage", 0, 0, 'u' },
525 { "version", 0, 0, 'v' },
527 /* --- Login style options --- */
529 { "preserve-environment", 0, 0, 'e' },
531 { "set-user", 0, 0, 's' },
532 { "login", 0, 0, 'l' },
534 /* --- Group style options --- */
536 { "group", gFlag_argReq, 0, 'g' },
537 #ifdef HAVE_SETGROUPS
538 { "keep-groups", 0, 0, 'k' },
539 { "merge-groups", 0, 0, 'm' },
540 { "replace-groups", 0, 0, 'r' },
543 /* --- Command to run options --- */
545 { "command", gFlag_argReq, 0, 'c' },
547 /* --- Server options --- */
549 { "daemon", 0, 0, 'd' },
550 { "port", gFlag_argReq, 0, 'p' },
551 { "config-file", gFlag_argReq, 0, 'f' },
553 /* --- Tracing options --- */
556 { "impersonate", gFlag_argReq, 0, 'I' },
557 { "trace", gFlag_argOpt, 0, 'T' },
558 { "trace-level", gFlag_argOpt, 0, 'L' },
564 i = mdwopt(argc, argv,
565 "-" /* Return non-options as options */
566 "huv" /* Asking for help */
567 "esl" /* Login style options */
568 #ifdef HAVE_SETGROUPS
569 "g:kmr" /* Group style options */
571 "g:" /* Group (without @setgroups@) */
573 "c:" /* Command to run options */
574 "dp:f:" /* Server options */
576 "I:T::L::" /* Tracing options */
579 opts, 0, 0, gFlag_envVar);
585 /* --- Asking for help --- */
588 bc__help(stdout, flags & f_setuid);
600 /* --- Login style options --- */
612 /* --- Group style options --- */
615 if (isdigit((unsigned char)optarg[0]))
616 group = atoi(optarg);
618 struct group *gr = getgrnam(optarg);
620 die("unknown group `%s'", optarg);
623 flags |= f_havegroup;
636 /* --- Command to run options --- */
642 /* --- Server options --- */
645 if (isdigit((unsigned char)optarg[0]))
646 port = htons(atoi(optarg));
648 struct servent *s = getservbyname(optarg, "udp");
650 die("unknown service name `%s'", optarg);
661 /* --- Pretend to be a different user --- *
663 * There are all sorts of nasty implications for this option. Don't
664 * allow it if we're running setuid. Disable the actual login anyway.
670 if (flags & f_setuid)
671 moan("shan't allow impersonation while running setuid");
674 if (isdigit((unsigned char)optarg[0]))
675 pw = getpwuid(atoi(optarg));
677 pw = getpwnam(optarg);
679 die("can't impersonate unknown user `%s'", optarg);
680 from_pw = userdb_copyUser(pw);
681 rq.from = from_pw->pw_uid;
688 /* --- Tracing support --- *
690 * Be careful not to zap a file I wouldn't normally be allowed to write
699 if (optarg == 0 || strcmp(optarg, "-") == 0)
702 uid_t eu = geteuid(), ru = getuid();
705 if (setreuid(eu, ru))
710 die("couldn't temporarily give up privileges: %s",
714 if ((fp = fopen(optarg, "w")) == 0) {
715 die("couldn't open trace file `%s' for writing: %s",
716 optarg, strerror(errno));
720 if (setreuid(ru, eu))
724 die("couldn't regain privileges: %s", strerror(errno));
726 traceon(fp, TRACE_DFL);
727 trace(TRACE_MISC, "become: tracing enabled");
732 /* --- Setting trace levels --- */
738 unsigned int lvl = 0, l;
739 const char *p = optarg;
741 /* --- Table of tracing facilities --- */
749 static tr lvltbl[] = {
750 { 'm', TRACE_MISC, "miscellaneous messages" },
751 { 's', TRACE_SETUP, "building the request block" },
752 { 'd', TRACE_DAEMON, "server process" },
753 { 'r', TRACE_RULE, "ruleset scanning" },
754 { 'c', TRACE_CHECK, "request checking" },
755 { 'l', TRACE_CLIENT, "client process" },
756 { 'R', TRACE_RAND, "random number generator" },
757 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
758 { 'y', TRACE_YACC, "parsing configuration file" },
759 { 'D', TRACE_DFL, "default tracing options" },
760 { 'A', TRACE_ALL, "all tracing options" },
765 /* --- Output some help if there's no arguemnt --- */
773 for (tp = lvltbl; tp->l; tp++) {
774 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
775 printf("%c -- %s\n", tp->ch, tp->help);
779 "Also, `+' and `-' options are recognised to turn on and off various\n"
780 "tracing options. For example, `A-r' enables everything except ruleset\n"
781 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
793 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
796 if (flags & f_setuid)
799 lvl = sense ? (lvl | l) : (lvl & ~l);
801 moan("unknown trace option `%c'", *p);
807 yydebug = ((lvl & TRACE_YACC) != 0);
812 /* --- Something that wasn't an option --- *
814 * The following nasties are supported:
816 * * NAME=VALUE -- preserve NAME, and give it a VALUE
817 * * NAME= -- preserve NAME, and give it an empty value
818 * * NAME- -- delete NAME
819 * * NAME! -- preserve NAME with existing value
821 * Anything else is either the user name (which is OK) or the start of
822 * the command (in which case I stop and read the rest of the command).
826 size_t sz = strcspn(optarg, "=-!");
829 /* --- None of the above --- */
831 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
841 /* --- Do the appropriate thing --- */
843 switch (optarg[sz]) {
845 bc__putenv(0, optarg, envFlag_preserve, 1);
849 bc__putenv(optarg, 0, 0, 1);
853 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
854 e->f |= envFlag_preserve;
859 /* --- Something I didn't understand has occurred --- */
868 if (flags & f_duff) {
873 /* --- Switch to daemon mode if requested --- */
875 if (flags & f_daemon) {
876 T( trace(TRACE_MISC, "become: daemon mode requested"); )
877 daemon_init(conffile, port);
881 /* --- Open a syslog --- */
883 openlog(quis(), 0, LOG_AUTH);
885 /* --- Pick out the uid --- */
895 if (isdigit((unsigned char)who[0]))
896 pw = getpwuid(atoi(who));
900 die("unknown user `%s'", who);
901 to_pw = userdb_copyUser(pw);
905 /* --- Fill in the easy bits of the request --- */
911 pw = getpwuid(rq.from);
913 die("who are you? (can't find user %li)", (long)rq.from);
914 from_pw = userdb_copyUser(pw);
917 /* --- Find the local host address --- */
923 if ((he = gethostbyname(u.nodename)) == 0)
924 die("who am I? (can't resolve `%s')", u.nodename);
925 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
928 /* --- Fiddle with group ownerships a bit --- */
931 #ifdef HAVE_SETGROUPS
932 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
936 /* --- Set the default login group, if there is one --- */
938 if (~flags & f_havegroup)
939 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
941 #ifndef HAVE_SETGROUPS
943 /* --- Check that it's valid --- */
945 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
946 die("invalid default group");
950 /* --- Set the default group style --- */
952 if (gstyle == g_unset)
953 gstyle = (style == l_login) ? g_replace : g_merge;
955 /* --- Read in my current set of groups --- */
957 n_fgr = getgroups(NGROUPS_MAX, from_gr);
959 /* --- Now read the groups for the target user --- *
961 * Do this even if I'm using the @g_keep@ style -- I'll use it for
962 * checking that @group@ is valid.
970 to_gr[n_tgr++] = to_pw->pw_gid;
973 while ((gr = getgrent()) != 0) {
974 if (gr->gr_gid == to_gr[0])
976 for (p = gr->gr_mem; *p; p++) {
977 if (strcmp(to_pw->pw_name, *p) == 0) {
978 to_gr[n_tgr++] = gr->gr_gid;
979 if (n_tgr >= NGROUPS_MAX)
990 /* --- Check that @group@ is reasonable --- */
995 if (group == getgid() || group == from_pw->pw_gid)
997 for (i = 0; i < n_fgr; i++) {
998 if (group == from_gr[i])
1001 for (i = 0; i < n_tgr; i++) {
1002 if (group == to_gr[i])
1005 die("invalid default group");
1009 /* --- Phew. Now comes the hard bit --- */
1017 if (gstyle & g_keep) {
1019 ga[i++] = from_pw->pw_gid;
1021 if (gstyle & g_replace)
1022 ga[i++] = to_pw->pw_gid;
1024 /* --- Style authorities will become apoplectic if shown this --- *
1026 * As far as I can see, it's the neatest way of writing it.
1030 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1031 ((gstyle & g_keep) &&
1032 bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
1033 ((gstyle & g_replace) &&
1034 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1039 /* --- Trace the results of all this --- */
1041 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1043 #ifdef HAVE_SETGROUPS
1044 IF_TRACING(TRACE_SETUP, {
1047 for (i = 1; i < ngroups; i++)
1048 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1053 /* --- Shell commands are easy --- */
1060 /* --- A command given on the command line isn't too hard --- */
1062 else if (optind < argc) {
1063 todo = argv + optind;
1072 /* --- An unadorned becoming requires little work --- */
1075 shell[0] = getenv("SHELL");
1077 shell[0] = from_pw->pw_shell;
1083 /* --- An su-like login needs slightly less effort --- */
1086 shell[0] = to_pw->pw_shell;
1092 /* --- A login request needs a little bit of work --- */
1095 const char *p = strrchr(to_pw->pw_shell, '/');
1100 p = to_pw->pw_shell;
1101 shell[0] = xmalloc(strlen(p) + 2);
1103 strcpy(shell[0] + 1, p);
1106 binary = to_pw->pw_shell;
1111 /* --- Mangle the environment --- *
1113 * This keeps getting more complicated all the time. (How true. Now I've
1114 * got all sorts of nasty environment mangling to do.)
1116 * The environment stuff now happens in seven phases:
1118 * 1. Mark very special variables to be preserved. Currently only TERM
1119 * and DISPLAY are treated in this way.
1121 * 2. Set and preserve Become's own environment variables.
1123 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1124 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1126 * 4. If we're preserving the environment or being `su'-like, process the
1127 * PATH variable a little. Otherwise reset it to something
1130 * 5. If we're being `login'-like, expunge all unpreserved variables.
1132 * 6. Expunge any security-critical variables.
1134 * 7. Build a new environment table to pass to child processes.
1138 /* --- Variables to be preserved always --- *
1140 * A user can explicitly expunge a variable in this list, in which case
1141 * we never get to see it here.
1144 static char *preserve[] = {
1145 "TERM", "DISPLAY", "TZ", 0
1148 /* --- Variables to be expunged --- *
1150 * Any environment string which has one of the following as a prefix will
1151 * be expunged from the environment passed to the called process. The
1152 * first line lists variables which have been used to list search paths
1153 * for shared libraries: by manipulating these, an attacker could replace
1154 * a standard library with one of his own. The second line lists other
1155 * well-known dangerous environment variables.
1158 static char *banned[] = {
1159 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1160 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1164 /* --- Other useful variables --- */
1173 /* --- Stage one. Preserve display-specific variables --- */
1175 for (pp = preserve; *pp; pp++) {
1176 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1177 e->f |= envFlag_preserve;
1180 /* --- Stage two. Set Become's own variables --- */
1182 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1184 bc__setenv(e, from_pw->pw_name);
1185 e->f |= envFlag_preserve;
1187 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1189 bc__setenv(e, from_pw->pw_dir);
1190 e->f |= envFlag_preserve;
1192 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1193 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1194 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1195 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1197 /* --- Stage three. Set user identity --- */
1201 static char *maildirs[] = {
1202 "/var/spool/mail", "/var/mail",
1203 "/usr/spool/mail", "/usr/mail",
1209 for (pp = maildirs; *pp; pp++) {
1210 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1211 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1212 bc__putenv("MAIL", b, envFlag_preserve, 0);
1216 } /* Fall through */
1219 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1220 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1221 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1222 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1226 /* --- Stage four. Set the user's PATH properly --- */
1229 /* --- Find an existing path --- *
1231 * If there's no path, or this is a login, then set a default path,
1232 * unless we're meant to preserve the existing one. Whew!
1235 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1237 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1239 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1240 envFlag_preserve, 0);
1243 /* --- Find the string --- */
1245 e->f = envFlag_preserve;
1246 p = strchr(e->val, '=') + 1;
1249 /* --- Write the new version to a dynamically allocated buffer --- */
1251 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1252 strcpy(e->val, "PATH=");
1255 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1270 /* --- Stages five and six. Expunge variables and count numbers --- *
1272 * Folded together, so I only need one pass through the table. Also
1273 * count the number of variables needed at this time.
1278 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1280 /* --- Login style expunges all unpreserved variables --- */
1282 if (style == l_login && ~e->f & envFlag_preserve)
1285 /* --- Otherwise just check the name against the list --- */
1287 for (pp = banned; *pp; pp++) {
1290 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1292 } else if (strcmp(e->_base.name, *pp) == 0)
1300 sym_remove(&bc__env, e);
1303 /* --- Stage seven. Build the new environment block --- */
1305 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1307 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1312 /* --- Trace the command --- */
1314 IF_TRACING(TRACE_SETUP, {
1317 trace(TRACE_SETUP, "setup: from user %s to user %s",
1318 from_pw->pw_name, to_pw->pw_name);
1319 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1320 for (i = 0; todo[i]; i++)
1321 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1322 for (i = 0; env[i]; i++)
1323 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1326 /* --- If necessary, resolve the path to the command --- */
1328 if (!strchr(binary, '/')) {
1332 if ((p = getenv("PATH")) == 0)
1333 p = "/bin:/usr/bin";
1336 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1338 /* --- Check length of string before copying --- */
1340 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1343 /* --- Now build the pathname and check it --- */
1345 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1346 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1347 st.st_mode & 0111 && /* Check it's executable */
1348 S_ISREG(st.st_mode)) /* Check it's a file */
1353 die("couldn't find `%s' in path", todo[0]);
1357 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1359 /* --- Canonicalise the path string, if necessary --- */
1366 /* --- Insert current directory name if path not absolute --- */
1371 if (!getcwd(b, sizeof(b)))
1372 die("couldn't read current directory: %s", strerror(errno));
1377 /* --- Now copy over characters from the path string --- */
1381 /* --- Check for buffer overflows here --- *
1383 * I write at most one byte per iteration so this is OK. Remember to
1384 * allow one for the null byte.
1387 if (p >= b + sizeof(b) - 1)
1388 die("internal error: buffer overflow while canonifying path");
1390 /* --- Reduce multiple slashes to just one --- */
1398 /* --- Handle dots in filenames --- *
1400 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1401 * we've just stuck the current directory on the end of the buffer,
1402 * or we've just put something else on the end.
1405 else if (*q == '.' && p[-1] == '/') {
1407 /* --- A simple `./' just gets removed --- */
1409 if (q[1] == 0 || q[1] == '/') {
1415 /* --- A `../' needs to be peeled back to the previous `/' --- */
1417 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1420 while (p > b && p[-1] != '/')
1433 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1435 /* --- Run the check --- *
1437 * If the user is already what she wants to be, then print a warning.
1438 * Then, if I was just going to spawn a shell, quit, to reduce user
1439 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1440 * checking if we're already root -- root can do anything anyway, and at
1441 * least this way we get some logging done, and offer a more friendly
1445 if (rq.from == rq.to) {
1446 moan("you already are `%s'!", to_pw->pw_name);
1447 if (flags & f_shell) {
1448 moan("(to prevent confusion, I'm not spawning a shell)");
1452 int a = (rq.from == 0) || check(&rq);
1455 "permission %s for %s to become %s to run `%s'",
1456 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1460 die("permission denied");
1463 /* --- Now do the job --- */
1465 T( trace(TRACE_MISC, "become: permission granted"); )
1467 if (flags & f_dummy) {
1468 puts("permission granted");
1472 #ifdef HAVE_SETGROUPS
1473 if (setgroups(ngroups, groups) < 0)
1474 die("couldn't set groups: %s", strerror(errno));
1477 if (setgid(group) < 0)
1478 die("couldn't set default group: %s", strerror(errno));
1479 if (setuid(rq.to) < 0)
1480 die("couldn't set uid: %s", strerror(errno));
1482 /* --- If this was a login, change current directory --- */
1484 if (flags & f_shell && style == l_login && chdir(to_pw->pw_dir) < 0) {
1485 moan("couldn't change directory to `%s': %s",
1486 to_pw->pw_dir, strerror(errno));
1489 /* --- Finally, call the program --- */
1492 execve(rq.cmd, todo, env);
1493 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1497 /*----- That's all, folks -------------------------------------------------*/