3 * $Id: become.c,v 1.13 1997/09/26 09:14:57 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.13 1997/09/26 09:14:57 mdw
33 * Merged blowfish branch into trunk.
35 * Revision 1.12 1997/09/25 16:04:48 mdw
36 * Change directory after becoming someone else, instead of before. This
37 * avoids problems with root-squashed NFS mounts.
39 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
40 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
41 * because I prefer Blowfish (without any particularly strong evidence) but
42 * mainly because IDEA is patented and Blowfish isn't.
44 * Revision 1.11 1997/09/24 09:48:45 mdw
45 * Fix (scary) overrun bug in group allocation stuff.
47 * Revision 1.10 1997/09/17 10:14:10 mdw
48 * Fix a typo. Support service names in `--port' option.
50 * Revision 1.9 1997/09/10 10:28:05 mdw
51 * Allow default port to be given as a service name or port number. Handle
52 * groups properly (lots of options here).
54 * Revision 1.8 1997/09/08 13:56:24 mdw
55 * Change criteria for expunging items from the user's PATH: instead of
56 * removing things starting with `.', remove things not starting with `/'.
58 * Revision 1.7 1997/09/08 13:43:20 mdw
59 * Change userid when creating tracefiles rather than fiddling with
60 * `access': it works rather better. Also, insert some stdio buffer
61 * flushing to ensure tracedumps are completely written.
63 * Revision 1.6 1997/09/05 13:47:44 mdw
64 * Make the `-L' (trace-level) option's argument optional, like the long
67 * Revision 1.5 1997/09/05 11:45:19 mdw
68 * Add support for different login styles, and environment variable
69 * manipulation in a safe and useful way.
71 * Revision 1.4 1997/08/20 16:15:13 mdw
72 * Overhaul of environment handling. Fix daft bug in path search code.
74 * Revision 1.3 1997/08/07 16:28:59 mdw
75 * Do something useful when users attempt to become themselves.
77 * Revision 1.2 1997/08/04 10:24:20 mdw
78 * Sources placed under CVS control.
80 * Revision 1.1 1997/07/21 13:47:54 mdw
85 /*----- Header files ------------------------------------------------------*/
87 /* --- ANSI headers --- */
97 /* --- Unix headers --- */
99 #include <sys/types.h>
100 #include <sys/stat.h>
101 #include <sys/socket.h>
102 #include <sys/utsname.h>
104 #include <netinet/in.h>
106 #include <arpa/inet.h>
114 extern char **environ;
116 /* --- Local headers --- */
131 /*----- Type definitions --------------------------------------------------*/
133 /* --- Symbol table entry for an environment variable --- */
135 typedef struct sym_env {
136 sym_base _base; /* Symbol table information */
137 unsigned f; /* Flags word (see below) */
138 char *val; /* Pointer to variable value */
141 /* --- Environment variable flags --- */
147 /* --- Login behaviour types --- */
150 l_preserve, /* Preserve the environment */
151 l_setuser, /* Update who I am */
152 l_login /* Do a full login */
155 /* --- Group behaviour types --- *
157 * Note that these make a handy bitfield.
160 #ifdef HAVE_SETGROUPS
163 g_unset = 0, /* Nobody's set a preference */
164 g_keep = 1, /* Leave the group memberships */
165 g_replace = 2, /* Replace group memberships */
166 g_merge = (g_keep | g_replace) /* Merge the group memberships */
171 /*----- Static variables --------------------------------------------------*/
173 static sym_table bc__env;
175 /*----- Main code ---------------------------------------------------------*/
177 /* --- @bc__write@ --- *
179 * Arguments: @FILE *fp@ = pointer to a stream to write on
180 * @const char *p@ = pointer to a string
184 * Use: Writes the string to the stream, substituting the program
185 * name (as returned by @quis@) for each occurrence of the
189 static void bc__write(FILE *fp, const char *p)
191 const char *n = quis();
193 size_t nl = strlen(n);
195 /* --- Try to be a little efficient --- *
197 * Gather up non-`$' characters using @strcspn@ and spew them out really
208 fwrite(n, nl, 1, fp);
213 /* --- @bc__setenv@ --- *
215 * Arguments: @sym_env *e@ = pointer to environment variable block
216 * @const char *val@ = value to set
220 * Use: Sets an environment variable block to the right value.
223 static void bc__setenv(sym_env *e, const char *val)
225 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
226 sprintf(e->val, "%s=%s", e->_base.name, val);
229 /* --- @bc__putenv@ --- *
231 * Arguments: @const char *var@ = name of the variable to set, or 0 if
232 * this is embedded in the value string
233 * @const char *val@ = value to set, or 0 if the variable must
235 * @unsigned int fl@ = flags to set
236 * @unsigned int force@ = force overwrite of preserved variables
238 * Returns: Pointer to symbol block, or zero if it was deleted.
240 * Use: Puts an item into the environment.
243 static sym_env *bc__putenv(const char *var, const char *val,
244 unsigned int fl, unsigned int force)
251 /* --- Sort out embedded variable names --- */
254 const char *p = strchr(val, '=');
269 /* --- Find the variable block --- */
272 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
273 if (!f || ~e->f & envFlag_preserve || force) {
279 e = sym_find(&bc__env, var, -1, 0, 0);
280 if (e && (force || ~e->f & envFlag_preserve))
281 sym_remove(&bc__env, e);
285 /* --- Tidy up and return --- */
294 /* --- @bc__addGroups@ --- *
296 * Arguments: @gid_t *g@ = pointer to a group array
297 * @int *png@ = pointer to number of entries in the array
298 * @const gid_t *a@ = pointer to groups to add
299 * @int na@ = number of groups to add
301 * Returns: Zero if it was OK, nonzero if we should stop now.
303 * Use: Adds groups to a groups array.
306 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
311 for (i = 0; i < na; i++) {
313 /* --- Ensure this group isn't already in the list --- */
315 for (j = 0; j < ng; j++) {
320 /* --- See if there's room for more --- */
322 if (ng >= NGROUPS_MAX) {
323 moan("too many groups (system limit exceeded) -- some have been lost");
328 /* --- Add the group --- */
338 /* --- @bc__banner@ --- *
340 * Arguments: @FILE *fp@ = stream to write on
344 * Use: Writes a banner containing copyright information.
347 static void bc__banner(FILE *fp)
349 bc__write(fp, "$ version " VERSION "\n");
352 /* --- @bc__usage@ --- *
354 * Arguments: @FILE *fp@ = stream to write on
358 * Use: Writes a terse reminder of command line syntax.
361 static void bc__usage(FILE *fp)
365 " $ -c <shell-command> <user>\n"
366 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
367 " $ -d [-p <port>] [-f <config-file>]\n");
370 /* --- @bc__help@ --- *
372 * Arguments: @FILE *fp@ = stream to write on
373 * @int suid@ = whether we're running set-uid
377 * Use: Displays a help message for this excellent piece of software.
380 static void bc__help(FILE *fp, int suid)
387 "The `$' program allows you to run a process as another user.\n"
388 "If a command name is given, this is the process executed. If the `-c'\n"
389 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
390 "given, your default login shell is used.\n"
392 "Your user id, the user id you wish to become, the name of the process\n"
393 "you wish to run, and the identity of the current host are looked up to\n"
394 "ensure that you have permission to do this.\n"
396 "Note that logs are kept of all uses of this program.\n"
398 "Options available are:\n"
400 "-h, --help Display this help text\n"
401 "-u, --usage Display a short usage summary\n"
402 "-v, --version Display $'s version number\n"
404 "-e, --preserve-environment Try to preserve the current environment\n"
405 "-s, --su, --set-user Set environment variables to reflect USER\n"
406 "-l, --login Really log in as USER\n"
408 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
409 #ifdef HAVE_SETGROUPS
410 "-k, --keep-groups Keep your current set of groups\n"
411 "-m, --merge-groups Merge the lists of groups\n"
412 "-r, --replace-groups Replace the list of groups\n"
415 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
417 "-d, --daemon Start a daemon\n"
418 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
419 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
424 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
427 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
428 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
434 * Arguments: @int argc@ = number of command line arguments
435 * @char *argv[]@ = pointer to the various arguments
437 * Returns: Zero if successful.
439 * Use: Allows a user to change UID.
442 int main(int argc, char *argv[])
444 /* --- Request block setup parameters --- */
446 request rq; /* Request buffer to build */
447 char *cmd = 0; /* Shell command to execute */
448 char *binary = "/bin/sh"; /* Default binary to execute */
449 char **env = environ; /* Default environment to pass */
450 char **todo = 0; /* Pointer to argument list */
451 char *who = 0; /* Who we're meant to become */
452 struct passwd *from_pw = 0; /* User we are right now */
453 struct passwd *to_pw = 0; /* User we want to become */
455 /* --- Become server setup parameters --- */
457 char *conffile = file_RULES; /* Default config file for daemon */
458 int port = 0; /* Default port for daemon */
460 /* --- Miscellanous shared variables --- */
462 unsigned flags = 0; /* Various useful flags */
463 int style = DEFAULT_LOGIN_STYLE; /* Login style */
464 gid_t group = -1; /* Default group to set */
465 int gstyle = g_unset; /* No group style set yet */
467 #ifdef HAVE_SETGROUPS
468 gid_t groups[NGROUPS_MAX]; /* Set of groups */
469 int ngroups; /* Number of groups in the set */
472 /* --- Default argument list executes a shell command --- */
474 static char *shell[] = {
475 "/bin/sh", /* Bourne shell */
476 "-c", /* Read from command line */
477 0, /* Pointer to shell command */
481 /* --- Definitions for the various flags --- */
484 f_daemon = 1, /* Start up in daemon mode */
485 f_duff = 2, /* Fault in arguments */
486 f_shell = 4, /* Run a default shell */
487 f_dummy = 8, /* Don't actually do anything */
488 f_setuid = 16, /* We're running setuid */
489 f_havegroup = 32 /* Set a default group */
492 /* --- Set up the program name --- */
496 if (getuid() != geteuid())
499 /* --- Read the environment into a hashtable --- */
504 sym_createTable(&bc__env);
505 for (p = environ; *p; p++)
506 bc__putenv(0, *p, 0, 0);
509 /* --- Parse some command line arguments --- */
513 static struct option opts[] = {
515 /* --- Asking for help --- */
517 { "help", 0, 0, 'h' },
518 { "usage", 0, 0, 'u' },
519 { "version", 0, 0, 'v' },
521 /* --- Login style options --- */
523 { "preserve-environment", 0, 0, 'e' },
525 { "set-user", 0, 0, 's' },
526 { "login", 0, 0, 'l' },
528 /* --- Group style options --- */
530 { "group", gFlag_argReq, 0, 'g' },
531 #ifdef HAVE_SETGROUPS
532 { "keep-groups", 0, 0, 'k' },
533 { "merge-groups", 0, 0, 'm' },
534 { "replace-groups", 0, 0, 'r' },
537 /* --- Command to run options --- */
539 { "command", gFlag_argReq, 0, 'c' },
541 /* --- Server options --- */
543 { "daemon", 0, 0, 'd' },
544 { "port", gFlag_argReq, 0, 'p' },
545 { "config-file", gFlag_argReq, 0, 'f' },
547 /* --- Tracing options --- */
550 { "impersonate", gFlag_argReq, 0, 'I' },
551 { "trace", gFlag_argOpt, 0, 'T' },
552 { "trace-level", gFlag_argOpt, 0, 'L' },
558 i = mdwopt(argc, argv,
559 "-" /* Return non-options as options */
560 "huv" /* Asking for help */
561 "esl" /* Login style options */
562 #ifdef HAVE_SETGROUPS
563 "g:kmr" /* Group style options */
565 "g:" /* Group (without @setgroups@) */
567 "c:" /* Command to run options */
568 "dp:f:" /* Server options */
570 "I:T::L::" /* Tracing options */
573 opts, 0, 0, gFlag_envVar);
579 /* --- Asking for help --- */
582 bc__help(stdout, flags & f_setuid);
594 /* --- Login style options --- */
606 /* --- Group style options --- */
609 if (isdigit((unsigned char)optarg[0]))
610 group = atoi(optarg);
612 struct group *gr = getgrnam(optarg);
614 die("unknown group `%s'", optarg);
617 flags |= f_havegroup;
630 /* --- Command to run options --- */
636 /* --- Server options --- */
639 if (isdigit((unsigned char)optarg[0]))
640 port = htons(atoi(optarg));
642 struct servent *s = getservbyname(optarg, "udp");
644 die("unknown service name `%s'", optarg);
655 /* --- Pretend to be a different user --- *
657 * There are all sorts of nasty implications for this option. Don't
658 * allow it if we're running setuid. Disable the actual login anyway.
664 if (flags & f_setuid)
665 moan("shan't allow impersonation while running setuid");
668 if (isdigit((unsigned char)optarg[0]))
669 pw = getpwuid(atoi(optarg));
671 pw = getpwnam(optarg);
673 die("can't impersonate unknown user `%s'", optarg);
674 from_pw = userdb_copyUser(pw);
675 rq.from = from_pw->pw_uid;
682 /* --- Tracing support --- *
684 * Be careful not to zap a file I wouldn't normally be allowed to write
693 if (optarg == 0 || strcmp(optarg, "-") == 0)
696 uid_t eu = geteuid(), ru = getuid();
699 if (setreuid(eu, ru))
704 die("couldn't temporarily give up privileges: %s",
708 if ((fp = fopen(optarg, "w")) == 0) {
709 die("couldn't open trace file `%s' for writing: %s",
710 optarg, strerror(errno));
714 if (setreuid(ru, eu))
718 die("couldn't regain privileges: %s", strerror(errno));
720 traceon(fp, TRACE_DFL);
721 trace(TRACE_MISC, "become: tracing enabled");
726 /* --- Setting trace levels --- */
732 unsigned int lvl = 0, l;
733 const char *p = optarg;
735 /* --- Table of tracing facilities --- */
743 static tr lvltbl[] = {
744 { 'm', TRACE_MISC, "miscellaneous messages" },
745 { 's', TRACE_SETUP, "building the request block" },
746 { 'd', TRACE_DAEMON, "server process" },
747 { 'r', TRACE_RULE, "ruleset scanning" },
748 { 'c', TRACE_CHECK, "request checking" },
749 { 'l', TRACE_CLIENT, "client process" },
750 { 'R', TRACE_RAND, "random number generator" },
751 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
752 { 'y', TRACE_YACC, "parsing configuration file" },
753 { 'D', TRACE_DFL, "default tracing options" },
754 { 'A', TRACE_ALL, "all tracing options" },
759 /* --- Output some help if there's no arguemnt --- */
767 for (tp = lvltbl; tp->l; tp++) {
768 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
769 printf("%c -- %s\n", tp->ch, tp->help);
773 "Also, `+' and `-' options are recognised to turn on and off various\n"
774 "tracing options. For example, `A-r' enables everything except ruleset\n"
775 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
787 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
790 if (flags & f_setuid)
793 lvl = sense ? (lvl | l) : (lvl & ~l);
795 moan("unknown trace option `%c'", *p);
801 yydebug = ((lvl & TRACE_YACC) != 0);
806 /* --- Something that wasn't an option --- *
808 * The following nasties are supported:
810 * * NAME=VALUE -- preserve NAME, and give it a VALUE
811 * * NAME= -- preserve NAME, and give it an empty value
812 * * NAME- -- delete NAME
813 * * NAME! -- preserve NAME with existing value
815 * Anything else is either the user name (which is OK) or the start of
816 * the command (in which case I stop and read the rest of the command).
820 size_t sz = strcspn(optarg, "=-!");
823 /* --- None of the above --- */
825 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
835 /* --- Do the appropriate thing --- */
837 switch (optarg[sz]) {
839 bc__putenv(0, optarg, envFlag_preserve, 1);
843 bc__putenv(optarg, 0, 0, 1);
847 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
848 e->f |= envFlag_preserve;
853 /* --- Something I didn't understand has occurred --- */
862 if (flags & f_duff) {
867 /* --- Switch to daemon mode if requested --- */
869 if (flags & f_daemon) {
870 T( trace(TRACE_MISC, "become: daemon mode requested"); )
871 daemon_init(conffile, port);
875 /* --- Open a syslog --- */
877 openlog(quis(), 0, LOG_AUTH);
879 /* --- Pick out the uid --- */
889 if (isdigit((unsigned char)who[0]))
890 pw = getpwuid(atoi(who));
894 die("unknown user `%s'", who);
895 to_pw = userdb_copyUser(pw);
899 /* --- Fill in the easy bits of the request --- */
905 pw = getpwuid(rq.from);
907 die("who are you? (can't find user %li)", (long)rq.from);
908 from_pw = userdb_copyUser(pw);
911 /* --- Find the local host address --- */
917 if ((he = gethostbyname(u.nodename)) == 0)
918 die("who am I? (can't resolve `%s')", u.nodename);
919 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
922 /* --- Fiddle with group ownerships a bit --- */
925 #ifdef HAVE_SETGROUPS
926 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
930 /* --- Set the default login group, if there is one --- */
932 if (~flags & f_havegroup)
933 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
935 #ifndef HAVE_SETGROUPS
937 /* --- Check that it's valid --- */
939 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
940 die("invalid default group");
944 /* --- Set the default group style --- */
946 if (gstyle == g_unset)
947 gstyle = (style == l_login) ? g_replace : g_merge;
949 /* --- Read in my current set of groups --- */
951 n_fgr = getgroups(NGROUPS_MAX, from_gr);
953 /* --- Now read the groups for the target user --- *
955 * Do this even if I'm using the @g_keep@ style -- I'll use it for
956 * checking that @group@ is valid.
964 to_gr[n_tgr++] = to_pw->pw_gid;
967 while ((gr = getgrent()) != 0) {
968 if (gr->gr_gid == to_gr[0])
970 for (p = gr->gr_mem; *p; p++) {
971 if (strcmp(to_pw->pw_name, *p) == 0) {
972 to_gr[n_tgr++] = gr->gr_gid;
973 if (n_tgr >= NGROUPS_MAX)
984 /* --- Check that @group@ is reasonable --- */
989 if (group == getgid() || group == from_pw->pw_gid)
991 for (i = 0; i < n_fgr; i++) {
992 if (group == from_gr[i])
995 for (i = 0; i < n_tgr; i++) {
996 if (group == to_gr[i])
999 die("invalid default group");
1003 /* --- Phew. Now comes the hard bit --- */
1011 if (gstyle & g_keep) {
1013 ga[i++] = from_pw->pw_gid;
1015 if (gstyle & g_replace)
1016 ga[i++] = to_pw->pw_gid;
1018 /* --- Style authorities will become apoplectic if shown this --- *
1020 * As far as I can see, it's the neatest way of writing it.
1024 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1025 ((gstyle & g_keep) &&
1026 bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
1027 ((gstyle & g_replace) &&
1028 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1033 /* --- Trace the results of all this --- */
1035 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1037 #ifdef HAVE_SETGROUPS
1038 IF_TRACING(TRACE_SETUP, {
1041 for (i = 1; i < ngroups; i++)
1042 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1047 /* --- Shell commands are easy --- */
1054 /* --- A command given on the command line isn't too hard --- */
1056 else if (optind < argc) {
1057 todo = argv + optind;
1066 /* --- An unadorned becoming requires little work --- */
1069 shell[0] = getenv("SHELL");
1071 shell[0] = from_pw->pw_shell;
1077 /* --- An su-like login needs slightly less effort --- */
1080 shell[0] = to_pw->pw_shell;
1086 /* --- A login request needs a little bit of work --- */
1089 const char *p = strrchr(to_pw->pw_shell, '/');
1094 p = to_pw->pw_shell;
1095 shell[0] = xmalloc(strlen(p) + 2);
1097 strcpy(shell[0] + 1, p);
1100 binary = to_pw->pw_shell;
1105 /* --- Mangle the environment --- *
1107 * This keeps getting more complicated all the time. (How true. Now I've
1108 * got all sorts of nasty environment mangling to do.)
1110 * The environment stuff now happens in seven phases:
1112 * 1. Mark very special variables to be preserved. Currently only TERM
1113 * and DISPLAY are treated in this way.
1115 * 2. Set and preserve Become's own environment variables.
1117 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1118 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1120 * 4. If we're preserving the environment or being `su'-like, process the
1121 * PATH variable a little. Otherwise reset it to something
1124 * 5. If we're being `login'-like, expunge all unpreserved variables.
1126 * 6. Expunge any security-critical variables.
1128 * 7. Build a new environment table to pass to child processes.
1132 /* --- Variables to be preserved always --- *
1134 * A user can explicitly expunge a variable in this list, in which case
1135 * we never get to see it here.
1138 static char *preserve[] = {
1139 "TERM", "DISPLAY", 0
1142 /* --- Variables to be expunged --- *
1144 * Any environment string which has one of the following as a prefix will
1145 * be expunged from the environment passed to the called process. The
1146 * first line lists variables which have been used to list search paths
1147 * for shared libraries: by manipulating these, an attacker could replace
1148 * a standard library with one of his own. The second line lists other
1149 * well-known dangerous environment variables.
1152 static char *banned[] = {
1153 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1154 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1158 /* --- Other useful variables --- */
1167 /* --- Stage one. Preserve display-specific variables --- */
1169 for (pp = preserve; *pp; pp++) {
1170 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1171 e->f |= envFlag_preserve;
1174 /* --- Stage two. Set Become's own variables --- */
1176 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1178 bc__setenv(e, from_pw->pw_name);
1179 e->f |= envFlag_preserve;
1181 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1183 bc__setenv(e, from_pw->pw_dir);
1184 e->f |= envFlag_preserve;
1186 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1187 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1188 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1189 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1191 /* --- Stage three. Set user identity --- */
1195 static char *maildirs[] = {
1196 "/var/spool/mail", "/var/mail",
1197 "/usr/spool/mail", "/usr/mail",
1203 for (pp = maildirs; *pp; pp++) {
1204 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1205 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1206 bc__putenv("MAIL", b, envFlag_preserve, 0);
1210 } /* Fall through */
1213 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1214 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1215 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1216 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1220 /* --- Stage four. Set the user's PATH properly --- */
1223 /* --- Find an existing path --- *
1225 * If there's no path, or this is a login, then set a default path,
1226 * unless we're meant to preserve the existing one. Whew!
1229 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1231 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1233 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1234 envFlag_preserve, 0);
1237 /* --- Find the string --- */
1239 e->f = envFlag_preserve;
1240 p = strchr(e->val, '=') + 1;
1243 /* --- Write the new version to a dynamically allocated buffer --- */
1245 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1246 strcpy(e->val, "PATH=");
1249 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1264 /* --- Stages five and six. Expunge variables and count numbers --- *
1266 * Folded together, so I only need one pass through the table. Also
1267 * count the number of variables needed at this time.
1272 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1274 /* --- Login style expunges all unpreserved variables --- */
1276 if (style == l_login && ~e->f & envFlag_preserve)
1279 /* --- Otherwise just check the name against the list --- */
1281 for (pp = banned; *pp; pp++) {
1284 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1286 } else if (strcmp(e->_base.name, *pp) == 0)
1294 sym_remove(&bc__env, e);
1297 /* --- Stage seven. Build the new environment block --- */
1299 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1301 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1306 /* --- Trace the command --- */
1308 IF_TRACING(TRACE_SETUP, {
1311 trace(TRACE_SETUP, "setup: from user %s to user %s",
1312 from_pw->pw_name, to_pw->pw_name);
1313 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1314 for (i = 0; todo[i]; i++)
1315 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1316 for (i = 0; env[i]; i++)
1317 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1320 /* --- If necessary, resolve the path to the command --- */
1322 if (!strchr(binary, '/')) {
1326 if ((p = getenv("PATH")) == 0)
1327 p = "/bin:/usr/bin";
1330 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1332 /* --- Check length of string before copying --- */
1334 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1337 /* --- Now build the pathname and check it --- */
1339 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1340 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1341 st.st_mode & 0111 && /* Check it's executable */
1342 S_ISREG(st.st_mode)) /* Check it's a file */
1347 die("couldn't find `%s' in path", todo[0]);
1351 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1353 /* --- Canonicalise the path string, if necessary --- */
1360 /* --- Insert current directory name if path not absolute --- */
1365 if (!getcwd(b, sizeof(b)))
1366 die("couldn't read current directory: %s", strerror(errno));
1371 /* --- Now copy over characters from the path string --- */
1375 /* --- Check for buffer overflows here --- *
1377 * I write at most one byte per iteration so this is OK. Remember to
1378 * allow one for the null byte.
1381 if (p >= b + sizeof(b) - 1)
1382 die("internal error: buffer overflow while canonifying path");
1384 /* --- Reduce multiple slashes to just one --- */
1392 /* --- Handle dots in filenames --- *
1394 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1395 * we've just stuck the current directory on the end of the buffer,
1396 * or we've just put something else on the end.
1399 else if (*q == '.' && p[-1] == '/') {
1401 /* --- A simple `./' just gets removed --- */
1403 if (q[1] == 0 || q[1] == '/') {
1409 /* --- A `../' needs to be peeled back to the previous `/' --- */
1411 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1414 while (p > b && p[-1] != '/')
1427 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1429 /* --- Run the check --- *
1431 * If the user is already what she wants to be, then print a warning.
1432 * Then, if I was just going to spawn a shell, quit, to reduce user
1433 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1434 * checking if we're already root -- root can do anything anyway, and at
1435 * least this way we get some logging done, and offer a more friendly
1439 if (rq.from == rq.to) {
1440 moan("you already are `%s'!", to_pw->pw_name);
1441 if (flags & f_shell) {
1442 moan("(to prevent confusion, I'm not spawning a shell)");
1446 int a = (rq.from == 0) || check(&rq);
1449 "permission %s for %s to become %s to run `%s'",
1450 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1454 die("permission denied");
1457 /* --- Now do the job --- */
1459 T( trace(TRACE_MISC, "become: permission granted"); )
1461 if (flags & f_dummy) {
1462 puts("permission granted");
1466 #ifdef HAVE_SETGROUPS
1467 if (setgroups(ngroups, groups) < 0)
1468 die("couldn't set groups: %s", strerror(errno));
1471 if (setgid(group) < 0)
1472 die("couldn't set default group: %s", strerror(errno));
1473 if (setuid(rq.to) < 0)
1474 die("couldn't set uid: %s", strerror(errno));
1476 /* --- If this was a login, change current directory --- */
1478 if (flags & f_shell && style == l_login && chdir(to_pw->pw_dir) < 0) {
1479 moan("couldn't change directory to `%s': %s",
1480 to_pw->pw_dir, strerror(errno));
1483 /* --- Finally, call the program --- */
1486 execve(rq.cmd, todo, env);
1487 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1491 /*----- That's all, folks -------------------------------------------------*/