3 * $Id: become.c,v 1.9 1997/09/10 10:28:05 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.9 1997/09/10 10:28:05 mdw
33 * Allow default port to be given as a service name or port number. Handle
34 * groups properly (lots of options here).
36 * Revision 1.8 1997/09/08 13:56:24 mdw
37 * Change criteria for expunging items from the user's PATH: instead of
38 * removing things starting with `.', remove things not starting with `/'.
40 * Revision 1.7 1997/09/08 13:43:20 mdw
41 * Change userid when creating tracefiles rather than fiddling with
42 * `access': it works rather better. Also, insert some stdio buffer
43 * flushing to ensure tracedumps are completely written.
45 * Revision 1.6 1997/09/05 13:47:44 mdw
46 * Make the `-L' (trace-level) option's argument optional, like the long
49 * Revision 1.5 1997/09/05 11:45:19 mdw
50 * Add support for different login styles, and environment variable
51 * manipulation in a safe and useful way.
53 * Revision 1.4 1997/08/20 16:15:13 mdw
54 * Overhaul of environment handling. Fix daft bug in path search code.
56 * Revision 1.3 1997/08/07 16:28:59 mdw
57 * Do something useful when users attempt to become themselves.
59 * Revision 1.2 1997/08/04 10:24:20 mdw
60 * Sources placed under CVS control.
62 * Revision 1.1 1997/07/21 13:47:54 mdw
67 /*----- Header files ------------------------------------------------------*/
69 /* --- ANSI headers --- */
79 /* --- Unix headers --- */
81 #include <sys/types.h>
83 #include <sys/socket.h>
84 #include <sys/utsname.h>
86 #include <netinet/in.h>
88 #include <arpa/inet.h>
96 extern char **environ;
98 /* --- Local headers --- */
113 /*----- Type definitions --------------------------------------------------*/
115 /* --- Symbol table entry for an environment variable --- */
117 typedef struct sym_env {
118 sym_base _base; /* Symbol table information */
119 unsigned f; /* Flags word (see below) */
120 char *val; /* Pointer to variable value */
123 /* --- Environment variable flags --- */
129 /* --- Login behaviour types --- */
132 l_preserve, /* Preserve the environment */
133 l_setuser, /* Update who I am */
134 l_login /* Do a full login */
137 /* --- Group behaviour types --- *
139 * Note that these make a handy bitfield.
142 #ifdef HAVE_SETGROUPS
145 g_unset = 0, /* Nobody's set a preference */
146 g_keep = 1, /* Leave the group memberships */
147 g_replace = 2, /* Replace group memberships */
148 g_merge = (g_keep | g_replace) /* Merge the group memberships */
153 /*----- Static variables --------------------------------------------------*/
155 static sym_table bc__env;
157 /*----- Main code ---------------------------------------------------------*/
159 /* --- @bc__write@ --- *
161 * Arguments: @FILE *fp@ = pointer to a stream to write on
162 * @const char *p@ = pointer to a string
166 * Use: Writes the string to the stream, substituting the program
167 * name (as returned by @quis@) for each occurrence of the
171 static void bc__write(FILE *fp, const char *p)
173 const char *n = quis();
175 size_t nl = strlen(n);
177 /* --- Try to be a little efficient --- *
179 * Gather up non-`$' characters using @strcspn@ and spew them out really
190 fwrite(n, nl, 1, fp);
195 /* --- @bc__setenv@ --- *
197 * Arguments: @sym_env *e@ = pointer to environment variable block
198 * @const char *val@ = value to set
202 * Use: Sets an environment variable block to the right value.
205 static void bc__setenv(sym_env *e, const char *val)
207 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
208 sprintf(e->val, "%s=%s", e->_base.name, val);
211 /* --- @bc__putenv@ --- *
213 * Arguments: @const char *var@ = name of the variable to set, or 0 if
214 * this is embedded in the value string
215 * @const char *val@ = value to set, or 0 if the variable must
217 * @unsigned int fl@ = flags to set
218 * @unsigned int force@ = force overwrite of preserved variables
220 * Returns: Pointer to symbol block, or zero if it was deleted.
222 * Use: Puts an item into the environment.
225 static sym_env *bc__putenv(const char *var, const char *val,
226 unsigned int fl, unsigned int force)
233 /* --- Sort out embedded variable names --- */
236 const char *p = strchr(val, '=');
251 /* --- Find the variable block --- */
254 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
255 if (!f || ~e->f & envFlag_preserve || force) {
261 e = sym_find(&bc__env, var, -1, 0, 0);
262 if (e && (force || ~e->f & envFlag_preserve))
263 sym_remove(&bc__env, e);
267 /* --- Tidy up and return --- */
276 /* --- @bc__addGroups@ --- *
278 * Arguments: @gid_t *g@ = pointer to a group array
279 * @int *png@ = pointer to number of entries in the array
280 * @const gid_t *a@ = pointer to groups to add
281 * @int na@ = number of groups to add
283 * Returns: Zero if it was OK, nonzero if we should stop now.
285 * Use: Adds groups to a groups array.
288 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
293 for (i = 0; i < na; i++) {
295 /* --- Ensure this group isn't already in the list --- */
297 for (j = 0; j < ng; j++) {
302 /* --- See if there's room for more --- */
304 if (ng > NGROUPS_MAX) {
305 moan("too many groups (system limit exceeded -- some have been lost");
310 /* --- Add the group --- */
320 /* --- @bc__banner@ --- *
322 * Arguments: @FILE *fp@ = stream to write on
326 * Use: Writes a banner containing copyright information.
329 static void bc__banner(FILE *fp)
331 bc__write(fp, "$ version " VERSION "\n");
334 /* --- @bc__usage@ --- *
336 * Arguments: @FILE *fp@ = stream to write on
340 * Use: Writes a terse reminder of command line syntax.
343 static void bc__usage(FILE *fp)
347 " $ -c <shell-command> <user>\n"
348 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
349 " $ -d [-p <port>] [-f <config-file>]\n");
352 /* --- @bc__help@ --- *
354 * Arguments: @FILE *fp@ = stream to write on
355 * @int suid@ = whether we're running set-uid
359 * Use: Displays a help message for this excellent piece of software.
362 static void bc__help(FILE *fp, int suid)
369 "The `$' program allows you to run a process as another user.\n"
370 "If a command name is given, this is the process executed. If the `-c'\n"
371 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
372 "given, your default login shell is used.\n"
374 "Your user id, the user id you wish to become, the name of the process\n"
375 "you wish to run, and the identity of the current host are looked up to\n"
376 "ensure that you have permission to do this.\n"
378 "Note that logs are kept of all uses of this program.\n"
380 "Options available are:\n"
382 "-h, --help Display this help text\n"
383 "-u, --usage Display a short usage summary\n"
384 "-v, --version Display $'s version number\n"
386 "-e, --preserve-environment Try to preserve the current environment\n"
387 "-s, --su, --set-user Set environment variables to reflect USER\n"
388 "-l, --login Really log in as USER\n"
390 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
391 #ifdef HAVE_SETGROUPS
392 "-k, --keep-groups Keep your current set of groups\n"
393 "-m, --merge-groups Merge the lists of groups\n"
394 "-r, --replace-groups Replace the list of groups\n"
397 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
399 "-d, --daemon Start a daemon\n"
400 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
401 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
406 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
409 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
410 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
416 * Arguments: @int argc@ = number of command line arguments
417 * @char *argv[]@ = pointer to the various arguments
419 * Returns: Zero if successful.
421 * Use: Allows a user to change UID.
424 int main(int argc, char *argv[])
426 /* --- Request block setup parameters --- */
428 request rq; /* Request buffer to build */
429 char *cmd = 0; /* Shell command to execute */
430 char *binary = "/bin/sh"; /* Default binary to execute */
431 char **env = environ; /* Default environment to pass */
432 char **todo = 0; /* Pointer to argument list */
433 char *who = 0; /* Who we're meant to become */
434 struct passwd *from_pw = 0; /* User we are right now */
435 struct passwd *to_pw = 0; /* User we want to become */
437 /* --- Become server setup parameters --- */
439 char *conffile = file_RULES; /* Default config file for daemon */
440 int port = -1; /* Default port for daemon */
442 /* --- Miscellanous shared variables --- */
444 unsigned flags = 0; /* Various useful flags */
445 int style = DEFAULT_LOGIN_STYLE; /* Login style */
446 gid_t group = -1; /* Default group to set */
447 int gstyle = g_unset; /* No group style set yet */
449 #ifdef HAVE_SETGROUPS
450 gid_t groups[NGROUPS_MAX]; /* Set of groups */
451 int ngroups; /* Number of groups in the set */
454 /* --- Default argument list executes a shell command --- */
456 static char *shell[] = {
457 "/bin/sh", /* Bourne shell */
458 "-c", /* Read from command line */
459 0, /* Pointer to shell command */
463 /* --- Definitions for the various flags --- */
466 f_daemon = 1, /* Start up in daemon mode */
467 f_duff = 2, /* Fault in arguments */
468 f_login = 4, /* Execute as a login shell */
469 f_dummy = 8, /* Don't actually do anything */
470 f_setuid = 16, /* We're running setuid */
471 f_havegroup = 32 /* Set a default group */
474 /* --- Set up the program name --- */
478 if (getuid() != geteuid())
481 /* --- Read the environment into a hashtable --- */
486 sym_createTable(&bc__env);
487 for (p = environ; *p; p++)
488 bc__putenv(0, *p, 0, 0);
491 /* --- Parse some command line arguments --- */
495 static struct option opts[] = {
497 /* --- Asking for help --- */
499 { "help", 0, 0, 'h' },
500 { "usage", 0, 0, 'u' },
501 { "version", 0, 0, 'v' },
503 /* --- Login style options --- */
505 { "preserve-environment", 0, 0, 'e' },
507 { "set-user", 0, 0, 's' },
508 { "login", 0, 0, 'l' },
510 /* --- Group style options --- */
512 { "group", gFlag_argReq, 0, 'g' },
513 #ifdef HAVE_SETGROUPS
514 { "keep-groups", 0, 0, 'k' },
515 { "merge-groups", 0, 0, 'm' },
516 { "replace-groups", 0, 0, 'r' },
519 /* --- Command to run options --- */
521 { "command", gFlag_argReq, 0, 'c' },
523 /* --- Server options --- */
525 { "daemon", 0, 0, 'd' },
526 { "port", gFlag_argReq, 0, 'p' },
527 { "config-file", gFlag_argReq, 0, 'f' },
529 /* --- Tracing options --- */
532 { "impersonate", gFlag_argReq, 0, 'I' },
533 { "trace", gFlag_argOpt, 0, 'T' },
534 { "trace-level", gFlag_argOpt, 0, 'L' },
540 i = mdwopt(argc, argv,
541 "-" /* Return non-options as options */
542 "huv" /* Asking for help */
543 "esl" /* Login style options */
544 #ifdef HAVE_SETGROUPS
545 "g:kmr" /* Group style options */
547 "g:" /* Group (without @setgroups@) */
549 "c:" /* Command to run options */
550 "dp:f:" /* Server options */
552 "I:T::L::" /* Tracing options */
555 opts, 0, 0, gFlag_envVar);
561 /* --- Asking for help --- */
564 bc__help(stdout, flags & f_setuid);
576 /* --- Login style options --- */
588 /* --- Group style options --- */
591 if (isdigit((unsigned char)optarg[0]))
592 group = atoi(optarg);
594 struct group *gr = getgrnam(optarg);
596 die("unknown group `%s'", optarg);
599 flags |= f_havegroup;
612 /* --- Command to run options --- */
618 /* --- Server options --- */
621 if (isdigit((unsigned char)optarg[0]))
622 port = htons(atoi(optarg));
624 struct servent *s = getservbyname(optarg, "udp");
626 die("unknown service name `%s'", optarg);
637 /* --- Pretend to be a different user --- *
639 * There are all sorts of nasty implications for this option. Don't
640 * allow it if we're running setuid. Disable the actual login anyway.
646 if (flags & f_setuid)
647 moan("shan't allow impersonation while running setuid");
650 if (isdigit((unsigned char)optarg[0]))
651 pw = getpwuid(atoi(optarg));
653 pw = getpwnam(optarg);
655 die("can't impersonate unknown user `%s'", optarg);
656 from_pw = userdb_copyUser(pw);
657 rq.from = from_pw->pw_uid;
664 /* --- Tracing support --- *
666 * Be careful not to zap a file I wouldn't normally be allowed to write
675 if (optarg == 0 || strcmp(optarg, "-") == 0)
678 uid_t eu = geteuid(), ru = getuid();
681 if (setreuid(eu, ru))
686 die("couldn't temporarily give up privileges: %s",
690 if ((fp = fopen(optarg, "w")) == 0) {
691 die("couldn't open trace file `%s' for writing: %s",
692 optarg, strerror(errno));
696 if (setreuid(ru, eu))
700 die("couldn't regain privileges: %s", strerror(errno));
702 traceon(fp, TRACE_DFL);
703 trace(TRACE_MISC, "become: tracing enabled");
708 /* --- Setting trace levels --- */
714 unsigned int lvl = 0, l;
715 const char *p = optarg;
717 /* --- Table of tracing facilities --- */
725 static tr lvltbl[] = {
726 { 'm', TRACE_MISC, "miscellaneous messages" },
727 { 's', TRACE_SETUP, "building the request block" },
728 { 'd', TRACE_DAEMON, "server process" },
729 { 'r', TRACE_RULE, "ruleset scanning" },
730 { 'c', TRACE_CHECK, "request checking" },
731 { 'l', TRACE_CLIENT, "client process" },
732 { 'R', TRACE_RAND, "random number generator" },
733 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
734 { 'y', TRACE_YACC, "parsing configuration file" },
735 { 'D', TRACE_DFL, "default tracing options" },
736 { 'A', TRACE_ALL, "all tracing options" },
741 /* --- Output some help if there's no arguemnt --- */
749 for (tp = lvltbl; tp->l; tp++) {
750 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
751 printf("%c -- %s\n", tp->ch, tp->help);
755 "Also, `+' and `-' options are recognised to turn on and off vavrious\n"
756 "tracing options. For example, `A-r' enables everything except ruleset\n"
757 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
769 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
772 if (flags & f_setuid)
775 lvl = sense ? (lvl | l) : (lvl & ~l);
777 moan("unknown trace option `%c'", *p);
783 yydebug = ((lvl & TRACE_YACC) != 0);
788 /* --- Something that wasn't an option --- *
790 * The following nasties are supported:
792 * * NAME=VALUE -- preserve NAME, and give it a VALUE
793 * * NAME= -- preserve NAME, and give it an empty value
794 * * NAME- -- delete NAME
795 * * NAME! -- preserve NAME with existing value
797 * Anything else is either the user name (which is OK) or the start of
798 * the command (in which case I stop and read the rest of the command).
802 size_t sz = strcspn(optarg, "=-!");
805 /* --- None of the above --- */
807 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
816 /* --- Do the appropriate thing --- */
818 switch (optarg[sz]) {
820 bc__putenv(0, optarg, envFlag_preserve, 1);
824 bc__putenv(optarg, 0, 0, 1);
828 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
829 e->f |= envFlag_preserve;
834 /* --- Something I didn't understand has occurred --- */
843 if (flags & f_duff) {
848 /* --- Switch to daemon mode if requested --- */
850 if (flags & f_daemon) {
851 T( trace(TRACE_MISC, "become: daemon mode requested"); )
852 daemon_init(conffile, port);
856 /* --- Open a syslog --- */
858 openlog(quis(), 0, LOG_AUTH);
860 /* --- Pick out the uid --- */
870 if (isdigit((unsigned char)who[0]))
871 pw = getpwuid(atoi(who));
875 die("unknown user `%s'", who);
876 to_pw = userdb_copyUser(pw);
880 /* --- Fill in the easy bits of the request --- */
886 pw = getpwuid(rq.from);
888 die("who are you? (can't find user %li)", (long)rq.from);
889 from_pw = userdb_copyUser(pw);
892 /* --- Find the local host address --- */
898 if ((he = gethostbyname(u.nodename)) == 0)
899 die("who am I? (can't resolve `%s')", u.nodename);
900 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
903 /* --- Fiddle with group ownerships a bit --- */
906 #ifdef HAVE_SETGROUPS
907 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
911 /* --- Set the default login group, if there is one --- */
913 if (~flags & f_havegroup)
914 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
916 #ifndef HAVE_SETGROUPS
918 /* --- Check that it's valid --- */
920 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
921 die("invalid default group");
925 /* --- Set the default group style --- */
927 if (gstyle == g_unset)
928 gstyle = (style == l_login) ? g_replace : g_merge;
930 /* --- Read in my current set of groups --- */
932 n_fgr = getgroups(NGROUPS_MAX, from_gr);
934 /* --- Now read the groups for the target user --- *
936 * Do this even if I'm using the @g_keep@ style -- I'll use it for
937 * checking that @group@ is valid.
945 to_gr[n_tgr++] = to_pw->pw_gid;
948 while ((gr = getgrent()) != 0) {
949 if (gr->gr_gid == to_gr[0])
951 for (p = gr->gr_mem; *p; p++) {
952 if (strcmp(to_pw->pw_name, *p) == 0) {
953 to_gr[n_tgr++] = gr->gr_gid;
954 if (n_tgr >= NGROUPS_MAX)
965 /* --- Check that @group@ is reasonable --- */
970 if (group == getgid() || group == from_pw->pw_gid)
972 for (i = 0; i < n_fgr; i++) {
973 if (group == from_gr[i])
976 for (i = 0; i < n_tgr; i++) {
977 if (group == to_gr[i])
980 die("invalid default group");
984 /* --- Phew. Now comes the hard bit --- */
992 if (gstyle & g_keep) {
994 ga[i++] = from_pw->pw_gid;
996 if (gstyle & g_replace)
997 ga[i++] = to_pw->pw_gid;
999 /* --- Style authorities will become apoplectic if shown this --- *
1001 * As far as I can see, it's the neatest way of writing it.
1005 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1006 ((gstyle & g_keep) &&
1007 bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
1008 ((gstyle & g_replace) &&
1009 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1014 /* --- Trace the results of all this --- */
1016 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1018 #ifdef HAVE_SETGROUPS
1019 IF_TRACING(TRACE_SETUP, {
1022 for (i = 1; i < ngroups; i++)
1023 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1028 /* --- Shell commands are easy --- */
1035 /* --- A command given on the command line isn't too hard --- */
1037 else if (optind < argc) {
1038 todo = argv + optind;
1042 else switch (style) {
1044 /* --- An unadorned becoming requires little work --- */
1047 shell[0] = getenv("SHELL");
1049 shell[0] = from_pw->pw_shell;
1055 /* --- An su-like login needs slightly less effort --- */
1058 shell[0] = to_pw->pw_shell;
1064 /* --- A login request needs a little bit of work --- */
1067 const char *p = strrchr(to_pw->pw_shell, '/');
1072 p = to_pw->pw_shell;
1073 shell[0] = xmalloc(strlen(p) + 2);
1075 strcpy(shell[0] + 1, p);
1078 binary = to_pw->pw_shell;
1079 chdir(to_pw->pw_dir);
1083 /* --- Mangle the environment --- *
1085 * This keeps getting more complicated all the time. (How true. Now I've
1086 * got all sorts of nasty environment mangling to do.)
1088 * The environment stuff now happens in seven phases:
1090 * 1. Mark very special variables to be preserved. Currently only TERM
1091 * and DISPLAY are treated in this way.
1093 * 2. Set and preserve Become's own environment variables.
1095 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1096 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1098 * 4. If we're preserving the environment or being `su'-like, process the
1099 * PATH variable a little. Otherwise reset it to something
1102 * 5. If we're being `login'-like, expunge all unpreserved variables.
1104 * 6. Expunge any security-critical variables.
1106 * 7. Build a new environment table to pass to child processes.
1110 /* --- Variables to be preserved always --- *
1112 * A user can explicitly expunge a variable in this list, in which case
1113 * we never get to see it here.
1116 static char *preserve[] = {
1117 "TERM", "DISPLAY", 0
1120 /* --- Variables to be expunged --- *
1122 * Any environment string which has one of the following as a prefix will
1123 * be expunged from the environment passed to the called process. The
1124 * first line lists variables which have been used to list search paths
1125 * for shared libraries: by manipulating these, an attacker could replace
1126 * a standard library with one of his own. The second line lists other
1127 * well-known dangerous environment variables.
1130 static char *banned[] = {
1131 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1132 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1136 /* --- Other useful variables --- */
1145 /* --- Stage one. Preserve display-specific variables --- */
1147 for (pp = preserve; *pp; pp++) {
1148 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1149 e->f |= envFlag_preserve;
1152 /* --- Stage two. Set Become's own variables --- */
1154 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1156 bc__setenv(e, from_pw->pw_name);
1157 e->f |= envFlag_preserve;
1159 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1161 bc__setenv(e, from_pw->pw_dir);
1162 e->f |= envFlag_preserve;
1164 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1165 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1166 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1167 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1169 /* --- Stage three. Set user identity --- */
1173 static char *maildirs[] = {
1174 "/var/spool/mail", "/var/mail",
1175 "/usr/spool/mail", "/usr/mail",
1181 for (pp = maildirs; *pp; pp++) {
1182 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1183 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1184 bc__putenv("MAIL", b, envFlag_preserve, 0);
1188 } /* Fall through */
1191 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1192 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1193 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1194 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1198 /* --- Stage four. Set the user's PATH properly --- */
1201 /* --- Find an existing path --- *
1203 * If there's no path, or this is a login, then set a default path,
1204 * unless we're meant to preserve the existing one. Whew!
1207 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1209 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1211 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1212 envFlag_preserve, 0);
1215 /* --- Find the string --- */
1217 e->f = envFlag_preserve;
1218 p = strchr(e->val, '=') + 1;
1221 /* --- Write the new version to a dynamically allocated buffer --- */
1223 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1224 strcpy(e->val, "PATH=");
1227 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1242 /* --- Stages five and six. Expunge variables and count numbers --- *
1244 * Folded together, so I only need one pass through the table. Also
1245 * count the number of variables needed at this time.
1250 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1252 /* --- Login style expunges all unpreserved variables --- */
1254 if (style == l_login && ~e->f & envFlag_preserve)
1257 /* --- Otherwise just check the name against the list --- */
1259 for (pp = banned; *pp; pp++) {
1262 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1264 } else if (strcmp(e->_base.name, *pp) == 0)
1272 sym_remove(&bc__env, e);
1275 /* --- Stage seven. Build the new environment block --- */
1277 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1279 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1284 /* --- Trace the command --- */
1286 IF_TRACING(TRACE_SETUP, {
1289 trace(TRACE_SETUP, "setup: from user %s to user %s",
1290 from_pw->pw_name, to_pw->pw_name);
1291 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1292 for (i = 0; todo[i]; i++)
1293 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1294 for (i = 0; env[i]; i++)
1295 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1298 /* --- If necessary, resolve the path to the command --- */
1300 if (!strchr(binary, '/')) {
1304 if ((p = getenv("PATH")) == 0)
1305 p = "/bin:/usr/bin";
1308 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1310 /* --- Check length of string before copying --- */
1312 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1315 /* --- Now build the pathname and check it --- */
1317 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1318 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1319 st.st_mode & 0111 && /* Check it's executable */
1320 S_ISREG(st.st_mode)) /* Check it's a file */
1325 die("couldn't find `%s' in path", todo[0]);
1329 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1331 /* --- Canonicalise the path string, if necessary --- */
1338 /* --- Insert current directory name if path not absolute --- */
1343 if (!getcwd(b, sizeof(b)))
1344 die("couldn't read current directory: %s", strerror(errno));
1349 /* --- Now copy over characters from the path string --- */
1353 /* --- Check for buffer overflows here --- *
1355 * I write at most one byte per iteration so this is OK. Remember to
1356 * allow one for the null byte.
1359 if (p >= b + sizeof(b) - 1)
1360 die("internal error: buffer overflow while canonifying path");
1362 /* --- Reduce multiple slashes to just one --- */
1370 /* --- Handle dots in filenames --- *
1372 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1373 * we've just stuck the current directory on the end of the buffer,
1374 * or we've just put something else on the end.
1377 else if (*q == '.' && p[-1] == '/') {
1379 /* --- A simple `./' just gets removed --- */
1381 if (q[1] == 0 || q[1] == '/') {
1387 /* --- A `../' needs to be peeled back to the previous `/' --- */
1389 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1392 while (p > b && p[-1] != '/')
1405 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1407 /* --- Run the check --- *
1409 * If the user is already what she wants to be, then print a warning.
1410 * Then, if I was just going to spawn a shell, quit, to reduce user
1411 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1412 * checking if we're already root -- root can do anything anyway, and at
1413 * least this way we get some logging done, and offer a more friendly
1417 if (rq.from == rq.to) {
1418 moan("you already are `%s'!", to_pw->pw_name);
1419 if (!cmd && todo == shell) {
1420 moan("(to prevent confusion, I'm not spawning a shell)");
1424 int a = (rq.from == 0) || check(&rq);
1427 "permission %s for %s to become %s to run `%s'",
1428 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1432 die("permission denied");
1435 /* --- Now do the job --- */
1437 T( trace(TRACE_MISC, "become: permission granted"); )
1439 if (flags & f_dummy) {
1440 puts("permission granted");
1444 #ifdef HAVE_SETGROUPS
1445 if (setgroups(ngroups, groups) < 0)
1446 die("couldn't set groups: %s", strerror(errno));
1449 if (setgid(group) < 0)
1450 die("couldn't set default group: %s", strerror(errno));
1451 if (setuid(rq.to) < 0)
1452 die("couldn't set uid: %s", strerror(errno));
1454 /* --- Finally, call the program --- */
1457 execve(rq.cmd, todo, env);
1458 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1462 /*----- That's all, folks -------------------------------------------------*/