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 /*----- Header files ------------------------------------------------------*/
31 /* --- ANSI headers --- */
41 /* --- Unix headers --- */
43 #include <sys/types.h>
45 #include <sys/socket.h>
46 #include <sys/utsname.h>
48 #include <netinet/in.h>
50 #include <arpa/inet.h>
59 extern char **environ;
63 #include <mLib/alloc.h>
64 #include <mLib/mdwopt.h>
65 #include <mLib/quis.h>
66 #include <mLib/report.h>
68 #include <mLib/trace.h>
70 /* --- Local headers --- */
82 /*----- Type definitions --------------------------------------------------*/
84 /* --- Symbol table entry for an environment variable --- */
86 typedef struct sym_env {
87 sym_base _base; /* Symbol table information */
88 unsigned f; /* Flags word (see below) */
89 char *val; /* Pointer to variable value */
92 /* --- Environment variable flags --- */
98 /* --- Login behaviour types --- */
100 #define l_preserve 0 /* Preserve the environment */
101 #define l_setuser 1 /* Update who I am */
102 #define l_login 2 /* Do a full login */
104 /* --- Group behaviour types --- *
106 * Note that these make a handy bitfield.
109 #ifdef HAVE_SETGROUPS
112 g_unset = 0, /* Nobody's set a preference */
113 g_keep = 1, /* Leave the group memberships */
114 g_replace = 2, /* Replace group memberships */
115 g_merge = (g_keep | g_replace) /* Merge the group memberships */
120 /*----- Static variables --------------------------------------------------*/
122 static sym_table bc__env;
124 /*----- Main code ---------------------------------------------------------*/
126 /* --- @bc__write@ --- *
128 * Arguments: @FILE *fp@ = pointer to a stream to write on
129 * @const char *p@ = pointer to a string
133 * Use: Writes the string to the stream, substituting the program
134 * name (as returned by @quis@) for each occurrence of the
138 static void bc__write(FILE *fp, const char *p)
140 const char *n = quis();
142 size_t nl = strlen(n);
144 /* --- Try to be a little efficient --- *
146 * Gather up non-`$' characters using @strcspn@ and spew them out really
157 fwrite(n, nl, 1, fp);
162 /* --- @bc__setenv@ --- *
164 * Arguments: @sym_env *e@ = pointer to environment variable block
165 * @const char *val@ = value to set
169 * Use: Sets an environment variable block to the right value.
172 static void bc__setenv(sym_env *e, const char *val)
174 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
175 sprintf(e->val, "%s=%s", e->_base.name, val);
178 /* --- @bc__putenv@ --- *
180 * Arguments: @const char *var@ = name of the variable to set, or 0 if
181 * this is embedded in the value string
182 * @const char *val@ = value to set, or 0 if the variable must
184 * @unsigned int fl@ = flags to set
185 * @unsigned int force@ = force overwrite of preserved variables
187 * Returns: Pointer to symbol block, or zero if it was deleted.
189 * Use: Puts an item into the environment.
192 static sym_env *bc__putenv(const char *var, const char *val,
193 unsigned int fl, unsigned int force)
200 /* --- Sort out embedded variable names --- */
203 const char *p = strchr(val, '=');
218 /* --- Find the variable block --- */
221 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
222 if (!f || ~e->f & envFlag_preserve || force) {
228 e = sym_find(&bc__env, var, -1, 0, 0);
229 if (e && (force || ~e->f & envFlag_preserve))
230 sym_remove(&bc__env, e);
234 /* --- Tidy up and return --- */
243 /* --- @bc__addGroups@ --- *
245 * Arguments: @gid_t *g@ = pointer to a group array
246 * @int *png@ = pointer to number of entries in the array
247 * @const gid_t *a@ = pointer to groups to add
248 * @int na@ = number of groups to add
250 * Returns: Zero if it was OK, nonzero if we should stop now.
252 * Use: Adds groups to a groups array.
255 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
260 for (i = 0; i < na; i++) {
262 /* --- Ensure this group isn't already in the list --- */
264 for (j = 0; j < ng; j++) {
269 /* --- See if there's room for more --- */
271 if (ng >= NGROUPS_MAX) {
272 moan("too many groups (system limit exceeded) -- some have been lost");
277 /* --- Add the group --- */
287 /* --- @bc__banner@ --- *
289 * Arguments: @FILE *fp@ = stream to write on
293 * Use: Writes a banner containing copyright information.
296 static void bc__banner(FILE *fp)
298 bc__write(fp, "$ version " VERSION "\n");
301 /* --- @bc__usage@ --- *
303 * Arguments: @FILE *fp@ = stream to write on
307 * Use: Writes a terse reminder of command line syntax.
310 static void bc__usage(FILE *fp)
314 " $ -c SHELL-COMMAND USER"
315 " $ [ENV-VAR] USER [COMMAND [ARGUMENTS]...]\n"
317 " $ -d [-p PORT] [-f CONFIG-FILE]\n"
322 /* --- @bc__help@ --- *
324 * Arguments: @FILE *fp@ = stream to write on
325 * @int suid@ = whether we're running set-uid
329 * Use: Displays a help message for this excellent piece of software.
332 static void bc__help(FILE *fp, int suid)
339 "The `$' program allows you to run a process as another user.\n"
340 "If a command name is given, this is the process executed. If the `-c'\n"
341 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
342 "given, your default login shell is used.\n"
344 "Your user id, the user id you wish to become, the name of the process\n"
345 "you wish to run, and the identity of the current host are looked up to\n"
346 "ensure that you have permission to do this.\n"
348 "Note that logs are kept of all uses of this program.\n"
350 "Options available are:\n"
352 "-h, --help Display this help text\n"
353 "-u, --usage Display a short usage summary\n"
354 "-v, --version Display $'s version number\n"
356 "-e, --preserve-environment Try to preserve the current environment\n"
357 "-s, --su, --set-user Set environment variables to reflect USER\n"
358 "-l, --login Really log in as USER\n"
360 #if DEFAULT_LOGIN_STYLE == l_preserve
361 "preserve-environment"
362 #elif DEFAULT_LOGIN_STYLE == l_setuser
364 #elif DEFAULT_LOGIN_STYLE == l_login
370 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
371 #ifdef HAVE_SETGROUPS
372 "-k, --keep-groups Keep your current set of groups\n"
373 "-m, --merge-groups Merge the lists of groups\n"
374 "-r, --replace-groups Replace the list of groups\n"
377 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
380 "-d, --daemon Start a daemon\n"
381 "-n, --nofork In daemon mode, don't fork into background\n"
382 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
383 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
390 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
393 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
394 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
400 * Arguments: @int argc@ = number of command line arguments
401 * @char *argv[]@ = pointer to the various arguments
403 * Returns: Zero if successful.
405 * Use: Allows a user to change UID.
408 int main(int argc, char *argv[])
410 /* --- Request block setup parameters --- */
412 request rq; /* Request buffer to build */
413 char *cmd = 0; /* Shell command to execute */
414 char *binary = "/bin/sh"; /* Default binary to execute */
415 char **env = environ; /* Default environment to pass */
416 char **todo = 0; /* Pointer to argument list */
417 char *who = 0; /* Who we're meant to become */
418 struct passwd *from_pw = 0; /* User we are right now */
419 struct passwd *to_pw = 0; /* User we want to become */
421 /* --- Become server setup parameters --- */
424 char *conffile = file_RULES; /* Default config file for daemon */
425 int port = 0; /* Default port for daemon */
428 /* --- Miscellanous shared variables --- */
430 unsigned flags = 0; /* Various useful flags */
431 int style = DEFAULT_LOGIN_STYLE; /* Login style */
432 gid_t group = -1; /* Default group to set */
433 int gstyle = g_unset; /* No group style set yet */
435 #ifdef HAVE_SETGROUPS
436 gid_t groups[NGROUPS_MAX]; /* Set of groups */
437 int ngroups; /* Number of groups in the set */
440 /* --- Default argument list executes a shell command --- */
442 static char *shell[] = {
443 "/bin/sh", /* Bourne shell */
444 "-c", /* Read from command line */
445 0, /* Pointer to shell command */
449 /* --- Definitions for the various flags --- */
451 #define f_daemon 1u /* Start up in daemon mode */
452 #define f_duff 2u /* Fault in arguments */
453 #define f_shell 4u /* Run a default shell */
454 #define f_dummy 8u /* Don't actually do anything */
455 #define f_setuid 16u /* We're running setuid */
456 #define f_havegroup 32u /* Set a default group */
457 #define f_nofork 64u /* Don't fork into background */
459 /* --- Set up the program name --- */
463 if (getuid() != geteuid())
466 /* --- Make sure standard file descriptors are open --- */
471 if ((fd = open("/dev/null", O_RDWR)) < 0)
472 die(1, "couldn't open /dev/null: %s", strerror(errno));
473 } while (fd <= STDERR_FILENO);
477 /* --- Read the environment into a hashtable --- */
482 sym_create(&bc__env);
483 for (p = environ; *p; p++)
484 bc__putenv(0, *p, 0, 0);
487 /* --- Parse some command line arguments --- */
491 static struct option opts[] = {
493 /* --- Asking for help --- */
495 { "help", 0, 0, 'h' },
496 { "usage", 0, 0, 'u' },
497 { "version", 0, 0, 'v' },
499 /* --- Login style options --- */
501 { "preserve-environment", 0, 0, 'e' },
503 { "set-user", 0, 0, 's' },
504 { "login", 0, 0, 'l' },
506 /* --- Group style options --- */
508 { "group", OPTF_ARGREQ, 0, 'g' },
509 #ifdef HAVE_SETGROUPS
510 { "keep-groups", 0, 0, 'k' },
511 { "merge-groups", 0, 0, 'm' },
512 { "replace-groups", 0, 0, 'r' },
515 /* --- Command to run options --- */
517 { "command", OPTF_ARGREQ, 0, 'c' },
519 /* --- Server options --- */
522 { "daemon", 0, 0, 'd' },
523 { "nofork", 0, 0, 'n' },
524 { "port", OPTF_ARGREQ, 0, 'p' },
525 { "config-file", OPTF_ARGREQ, 0, 'f' },
528 /* --- Tracing options --- */
531 { "impersonate", OPTF_ARGREQ, 0, 'I' },
532 { "trace", OPTF_ARGOPT, 0, 'T' },
533 { "trace-level", OPTF_ARGOPT, 0, 'L' },
539 i = mdwopt(argc, argv,
540 "-" /* Return non-options as options */
541 "huv" /* Asking for help */
542 "esl" /* Login style options */
543 #ifdef HAVE_SETGROUPS
544 "g:kmr" /* Group style options */
546 "g:" /* Group (without @setgroups@) */
548 "c:" /* Command to run options */
550 "dnp:f:" /* Server options */
553 "I:T::L::" /* Tracing options */
556 opts, 0, 0, gFlag_envVar);
562 /* --- Asking for help --- */
565 bc__help(stdout, flags & f_setuid);
577 /* --- Login style options --- */
589 /* --- Group style options --- */
592 if (isdigit((unsigned char)optarg[0]))
593 group = atoi(optarg);
595 struct group *gr = getgrnam(optarg);
597 die(1, "unknown group `%s'", optarg);
600 flags |= f_havegroup;
613 /* --- Command to run options --- */
619 /* --- Server options --- */
623 if (isdigit((unsigned char)optarg[0]))
624 port = htons(atoi(optarg));
626 struct servent *s = getservbyname(optarg, "udp");
628 die(1, "unknown service name `%s'", optarg);
643 /* --- Pretend to be a different user --- *
645 * There are all sorts of nasty implications for this option. Don't
646 * allow it if we're running setuid. Disable the actual login anyway.
652 if (flags & f_setuid)
653 moan("shan't allow impersonation while running setuid");
656 if (isdigit((unsigned char)optarg[0]))
657 pw = getpwuid(atoi(optarg));
659 pw = getpwnam(optarg);
661 die(1, "can't impersonate unknown user `%s'", optarg);
662 from_pw = userdb_copyUser(pw);
663 rq.from = from_pw->pw_uid;
670 /* --- Tracing support --- *
672 * Be careful not to zap a file I wouldn't normally be allowed to write
681 if (optarg == 0 || strcmp(optarg, "-") == 0)
684 uid_t eu = geteuid(), ru = getuid();
687 if (setreuid(eu, ru))
692 die(1, "couldn't temporarily give up privileges: %s",
696 if ((fp = fopen(optarg, "w")) == 0) {
697 die(1, "couldn't open trace file `%s' for writing: %s",
698 optarg, strerror(errno));
702 if (setreuid(ru, eu))
706 die(1, "couldn't regain privileges: %s", strerror(errno));
708 trace_on(fp, TRACE_DFL);
709 trace(TRACE_MISC, "become: tracing enabled");
714 /* --- Setting trace levels --- */
720 /* --- Table of tracing facilities --- */
722 static trace_opt lvltbl[] = {
723 { 'm', TRACE_MISC, "miscellaneous messages" },
724 { 's', TRACE_SETUP, "building the request block" },
725 { 'r', TRACE_RULE, "ruleset scanning" },
726 { 'c', TRACE_CHECK, "request checking" },
728 { 'd', TRACE_DAEMON, "server process" },
729 { 'l', TRACE_CLIENT, "client process" },
730 { 'R', TRACE_RAND, "random number generator" },
731 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
733 { 'y', TRACE_YACC, "parsing configuration file" },
734 { 'D', TRACE_DFL, "default tracing options" },
735 { 'A', TRACE_ALL, "all tracing options" },
739 /* --- Output some help if there's no arguemnt --- */
741 trace_level(traceopt(lvltbl, optarg, TRACE_DFL,
742 (flags & f_setuid) ? TRACE_PRIV : 0));
747 /* --- Something that wasn't an option --- *
749 * The following nasties are supported:
751 * * NAME=VALUE -- preserve NAME, and give it a VALUE
752 * * NAME= -- preserve NAME, and give it an empty value
753 * * NAME- -- delete NAME
754 * * NAME! -- preserve NAME with existing value
756 * Anything else is either the user name (which is OK) or the start of
757 * the command (in which case I stop and read the rest of the command).
761 size_t sz = strcspn(optarg, "=-!");
764 /* --- None of the above --- */
766 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
776 /* --- Do the appropriate thing --- */
778 switch (optarg[sz]) {
780 bc__putenv(0, optarg, envFlag_preserve, 1);
784 bc__putenv(optarg, 0, 0, 1);
788 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
789 e->f |= envFlag_preserve;
794 /* --- Something I didn't understand has occurred --- */
803 if (flags & f_duff) {
808 /* --- Switch to daemon mode if requested --- */
811 if (flags & f_daemon) {
812 T( trace(TRACE_MISC, "become: daemon mode requested"); )
813 daemon_init(conffile, port, (flags & f_nofork) ? df_nofork : 0);
818 /* --- Open a syslog --- */
820 openlog(quis(), 0, LOG_AUTH);
822 /* --- Pick out the uid --- */
832 if (isdigit((unsigned char)who[0]))
833 pw = getpwuid(atoi(who));
837 die(1, "unknown user `%s'", who);
838 to_pw = userdb_copyUser(pw);
842 /* --- Fill in the easy bits of the request --- */
848 pw = getpwuid(rq.from);
850 die(1, "who are you? (can't find user %li)", (long)rq.from);
851 from_pw = userdb_copyUser(pw);
854 /* --- Find the local host address --- */
860 if ((he = gethostbyname(u.nodename)) == 0)
861 die(1, "who am I? (can't resolve `%s')", u.nodename);
862 memcpy(&rq.host, he->h_addr, sizeof(rq.host));
865 /* --- Fiddle with group ownerships a bit --- */
868 #ifdef HAVE_SETGROUPS
869 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
873 /* --- Set the default login group, if there is one --- */
875 if (~flags & f_havegroup)
876 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
878 #ifndef HAVE_SETGROUPS
880 /* --- Check that it's valid --- */
882 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
883 die(1, "invalid default group");
887 /* --- Set the default group style --- */
889 if (gstyle == g_unset)
890 gstyle = (style == l_login) ? g_replace : g_merge;
892 /* --- Read in my current set of groups --- */
894 n_fgr = getgroups(NGROUPS_MAX, from_gr);
896 /* --- Now read the groups for the target user --- *
898 * Do this even if I'm using the @g_keep@ style -- I'll use it for
899 * checking that @group@ is valid.
907 to_gr[n_tgr++] = to_pw->pw_gid;
910 while ((gr = getgrent()) != 0) {
911 if (gr->gr_gid == to_gr[0])
913 for (p = gr->gr_mem; *p; p++) {
914 if (strcmp(to_pw->pw_name, *p) == 0) {
915 to_gr[n_tgr++] = gr->gr_gid;
916 if (n_tgr >= NGROUPS_MAX)
927 /* --- Check that @group@ is reasonable --- */
932 if (group == getgid() || group == from_pw->pw_gid)
934 for (i = 0; i < n_fgr; i++) {
935 if (group == from_gr[i])
938 for (i = 0; i < n_tgr; i++) {
939 if (group == to_gr[i])
942 die(1, "invalid default group");
946 /* --- Phew. Now comes the hard bit --- */
954 if (gstyle & g_keep) {
956 ga[i++] = from_pw->pw_gid;
958 if (gstyle & g_replace)
959 ga[i++] = to_pw->pw_gid;
961 /* --- Style authorities will become apoplectic if shown this --- *
963 * As far as I can see, it's the neatest way of writing it.
967 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
968 ((gstyle & g_keep) &&
969 bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
970 ((gstyle & g_replace) &&
971 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
976 /* --- Trace the results of all this --- */
978 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
980 #ifdef HAVE_SETGROUPS
981 IF_TRACING(TRACE_SETUP, {
984 for (i = 1; i < ngroups; i++)
985 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
990 /* --- Shell commands are easy --- */
997 /* --- A command given on the command line isn't too hard --- */
999 else if (optind < argc) {
1000 todo = argv + optind;
1009 /* --- An unadorned becoming requires little work --- */
1012 shell[0] = getenv("SHELL");
1014 shell[0] = from_pw->pw_shell;
1020 /* --- An su-like login needs slightly less effort --- */
1023 shell[0] = to_pw->pw_shell;
1029 /* --- A login request needs a little bit of work --- */
1032 const char *p = strrchr(to_pw->pw_shell, '/');
1037 p = to_pw->pw_shell;
1038 shell[0] = xmalloc(strlen(p) + 2);
1040 strcpy(shell[0] + 1, p);
1043 binary = to_pw->pw_shell;
1048 /* --- Mangle the environment --- *
1050 * This keeps getting more complicated all the time. (How true. Now I've
1051 * got all sorts of nasty environment mangling to do.)
1053 * The environment stuff now happens in seven phases:
1055 * 1. Mark very special variables to be preserved. Currently only TERM
1056 * and DISPLAY are treated in this way.
1058 * 2. Set and preserve Become's own environment variables.
1060 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1061 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1063 * 4. If we're preserving the environment or being `su'-like, process the
1064 * PATH variable a little. Otherwise reset it to something
1067 * 5. If we're being `login'-like, expunge all unpreserved variables.
1069 * 6. Expunge any security-critical variables.
1071 * 7. Build a new environment table to pass to child processes.
1075 /* --- Variables to be preserved always --- *
1077 * A user can explicitly expunge a variable in this list, in which case
1078 * we never get to see it here.
1081 static char *preserve[] = {
1082 "TERM", "DISPLAY", "TZ", 0
1085 /* --- Variables to be expunged --- *
1087 * Any environment string which has one of the following as a prefix will
1088 * be expunged from the environment passed to the called process. The
1089 * first line lists variables which have been used to list search paths
1090 * for shared libraries: by manipulating these, an attacker could replace
1091 * a standard library with one of his own. The second line lists other
1092 * well-known dangerous environment variables.
1095 static char *banned[] = {
1096 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1097 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1101 /* --- Other useful variables --- */
1110 /* --- Stage one. Preserve display-specific variables --- */
1112 for (pp = preserve; *pp; pp++) {
1113 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1114 e->f |= envFlag_preserve;
1117 /* --- Stage two. Set Become's own variables --- */
1119 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1121 bc__setenv(e, from_pw->pw_name);
1122 e->f |= envFlag_preserve;
1124 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1126 bc__setenv(e, from_pw->pw_dir);
1127 e->f |= envFlag_preserve;
1129 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1130 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1131 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1132 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1134 /* --- Stage three. Set user identity --- */
1138 static char *maildirs[] = {
1139 "/var/spool/mail", "/var/mail",
1140 "/usr/spool/mail", "/usr/mail",
1146 for (pp = maildirs; *pp; pp++) {
1147 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1148 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1149 bc__putenv("MAIL", b, envFlag_preserve, 0);
1153 } /* Fall through */
1156 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1157 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1158 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1159 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1163 /* --- Stage four. Set the user's PATH properly --- */
1166 /* --- Find an existing path --- *
1168 * If there's no path, or this is a login, then set a default path,
1169 * unless we're meant to preserve the existing one. Whew!
1172 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1174 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1176 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1177 envFlag_preserve, 0);
1180 /* --- Find the string --- */
1182 e->f = envFlag_preserve;
1183 p = strchr(e->val, '=') + 1;
1186 /* --- Write the new version to a dynamically allocated buffer --- */
1188 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1189 strcpy(e->val, "PATH=");
1192 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1207 /* --- Stages five and six. Expunge variables and count numbers --- *
1209 * Folded together, so I only need one pass through the table. Also
1210 * count the number of variables needed at this time.
1215 for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1217 /* --- Login style expunges all unpreserved variables --- */
1219 if (style == l_login && ~e->f & envFlag_preserve)
1222 /* --- Otherwise just check the name against the list --- */
1224 for (pp = banned; *pp; pp++) {
1227 if (strncmp(e->_base.name, p, strlen(p)) == 0)
1229 } else if (strcmp(e->_base.name, *pp) == 0)
1237 sym_remove(&bc__env, e);
1240 /* --- Stage seven. Build the new environment block --- */
1242 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1244 for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1249 /* --- Trace the command --- */
1251 IF_TRACING(TRACE_SETUP, {
1254 trace(TRACE_SETUP, "setup: from user %s to user %s",
1255 from_pw->pw_name, to_pw->pw_name);
1256 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1257 for (i = 0; todo[i]; i++)
1258 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1259 for (i = 0; env[i]; i++)
1260 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1263 /* --- If necessary, resolve the path to the command --- */
1265 if (!strchr(binary, '/')) {
1269 if ((p = getenv("PATH")) == 0)
1270 p = "/bin:/usr/bin";
1273 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1275 /* --- Check length of string before copying --- */
1277 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1280 /* --- Now build the pathname and check it --- *
1282 * Issue: user can take advantage of these privileges to decide whether
1283 * a program with a given name exists. I'm not sure that's
1284 * particularly significant: it only works on regular files with
1285 * execute permissions, and if you're relying on the names of these
1286 * being secret to keep your security up, then you're doing something
1287 * deeply wrong anyway. On the other hand, it's useful to allow people
1288 * to be able to execute programs and scripts which they wouldn't
1289 * otherwise have access to. [This problem was brought up on
1290 * Bugtraq, as a complaint against sudo.]
1294 sprintf(rq.cmd, "%s/%s", p, binary);
1295 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1296 st.st_mode & 0111 && /* Check it's executable */
1297 S_ISREG(st.st_mode)) /* Check it's a file */
1302 die(1, "couldn't find `%s' in path", todo[0]);
1306 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1308 /* --- Canonicalise the path string, if necessary --- */
1315 /* --- Insert current directory name if path not absolute --- */
1320 if (!getcwd(b, sizeof(b)))
1321 die(1, "couldn't read current directory: %s", strerror(errno));
1326 /* --- Now copy over characters from the path string --- */
1330 /* --- Check for buffer overflows here --- *
1332 * I write at most one byte per iteration so this is OK. Remember to
1333 * allow one for the null byte.
1336 if (p >= b + sizeof(b) - 1)
1337 die(1, "internal error: buffer overflow while canonifying path");
1339 /* --- Reduce multiple slashes to just one --- */
1347 /* --- Handle dots in filenames --- *
1349 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1350 * we've just stuck the current directory on the end of the buffer,
1351 * or we've just put something else on the end.
1354 else if (*q == '.' && p[-1] == '/') {
1356 /* --- A simple `./' just gets removed --- */
1358 if (q[1] == 0 || q[1] == '/') {
1364 /* --- A `../' needs to be peeled back to the previous `/' --- */
1366 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1369 while (p > b && p[-1] != '/')
1382 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1384 /* --- Run the check --- *
1386 * If the user is already what she wants to be, then print a warning.
1387 * Then, if I was just going to spawn a shell, quit, to reduce user
1388 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1389 * checking if we're already root -- root can do anything anyway, and at
1390 * least this way we get some logging done, and offer a more friendly
1394 if (rq.from == rq.to) {
1395 moan("you already are `%s'!", to_pw->pw_name);
1396 if (flags & f_shell) {
1397 moan("(to prevent confusion, I'm not spawning a shell)");
1401 int a = (rq.from == 0) || check(&rq);
1404 "permission %s for %s to become %s to run `%s'",
1405 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1409 die(1, "permission denied");
1412 /* --- Now do the job --- */
1414 T( trace(TRACE_MISC, "become: permission granted"); )
1416 if (flags & f_dummy) {
1417 puts("permission granted");
1421 #ifdef HAVE_SETGROUPS
1422 if (setgroups(ngroups, groups) < 0)
1423 die(1, "couldn't set groups: %s", strerror(errno));
1426 if (setgid(group) < 0)
1427 die(1, "couldn't set default group: %s", strerror(errno));
1428 if (setuid(rq.to) < 0)
1429 die(1, "couldn't set uid: %s", strerror(errno));
1431 /* --- If this was a login, change current directory --- */
1433 if ((flags & f_shell) &&
1435 chdir(to_pw->pw_dir) < 0) {
1436 moan("couldn't change directory to `%s': %s",
1437 to_pw->pw_dir, strerror(errno));
1440 /* --- Finally, call the program --- */
1444 execve(rq.cmd, todo, env);
1445 die(1, "couldn't exec `%s': %s", rq.cmd, strerror(errno));
1449 /*----- That's all, folks -------------------------------------------------*/