3 * $Id: become.c,v 1.22 2003/10/12 00:14:55 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.22 2003/10/12 00:14:55 mdw
33 * Major overhaul. Now uses DSA signatures rather than the bogus symmetric
34 * encrypt-and-hope thing. Integrated with mLib and Catacomb.
36 * Revision 1.21 1999/07/28 09:31:01 mdw
37 * Empty path components are equivalent to `.'.
39 * Revision 1.20 1999/05/04 16:17:11 mdw
40 * Change to header file name for parser. See log for `parse.h' for
43 * Revision 1.19 1998/06/29 13:10:41 mdw
44 * Add some commentary regarding an issue in `sudo' which affects `become';
45 * I'm not fixing it yet because I don't think it's important.
47 * Fixed the PATH lookup code to use the right binary name: `binary' rather
48 * than `todo[0]'. The two only differ when `style' is `l_login', in which
49 * case `binary' has an initial `/' anyway, and the PATH lookup code is
50 * never invoked. The name is used in a buffer-overflow precheck, though,
51 * and auditing is easier if the naming is consistent.
53 * Revision 1.18 1998/06/26 10:32:54 mdw
54 * Cosmetic change: use sizeof(destination) in memcpy.
56 * Revision 1.17 1998/06/18 15:06:59 mdw
57 * Close log before execing program to avoid leaving a socket open.
59 * Revision 1.16 1998/04/23 13:21:04 mdw
60 * Small tweaks. Support no-network configuration option, and rearrange
61 * the help text a little.
63 * Revision 1.15 1998/01/13 11:10:44 mdw
64 * Add `TZ' to the list of variables to be preserved.
66 * Revision 1.14 1998/01/12 16:45:39 mdw
69 * Revision 1.13 1997/09/26 09:14:57 mdw
70 * Merged blowfish branch into trunk.
72 * Revision 1.12 1997/09/25 16:04:48 mdw
73 * Change directory after becoming someone else, instead of before. This
74 * avoids problems with root-squashed NFS mounts.
76 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
77 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
78 * because I prefer Blowfish (without any particularly strong evidence) but
79 * mainly because IDEA is patented and Blowfish isn't.
81 * Revision 1.11 1997/09/24 09:48:45 mdw
82 * Fix (scary) overrun bug in group allocation stuff.
84 * Revision 1.10 1997/09/17 10:14:10 mdw
85 * Fix a typo. Support service names in `--port' option.
87 * Revision 1.9 1997/09/10 10:28:05 mdw
88 * Allow default port to be given as a service name or port number. Handle
89 * groups properly (lots of options here).
91 * Revision 1.8 1997/09/08 13:56:24 mdw
92 * Change criteria for expunging items from the user's PATH: instead of
93 * removing things starting with `.', remove things not starting with `/'.
95 * Revision 1.7 1997/09/08 13:43:20 mdw
96 * Change userid when creating tracefiles rather than fiddling with
97 * `access': it works rather better. Also, insert some stdio buffer
98 * flushing to ensure tracedumps are completely written.
100 * Revision 1.6 1997/09/05 13:47:44 mdw
101 * Make the `-L' (trace-level) option's argument optional, like the long
104 * Revision 1.5 1997/09/05 11:45:19 mdw
105 * Add support for different login styles, and environment variable
106 * manipulation in a safe and useful way.
108 * Revision 1.4 1997/08/20 16:15:13 mdw
109 * Overhaul of environment handling. Fix daft bug in path search code.
111 * Revision 1.3 1997/08/07 16:28:59 mdw
112 * Do something useful when users attempt to become themselves.
114 * Revision 1.2 1997/08/04 10:24:20 mdw
115 * Sources placed under CVS control.
117 * Revision 1.1 1997/07/21 13:47:54 mdw
122 /*----- Header files ------------------------------------------------------*/
124 /* --- ANSI headers --- */
134 /* --- Unix headers --- */
136 #include <sys/types.h>
137 #include <sys/stat.h>
138 #include <sys/socket.h>
139 #include <sys/utsname.h>
141 #include <netinet/in.h>
143 #include <arpa/inet.h>
151 extern char **environ;
155 #include <mLib/alloc.h>
156 #include <mLib/mdwopt.h>
157 #include <mLib/quis.h>
158 #include <mLib/report.h>
159 #include <mLib/sym.h>
160 #include <mLib/trace.h>
162 /* --- Local headers --- */
174 /*----- Type definitions --------------------------------------------------*/
176 /* --- Symbol table entry for an environment variable --- */
178 typedef struct sym_env {
179 sym_base _base; /* Symbol table information */
180 unsigned f; /* Flags word (see below) */
181 char *val; /* Pointer to variable value */
184 /* --- Environment variable flags --- */
190 /* --- Login behaviour types --- */
192 #define l_preserve 0 /* Preserve the environment */
193 #define l_setuser 1 /* Update who I am */
194 #define l_login 2 /* Do a full login */
196 /* --- Group behaviour types --- *
198 * Note that these make a handy bitfield.
201 #ifdef HAVE_SETGROUPS
204 g_unset = 0, /* Nobody's set a preference */
205 g_keep = 1, /* Leave the group memberships */
206 g_replace = 2, /* Replace group memberships */
207 g_merge = (g_keep | g_replace) /* Merge the group memberships */
212 /*----- Static variables --------------------------------------------------*/
214 static sym_table bc__env;
216 /*----- Main code ---------------------------------------------------------*/
218 /* --- @bc__write@ --- *
220 * Arguments: @FILE *fp@ = pointer to a stream to write on
221 * @const char *p@ = pointer to a string
225 * Use: Writes the string to the stream, substituting the program
226 * name (as returned by @quis@) for each occurrence of the
230 static void bc__write(FILE *fp, const char *p)
232 const char *n = quis();
234 size_t nl = strlen(n);
236 /* --- Try to be a little efficient --- *
238 * Gather up non-`$' characters using @strcspn@ and spew them out really
249 fwrite(n, nl, 1, fp);
254 /* --- @bc__setenv@ --- *
256 * Arguments: @sym_env *e@ = pointer to environment variable block
257 * @const char *val@ = value to set
261 * Use: Sets an environment variable block to the right value.
264 static void bc__setenv(sym_env *e, const char *val)
266 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
267 sprintf(e->val, "%s=%s", e->_base.name, val);
270 /* --- @bc__putenv@ --- *
272 * Arguments: @const char *var@ = name of the variable to set, or 0 if
273 * this is embedded in the value string
274 * @const char *val@ = value to set, or 0 if the variable must
276 * @unsigned int fl@ = flags to set
277 * @unsigned int force@ = force overwrite of preserved variables
279 * Returns: Pointer to symbol block, or zero if it was deleted.
281 * Use: Puts an item into the environment.
284 static sym_env *bc__putenv(const char *var, const char *val,
285 unsigned int fl, unsigned int force)
292 /* --- Sort out embedded variable names --- */
295 const char *p = strchr(val, '=');
310 /* --- Find the variable block --- */
313 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
314 if (!f || ~e->f & envFlag_preserve || force) {
320 e = sym_find(&bc__env, var, -1, 0, 0);
321 if (e && (force || ~e->f & envFlag_preserve))
322 sym_remove(&bc__env, e);
326 /* --- Tidy up and return --- */
335 /* --- @bc__addGroups@ --- *
337 * Arguments: @gid_t *g@ = pointer to a group array
338 * @int *png@ = pointer to number of entries in the array
339 * @const gid_t *a@ = pointer to groups to add
340 * @int na@ = number of groups to add
342 * Returns: Zero if it was OK, nonzero if we should stop now.
344 * Use: Adds groups to a groups array.
347 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
352 for (i = 0; i < na; i++) {
354 /* --- Ensure this group isn't already in the list --- */
356 for (j = 0; j < ng; j++) {
361 /* --- See if there's room for more --- */
363 if (ng >= NGROUPS_MAX) {
364 moan("too many groups (system limit exceeded) -- some have been lost");
369 /* --- Add the group --- */
379 /* --- @bc__banner@ --- *
381 * Arguments: @FILE *fp@ = stream to write on
385 * Use: Writes a banner containing copyright information.
388 static void bc__banner(FILE *fp)
390 bc__write(fp, "$ version " VERSION "\n");
393 /* --- @bc__usage@ --- *
395 * Arguments: @FILE *fp@ = stream to write on
399 * Use: Writes a terse reminder of command line syntax.
402 static void bc__usage(FILE *fp)
406 " $ -c <shell-command> <user>\n"
407 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
409 " $ -d [-p <port>] [-f <config-file>]\n"
414 /* --- @bc__help@ --- *
416 * Arguments: @FILE *fp@ = stream to write on
417 * @int suid@ = whether we're running set-uid
421 * Use: Displays a help message for this excellent piece of software.
424 static void bc__help(FILE *fp, int suid)
431 "The `$' program allows you to run a process as another user.\n"
432 "If a command name is given, this is the process executed. If the `-c'\n"
433 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
434 "given, your default login shell is used.\n"
436 "Your user id, the user id you wish to become, the name of the process\n"
437 "you wish to run, and the identity of the current host are looked up to\n"
438 "ensure that you have permission to do this.\n"
440 "Note that logs are kept of all uses of this program.\n"
442 "Options available are:\n"
444 "-h, --help Display this help text\n"
445 "-u, --usage Display a short usage summary\n"
446 "-v, --version Display $'s version number\n"
448 "-e, --preserve-environment Try to preserve the current environment\n"
449 "-s, --su, --set-user Set environment variables to reflect USER\n"
450 "-l, --login Really log in as USER\n"
452 #if DEFAULT_LOGIN_STYLE == l_preserve
453 "preserve-environment"
454 #elif DEFAULT_LOGIN_STYLE == l_setuser
456 #elif DEFAULT_LOGIN_STYLE == l_login
462 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
463 #ifdef HAVE_SETGROUPS
464 "-k, --keep-groups Keep your current set of groups\n"
465 "-m, --merge-groups Merge the lists of groups\n"
466 "-r, --replace-groups Replace the list of groups\n"
469 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
472 "-d, --daemon Start a daemon\n"
473 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
474 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
481 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
484 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
485 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
491 * Arguments: @int argc@ = number of command line arguments
492 * @char *argv[]@ = pointer to the various arguments
494 * Returns: Zero if successful.
496 * Use: Allows a user to change UID.
499 int main(int argc, char *argv[])
501 /* --- Request block setup parameters --- */
503 request rq; /* Request buffer to build */
504 char *cmd = 0; /* Shell command to execute */
505 char *binary = "/bin/sh"; /* Default binary to execute */
506 char **env = environ; /* Default environment to pass */
507 char **todo = 0; /* Pointer to argument list */
508 char *who = 0; /* Who we're meant to become */
509 struct passwd *from_pw = 0; /* User we are right now */
510 struct passwd *to_pw = 0; /* User we want to become */
512 /* --- Become server setup parameters --- */
515 char *conffile = file_RULES; /* Default config file for daemon */
516 int port = 0; /* Default port for daemon */
519 /* --- Miscellanous shared variables --- */
521 unsigned flags = 0; /* Various useful flags */
522 int style = DEFAULT_LOGIN_STYLE; /* Login style */
523 gid_t group = -1; /* Default group to set */
524 int gstyle = g_unset; /* No group style set yet */
526 #ifdef HAVE_SETGROUPS
527 gid_t groups[NGROUPS_MAX]; /* Set of groups */
528 int ngroups; /* Number of groups in the set */
531 /* --- Default argument list executes a shell command --- */
533 static char *shell[] = {
534 "/bin/sh", /* Bourne shell */
535 "-c", /* Read from command line */
536 0, /* Pointer to shell command */
540 /* --- Definitions for the various flags --- */
543 f_daemon = 1, /* Start up in daemon mode */
544 f_duff = 2, /* Fault in arguments */
545 f_shell = 4, /* Run a default shell */
546 f_dummy = 8, /* Don't actually do anything */
547 f_setuid = 16, /* We're running setuid */
548 f_havegroup = 32 /* Set a default group */
551 /* --- Set up the program name --- */
555 if (getuid() != geteuid())
558 /* --- Read the environment into a hashtable --- */
563 sym_create(&bc__env);
564 for (p = environ; *p; p++)
565 bc__putenv(0, *p, 0, 0);
568 /* --- Parse some command line arguments --- */
572 static struct option opts[] = {
574 /* --- Asking for help --- */
576 { "help", 0, 0, 'h' },
577 { "usage", 0, 0, 'u' },
578 { "version", 0, 0, 'v' },
580 /* --- Login style options --- */
582 { "preserve-environment", 0, 0, 'e' },
584 { "set-user", 0, 0, 's' },
585 { "login", 0, 0, 'l' },
587 /* --- Group style options --- */
589 { "group", gFlag_argReq, 0, 'g' },
590 #ifdef HAVE_SETGROUPS
591 { "keep-groups", 0, 0, 'k' },
592 { "merge-groups", 0, 0, 'm' },
593 { "replace-groups", 0, 0, 'r' },
596 /* --- Command to run options --- */
598 { "command", gFlag_argReq, 0, 'c' },
600 /* --- Server options --- */
603 { "daemon", 0, 0, 'd' },
604 { "port", gFlag_argReq, 0, 'p' },
605 { "config-file", gFlag_argReq, 0, 'f' },
608 /* --- Tracing options --- */
611 { "impersonate", gFlag_argReq, 0, 'I' },
612 { "trace", gFlag_argOpt, 0, 'T' },
613 { "trace-level", gFlag_argOpt, 0, 'L' },
619 i = mdwopt(argc, argv,
620 "-" /* Return non-options as options */
621 "huv" /* Asking for help */
622 "esl" /* Login style options */
623 #ifdef HAVE_SETGROUPS
624 "g:kmr" /* Group style options */
626 "g:" /* Group (without @setgroups@) */
628 "c:" /* Command to run options */
630 "dp:f:" /* Server options */
633 "I:T::L::" /* Tracing options */
636 opts, 0, 0, gFlag_envVar);
642 /* --- Asking for help --- */
645 bc__help(stdout, flags & f_setuid);
657 /* --- Login style options --- */
669 /* --- Group style options --- */
672 if (isdigit((unsigned char)optarg[0]))
673 group = atoi(optarg);
675 struct group *gr = getgrnam(optarg);
677 die(1, "unknown group `%s'", optarg);
680 flags |= f_havegroup;
693 /* --- Command to run options --- */
699 /* --- Server options --- */
703 if (isdigit((unsigned char)optarg[0]))
704 port = htons(atoi(optarg));
706 struct servent *s = getservbyname(optarg, "udp");
708 die(1, "unknown service name `%s'", optarg);
720 /* --- Pretend to be a different user --- *
722 * There are all sorts of nasty implications for this option. Don't
723 * allow it if we're running setuid. Disable the actual login anyway.
729 if (flags & f_setuid)
730 moan("shan't allow impersonation while running setuid");
733 if (isdigit((unsigned char)optarg[0]))
734 pw = getpwuid(atoi(optarg));
736 pw = getpwnam(optarg);
738 die(1, "can't impersonate unknown user `%s'", optarg);
739 from_pw = userdb_copyUser(pw);
740 rq.from = from_pw->pw_uid;
747 /* --- Tracing support --- *
749 * Be careful not to zap a file I wouldn't normally be allowed to write
758 if (optarg == 0 || strcmp(optarg, "-") == 0)
761 uid_t eu = geteuid(), ru = getuid();
764 if (setreuid(eu, ru))
769 die(1, "couldn't temporarily give up privileges: %s",
773 if ((fp = fopen(optarg, "w")) == 0) {
774 die(1, "couldn't open trace file `%s' for writing: %s",
775 optarg, strerror(errno));
779 if (setreuid(ru, eu))
783 die(1, "couldn't regain privileges: %s", strerror(errno));
785 trace_on(fp, TRACE_DFL);
786 trace(TRACE_MISC, "become: tracing enabled");
791 /* --- Setting trace levels --- */
797 /* --- Table of tracing facilities --- */
799 static trace_opt lvltbl[] = {
800 { 'm', TRACE_MISC, "miscellaneous messages" },
801 { 's', TRACE_SETUP, "building the request block" },
802 { 'r', TRACE_RULE, "ruleset scanning" },
803 { 'c', TRACE_CHECK, "request checking" },
805 { 'd', TRACE_DAEMON, "server process" },
806 { 'l', TRACE_CLIENT, "client process" },
807 { 'R', TRACE_RAND, "random number generator" },
808 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
810 { 'y', TRACE_YACC, "parsing configuration file" },
811 { 'D', TRACE_DFL, "default tracing options" },
812 { 'A', TRACE_ALL, "all tracing options" },
816 /* --- Output some help if there's no arguemnt --- */
818 trace_level(traceopt(lvltbl, optarg, TRACE_DFL,
819 (flags & f_setuid) ? TRACE_PRIV : 0));
824 /* --- Something that wasn't an option --- *
826 * The following nasties are supported:
828 * * NAME=VALUE -- preserve NAME, and give it a VALUE
829 * * NAME= -- preserve NAME, and give it an empty value
830 * * NAME- -- delete NAME
831 * * NAME! -- preserve NAME with existing value
833 * Anything else is either the user name (which is OK) or the start of
834 * the command (in which case I stop and read the rest of the command).
838 size_t sz = strcspn(optarg, "=-!");
841 /* --- None of the above --- */
843 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
853 /* --- Do the appropriate thing --- */
855 switch (optarg[sz]) {
857 bc__putenv(0, optarg, envFlag_preserve, 1);
861 bc__putenv(optarg, 0, 0, 1);
865 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
866 e->f |= envFlag_preserve;
871 /* --- Something I didn't understand has occurred --- */
880 if (flags & f_duff) {
885 /* --- Switch to daemon mode if requested --- */
888 if (flags & f_daemon) {
889 T( trace(TRACE_MISC, "become: daemon mode requested"); )
890 daemon_init(conffile, port);
895 /* --- Open a syslog --- */
897 openlog(quis(), 0, LOG_AUTH);
899 /* --- Pick out the uid --- */
909 if (isdigit((unsigned char)who[0]))
910 pw = getpwuid(atoi(who));
914 die(1, "unknown user `%s'", who);
915 to_pw = userdb_copyUser(pw);
919 /* --- Fill in the easy bits of the request --- */
925 pw = getpwuid(rq.from);
927 die(1, "who are you? (can't find user %li)", (long)rq.from);
928 from_pw = userdb_copyUser(pw);
931 /* --- Find the local host address --- */
937 if ((he = gethostbyname(u.nodename)) == 0)
938 die(1, "who am I? (can't resolve `%s')", u.nodename);
939 memcpy(&rq.host, he->h_addr, sizeof(rq.host));
942 /* --- Fiddle with group ownerships a bit --- */
945 #ifdef HAVE_SETGROUPS
946 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
950 /* --- Set the default login group, if there is one --- */
952 if (~flags & f_havegroup)
953 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
955 #ifndef HAVE_SETGROUPS
957 /* --- Check that it's valid --- */
959 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
960 die(1, "invalid default group");
964 /* --- Set the default group style --- */
966 if (gstyle == g_unset)
967 gstyle = (style == l_login) ? g_replace : g_merge;
969 /* --- Read in my current set of groups --- */
971 n_fgr = getgroups(NGROUPS_MAX, from_gr);
973 /* --- Now read the groups for the target user --- *
975 * Do this even if I'm using the @g_keep@ style -- I'll use it for
976 * checking that @group@ is valid.
984 to_gr[n_tgr++] = to_pw->pw_gid;
987 while ((gr = getgrent()) != 0) {
988 if (gr->gr_gid == to_gr[0])
990 for (p = gr->gr_mem; *p; p++) {
991 if (strcmp(to_pw->pw_name, *p) == 0) {
992 to_gr[n_tgr++] = gr->gr_gid;
993 if (n_tgr >= NGROUPS_MAX)
1004 /* --- Check that @group@ is reasonable --- */
1009 if (group == getgid() || group == from_pw->pw_gid)
1011 for (i = 0; i < n_fgr; i++) {
1012 if (group == from_gr[i])
1015 for (i = 0; i < n_tgr; i++) {
1016 if (group == to_gr[i])
1019 die(1, "invalid default group");
1023 /* --- Phew. Now comes the hard bit --- */
1031 if (gstyle & g_keep) {
1033 ga[i++] = from_pw->pw_gid;
1035 if (gstyle & g_replace)
1036 ga[i++] = to_pw->pw_gid;
1038 /* --- Style authorities will become apoplectic if shown this --- *
1040 * As far as I can see, it's the neatest way of writing it.
1044 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1045 ((gstyle & g_keep) &&
1046 bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
1047 ((gstyle & g_replace) &&
1048 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1053 /* --- Trace the results of all this --- */
1055 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1057 #ifdef HAVE_SETGROUPS
1058 IF_TRACING(TRACE_SETUP, {
1061 for (i = 1; i < ngroups; i++)
1062 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1067 /* --- Shell commands are easy --- */
1074 /* --- A command given on the command line isn't too hard --- */
1076 else if (optind < argc) {
1077 todo = argv + optind;
1086 /* --- An unadorned becoming requires little work --- */
1089 shell[0] = getenv("SHELL");
1091 shell[0] = from_pw->pw_shell;
1097 /* --- An su-like login needs slightly less effort --- */
1100 shell[0] = to_pw->pw_shell;
1106 /* --- A login request needs a little bit of work --- */
1109 const char *p = strrchr(to_pw->pw_shell, '/');
1114 p = to_pw->pw_shell;
1115 shell[0] = xmalloc(strlen(p) + 2);
1117 strcpy(shell[0] + 1, p);
1120 binary = to_pw->pw_shell;
1125 /* --- Mangle the environment --- *
1127 * This keeps getting more complicated all the time. (How true. Now I've
1128 * got all sorts of nasty environment mangling to do.)
1130 * The environment stuff now happens in seven phases:
1132 * 1. Mark very special variables to be preserved. Currently only TERM
1133 * and DISPLAY are treated in this way.
1135 * 2. Set and preserve Become's own environment variables.
1137 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1138 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1140 * 4. If we're preserving the environment or being `su'-like, process the
1141 * PATH variable a little. Otherwise reset it to something
1144 * 5. If we're being `login'-like, expunge all unpreserved variables.
1146 * 6. Expunge any security-critical variables.
1148 * 7. Build a new environment table to pass to child processes.
1152 /* --- Variables to be preserved always --- *
1154 * A user can explicitly expunge a variable in this list, in which case
1155 * we never get to see it here.
1158 static char *preserve[] = {
1159 "TERM", "DISPLAY", "TZ", 0
1162 /* --- Variables to be expunged --- *
1164 * Any environment string which has one of the following as a prefix will
1165 * be expunged from the environment passed to the called process. The
1166 * first line lists variables which have been used to list search paths
1167 * for shared libraries: by manipulating these, an attacker could replace
1168 * a standard library with one of his own. The second line lists other
1169 * well-known dangerous environment variables.
1172 static char *banned[] = {
1173 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1174 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1178 /* --- Other useful variables --- */
1187 /* --- Stage one. Preserve display-specific variables --- */
1189 for (pp = preserve; *pp; pp++) {
1190 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1191 e->f |= envFlag_preserve;
1194 /* --- Stage two. Set Become's own variables --- */
1196 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1198 bc__setenv(e, from_pw->pw_name);
1199 e->f |= envFlag_preserve;
1201 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1203 bc__setenv(e, from_pw->pw_dir);
1204 e->f |= envFlag_preserve;
1206 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1207 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1208 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1209 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1211 /* --- Stage three. Set user identity --- */
1215 static char *maildirs[] = {
1216 "/var/spool/mail", "/var/mail",
1217 "/usr/spool/mail", "/usr/mail",
1223 for (pp = maildirs; *pp; pp++) {
1224 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1225 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1226 bc__putenv("MAIL", b, envFlag_preserve, 0);
1230 } /* Fall through */
1233 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1234 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1235 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1236 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1240 /* --- Stage four. Set the user's PATH properly --- */
1243 /* --- Find an existing path --- *
1245 * If there's no path, or this is a login, then set a default path,
1246 * unless we're meant to preserve the existing one. Whew!
1249 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1251 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1253 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1254 envFlag_preserve, 0);
1257 /* --- Find the string --- */
1259 e->f = envFlag_preserve;
1260 p = strchr(e->val, '=') + 1;
1263 /* --- Write the new version to a dynamically allocated buffer --- */
1265 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1266 strcpy(e->val, "PATH=");
1269 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1284 /* --- Stages five and six. Expunge variables and count numbers --- *
1286 * Folded together, so I only need one pass through the table. Also
1287 * count the number of variables needed at this time.
1292 for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1294 /* --- Login style expunges all unpreserved variables --- */
1296 if (style == l_login && ~e->f & envFlag_preserve)
1299 /* --- Otherwise just check the name against the list --- */
1301 for (pp = banned; *pp; pp++) {
1304 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1306 } else if (strcmp(e->_base.name, *pp) == 0)
1314 sym_remove(&bc__env, e);
1317 /* --- Stage seven. Build the new environment block --- */
1319 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1321 for (sym_mkiter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1326 /* --- Trace the command --- */
1328 IF_TRACING(TRACE_SETUP, {
1331 trace(TRACE_SETUP, "setup: from user %s to user %s",
1332 from_pw->pw_name, to_pw->pw_name);
1333 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1334 for (i = 0; todo[i]; i++)
1335 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1336 for (i = 0; env[i]; i++)
1337 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1340 /* --- If necessary, resolve the path to the command --- */
1342 if (!strchr(binary, '/')) {
1346 if ((p = getenv("PATH")) == 0)
1347 p = "/bin:/usr/bin";
1350 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1352 /* --- Check length of string before copying --- */
1354 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1357 /* --- Now build the pathname and check it --- *
1359 * Issue: user can take advantage of these privileges to decide whether
1360 * a program with a given name exists. I'm not sure that's
1361 * particularly significant: it only works on regular files with
1362 * execute permissions, and if you're relying on the names of these
1363 * being secret to keep your security up, then you're doing something
1364 * deeply wrong anyway. On the other hand, it's useful to allow people
1365 * to be able to execute programs and scripts which they wouldn't
1366 * otherwise have access to. [This problem was brought up on
1367 * Bugtraq, as a complaint against sudo.]
1371 sprintf(rq.cmd, "%s/%s", p, binary);
1372 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1373 st.st_mode & 0111 && /* Check it's executable */
1374 S_ISREG(st.st_mode)) /* Check it's a file */
1379 die(1, "couldn't find `%s' in path", todo[0]);
1383 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1385 /* --- Canonicalise the path string, if necessary --- */
1392 /* --- Insert current directory name if path not absolute --- */
1397 if (!getcwd(b, sizeof(b)))
1398 die(1, "couldn't read current directory: %s", strerror(errno));
1403 /* --- Now copy over characters from the path string --- */
1407 /* --- Check for buffer overflows here --- *
1409 * I write at most one byte per iteration so this is OK. Remember to
1410 * allow one for the null byte.
1413 if (p >= b + sizeof(b) - 1)
1414 die(1, "internal error: buffer overflow while canonifying path");
1416 /* --- Reduce multiple slashes to just one --- */
1424 /* --- Handle dots in filenames --- *
1426 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1427 * we've just stuck the current directory on the end of the buffer,
1428 * or we've just put something else on the end.
1431 else if (*q == '.' && p[-1] == '/') {
1433 /* --- A simple `./' just gets removed --- */
1435 if (q[1] == 0 || q[1] == '/') {
1441 /* --- A `../' needs to be peeled back to the previous `/' --- */
1443 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1446 while (p > b && p[-1] != '/')
1459 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1461 /* --- Run the check --- *
1463 * If the user is already what she wants to be, then print a warning.
1464 * Then, if I was just going to spawn a shell, quit, to reduce user
1465 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1466 * checking if we're already root -- root can do anything anyway, and at
1467 * least this way we get some logging done, and offer a more friendly
1471 if (rq.from == rq.to) {
1472 moan("you already are `%s'!", to_pw->pw_name);
1473 if (flags & f_shell) {
1474 moan("(to prevent confusion, I'm not spawning a shell)");
1478 int a = (rq.from == 0) || check(&rq);
1481 "permission %s for %s to become %s to run `%s'",
1482 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1486 die(1, "permission denied");
1489 /* --- Now do the job --- */
1491 T( trace(TRACE_MISC, "become: permission granted"); )
1493 if (flags & f_dummy) {
1494 puts("permission granted");
1498 #ifdef HAVE_SETGROUPS
1499 if (setgroups(ngroups, groups) < 0)
1500 die(1, "couldn't set groups: %s", strerror(errno));
1503 if (setgid(group) < 0)
1504 die(1, "couldn't set default group: %s", strerror(errno));
1505 if (setuid(rq.to) < 0)
1506 die(1, "couldn't set uid: %s", strerror(errno));
1508 /* --- If this was a login, change current directory --- */
1510 if ((flags & f_shell) &&
1512 chdir(to_pw->pw_dir) < 0) {
1513 moan("couldn't change directory to `%s': %s",
1514 to_pw->pw_dir, strerror(errno));
1517 /* --- Finally, call the program --- */
1521 execve(rq.cmd, todo, env);
1522 die(1, "couldn't exec `%s': %s", rq.cmd, strerror(errno));
1526 /*----- That's all, folks -------------------------------------------------*/