3 * $Id: become.c,v 1.21 1999/07/28 09:31:01 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.21 1999/07/28 09:31:01 mdw
33 * Empty path components are equivalent to `.'.
35 * Revision 1.20 1999/05/04 16:17:11 mdw
36 * Change to header file name for parser. See log for `parse.h' for
39 * Revision 1.19 1998/06/29 13:10:41 mdw
40 * Add some commentary regarding an issue in `sudo' which affects `become';
41 * I'm not fixing it yet because I don't think it's important.
43 * Fixed the PATH lookup code to use the right binary name: `binary' rather
44 * than `todo[0]'. The two only differ when `style' is `l_login', in which
45 * case `binary' has an initial `/' anyway, and the PATH lookup code is
46 * never invoked. The name is used in a buffer-overflow precheck, though,
47 * and auditing is easier if the naming is consistent.
49 * Revision 1.18 1998/06/26 10:32:54 mdw
50 * Cosmetic change: use sizeof(destination) in memcpy.
52 * Revision 1.17 1998/06/18 15:06:59 mdw
53 * Close log before execing program to avoid leaving a socket open.
55 * Revision 1.16 1998/04/23 13:21:04 mdw
56 * Small tweaks. Support no-network configuration option, and rearrange
57 * the help text a little.
59 * Revision 1.15 1998/01/13 11:10:44 mdw
60 * Add `TZ' to the list of variables to be preserved.
62 * Revision 1.14 1998/01/12 16:45:39 mdw
65 * Revision 1.13 1997/09/26 09:14:57 mdw
66 * Merged blowfish branch into trunk.
68 * Revision 1.12 1997/09/25 16:04:48 mdw
69 * Change directory after becoming someone else, instead of before. This
70 * avoids problems with root-squashed NFS mounts.
72 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
73 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
74 * because I prefer Blowfish (without any particularly strong evidence) but
75 * mainly because IDEA is patented and Blowfish isn't.
77 * Revision 1.11 1997/09/24 09:48:45 mdw
78 * Fix (scary) overrun bug in group allocation stuff.
80 * Revision 1.10 1997/09/17 10:14:10 mdw
81 * Fix a typo. Support service names in `--port' option.
83 * Revision 1.9 1997/09/10 10:28:05 mdw
84 * Allow default port to be given as a service name or port number. Handle
85 * groups properly (lots of options here).
87 * Revision 1.8 1997/09/08 13:56:24 mdw
88 * Change criteria for expunging items from the user's PATH: instead of
89 * removing things starting with `.', remove things not starting with `/'.
91 * Revision 1.7 1997/09/08 13:43:20 mdw
92 * Change userid when creating tracefiles rather than fiddling with
93 * `access': it works rather better. Also, insert some stdio buffer
94 * flushing to ensure tracedumps are completely written.
96 * Revision 1.6 1997/09/05 13:47:44 mdw
97 * Make the `-L' (trace-level) option's argument optional, like the long
100 * Revision 1.5 1997/09/05 11:45:19 mdw
101 * Add support for different login styles, and environment variable
102 * manipulation in a safe and useful way.
104 * Revision 1.4 1997/08/20 16:15:13 mdw
105 * Overhaul of environment handling. Fix daft bug in path search code.
107 * Revision 1.3 1997/08/07 16:28:59 mdw
108 * Do something useful when users attempt to become themselves.
110 * Revision 1.2 1997/08/04 10:24:20 mdw
111 * Sources placed under CVS control.
113 * Revision 1.1 1997/07/21 13:47:54 mdw
118 /*----- Header files ------------------------------------------------------*/
120 /* --- ANSI headers --- */
130 /* --- Unix headers --- */
132 #include <sys/types.h>
133 #include <sys/stat.h>
134 #include <sys/socket.h>
135 #include <sys/utsname.h>
137 #include <netinet/in.h>
139 #include <arpa/inet.h>
147 extern char **environ;
149 /* --- Local headers --- */
164 /*----- Type definitions --------------------------------------------------*/
166 /* --- Symbol table entry for an environment variable --- */
168 typedef struct sym_env {
169 sym_base _base; /* Symbol table information */
170 unsigned f; /* Flags word (see below) */
171 char *val; /* Pointer to variable value */
174 /* --- Environment variable flags --- */
180 /* --- Login behaviour types --- */
182 #define l_preserve 0 /* Preserve the environment */
183 #define l_setuser 1 /* Update who I am */
184 #define l_login 2 /* Do a full login */
186 /* --- Group behaviour types --- *
188 * Note that these make a handy bitfield.
191 #ifdef HAVE_SETGROUPS
194 g_unset = 0, /* Nobody's set a preference */
195 g_keep = 1, /* Leave the group memberships */
196 g_replace = 2, /* Replace group memberships */
197 g_merge = (g_keep | g_replace) /* Merge the group memberships */
202 /*----- Static variables --------------------------------------------------*/
204 static sym_table bc__env;
206 /*----- Main code ---------------------------------------------------------*/
208 /* --- @bc__write@ --- *
210 * Arguments: @FILE *fp@ = pointer to a stream to write on
211 * @const char *p@ = pointer to a string
215 * Use: Writes the string to the stream, substituting the program
216 * name (as returned by @quis@) for each occurrence of the
220 static void bc__write(FILE *fp, const char *p)
222 const char *n = quis();
224 size_t nl = strlen(n);
226 /* --- Try to be a little efficient --- *
228 * Gather up non-`$' characters using @strcspn@ and spew them out really
239 fwrite(n, nl, 1, fp);
244 /* --- @bc__setenv@ --- *
246 * Arguments: @sym_env *e@ = pointer to environment variable block
247 * @const char *val@ = value to set
251 * Use: Sets an environment variable block to the right value.
254 static void bc__setenv(sym_env *e, const char *val)
256 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
257 sprintf(e->val, "%s=%s", e->_base.name, val);
260 /* --- @bc__putenv@ --- *
262 * Arguments: @const char *var@ = name of the variable to set, or 0 if
263 * this is embedded in the value string
264 * @const char *val@ = value to set, or 0 if the variable must
266 * @unsigned int fl@ = flags to set
267 * @unsigned int force@ = force overwrite of preserved variables
269 * Returns: Pointer to symbol block, or zero if it was deleted.
271 * Use: Puts an item into the environment.
274 static sym_env *bc__putenv(const char *var, const char *val,
275 unsigned int fl, unsigned int force)
282 /* --- Sort out embedded variable names --- */
285 const char *p = strchr(val, '=');
300 /* --- Find the variable block --- */
303 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
304 if (!f || ~e->f & envFlag_preserve || force) {
310 e = sym_find(&bc__env, var, -1, 0, 0);
311 if (e && (force || ~e->f & envFlag_preserve))
312 sym_remove(&bc__env, e);
316 /* --- Tidy up and return --- */
325 /* --- @bc__addGroups@ --- *
327 * Arguments: @gid_t *g@ = pointer to a group array
328 * @int *png@ = pointer to number of entries in the array
329 * @const gid_t *a@ = pointer to groups to add
330 * @int na@ = number of groups to add
332 * Returns: Zero if it was OK, nonzero if we should stop now.
334 * Use: Adds groups to a groups array.
337 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
342 for (i = 0; i < na; i++) {
344 /* --- Ensure this group isn't already in the list --- */
346 for (j = 0; j < ng; j++) {
351 /* --- See if there's room for more --- */
353 if (ng >= NGROUPS_MAX) {
354 moan("too many groups (system limit exceeded) -- some have been lost");
359 /* --- Add the group --- */
369 /* --- @bc__banner@ --- *
371 * Arguments: @FILE *fp@ = stream to write on
375 * Use: Writes a banner containing copyright information.
378 static void bc__banner(FILE *fp)
380 bc__write(fp, "$ version " VERSION "\n");
383 /* --- @bc__usage@ --- *
385 * Arguments: @FILE *fp@ = stream to write on
389 * Use: Writes a terse reminder of command line syntax.
392 static void bc__usage(FILE *fp)
396 " $ -c <shell-command> <user>\n"
397 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
399 " $ -d [-p <port>] [-f <config-file>]\n"
404 /* --- @bc__help@ --- *
406 * Arguments: @FILE *fp@ = stream to write on
407 * @int suid@ = whether we're running set-uid
411 * Use: Displays a help message for this excellent piece of software.
414 static void bc__help(FILE *fp, int suid)
421 "The `$' program allows you to run a process as another user.\n"
422 "If a command name is given, this is the process executed. If the `-c'\n"
423 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
424 "given, your default login shell is used.\n"
426 "Your user id, the user id you wish to become, the name of the process\n"
427 "you wish to run, and the identity of the current host are looked up to\n"
428 "ensure that you have permission to do this.\n"
430 "Note that logs are kept of all uses of this program.\n"
432 "Options available are:\n"
434 "-h, --help Display this help text\n"
435 "-u, --usage Display a short usage summary\n"
436 "-v, --version Display $'s version number\n"
438 "-e, --preserve-environment Try to preserve the current environment\n"
439 "-s, --su, --set-user Set environment variables to reflect USER\n"
440 "-l, --login Really log in as USER\n"
442 #if DEFAULT_LOGIN_STYLE == l_preserve
443 "preserve-environment"
444 #elif DEFAULT_LOGIN_STYLE == l_setuser
446 #elif DEFAULT_LOGIN_STYLE == l_login
452 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
453 #ifdef HAVE_SETGROUPS
454 "-k, --keep-groups Keep your current set of groups\n"
455 "-m, --merge-groups Merge the lists of groups\n"
456 "-r, --replace-groups Replace the list of groups\n"
459 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
462 "-d, --daemon Start a daemon\n"
463 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
464 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
471 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
474 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
475 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
481 * Arguments: @int argc@ = number of command line arguments
482 * @char *argv[]@ = pointer to the various arguments
484 * Returns: Zero if successful.
486 * Use: Allows a user to change UID.
489 int main(int argc, char *argv[])
491 /* --- Request block setup parameters --- */
493 request rq; /* Request buffer to build */
494 char *cmd = 0; /* Shell command to execute */
495 char *binary = "/bin/sh"; /* Default binary to execute */
496 char **env = environ; /* Default environment to pass */
497 char **todo = 0; /* Pointer to argument list */
498 char *who = 0; /* Who we're meant to become */
499 struct passwd *from_pw = 0; /* User we are right now */
500 struct passwd *to_pw = 0; /* User we want to become */
502 /* --- Become server setup parameters --- */
505 char *conffile = file_RULES; /* Default config file for daemon */
506 int port = 0; /* Default port for daemon */
509 /* --- Miscellanous shared variables --- */
511 unsigned flags = 0; /* Various useful flags */
512 int style = DEFAULT_LOGIN_STYLE; /* Login style */
513 gid_t group = -1; /* Default group to set */
514 int gstyle = g_unset; /* No group style set yet */
516 #ifdef HAVE_SETGROUPS
517 gid_t groups[NGROUPS_MAX]; /* Set of groups */
518 int ngroups; /* Number of groups in the set */
521 /* --- Default argument list executes a shell command --- */
523 static char *shell[] = {
524 "/bin/sh", /* Bourne shell */
525 "-c", /* Read from command line */
526 0, /* Pointer to shell command */
530 /* --- Definitions for the various flags --- */
533 f_daemon = 1, /* Start up in daemon mode */
534 f_duff = 2, /* Fault in arguments */
535 f_shell = 4, /* Run a default shell */
536 f_dummy = 8, /* Don't actually do anything */
537 f_setuid = 16, /* We're running setuid */
538 f_havegroup = 32 /* Set a default group */
541 /* --- Set up the program name --- */
545 if (getuid() != geteuid())
548 /* --- Read the environment into a hashtable --- */
553 sym_createTable(&bc__env);
554 for (p = environ; *p; p++)
555 bc__putenv(0, *p, 0, 0);
558 /* --- Parse some command line arguments --- */
562 static struct option opts[] = {
564 /* --- Asking for help --- */
566 { "help", 0, 0, 'h' },
567 { "usage", 0, 0, 'u' },
568 { "version", 0, 0, 'v' },
570 /* --- Login style options --- */
572 { "preserve-environment", 0, 0, 'e' },
574 { "set-user", 0, 0, 's' },
575 { "login", 0, 0, 'l' },
577 /* --- Group style options --- */
579 { "group", gFlag_argReq, 0, 'g' },
580 #ifdef HAVE_SETGROUPS
581 { "keep-groups", 0, 0, 'k' },
582 { "merge-groups", 0, 0, 'm' },
583 { "replace-groups", 0, 0, 'r' },
586 /* --- Command to run options --- */
588 { "command", gFlag_argReq, 0, 'c' },
590 /* --- Server options --- */
593 { "daemon", 0, 0, 'd' },
594 { "port", gFlag_argReq, 0, 'p' },
595 { "config-file", gFlag_argReq, 0, 'f' },
598 /* --- Tracing options --- */
601 { "impersonate", gFlag_argReq, 0, 'I' },
602 { "trace", gFlag_argOpt, 0, 'T' },
603 { "trace-level", gFlag_argOpt, 0, 'L' },
609 i = mdwopt(argc, argv,
610 "-" /* Return non-options as options */
611 "huv" /* Asking for help */
612 "esl" /* Login style options */
613 #ifdef HAVE_SETGROUPS
614 "g:kmr" /* Group style options */
616 "g:" /* Group (without @setgroups@) */
618 "c:" /* Command to run options */
620 "dp:f:" /* Server options */
623 "I:T::L::" /* Tracing options */
626 opts, 0, 0, gFlag_envVar);
632 /* --- Asking for help --- */
635 bc__help(stdout, flags & f_setuid);
647 /* --- Login style options --- */
659 /* --- Group style options --- */
662 if (isdigit((unsigned char)optarg[0]))
663 group = atoi(optarg);
665 struct group *gr = getgrnam(optarg);
667 die("unknown group `%s'", optarg);
670 flags |= f_havegroup;
683 /* --- Command to run options --- */
689 /* --- Server options --- */
693 if (isdigit((unsigned char)optarg[0]))
694 port = htons(atoi(optarg));
696 struct servent *s = getservbyname(optarg, "udp");
698 die("unknown service name `%s'", optarg);
710 /* --- Pretend to be a different user --- *
712 * There are all sorts of nasty implications for this option. Don't
713 * allow it if we're running setuid. Disable the actual login anyway.
719 if (flags & f_setuid)
720 moan("shan't allow impersonation while running setuid");
723 if (isdigit((unsigned char)optarg[0]))
724 pw = getpwuid(atoi(optarg));
726 pw = getpwnam(optarg);
728 die("can't impersonate unknown user `%s'", optarg);
729 from_pw = userdb_copyUser(pw);
730 rq.from = from_pw->pw_uid;
737 /* --- Tracing support --- *
739 * Be careful not to zap a file I wouldn't normally be allowed to write
748 if (optarg == 0 || strcmp(optarg, "-") == 0)
751 uid_t eu = geteuid(), ru = getuid();
754 if (setreuid(eu, ru))
759 die("couldn't temporarily give up privileges: %s",
763 if ((fp = fopen(optarg, "w")) == 0) {
764 die("couldn't open trace file `%s' for writing: %s",
765 optarg, strerror(errno));
769 if (setreuid(ru, eu))
773 die("couldn't regain privileges: %s", strerror(errno));
775 traceon(fp, TRACE_DFL);
776 trace(TRACE_MISC, "become: tracing enabled");
781 /* --- Setting trace levels --- */
787 unsigned int lvl = 0, l;
788 const char *p = optarg;
790 /* --- Table of tracing facilities --- */
798 static tr lvltbl[] = {
799 { 'm', TRACE_MISC, "miscellaneous messages" },
800 { 's', TRACE_SETUP, "building the request block" },
801 { 'r', TRACE_RULE, "ruleset scanning" },
802 { 'c', TRACE_CHECK, "request checking" },
804 { 'd', TRACE_DAEMON, "server process" },
805 { 'l', TRACE_CLIENT, "client process" },
806 { 'R', TRACE_RAND, "random number generator" },
807 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
809 { 'y', TRACE_YACC, "parsing configuration file" },
810 { 'D', TRACE_DFL, "default tracing options" },
811 { 'A', TRACE_ALL, "all tracing options" },
816 /* --- Output some help if there's no arguemnt --- */
824 for (tp = lvltbl; tp->l; tp++) {
825 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
826 printf("%c -- %s\n", tp->ch, tp->help);
830 "Also, `+' and `-' options are recognised to turn on and off various\n"
831 "tracing options. For example, `A-r' enables everything except ruleset\n"
832 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
844 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
847 if (flags & f_setuid)
850 lvl = sense ? (lvl | l) : (lvl & ~l);
852 moan("unknown trace option `%c'", *p);
858 yydebug = ((lvl & TRACE_YACC) != 0);
863 /* --- Something that wasn't an option --- *
865 * The following nasties are supported:
867 * * NAME=VALUE -- preserve NAME, and give it a VALUE
868 * * NAME= -- preserve NAME, and give it an empty value
869 * * NAME- -- delete NAME
870 * * NAME! -- preserve NAME with existing value
872 * Anything else is either the user name (which is OK) or the start of
873 * the command (in which case I stop and read the rest of the command).
877 size_t sz = strcspn(optarg, "=-!");
880 /* --- None of the above --- */
882 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
892 /* --- Do the appropriate thing --- */
894 switch (optarg[sz]) {
896 bc__putenv(0, optarg, envFlag_preserve, 1);
900 bc__putenv(optarg, 0, 0, 1);
904 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
905 e->f |= envFlag_preserve;
910 /* --- Something I didn't understand has occurred --- */
919 if (flags & f_duff) {
924 /* --- Switch to daemon mode if requested --- */
927 if (flags & f_daemon) {
928 T( trace(TRACE_MISC, "become: daemon mode requested"); )
929 daemon_init(conffile, port);
934 /* --- Open a syslog --- */
936 openlog(quis(), 0, LOG_AUTH);
938 /* --- Pick out the uid --- */
948 if (isdigit((unsigned char)who[0]))
949 pw = getpwuid(atoi(who));
953 die("unknown user `%s'", who);
954 to_pw = userdb_copyUser(pw);
958 /* --- Fill in the easy bits of the request --- */
964 pw = getpwuid(rq.from);
966 die("who are you? (can't find user %li)", (long)rq.from);
967 from_pw = userdb_copyUser(pw);
970 /* --- Find the local host address --- */
976 if ((he = gethostbyname(u.nodename)) == 0)
977 die("who am I? (can't resolve `%s')", u.nodename);
978 memcpy(&rq.host, he->h_addr, sizeof(rq.host));
981 /* --- Fiddle with group ownerships a bit --- */
984 #ifdef HAVE_SETGROUPS
985 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
989 /* --- Set the default login group, if there is one --- */
991 if (~flags & f_havegroup)
992 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
994 #ifndef HAVE_SETGROUPS
996 /* --- Check that it's valid --- */
998 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
999 die("invalid default group");
1003 /* --- Set the default group style --- */
1005 if (gstyle == g_unset)
1006 gstyle = (style == l_login) ? g_replace : g_merge;
1008 /* --- Read in my current set of groups --- */
1010 n_fgr = getgroups(NGROUPS_MAX, from_gr);
1012 /* --- Now read the groups for the target user --- *
1014 * Do this even if I'm using the @g_keep@ style -- I'll use it for
1015 * checking that @group@ is valid.
1023 to_gr[n_tgr++] = to_pw->pw_gid;
1026 while ((gr = getgrent()) != 0) {
1027 if (gr->gr_gid == to_gr[0])
1029 for (p = gr->gr_mem; *p; p++) {
1030 if (strcmp(to_pw->pw_name, *p) == 0) {
1031 to_gr[n_tgr++] = gr->gr_gid;
1032 if (n_tgr >= NGROUPS_MAX)
1043 /* --- Check that @group@ is reasonable --- */
1048 if (group == getgid() || group == from_pw->pw_gid)
1050 for (i = 0; i < n_fgr; i++) {
1051 if (group == from_gr[i])
1054 for (i = 0; i < n_tgr; i++) {
1055 if (group == to_gr[i])
1058 die("invalid default group");
1062 /* --- Phew. Now comes the hard bit --- */
1070 if (gstyle & g_keep) {
1072 ga[i++] = from_pw->pw_gid;
1074 if (gstyle & g_replace)
1075 ga[i++] = to_pw->pw_gid;
1077 /* --- Style authorities will become apoplectic if shown this --- *
1079 * As far as I can see, it's the neatest way of writing it.
1083 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1084 ((gstyle & g_keep) &&
1085 bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
1086 ((gstyle & g_replace) &&
1087 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1092 /* --- Trace the results of all this --- */
1094 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1096 #ifdef HAVE_SETGROUPS
1097 IF_TRACING(TRACE_SETUP, {
1100 for (i = 1; i < ngroups; i++)
1101 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1106 /* --- Shell commands are easy --- */
1113 /* --- A command given on the command line isn't too hard --- */
1115 else if (optind < argc) {
1116 todo = argv + optind;
1125 /* --- An unadorned becoming requires little work --- */
1128 shell[0] = getenv("SHELL");
1130 shell[0] = from_pw->pw_shell;
1136 /* --- An su-like login needs slightly less effort --- */
1139 shell[0] = to_pw->pw_shell;
1145 /* --- A login request needs a little bit of work --- */
1148 const char *p = strrchr(to_pw->pw_shell, '/');
1153 p = to_pw->pw_shell;
1154 shell[0] = xmalloc(strlen(p) + 2);
1156 strcpy(shell[0] + 1, p);
1159 binary = to_pw->pw_shell;
1164 /* --- Mangle the environment --- *
1166 * This keeps getting more complicated all the time. (How true. Now I've
1167 * got all sorts of nasty environment mangling to do.)
1169 * The environment stuff now happens in seven phases:
1171 * 1. Mark very special variables to be preserved. Currently only TERM
1172 * and DISPLAY are treated in this way.
1174 * 2. Set and preserve Become's own environment variables.
1176 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1177 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1179 * 4. If we're preserving the environment or being `su'-like, process the
1180 * PATH variable a little. Otherwise reset it to something
1183 * 5. If we're being `login'-like, expunge all unpreserved variables.
1185 * 6. Expunge any security-critical variables.
1187 * 7. Build a new environment table to pass to child processes.
1191 /* --- Variables to be preserved always --- *
1193 * A user can explicitly expunge a variable in this list, in which case
1194 * we never get to see it here.
1197 static char *preserve[] = {
1198 "TERM", "DISPLAY", "TZ", 0
1201 /* --- Variables to be expunged --- *
1203 * Any environment string which has one of the following as a prefix will
1204 * be expunged from the environment passed to the called process. The
1205 * first line lists variables which have been used to list search paths
1206 * for shared libraries: by manipulating these, an attacker could replace
1207 * a standard library with one of his own. The second line lists other
1208 * well-known dangerous environment variables.
1211 static char *banned[] = {
1212 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1213 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1217 /* --- Other useful variables --- */
1226 /* --- Stage one. Preserve display-specific variables --- */
1228 for (pp = preserve; *pp; pp++) {
1229 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1230 e->f |= envFlag_preserve;
1233 /* --- Stage two. Set Become's own variables --- */
1235 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1237 bc__setenv(e, from_pw->pw_name);
1238 e->f |= envFlag_preserve;
1240 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1242 bc__setenv(e, from_pw->pw_dir);
1243 e->f |= envFlag_preserve;
1245 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1246 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1247 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1248 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1250 /* --- Stage three. Set user identity --- */
1254 static char *maildirs[] = {
1255 "/var/spool/mail", "/var/mail",
1256 "/usr/spool/mail", "/usr/mail",
1262 for (pp = maildirs; *pp; pp++) {
1263 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1264 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1265 bc__putenv("MAIL", b, envFlag_preserve, 0);
1269 } /* Fall through */
1272 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1273 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1274 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1275 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1279 /* --- Stage four. Set the user's PATH properly --- */
1282 /* --- Find an existing path --- *
1284 * If there's no path, or this is a login, then set a default path,
1285 * unless we're meant to preserve the existing one. Whew!
1288 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1290 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1292 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1293 envFlag_preserve, 0);
1296 /* --- Find the string --- */
1298 e->f = envFlag_preserve;
1299 p = strchr(e->val, '=') + 1;
1302 /* --- Write the new version to a dynamically allocated buffer --- */
1304 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1305 strcpy(e->val, "PATH=");
1308 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1323 /* --- Stages five and six. Expunge variables and count numbers --- *
1325 * Folded together, so I only need one pass through the table. Also
1326 * count the number of variables needed at this time.
1331 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1333 /* --- Login style expunges all unpreserved variables --- */
1335 if (style == l_login && ~e->f & envFlag_preserve)
1338 /* --- Otherwise just check the name against the list --- */
1340 for (pp = banned; *pp; pp++) {
1343 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1345 } else if (strcmp(e->_base.name, *pp) == 0)
1353 sym_remove(&bc__env, e);
1356 /* --- Stage seven. Build the new environment block --- */
1358 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1360 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1365 /* --- Trace the command --- */
1367 IF_TRACING(TRACE_SETUP, {
1370 trace(TRACE_SETUP, "setup: from user %s to user %s",
1371 from_pw->pw_name, to_pw->pw_name);
1372 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1373 for (i = 0; todo[i]; i++)
1374 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1375 for (i = 0; env[i]; i++)
1376 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1379 /* --- If necessary, resolve the path to the command --- */
1381 if (!strchr(binary, '/')) {
1385 if ((p = getenv("PATH")) == 0)
1386 p = "/bin:/usr/bin";
1389 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1391 /* --- Check length of string before copying --- */
1393 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1396 /* --- Now build the pathname and check it --- *
1398 * Issue: user can take advantage of these privileges to decide whether
1399 * a program with a given name exists. I'm not sure that's
1400 * particularly significant: it only works on regular files with
1401 * execute permissions, and if you're relying on the names of these
1402 * being secret to keep your security up, then you're doing something
1403 * deeply wrong anyway. On the other hand, it's useful to allow people
1404 * to be able to execute programs and scripts which they wouldn't
1405 * otherwise have access to. [This problem was brought up on
1406 * Bugtraq, as a complaint against sudo.]
1410 sprintf(rq.cmd, "%s/%s", p, binary);
1411 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1412 st.st_mode & 0111 && /* Check it's executable */
1413 S_ISREG(st.st_mode)) /* Check it's a file */
1418 die("couldn't find `%s' in path", todo[0]);
1422 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1424 /* --- Canonicalise the path string, if necessary --- */
1431 /* --- Insert current directory name if path not absolute --- */
1436 if (!getcwd(b, sizeof(b)))
1437 die("couldn't read current directory: %s", strerror(errno));
1442 /* --- Now copy over characters from the path string --- */
1446 /* --- Check for buffer overflows here --- *
1448 * I write at most one byte per iteration so this is OK. Remember to
1449 * allow one for the null byte.
1452 if (p >= b + sizeof(b) - 1)
1453 die("internal error: buffer overflow while canonifying path");
1455 /* --- Reduce multiple slashes to just one --- */
1463 /* --- Handle dots in filenames --- *
1465 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1466 * we've just stuck the current directory on the end of the buffer,
1467 * or we've just put something else on the end.
1470 else if (*q == '.' && p[-1] == '/') {
1472 /* --- A simple `./' just gets removed --- */
1474 if (q[1] == 0 || q[1] == '/') {
1480 /* --- A `../' needs to be peeled back to the previous `/' --- */
1482 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1485 while (p > b && p[-1] != '/')
1498 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1500 /* --- Run the check --- *
1502 * If the user is already what she wants to be, then print a warning.
1503 * Then, if I was just going to spawn a shell, quit, to reduce user
1504 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1505 * checking if we're already root -- root can do anything anyway, and at
1506 * least this way we get some logging done, and offer a more friendly
1510 if (rq.from == rq.to) {
1511 moan("you already are `%s'!", to_pw->pw_name);
1512 if (flags & f_shell) {
1513 moan("(to prevent confusion, I'm not spawning a shell)");
1517 int a = (rq.from == 0) || check(&rq);
1520 "permission %s for %s to become %s to run `%s'",
1521 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1525 die("permission denied");
1528 /* --- Now do the job --- */
1530 T( trace(TRACE_MISC, "become: permission granted"); )
1532 if (flags & f_dummy) {
1533 puts("permission granted");
1537 #ifdef HAVE_SETGROUPS
1538 if (setgroups(ngroups, groups) < 0)
1539 die("couldn't set groups: %s", strerror(errno));
1542 if (setgid(group) < 0)
1543 die("couldn't set default group: %s", strerror(errno));
1544 if (setuid(rq.to) < 0)
1545 die("couldn't set uid: %s", strerror(errno));
1547 /* --- If this was a login, change current directory --- */
1549 if ((flags & f_shell) &&
1551 chdir(to_pw->pw_dir) < 0) {
1552 moan("couldn't change directory to `%s': %s",
1553 to_pw->pw_dir, strerror(errno));
1556 /* --- Finally, call the program --- */
1560 execve(rq.cmd, todo, env);
1561 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1565 /*----- That's all, folks -------------------------------------------------*/