3 * $Id: become.c,v 1.19 1998/06/29 13:10:41 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.19 1998/06/29 13:10:41 mdw
33 * Add some commentary regarding an issue in `sudo' which affects `become';
34 * I'm not fixing it yet because I don't think it's important.
36 * Fixed the PATH lookup code to use the right binary name: `binary' rather
37 * than `todo[0]'. The two only differ when `style' is `l_login', in which
38 * case `binary' has an initial `/' anyway, and the PATH lookup code is
39 * never invoked. The name is used in a buffer-overflow precheck, though,
40 * and auditing is easier if the naming is consistent.
42 * Revision 1.18 1998/06/26 10:32:54 mdw
43 * Cosmetic change: use sizeof(destination) in memcpy.
45 * Revision 1.17 1998/06/18 15:06:59 mdw
46 * Close log before execing program to avoid leaving a socket open.
48 * Revision 1.16 1998/04/23 13:21:04 mdw
49 * Small tweaks. Support no-network configuration option, and rearrange
50 * the help text a little.
52 * Revision 1.15 1998/01/13 11:10:44 mdw
53 * Add `TZ' to the list of variables to be preserved.
55 * Revision 1.14 1998/01/12 16:45:39 mdw
58 * Revision 1.13 1997/09/26 09:14:57 mdw
59 * Merged blowfish branch into trunk.
61 * Revision 1.12 1997/09/25 16:04:48 mdw
62 * Change directory after becoming someone else, instead of before. This
63 * avoids problems with root-squashed NFS mounts.
65 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
66 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
67 * because I prefer Blowfish (without any particularly strong evidence) but
68 * mainly because IDEA is patented and Blowfish isn't.
70 * Revision 1.11 1997/09/24 09:48:45 mdw
71 * Fix (scary) overrun bug in group allocation stuff.
73 * Revision 1.10 1997/09/17 10:14:10 mdw
74 * Fix a typo. Support service names in `--port' option.
76 * Revision 1.9 1997/09/10 10:28:05 mdw
77 * Allow default port to be given as a service name or port number. Handle
78 * groups properly (lots of options here).
80 * Revision 1.8 1997/09/08 13:56:24 mdw
81 * Change criteria for expunging items from the user's PATH: instead of
82 * removing things starting with `.', remove things not starting with `/'.
84 * Revision 1.7 1997/09/08 13:43:20 mdw
85 * Change userid when creating tracefiles rather than fiddling with
86 * `access': it works rather better. Also, insert some stdio buffer
87 * flushing to ensure tracedumps are completely written.
89 * Revision 1.6 1997/09/05 13:47:44 mdw
90 * Make the `-L' (trace-level) option's argument optional, like the long
93 * Revision 1.5 1997/09/05 11:45:19 mdw
94 * Add support for different login styles, and environment variable
95 * manipulation in a safe and useful way.
97 * Revision 1.4 1997/08/20 16:15:13 mdw
98 * Overhaul of environment handling. Fix daft bug in path search code.
100 * Revision 1.3 1997/08/07 16:28:59 mdw
101 * Do something useful when users attempt to become themselves.
103 * Revision 1.2 1997/08/04 10:24:20 mdw
104 * Sources placed under CVS control.
106 * Revision 1.1 1997/07/21 13:47:54 mdw
111 /*----- Header files ------------------------------------------------------*/
113 /* --- ANSI headers --- */
123 /* --- Unix headers --- */
125 #include <sys/types.h>
126 #include <sys/stat.h>
127 #include <sys/socket.h>
128 #include <sys/utsname.h>
130 #include <netinet/in.h>
132 #include <arpa/inet.h>
140 extern char **environ;
142 /* --- Local headers --- */
157 /*----- Type definitions --------------------------------------------------*/
159 /* --- Symbol table entry for an environment variable --- */
161 typedef struct sym_env {
162 sym_base _base; /* Symbol table information */
163 unsigned f; /* Flags word (see below) */
164 char *val; /* Pointer to variable value */
167 /* --- Environment variable flags --- */
173 /* --- Login behaviour types --- */
175 #define l_preserve 0 /* Preserve the environment */
176 #define l_setuser 1 /* Update who I am */
177 #define l_login 2 /* Do a full login */
179 /* --- Group behaviour types --- *
181 * Note that these make a handy bitfield.
184 #ifdef HAVE_SETGROUPS
187 g_unset = 0, /* Nobody's set a preference */
188 g_keep = 1, /* Leave the group memberships */
189 g_replace = 2, /* Replace group memberships */
190 g_merge = (g_keep | g_replace) /* Merge the group memberships */
195 /*----- Static variables --------------------------------------------------*/
197 static sym_table bc__env;
199 /*----- Main code ---------------------------------------------------------*/
201 /* --- @bc__write@ --- *
203 * Arguments: @FILE *fp@ = pointer to a stream to write on
204 * @const char *p@ = pointer to a string
208 * Use: Writes the string to the stream, substituting the program
209 * name (as returned by @quis@) for each occurrence of the
213 static void bc__write(FILE *fp, const char *p)
215 const char *n = quis();
217 size_t nl = strlen(n);
219 /* --- Try to be a little efficient --- *
221 * Gather up non-`$' characters using @strcspn@ and spew them out really
232 fwrite(n, nl, 1, fp);
237 /* --- @bc__setenv@ --- *
239 * Arguments: @sym_env *e@ = pointer to environment variable block
240 * @const char *val@ = value to set
244 * Use: Sets an environment variable block to the right value.
247 static void bc__setenv(sym_env *e, const char *val)
249 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
250 sprintf(e->val, "%s=%s", e->_base.name, val);
253 /* --- @bc__putenv@ --- *
255 * Arguments: @const char *var@ = name of the variable to set, or 0 if
256 * this is embedded in the value string
257 * @const char *val@ = value to set, or 0 if the variable must
259 * @unsigned int fl@ = flags to set
260 * @unsigned int force@ = force overwrite of preserved variables
262 * Returns: Pointer to symbol block, or zero if it was deleted.
264 * Use: Puts an item into the environment.
267 static sym_env *bc__putenv(const char *var, const char *val,
268 unsigned int fl, unsigned int force)
275 /* --- Sort out embedded variable names --- */
278 const char *p = strchr(val, '=');
293 /* --- Find the variable block --- */
296 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
297 if (!f || ~e->f & envFlag_preserve || force) {
303 e = sym_find(&bc__env, var, -1, 0, 0);
304 if (e && (force || ~e->f & envFlag_preserve))
305 sym_remove(&bc__env, e);
309 /* --- Tidy up and return --- */
318 /* --- @bc__addGroups@ --- *
320 * Arguments: @gid_t *g@ = pointer to a group array
321 * @int *png@ = pointer to number of entries in the array
322 * @const gid_t *a@ = pointer to groups to add
323 * @int na@ = number of groups to add
325 * Returns: Zero if it was OK, nonzero if we should stop now.
327 * Use: Adds groups to a groups array.
330 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
335 for (i = 0; i < na; i++) {
337 /* --- Ensure this group isn't already in the list --- */
339 for (j = 0; j < ng; j++) {
344 /* --- See if there's room for more --- */
346 if (ng >= NGROUPS_MAX) {
347 moan("too many groups (system limit exceeded) -- some have been lost");
352 /* --- Add the group --- */
362 /* --- @bc__banner@ --- *
364 * Arguments: @FILE *fp@ = stream to write on
368 * Use: Writes a banner containing copyright information.
371 static void bc__banner(FILE *fp)
373 bc__write(fp, "$ version " VERSION "\n");
376 /* --- @bc__usage@ --- *
378 * Arguments: @FILE *fp@ = stream to write on
382 * Use: Writes a terse reminder of command line syntax.
385 static void bc__usage(FILE *fp)
389 " $ -c <shell-command> <user>\n"
390 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
392 " $ -d [-p <port>] [-f <config-file>]\n"
397 /* --- @bc__help@ --- *
399 * Arguments: @FILE *fp@ = stream to write on
400 * @int suid@ = whether we're running set-uid
404 * Use: Displays a help message for this excellent piece of software.
407 static void bc__help(FILE *fp, int suid)
414 "The `$' program allows you to run a process as another user.\n"
415 "If a command name is given, this is the process executed. If the `-c'\n"
416 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
417 "given, your default login shell is used.\n"
419 "Your user id, the user id you wish to become, the name of the process\n"
420 "you wish to run, and the identity of the current host are looked up to\n"
421 "ensure that you have permission to do this.\n"
423 "Note that logs are kept of all uses of this program.\n"
425 "Options available are:\n"
427 "-h, --help Display this help text\n"
428 "-u, --usage Display a short usage summary\n"
429 "-v, --version Display $'s version number\n"
431 "-e, --preserve-environment Try to preserve the current environment\n"
432 "-s, --su, --set-user Set environment variables to reflect USER\n"
433 "-l, --login Really log in as USER\n"
435 #if DEFAULT_LOGIN_STYLE == l_preserve
436 "preserve-environment"
437 #elif DEFAULT_LOGIN_STYLE == l_setuser
439 #elif DEFAULT_LOGIN_STYLE == l_login
445 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
446 #ifdef HAVE_SETGROUPS
447 "-k, --keep-groups Keep your current set of groups\n"
448 "-m, --merge-groups Merge the lists of groups\n"
449 "-r, --replace-groups Replace the list of groups\n"
452 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
455 "-d, --daemon Start a daemon\n"
456 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
457 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
464 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
467 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
468 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
474 * Arguments: @int argc@ = number of command line arguments
475 * @char *argv[]@ = pointer to the various arguments
477 * Returns: Zero if successful.
479 * Use: Allows a user to change UID.
482 int main(int argc, char *argv[])
484 /* --- Request block setup parameters --- */
486 request rq; /* Request buffer to build */
487 char *cmd = 0; /* Shell command to execute */
488 char *binary = "/bin/sh"; /* Default binary to execute */
489 char **env = environ; /* Default environment to pass */
490 char **todo = 0; /* Pointer to argument list */
491 char *who = 0; /* Who we're meant to become */
492 struct passwd *from_pw = 0; /* User we are right now */
493 struct passwd *to_pw = 0; /* User we want to become */
495 /* --- Become server setup parameters --- */
498 char *conffile = file_RULES; /* Default config file for daemon */
499 int port = 0; /* Default port for daemon */
502 /* --- Miscellanous shared variables --- */
504 unsigned flags = 0; /* Various useful flags */
505 int style = DEFAULT_LOGIN_STYLE; /* Login style */
506 gid_t group = -1; /* Default group to set */
507 int gstyle = g_unset; /* No group style set yet */
509 #ifdef HAVE_SETGROUPS
510 gid_t groups[NGROUPS_MAX]; /* Set of groups */
511 int ngroups; /* Number of groups in the set */
514 /* --- Default argument list executes a shell command --- */
516 static char *shell[] = {
517 "/bin/sh", /* Bourne shell */
518 "-c", /* Read from command line */
519 0, /* Pointer to shell command */
523 /* --- Definitions for the various flags --- */
526 f_daemon = 1, /* Start up in daemon mode */
527 f_duff = 2, /* Fault in arguments */
528 f_shell = 4, /* Run a default shell */
529 f_dummy = 8, /* Don't actually do anything */
530 f_setuid = 16, /* We're running setuid */
531 f_havegroup = 32 /* Set a default group */
534 /* --- Set up the program name --- */
538 if (getuid() != geteuid())
541 /* --- Read the environment into a hashtable --- */
546 sym_createTable(&bc__env);
547 for (p = environ; *p; p++)
548 bc__putenv(0, *p, 0, 0);
551 /* --- Parse some command line arguments --- */
555 static struct option opts[] = {
557 /* --- Asking for help --- */
559 { "help", 0, 0, 'h' },
560 { "usage", 0, 0, 'u' },
561 { "version", 0, 0, 'v' },
563 /* --- Login style options --- */
565 { "preserve-environment", 0, 0, 'e' },
567 { "set-user", 0, 0, 's' },
568 { "login", 0, 0, 'l' },
570 /* --- Group style options --- */
572 { "group", gFlag_argReq, 0, 'g' },
573 #ifdef HAVE_SETGROUPS
574 { "keep-groups", 0, 0, 'k' },
575 { "merge-groups", 0, 0, 'm' },
576 { "replace-groups", 0, 0, 'r' },
579 /* --- Command to run options --- */
581 { "command", gFlag_argReq, 0, 'c' },
583 /* --- Server options --- */
586 { "daemon", 0, 0, 'd' },
587 { "port", gFlag_argReq, 0, 'p' },
588 { "config-file", gFlag_argReq, 0, 'f' },
591 /* --- Tracing options --- */
594 { "impersonate", gFlag_argReq, 0, 'I' },
595 { "trace", gFlag_argOpt, 0, 'T' },
596 { "trace-level", gFlag_argOpt, 0, 'L' },
602 i = mdwopt(argc, argv,
603 "-" /* Return non-options as options */
604 "huv" /* Asking for help */
605 "esl" /* Login style options */
606 #ifdef HAVE_SETGROUPS
607 "g:kmr" /* Group style options */
609 "g:" /* Group (without @setgroups@) */
611 "c:" /* Command to run options */
613 "dp:f:" /* Server options */
616 "I:T::L::" /* Tracing options */
619 opts, 0, 0, gFlag_envVar);
625 /* --- Asking for help --- */
628 bc__help(stdout, flags & f_setuid);
640 /* --- Login style options --- */
652 /* --- Group style options --- */
655 if (isdigit((unsigned char)optarg[0]))
656 group = atoi(optarg);
658 struct group *gr = getgrnam(optarg);
660 die("unknown group `%s'", optarg);
663 flags |= f_havegroup;
676 /* --- Command to run options --- */
682 /* --- Server options --- */
686 if (isdigit((unsigned char)optarg[0]))
687 port = htons(atoi(optarg));
689 struct servent *s = getservbyname(optarg, "udp");
691 die("unknown service name `%s'", optarg);
703 /* --- Pretend to be a different user --- *
705 * There are all sorts of nasty implications for this option. Don't
706 * allow it if we're running setuid. Disable the actual login anyway.
712 if (flags & f_setuid)
713 moan("shan't allow impersonation while running setuid");
716 if (isdigit((unsigned char)optarg[0]))
717 pw = getpwuid(atoi(optarg));
719 pw = getpwnam(optarg);
721 die("can't impersonate unknown user `%s'", optarg);
722 from_pw = userdb_copyUser(pw);
723 rq.from = from_pw->pw_uid;
730 /* --- Tracing support --- *
732 * Be careful not to zap a file I wouldn't normally be allowed to write
741 if (optarg == 0 || strcmp(optarg, "-") == 0)
744 uid_t eu = geteuid(), ru = getuid();
747 if (setreuid(eu, ru))
752 die("couldn't temporarily give up privileges: %s",
756 if ((fp = fopen(optarg, "w")) == 0) {
757 die("couldn't open trace file `%s' for writing: %s",
758 optarg, strerror(errno));
762 if (setreuid(ru, eu))
766 die("couldn't regain privileges: %s", strerror(errno));
768 traceon(fp, TRACE_DFL);
769 trace(TRACE_MISC, "become: tracing enabled");
774 /* --- Setting trace levels --- */
780 unsigned int lvl = 0, l;
781 const char *p = optarg;
783 /* --- Table of tracing facilities --- */
791 static tr lvltbl[] = {
792 { 'm', TRACE_MISC, "miscellaneous messages" },
793 { 's', TRACE_SETUP, "building the request block" },
794 { 'r', TRACE_RULE, "ruleset scanning" },
795 { 'c', TRACE_CHECK, "request checking" },
797 { 'd', TRACE_DAEMON, "server process" },
798 { 'l', TRACE_CLIENT, "client process" },
799 { 'R', TRACE_RAND, "random number generator" },
800 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
802 { 'y', TRACE_YACC, "parsing configuration file" },
803 { 'D', TRACE_DFL, "default tracing options" },
804 { 'A', TRACE_ALL, "all tracing options" },
809 /* --- Output some help if there's no arguemnt --- */
817 for (tp = lvltbl; tp->l; tp++) {
818 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
819 printf("%c -- %s\n", tp->ch, tp->help);
823 "Also, `+' and `-' options are recognised to turn on and off various\n"
824 "tracing options. For example, `A-r' enables everything except ruleset\n"
825 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
837 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
840 if (flags & f_setuid)
843 lvl = sense ? (lvl | l) : (lvl & ~l);
845 moan("unknown trace option `%c'", *p);
851 yydebug = ((lvl & TRACE_YACC) != 0);
856 /* --- Something that wasn't an option --- *
858 * The following nasties are supported:
860 * * NAME=VALUE -- preserve NAME, and give it a VALUE
861 * * NAME= -- preserve NAME, and give it an empty value
862 * * NAME- -- delete NAME
863 * * NAME! -- preserve NAME with existing value
865 * Anything else is either the user name (which is OK) or the start of
866 * the command (in which case I stop and read the rest of the command).
870 size_t sz = strcspn(optarg, "=-!");
873 /* --- None of the above --- */
875 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
885 /* --- Do the appropriate thing --- */
887 switch (optarg[sz]) {
889 bc__putenv(0, optarg, envFlag_preserve, 1);
893 bc__putenv(optarg, 0, 0, 1);
897 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
898 e->f |= envFlag_preserve;
903 /* --- Something I didn't understand has occurred --- */
912 if (flags & f_duff) {
917 /* --- Switch to daemon mode if requested --- */
920 if (flags & f_daemon) {
921 T( trace(TRACE_MISC, "become: daemon mode requested"); )
922 daemon_init(conffile, port);
927 /* --- Open a syslog --- */
929 openlog(quis(), 0, LOG_AUTH);
931 /* --- Pick out the uid --- */
941 if (isdigit((unsigned char)who[0]))
942 pw = getpwuid(atoi(who));
946 die("unknown user `%s'", who);
947 to_pw = userdb_copyUser(pw);
951 /* --- Fill in the easy bits of the request --- */
957 pw = getpwuid(rq.from);
959 die("who are you? (can't find user %li)", (long)rq.from);
960 from_pw = userdb_copyUser(pw);
963 /* --- Find the local host address --- */
969 if ((he = gethostbyname(u.nodename)) == 0)
970 die("who am I? (can't resolve `%s')", u.nodename);
971 memcpy(&rq.host, he->h_addr, sizeof(rq.host));
974 /* --- Fiddle with group ownerships a bit --- */
977 #ifdef HAVE_SETGROUPS
978 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
982 /* --- Set the default login group, if there is one --- */
984 if (~flags & f_havegroup)
985 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
987 #ifndef HAVE_SETGROUPS
989 /* --- Check that it's valid --- */
991 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
992 die("invalid default group");
996 /* --- Set the default group style --- */
998 if (gstyle == g_unset)
999 gstyle = (style == l_login) ? g_replace : g_merge;
1001 /* --- Read in my current set of groups --- */
1003 n_fgr = getgroups(NGROUPS_MAX, from_gr);
1005 /* --- Now read the groups for the target user --- *
1007 * Do this even if I'm using the @g_keep@ style -- I'll use it for
1008 * checking that @group@ is valid.
1016 to_gr[n_tgr++] = to_pw->pw_gid;
1019 while ((gr = getgrent()) != 0) {
1020 if (gr->gr_gid == to_gr[0])
1022 for (p = gr->gr_mem; *p; p++) {
1023 if (strcmp(to_pw->pw_name, *p) == 0) {
1024 to_gr[n_tgr++] = gr->gr_gid;
1025 if (n_tgr >= NGROUPS_MAX)
1036 /* --- Check that @group@ is reasonable --- */
1041 if (group == getgid() || group == from_pw->pw_gid)
1043 for (i = 0; i < n_fgr; i++) {
1044 if (group == from_gr[i])
1047 for (i = 0; i < n_tgr; i++) {
1048 if (group == to_gr[i])
1051 die("invalid default group");
1055 /* --- Phew. Now comes the hard bit --- */
1063 if (gstyle & g_keep) {
1065 ga[i++] = from_pw->pw_gid;
1067 if (gstyle & g_replace)
1068 ga[i++] = to_pw->pw_gid;
1070 /* --- Style authorities will become apoplectic if shown this --- *
1072 * As far as I can see, it's the neatest way of writing it.
1076 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1077 ((gstyle & g_keep) &&
1078 bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
1079 ((gstyle & g_replace) &&
1080 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1085 /* --- Trace the results of all this --- */
1087 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1089 #ifdef HAVE_SETGROUPS
1090 IF_TRACING(TRACE_SETUP, {
1093 for (i = 1; i < ngroups; i++)
1094 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1099 /* --- Shell commands are easy --- */
1106 /* --- A command given on the command line isn't too hard --- */
1108 else if (optind < argc) {
1109 todo = argv + optind;
1118 /* --- An unadorned becoming requires little work --- */
1121 shell[0] = getenv("SHELL");
1123 shell[0] = from_pw->pw_shell;
1129 /* --- An su-like login needs slightly less effort --- */
1132 shell[0] = to_pw->pw_shell;
1138 /* --- A login request needs a little bit of work --- */
1141 const char *p = strrchr(to_pw->pw_shell, '/');
1146 p = to_pw->pw_shell;
1147 shell[0] = xmalloc(strlen(p) + 2);
1149 strcpy(shell[0] + 1, p);
1152 binary = to_pw->pw_shell;
1157 /* --- Mangle the environment --- *
1159 * This keeps getting more complicated all the time. (How true. Now I've
1160 * got all sorts of nasty environment mangling to do.)
1162 * The environment stuff now happens in seven phases:
1164 * 1. Mark very special variables to be preserved. Currently only TERM
1165 * and DISPLAY are treated in this way.
1167 * 2. Set and preserve Become's own environment variables.
1169 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1170 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1172 * 4. If we're preserving the environment or being `su'-like, process the
1173 * PATH variable a little. Otherwise reset it to something
1176 * 5. If we're being `login'-like, expunge all unpreserved variables.
1178 * 6. Expunge any security-critical variables.
1180 * 7. Build a new environment table to pass to child processes.
1184 /* --- Variables to be preserved always --- *
1186 * A user can explicitly expunge a variable in this list, in which case
1187 * we never get to see it here.
1190 static char *preserve[] = {
1191 "TERM", "DISPLAY", "TZ", 0
1194 /* --- Variables to be expunged --- *
1196 * Any environment string which has one of the following as a prefix will
1197 * be expunged from the environment passed to the called process. The
1198 * first line lists variables which have been used to list search paths
1199 * for shared libraries: by manipulating these, an attacker could replace
1200 * a standard library with one of his own. The second line lists other
1201 * well-known dangerous environment variables.
1204 static char *banned[] = {
1205 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1206 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1210 /* --- Other useful variables --- */
1219 /* --- Stage one. Preserve display-specific variables --- */
1221 for (pp = preserve; *pp; pp++) {
1222 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1223 e->f |= envFlag_preserve;
1226 /* --- Stage two. Set Become's own variables --- */
1228 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1230 bc__setenv(e, from_pw->pw_name);
1231 e->f |= envFlag_preserve;
1233 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1235 bc__setenv(e, from_pw->pw_dir);
1236 e->f |= envFlag_preserve;
1238 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1239 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1240 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1241 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1243 /* --- Stage three. Set user identity --- */
1247 static char *maildirs[] = {
1248 "/var/spool/mail", "/var/mail",
1249 "/usr/spool/mail", "/usr/mail",
1255 for (pp = maildirs; *pp; pp++) {
1256 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1257 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1258 bc__putenv("MAIL", b, envFlag_preserve, 0);
1262 } /* Fall through */
1265 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1266 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1267 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1268 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1272 /* --- Stage four. Set the user's PATH properly --- */
1275 /* --- Find an existing path --- *
1277 * If there's no path, or this is a login, then set a default path,
1278 * unless we're meant to preserve the existing one. Whew!
1281 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1283 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1285 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1286 envFlag_preserve, 0);
1289 /* --- Find the string --- */
1291 e->f = envFlag_preserve;
1292 p = strchr(e->val, '=') + 1;
1295 /* --- Write the new version to a dynamically allocated buffer --- */
1297 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1298 strcpy(e->val, "PATH=");
1301 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1316 /* --- Stages five and six. Expunge variables and count numbers --- *
1318 * Folded together, so I only need one pass through the table. Also
1319 * count the number of variables needed at this time.
1324 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1326 /* --- Login style expunges all unpreserved variables --- */
1328 if (style == l_login && ~e->f & envFlag_preserve)
1331 /* --- Otherwise just check the name against the list --- */
1333 for (pp = banned; *pp; pp++) {
1336 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1338 } else if (strcmp(e->_base.name, *pp) == 0)
1346 sym_remove(&bc__env, e);
1349 /* --- Stage seven. Build the new environment block --- */
1351 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1353 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1358 /* --- Trace the command --- */
1360 IF_TRACING(TRACE_SETUP, {
1363 trace(TRACE_SETUP, "setup: from user %s to user %s",
1364 from_pw->pw_name, to_pw->pw_name);
1365 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1366 for (i = 0; todo[i]; i++)
1367 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1368 for (i = 0; env[i]; i++)
1369 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1372 /* --- If necessary, resolve the path to the command --- */
1374 if (!strchr(binary, '/')) {
1378 if ((p = getenv("PATH")) == 0)
1379 p = "/bin:/usr/bin";
1382 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1384 /* --- Check length of string before copying --- */
1386 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1389 /* --- Now build the pathname and check it --- *
1391 * Issue: user can take advantage of these privileges to decide whether
1392 * a program with a given name exists. I'm not sure that's
1393 * particularly significant: it only works on regular files with
1394 * execute permissions, and if you're relying on the names of these
1395 * being secret to keep your security up, then you're doing something
1396 * deeply wrong anyway. On the other hand, it's useful to allow people
1397 * to be able to execute programs and scripts which they wouldn't
1398 * otherwise have access to. [This problem was brought up on
1399 * Bugtraq, as a complaint against sudo.]
1402 sprintf(rq.cmd, "%s/%s", p, binary);
1403 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1404 st.st_mode & 0111 && /* Check it's executable */
1405 S_ISREG(st.st_mode)) /* Check it's a file */
1410 die("couldn't find `%s' in path", todo[0]);
1414 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1416 /* --- Canonicalise the path string, if necessary --- */
1423 /* --- Insert current directory name if path not absolute --- */
1428 if (!getcwd(b, sizeof(b)))
1429 die("couldn't read current directory: %s", strerror(errno));
1434 /* --- Now copy over characters from the path string --- */
1438 /* --- Check for buffer overflows here --- *
1440 * I write at most one byte per iteration so this is OK. Remember to
1441 * allow one for the null byte.
1444 if (p >= b + sizeof(b) - 1)
1445 die("internal error: buffer overflow while canonifying path");
1447 /* --- Reduce multiple slashes to just one --- */
1455 /* --- Handle dots in filenames --- *
1457 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1458 * we've just stuck the current directory on the end of the buffer,
1459 * or we've just put something else on the end.
1462 else if (*q == '.' && p[-1] == '/') {
1464 /* --- A simple `./' just gets removed --- */
1466 if (q[1] == 0 || q[1] == '/') {
1472 /* --- A `../' needs to be peeled back to the previous `/' --- */
1474 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1477 while (p > b && p[-1] != '/')
1490 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1492 /* --- Run the check --- *
1494 * If the user is already what she wants to be, then print a warning.
1495 * Then, if I was just going to spawn a shell, quit, to reduce user
1496 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1497 * checking if we're already root -- root can do anything anyway, and at
1498 * least this way we get some logging done, and offer a more friendly
1502 if (rq.from == rq.to) {
1503 moan("you already are `%s'!", to_pw->pw_name);
1504 if (flags & f_shell) {
1505 moan("(to prevent confusion, I'm not spawning a shell)");
1509 int a = (rq.from == 0) || check(&rq);
1512 "permission %s for %s to become %s to run `%s'",
1513 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1517 die("permission denied");
1520 /* --- Now do the job --- */
1522 T( trace(TRACE_MISC, "become: permission granted"); )
1524 if (flags & f_dummy) {
1525 puts("permission granted");
1529 #ifdef HAVE_SETGROUPS
1530 if (setgroups(ngroups, groups) < 0)
1531 die("couldn't set groups: %s", strerror(errno));
1534 if (setgid(group) < 0)
1535 die("couldn't set default group: %s", strerror(errno));
1536 if (setuid(rq.to) < 0)
1537 die("couldn't set uid: %s", strerror(errno));
1539 /* --- If this was a login, change current directory --- */
1541 if ((flags & f_shell) &&
1543 chdir(to_pw->pw_dir) < 0) {
1544 moan("couldn't change directory to `%s': %s",
1545 to_pw->pw_dir, strerror(errno));
1548 /* --- Finally, call the program --- */
1552 execve(rq.cmd, todo, env);
1553 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1557 /*----- That's all, folks -------------------------------------------------*/