3 * $Id: become.c,v 1.25 2003/11/29 23:39:16 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.25 2003/11/29 23:39:16 mdw
35 * Revision 1.24 2003/10/15 09:27:06 mdw
36 * Make sure standard file descriptors are open before starting properly.
38 * Revision 1.23 2003/10/12 10:00:06 mdw
39 * Fix for daemon mode. Oops.
41 * Revision 1.22 2003/10/12 00:14:55 mdw
42 * Major overhaul. Now uses DSA signatures rather than the bogus symmetric
43 * encrypt-and-hope thing. Integrated with mLib and Catacomb.
45 * Revision 1.21 1999/07/28 09:31:01 mdw
46 * Empty path components are equivalent to `.'.
48 * Revision 1.20 1999/05/04 16:17:11 mdw
49 * Change to header file name for parser. See log for `parse.h' for
52 * Revision 1.19 1998/06/29 13:10:41 mdw
53 * Add some commentary regarding an issue in `sudo' which affects `become';
54 * I'm not fixing it yet because I don't think it's important.
56 * Fixed the PATH lookup code to use the right binary name: `binary' rather
57 * than `todo[0]'. The two only differ when `style' is `l_login', in which
58 * case `binary' has an initial `/' anyway, and the PATH lookup code is
59 * never invoked. The name is used in a buffer-overflow precheck, though,
60 * and auditing is easier if the naming is consistent.
62 * Revision 1.18 1998/06/26 10:32:54 mdw
63 * Cosmetic change: use sizeof(destination) in memcpy.
65 * Revision 1.17 1998/06/18 15:06:59 mdw
66 * Close log before execing program to avoid leaving a socket open.
68 * Revision 1.16 1998/04/23 13:21:04 mdw
69 * Small tweaks. Support no-network configuration option, and rearrange
70 * the help text a little.
72 * Revision 1.15 1998/01/13 11:10:44 mdw
73 * Add `TZ' to the list of variables to be preserved.
75 * Revision 1.14 1998/01/12 16:45:39 mdw
78 * Revision 1.13 1997/09/26 09:14:57 mdw
79 * Merged blowfish branch into trunk.
81 * Revision 1.12 1997/09/25 16:04:48 mdw
82 * Change directory after becoming someone else, instead of before. This
83 * avoids problems with root-squashed NFS mounts.
85 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
86 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
87 * because I prefer Blowfish (without any particularly strong evidence) but
88 * mainly because IDEA is patented and Blowfish isn't.
90 * Revision 1.11 1997/09/24 09:48:45 mdw
91 * Fix (scary) overrun bug in group allocation stuff.
93 * Revision 1.10 1997/09/17 10:14:10 mdw
94 * Fix a typo. Support service names in `--port' option.
96 * Revision 1.9 1997/09/10 10:28:05 mdw
97 * Allow default port to be given as a service name or port number. Handle
98 * groups properly (lots of options here).
100 * Revision 1.8 1997/09/08 13:56:24 mdw
101 * Change criteria for expunging items from the user's PATH: instead of
102 * removing things starting with `.', remove things not starting with `/'.
104 * Revision 1.7 1997/09/08 13:43:20 mdw
105 * Change userid when creating tracefiles rather than fiddling with
106 * `access': it works rather better. Also, insert some stdio buffer
107 * flushing to ensure tracedumps are completely written.
109 * Revision 1.6 1997/09/05 13:47:44 mdw
110 * Make the `-L' (trace-level) option's argument optional, like the long
113 * Revision 1.5 1997/09/05 11:45:19 mdw
114 * Add support for different login styles, and environment variable
115 * manipulation in a safe and useful way.
117 * Revision 1.4 1997/08/20 16:15:13 mdw
118 * Overhaul of environment handling. Fix daft bug in path search code.
120 * Revision 1.3 1997/08/07 16:28:59 mdw
121 * Do something useful when users attempt to become themselves.
123 * Revision 1.2 1997/08/04 10:24:20 mdw
124 * Sources placed under CVS control.
126 * Revision 1.1 1997/07/21 13:47:54 mdw
131 /*----- Header files ------------------------------------------------------*/
133 /* --- ANSI headers --- */
143 /* --- Unix headers --- */
145 #include <sys/types.h>
146 #include <sys/stat.h>
147 #include <sys/socket.h>
148 #include <sys/utsname.h>
150 #include <netinet/in.h>
152 #include <arpa/inet.h>
161 extern char **environ;
165 #include <mLib/alloc.h>
166 #include <mLib/mdwopt.h>
167 #include <mLib/quis.h>
168 #include <mLib/report.h>
169 #include <mLib/sym.h>
170 #include <mLib/trace.h>
172 /* --- Local headers --- */
184 /*----- Type definitions --------------------------------------------------*/
186 /* --- Symbol table entry for an environment variable --- */
188 typedef struct sym_env {
189 sym_base _base; /* Symbol table information */
190 unsigned f; /* Flags word (see below) */
191 char *val; /* Pointer to variable value */
194 /* --- Environment variable flags --- */
200 /* --- Login behaviour types --- */
202 #define l_preserve 0 /* Preserve the environment */
203 #define l_setuser 1 /* Update who I am */
204 #define l_login 2 /* Do a full login */
206 /* --- Group behaviour types --- *
208 * Note that these make a handy bitfield.
211 #ifdef HAVE_SETGROUPS
214 g_unset = 0, /* Nobody's set a preference */
215 g_keep = 1, /* Leave the group memberships */
216 g_replace = 2, /* Replace group memberships */
217 g_merge = (g_keep | g_replace) /* Merge the group memberships */
222 /*----- Static variables --------------------------------------------------*/
224 static sym_table bc__env;
226 /*----- Main code ---------------------------------------------------------*/
228 /* --- @bc__write@ --- *
230 * Arguments: @FILE *fp@ = pointer to a stream to write on
231 * @const char *p@ = pointer to a string
235 * Use: Writes the string to the stream, substituting the program
236 * name (as returned by @quis@) for each occurrence of the
240 static void bc__write(FILE *fp, const char *p)
242 const char *n = quis();
244 size_t nl = strlen(n);
246 /* --- Try to be a little efficient --- *
248 * Gather up non-`$' characters using @strcspn@ and spew them out really
259 fwrite(n, nl, 1, fp);
264 /* --- @bc__setenv@ --- *
266 * Arguments: @sym_env *e@ = pointer to environment variable block
267 * @const char *val@ = value to set
271 * Use: Sets an environment variable block to the right value.
274 static void bc__setenv(sym_env *e, const char *val)
276 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
277 sprintf(e->val, "%s=%s", e->_base.name, val);
280 /* --- @bc__putenv@ --- *
282 * Arguments: @const char *var@ = name of the variable to set, or 0 if
283 * this is embedded in the value string
284 * @const char *val@ = value to set, or 0 if the variable must
286 * @unsigned int fl@ = flags to set
287 * @unsigned int force@ = force overwrite of preserved variables
289 * Returns: Pointer to symbol block, or zero if it was deleted.
291 * Use: Puts an item into the environment.
294 static sym_env *bc__putenv(const char *var, const char *val,
295 unsigned int fl, unsigned int force)
302 /* --- Sort out embedded variable names --- */
305 const char *p = strchr(val, '=');
320 /* --- Find the variable block --- */
323 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
324 if (!f || ~e->f & envFlag_preserve || force) {
330 e = sym_find(&bc__env, var, -1, 0, 0);
331 if (e && (force || ~e->f & envFlag_preserve))
332 sym_remove(&bc__env, e);
336 /* --- Tidy up and return --- */
345 /* --- @bc__addGroups@ --- *
347 * Arguments: @gid_t *g@ = pointer to a group array
348 * @int *png@ = pointer to number of entries in the array
349 * @const gid_t *a@ = pointer to groups to add
350 * @int na@ = number of groups to add
352 * Returns: Zero if it was OK, nonzero if we should stop now.
354 * Use: Adds groups to a groups array.
357 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
362 for (i = 0; i < na; i++) {
364 /* --- Ensure this group isn't already in the list --- */
366 for (j = 0; j < ng; j++) {
371 /* --- See if there's room for more --- */
373 if (ng >= NGROUPS_MAX) {
374 moan("too many groups (system limit exceeded) -- some have been lost");
379 /* --- Add the group --- */
389 /* --- @bc__banner@ --- *
391 * Arguments: @FILE *fp@ = stream to write on
395 * Use: Writes a banner containing copyright information.
398 static void bc__banner(FILE *fp)
400 bc__write(fp, "$ version " VERSION "\n");
403 /* --- @bc__usage@ --- *
405 * Arguments: @FILE *fp@ = stream to write on
409 * Use: Writes a terse reminder of command line syntax.
412 static void bc__usage(FILE *fp)
416 " $ -c <shell-command> <user>\n"
417 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
419 " $ -d [-p <port>] [-f <config-file>]\n"
424 /* --- @bc__help@ --- *
426 * Arguments: @FILE *fp@ = stream to write on
427 * @int suid@ = whether we're running set-uid
431 * Use: Displays a help message for this excellent piece of software.
434 static void bc__help(FILE *fp, int suid)
441 "The `$' program allows you to run a process as another user.\n"
442 "If a command name is given, this is the process executed. If the `-c'\n"
443 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
444 "given, your default login shell is used.\n"
446 "Your user id, the user id you wish to become, the name of the process\n"
447 "you wish to run, and the identity of the current host are looked up to\n"
448 "ensure that you have permission to do this.\n"
450 "Note that logs are kept of all uses of this program.\n"
452 "Options available are:\n"
454 "-h, --help Display this help text\n"
455 "-u, --usage Display a short usage summary\n"
456 "-v, --version Display $'s version number\n"
458 "-e, --preserve-environment Try to preserve the current environment\n"
459 "-s, --su, --set-user Set environment variables to reflect USER\n"
460 "-l, --login Really log in as USER\n"
462 #if DEFAULT_LOGIN_STYLE == l_preserve
463 "preserve-environment"
464 #elif DEFAULT_LOGIN_STYLE == l_setuser
466 #elif DEFAULT_LOGIN_STYLE == l_login
472 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
473 #ifdef HAVE_SETGROUPS
474 "-k, --keep-groups Keep your current set of groups\n"
475 "-m, --merge-groups Merge the lists of groups\n"
476 "-r, --replace-groups Replace the list of groups\n"
479 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
482 "-d, --daemon Start a daemon\n"
483 "-n, --nofork In daemon mode, don't fork into background\n"
484 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
485 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
492 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
495 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
496 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
502 * Arguments: @int argc@ = number of command line arguments
503 * @char *argv[]@ = pointer to the various arguments
505 * Returns: Zero if successful.
507 * Use: Allows a user to change UID.
510 int main(int argc, char *argv[])
512 /* --- Request block setup parameters --- */
514 request rq; /* Request buffer to build */
515 char *cmd = 0; /* Shell command to execute */
516 char *binary = "/bin/sh"; /* Default binary to execute */
517 char **env = environ; /* Default environment to pass */
518 char **todo = 0; /* Pointer to argument list */
519 char *who = 0; /* Who we're meant to become */
520 struct passwd *from_pw = 0; /* User we are right now */
521 struct passwd *to_pw = 0; /* User we want to become */
523 /* --- Become server setup parameters --- */
526 char *conffile = file_RULES; /* Default config file for daemon */
527 int port = 0; /* Default port for daemon */
530 /* --- Miscellanous shared variables --- */
532 unsigned flags = 0; /* Various useful flags */
533 int style = DEFAULT_LOGIN_STYLE; /* Login style */
534 gid_t group = -1; /* Default group to set */
535 int gstyle = g_unset; /* No group style set yet */
537 #ifdef HAVE_SETGROUPS
538 gid_t groups[NGROUPS_MAX]; /* Set of groups */
539 int ngroups; /* Number of groups in the set */
542 /* --- Default argument list executes a shell command --- */
544 static char *shell[] = {
545 "/bin/sh", /* Bourne shell */
546 "-c", /* Read from command line */
547 0, /* Pointer to shell command */
551 /* --- Definitions for the various flags --- */
553 #define f_daemon 1u /* Start up in daemon mode */
554 #define f_duff 2u /* Fault in arguments */
555 #define f_shell 4u /* Run a default shell */
556 #define f_dummy 8u /* Don't actually do anything */
557 #define f_setuid 16u /* We're running setuid */
558 #define f_havegroup 32u /* Set a default group */
559 #define f_nofork 64u /* Don't fork into background */
561 /* --- Set up the program name --- */
565 if (getuid() != geteuid())
568 /* --- Make sure standard file descriptors are open --- */
573 if ((fd = open("/dev/null", O_RDWR)) < 0)
574 die(1, "couldn't open /dev/null: %s", strerror(errno));
575 } while (fd <= STDERR_FILENO);
579 /* --- Read the environment into a hashtable --- */
584 sym_create(&bc__env);
585 for (p = environ; *p; p++)
586 bc__putenv(0, *p, 0, 0);
589 /* --- Parse some command line arguments --- */
593 static struct option opts[] = {
595 /* --- Asking for help --- */
597 { "help", 0, 0, 'h' },
598 { "usage", 0, 0, 'u' },
599 { "version", 0, 0, 'v' },
601 /* --- Login style options --- */
603 { "preserve-environment", 0, 0, 'e' },
605 { "set-user", 0, 0, 's' },
606 { "login", 0, 0, 'l' },
608 /* --- Group style options --- */
610 { "group", OPTF_ARGREQ, 0, 'g' },
611 #ifdef HAVE_SETGROUPS
612 { "keep-groups", 0, 0, 'k' },
613 { "merge-groups", 0, 0, 'm' },
614 { "replace-groups", 0, 0, 'r' },
617 /* --- Command to run options --- */
619 { "command", OPTF_ARGREQ, 0, 'c' },
621 /* --- Server options --- */
624 { "daemon", 0, 0, 'd' },
625 { "nofork", 0, 0, 'n' },
626 { "port", OPTF_ARGREQ, 0, 'p' },
627 { "config-file", OPTF_ARGREQ, 0, 'f' },
630 /* --- Tracing options --- */
633 { "impersonate", OPTF_ARGREQ, 0, 'I' },
634 { "trace", OPTF_ARGOPT, 0, 'T' },
635 { "trace-level", OPTF_ARGOPT, 0, 'L' },
641 i = mdwopt(argc, argv,
642 "-" /* Return non-options as options */
643 "huv" /* Asking for help */
644 "esl" /* Login style options */
645 #ifdef HAVE_SETGROUPS
646 "g:kmr" /* Group style options */
648 "g:" /* Group (without @setgroups@) */
650 "c:" /* Command to run options */
652 "dnp:f:" /* Server options */
655 "I:T::L::" /* Tracing options */
658 opts, 0, 0, gFlag_envVar);
664 /* --- Asking for help --- */
667 bc__help(stdout, flags & f_setuid);
679 /* --- Login style options --- */
691 /* --- Group style options --- */
694 if (isdigit((unsigned char)optarg[0]))
695 group = atoi(optarg);
697 struct group *gr = getgrnam(optarg);
699 die(1, "unknown group `%s'", optarg);
702 flags |= f_havegroup;
715 /* --- Command to run options --- */
721 /* --- Server options --- */
725 if (isdigit((unsigned char)optarg[0]))
726 port = htons(atoi(optarg));
728 struct servent *s = getservbyname(optarg, "udp");
730 die(1, "unknown service name `%s'", optarg);
745 /* --- Pretend to be a different user --- *
747 * There are all sorts of nasty implications for this option. Don't
748 * allow it if we're running setuid. Disable the actual login anyway.
754 if (flags & f_setuid)
755 moan("shan't allow impersonation while running setuid");
758 if (isdigit((unsigned char)optarg[0]))
759 pw = getpwuid(atoi(optarg));
761 pw = getpwnam(optarg);
763 die(1, "can't impersonate unknown user `%s'", optarg);
764 from_pw = userdb_copyUser(pw);
765 rq.from = from_pw->pw_uid;
772 /* --- Tracing support --- *
774 * Be careful not to zap a file I wouldn't normally be allowed to write
783 if (optarg == 0 || strcmp(optarg, "-") == 0)
786 uid_t eu = geteuid(), ru = getuid();
789 if (setreuid(eu, ru))
794 die(1, "couldn't temporarily give up privileges: %s",
798 if ((fp = fopen(optarg, "w")) == 0) {
799 die(1, "couldn't open trace file `%s' for writing: %s",
800 optarg, strerror(errno));
804 if (setreuid(ru, eu))
808 die(1, "couldn't regain privileges: %s", strerror(errno));
810 trace_on(fp, TRACE_DFL);
811 trace(TRACE_MISC, "become: tracing enabled");
816 /* --- Setting trace levels --- */
822 /* --- Table of tracing facilities --- */
824 static trace_opt lvltbl[] = {
825 { 'm', TRACE_MISC, "miscellaneous messages" },
826 { 's', TRACE_SETUP, "building the request block" },
827 { 'r', TRACE_RULE, "ruleset scanning" },
828 { 'c', TRACE_CHECK, "request checking" },
830 { 'd', TRACE_DAEMON, "server process" },
831 { 'l', TRACE_CLIENT, "client process" },
832 { 'R', TRACE_RAND, "random number generator" },
833 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
835 { 'y', TRACE_YACC, "parsing configuration file" },
836 { 'D', TRACE_DFL, "default tracing options" },
837 { 'A', TRACE_ALL, "all tracing options" },
841 /* --- Output some help if there's no arguemnt --- */
843 trace_level(traceopt(lvltbl, optarg, TRACE_DFL,
844 (flags & f_setuid) ? TRACE_PRIV : 0));
849 /* --- Something that wasn't an option --- *
851 * The following nasties are supported:
853 * * NAME=VALUE -- preserve NAME, and give it a VALUE
854 * * NAME= -- preserve NAME, and give it an empty value
855 * * NAME- -- delete NAME
856 * * NAME! -- preserve NAME with existing value
858 * Anything else is either the user name (which is OK) or the start of
859 * the command (in which case I stop and read the rest of the command).
863 size_t sz = strcspn(optarg, "=-!");
866 /* --- None of the above --- */
868 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
878 /* --- Do the appropriate thing --- */
880 switch (optarg[sz]) {
882 bc__putenv(0, optarg, envFlag_preserve, 1);
886 bc__putenv(optarg, 0, 0, 1);
890 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
891 e->f |= envFlag_preserve;
896 /* --- Something I didn't understand has occurred --- */
905 if (flags & f_duff) {
910 /* --- Switch to daemon mode if requested --- */
913 if (flags & f_daemon) {
914 T( trace(TRACE_MISC, "become: daemon mode requested"); )
915 daemon_init(conffile, port, (flags & f_nofork) ? df_nofork : 0);
920 /* --- Open a syslog --- */
922 openlog(quis(), 0, LOG_AUTH);
924 /* --- Pick out the uid --- */
934 if (isdigit((unsigned char)who[0]))
935 pw = getpwuid(atoi(who));
939 die(1, "unknown user `%s'", who);
940 to_pw = userdb_copyUser(pw);
944 /* --- Fill in the easy bits of the request --- */
950 pw = getpwuid(rq.from);
952 die(1, "who are you? (can't find user %li)", (long)rq.from);
953 from_pw = userdb_copyUser(pw);
956 /* --- Find the local host address --- */
962 if ((he = gethostbyname(u.nodename)) == 0)
963 die(1, "who am I? (can't resolve `%s')", u.nodename);
964 memcpy(&rq.host, he->h_addr, sizeof(rq.host));
967 /* --- Fiddle with group ownerships a bit --- */
970 #ifdef HAVE_SETGROUPS
971 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
975 /* --- Set the default login group, if there is one --- */
977 if (~flags & f_havegroup)
978 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
980 #ifndef HAVE_SETGROUPS
982 /* --- Check that it's valid --- */
984 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
985 die(1, "invalid default group");
989 /* --- Set the default group style --- */
991 if (gstyle == g_unset)
992 gstyle = (style == l_login) ? g_replace : g_merge;
994 /* --- Read in my current set of groups --- */
996 n_fgr = getgroups(NGROUPS_MAX, from_gr);
998 /* --- Now read the groups for the target user --- *
1000 * Do this even if I'm using the @g_keep@ style -- I'll use it for
1001 * checking that @group@ is valid.
1009 to_gr[n_tgr++] = to_pw->pw_gid;
1012 while ((gr = getgrent()) != 0) {
1013 if (gr->gr_gid == to_gr[0])
1015 for (p = gr->gr_mem; *p; p++) {
1016 if (strcmp(to_pw->pw_name, *p) == 0) {
1017 to_gr[n_tgr++] = gr->gr_gid;
1018 if (n_tgr >= NGROUPS_MAX)
1029 /* --- Check that @group@ is reasonable --- */
1034 if (group == getgid() || group == from_pw->pw_gid)
1036 for (i = 0; i < n_fgr; i++) {
1037 if (group == from_gr[i])
1040 for (i = 0; i < n_tgr; i++) {
1041 if (group == to_gr[i])
1044 die(1, "invalid default group");
1048 /* --- Phew. Now comes the hard bit --- */
1056 if (gstyle & g_keep) {
1058 ga[i++] = from_pw->pw_gid;
1060 if (gstyle & g_replace)
1061 ga[i++] = to_pw->pw_gid;
1063 /* --- Style authorities will become apoplectic if shown this --- *
1065 * As far as I can see, it's the neatest way of writing it.
1069 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1070 ((gstyle & g_keep) &&
1071 bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
1072 ((gstyle & g_replace) &&
1073 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1078 /* --- Trace the results of all this --- */
1080 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1082 #ifdef HAVE_SETGROUPS
1083 IF_TRACING(TRACE_SETUP, {
1086 for (i = 1; i < ngroups; i++)
1087 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1092 /* --- Shell commands are easy --- */
1099 /* --- A command given on the command line isn't too hard --- */
1101 else if (optind < argc) {
1102 todo = argv + optind;
1111 /* --- An unadorned becoming requires little work --- */
1114 shell[0] = getenv("SHELL");
1116 shell[0] = from_pw->pw_shell;
1122 /* --- An su-like login needs slightly less effort --- */
1125 shell[0] = to_pw->pw_shell;
1131 /* --- A login request needs a little bit of work --- */
1134 const char *p = strrchr(to_pw->pw_shell, '/');
1139 p = to_pw->pw_shell;
1140 shell[0] = xmalloc(strlen(p) + 2);
1142 strcpy(shell[0] + 1, p);
1145 binary = to_pw->pw_shell;
1150 /* --- Mangle the environment --- *
1152 * This keeps getting more complicated all the time. (How true. Now I've
1153 * got all sorts of nasty environment mangling to do.)
1155 * The environment stuff now happens in seven phases:
1157 * 1. Mark very special variables to be preserved. Currently only TERM
1158 * and DISPLAY are treated in this way.
1160 * 2. Set and preserve Become's own environment variables.
1162 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1163 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1165 * 4. If we're preserving the environment or being `su'-like, process the
1166 * PATH variable a little. Otherwise reset it to something
1169 * 5. If we're being `login'-like, expunge all unpreserved variables.
1171 * 6. Expunge any security-critical variables.
1173 * 7. Build a new environment table to pass to child processes.
1177 /* --- Variables to be preserved always --- *
1179 * A user can explicitly expunge a variable in this list, in which case
1180 * we never get to see it here.
1183 static char *preserve[] = {
1184 "TERM", "DISPLAY", "TZ", 0
1187 /* --- Variables to be expunged --- *
1189 * Any environment string which has one of the following as a prefix will
1190 * be expunged from the environment passed to the called process. The
1191 * first line lists variables which have been used to list search paths
1192 * for shared libraries: by manipulating these, an attacker could replace
1193 * a standard library with one of his own. The second line lists other
1194 * well-known dangerous environment variables.
1197 static char *banned[] = {
1198 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1199 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1203 /* --- Other useful variables --- */
1212 /* --- Stage one. Preserve display-specific variables --- */
1214 for (pp = preserve; *pp; pp++) {
1215 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1216 e->f |= envFlag_preserve;
1219 /* --- Stage two. Set Become's own variables --- */
1221 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1223 bc__setenv(e, from_pw->pw_name);
1224 e->f |= envFlag_preserve;
1226 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1228 bc__setenv(e, from_pw->pw_dir);
1229 e->f |= envFlag_preserve;
1231 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1232 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1233 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1234 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1236 /* --- Stage three. Set user identity --- */
1240 static char *maildirs[] = {
1241 "/var/spool/mail", "/var/mail",
1242 "/usr/spool/mail", "/usr/mail",
1248 for (pp = maildirs; *pp; pp++) {
1249 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1250 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1251 bc__putenv("MAIL", b, envFlag_preserve, 0);
1255 } /* Fall through */
1258 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1259 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1260 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1261 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1265 /* --- Stage four. Set the user's PATH properly --- */
1268 /* --- Find an existing path --- *
1270 * If there's no path, or this is a login, then set a default path,
1271 * unless we're meant to preserve the existing one. Whew!
1274 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1276 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1278 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1279 envFlag_preserve, 0);
1282 /* --- Find the string --- */
1284 e->f = envFlag_preserve;
1285 p = strchr(e->val, '=') + 1;
1288 /* --- Write the new version to a dynamically allocated buffer --- */
1290 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1291 strcpy(e->val, "PATH=");
1294 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1309 /* --- Stages five and six. Expunge variables and count numbers --- *
1311 * Folded together, so I only need one pass through the table. Also
1312 * count the number of variables needed at this time.
1317 for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1319 /* --- Login style expunges all unpreserved variables --- */
1321 if (style == l_login && ~e->f & envFlag_preserve)
1324 /* --- Otherwise just check the name against the list --- */
1326 for (pp = banned; *pp; pp++) {
1329 if (strncmp(e->_base.name, p, strlen(p)) == 0)
1331 } else if (strcmp(e->_base.name, *pp) == 0)
1339 sym_remove(&bc__env, e);
1342 /* --- Stage seven. Build the new environment block --- */
1344 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1346 for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1351 /* --- Trace the command --- */
1353 IF_TRACING(TRACE_SETUP, {
1356 trace(TRACE_SETUP, "setup: from user %s to user %s",
1357 from_pw->pw_name, to_pw->pw_name);
1358 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1359 for (i = 0; todo[i]; i++)
1360 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1361 for (i = 0; env[i]; i++)
1362 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1365 /* --- If necessary, resolve the path to the command --- */
1367 if (!strchr(binary, '/')) {
1371 if ((p = getenv("PATH")) == 0)
1372 p = "/bin:/usr/bin";
1375 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1377 /* --- Check length of string before copying --- */
1379 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1382 /* --- Now build the pathname and check it --- *
1384 * Issue: user can take advantage of these privileges to decide whether
1385 * a program with a given name exists. I'm not sure that's
1386 * particularly significant: it only works on regular files with
1387 * execute permissions, and if you're relying on the names of these
1388 * being secret to keep your security up, then you're doing something
1389 * deeply wrong anyway. On the other hand, it's useful to allow people
1390 * to be able to execute programs and scripts which they wouldn't
1391 * otherwise have access to. [This problem was brought up on
1392 * Bugtraq, as a complaint against sudo.]
1396 sprintf(rq.cmd, "%s/%s", p, binary);
1397 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1398 st.st_mode & 0111 && /* Check it's executable */
1399 S_ISREG(st.st_mode)) /* Check it's a file */
1404 die(1, "couldn't find `%s' in path", todo[0]);
1408 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1410 /* --- Canonicalise the path string, if necessary --- */
1417 /* --- Insert current directory name if path not absolute --- */
1422 if (!getcwd(b, sizeof(b)))
1423 die(1, "couldn't read current directory: %s", strerror(errno));
1428 /* --- Now copy over characters from the path string --- */
1432 /* --- Check for buffer overflows here --- *
1434 * I write at most one byte per iteration so this is OK. Remember to
1435 * allow one for the null byte.
1438 if (p >= b + sizeof(b) - 1)
1439 die(1, "internal error: buffer overflow while canonifying path");
1441 /* --- Reduce multiple slashes to just one --- */
1449 /* --- Handle dots in filenames --- *
1451 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1452 * we've just stuck the current directory on the end of the buffer,
1453 * or we've just put something else on the end.
1456 else if (*q == '.' && p[-1] == '/') {
1458 /* --- A simple `./' just gets removed --- */
1460 if (q[1] == 0 || q[1] == '/') {
1466 /* --- A `../' needs to be peeled back to the previous `/' --- */
1468 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1471 while (p > b && p[-1] != '/')
1484 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1486 /* --- Run the check --- *
1488 * If the user is already what she wants to be, then print a warning.
1489 * Then, if I was just going to spawn a shell, quit, to reduce user
1490 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1491 * checking if we're already root -- root can do anything anyway, and at
1492 * least this way we get some logging done, and offer a more friendly
1496 if (rq.from == rq.to) {
1497 moan("you already are `%s'!", to_pw->pw_name);
1498 if (flags & f_shell) {
1499 moan("(to prevent confusion, I'm not spawning a shell)");
1503 int a = (rq.from == 0) || check(&rq);
1506 "permission %s for %s to become %s to run `%s'",
1507 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1511 die(1, "permission denied");
1514 /* --- Now do the job --- */
1516 T( trace(TRACE_MISC, "become: permission granted"); )
1518 if (flags & f_dummy) {
1519 puts("permission granted");
1523 #ifdef HAVE_SETGROUPS
1524 if (setgroups(ngroups, groups) < 0)
1525 die(1, "couldn't set groups: %s", strerror(errno));
1528 if (setgid(group) < 0)
1529 die(1, "couldn't set default group: %s", strerror(errno));
1530 if (setuid(rq.to) < 0)
1531 die(1, "couldn't set uid: %s", strerror(errno));
1533 /* --- If this was a login, change current directory --- */
1535 if ((flags & f_shell) &&
1537 chdir(to_pw->pw_dir) < 0) {
1538 moan("couldn't change directory to `%s': %s",
1539 to_pw->pw_dir, strerror(errno));
1542 /* --- Finally, call the program --- */
1546 execve(rq.cmd, todo, env);
1547 die(1, "couldn't exec `%s': %s", rq.cmd, strerror(errno));
1551 /*----- That's all, folks -------------------------------------------------*/