3 * $Id: become.c,v 1.18 1998/06/26 10:32:54 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.18 1998/06/26 10:32:54 mdw
33 * Cosmetic change: use sizeof(destination) in memcpy.
35 * Revision 1.17 1998/06/18 15:06:59 mdw
36 * Close log before execing program to avoid leaving a socket open.
38 * Revision 1.16 1998/04/23 13:21:04 mdw
39 * Small tweaks. Support no-network configuration option, and rearrange
40 * the help text a little.
42 * Revision 1.15 1998/01/13 11:10:44 mdw
43 * Add `TZ' to the list of variables to be preserved.
45 * Revision 1.14 1998/01/12 16:45:39 mdw
48 * Revision 1.13 1997/09/26 09:14:57 mdw
49 * Merged blowfish branch into trunk.
51 * Revision 1.12 1997/09/25 16:04:48 mdw
52 * Change directory after becoming someone else, instead of before. This
53 * avoids problems with root-squashed NFS mounts.
55 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
56 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
57 * because I prefer Blowfish (without any particularly strong evidence) but
58 * mainly because IDEA is patented and Blowfish isn't.
60 * Revision 1.11 1997/09/24 09:48:45 mdw
61 * Fix (scary) overrun bug in group allocation stuff.
63 * Revision 1.10 1997/09/17 10:14:10 mdw
64 * Fix a typo. Support service names in `--port' option.
66 * Revision 1.9 1997/09/10 10:28:05 mdw
67 * Allow default port to be given as a service name or port number. Handle
68 * groups properly (lots of options here).
70 * Revision 1.8 1997/09/08 13:56:24 mdw
71 * Change criteria for expunging items from the user's PATH: instead of
72 * removing things starting with `.', remove things not starting with `/'.
74 * Revision 1.7 1997/09/08 13:43:20 mdw
75 * Change userid when creating tracefiles rather than fiddling with
76 * `access': it works rather better. Also, insert some stdio buffer
77 * flushing to ensure tracedumps are completely written.
79 * Revision 1.6 1997/09/05 13:47:44 mdw
80 * Make the `-L' (trace-level) option's argument optional, like the long
83 * Revision 1.5 1997/09/05 11:45:19 mdw
84 * Add support for different login styles, and environment variable
85 * manipulation in a safe and useful way.
87 * Revision 1.4 1997/08/20 16:15:13 mdw
88 * Overhaul of environment handling. Fix daft bug in path search code.
90 * Revision 1.3 1997/08/07 16:28:59 mdw
91 * Do something useful when users attempt to become themselves.
93 * Revision 1.2 1997/08/04 10:24:20 mdw
94 * Sources placed under CVS control.
96 * Revision 1.1 1997/07/21 13:47:54 mdw
101 /*----- Header files ------------------------------------------------------*/
103 /* --- ANSI headers --- */
113 /* --- Unix headers --- */
115 #include <sys/types.h>
116 #include <sys/stat.h>
117 #include <sys/socket.h>
118 #include <sys/utsname.h>
120 #include <netinet/in.h>
122 #include <arpa/inet.h>
130 extern char **environ;
132 /* --- Local headers --- */
147 /*----- Type definitions --------------------------------------------------*/
149 /* --- Symbol table entry for an environment variable --- */
151 typedef struct sym_env {
152 sym_base _base; /* Symbol table information */
153 unsigned f; /* Flags word (see below) */
154 char *val; /* Pointer to variable value */
157 /* --- Environment variable flags --- */
163 /* --- Login behaviour types --- */
165 #define l_preserve 0 /* Preserve the environment */
166 #define l_setuser 1 /* Update who I am */
167 #define l_login 2 /* Do a full login */
169 /* --- Group behaviour types --- *
171 * Note that these make a handy bitfield.
174 #ifdef HAVE_SETGROUPS
177 g_unset = 0, /* Nobody's set a preference */
178 g_keep = 1, /* Leave the group memberships */
179 g_replace = 2, /* Replace group memberships */
180 g_merge = (g_keep | g_replace) /* Merge the group memberships */
185 /*----- Static variables --------------------------------------------------*/
187 static sym_table bc__env;
189 /*----- Main code ---------------------------------------------------------*/
191 /* --- @bc__write@ --- *
193 * Arguments: @FILE *fp@ = pointer to a stream to write on
194 * @const char *p@ = pointer to a string
198 * Use: Writes the string to the stream, substituting the program
199 * name (as returned by @quis@) for each occurrence of the
203 static void bc__write(FILE *fp, const char *p)
205 const char *n = quis();
207 size_t nl = strlen(n);
209 /* --- Try to be a little efficient --- *
211 * Gather up non-`$' characters using @strcspn@ and spew them out really
222 fwrite(n, nl, 1, fp);
227 /* --- @bc__setenv@ --- *
229 * Arguments: @sym_env *e@ = pointer to environment variable block
230 * @const char *val@ = value to set
234 * Use: Sets an environment variable block to the right value.
237 static void bc__setenv(sym_env *e, const char *val)
239 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
240 sprintf(e->val, "%s=%s", e->_base.name, val);
243 /* --- @bc__putenv@ --- *
245 * Arguments: @const char *var@ = name of the variable to set, or 0 if
246 * this is embedded in the value string
247 * @const char *val@ = value to set, or 0 if the variable must
249 * @unsigned int fl@ = flags to set
250 * @unsigned int force@ = force overwrite of preserved variables
252 * Returns: Pointer to symbol block, or zero if it was deleted.
254 * Use: Puts an item into the environment.
257 static sym_env *bc__putenv(const char *var, const char *val,
258 unsigned int fl, unsigned int force)
265 /* --- Sort out embedded variable names --- */
268 const char *p = strchr(val, '=');
283 /* --- Find the variable block --- */
286 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
287 if (!f || ~e->f & envFlag_preserve || force) {
293 e = sym_find(&bc__env, var, -1, 0, 0);
294 if (e && (force || ~e->f & envFlag_preserve))
295 sym_remove(&bc__env, e);
299 /* --- Tidy up and return --- */
308 /* --- @bc__addGroups@ --- *
310 * Arguments: @gid_t *g@ = pointer to a group array
311 * @int *png@ = pointer to number of entries in the array
312 * @const gid_t *a@ = pointer to groups to add
313 * @int na@ = number of groups to add
315 * Returns: Zero if it was OK, nonzero if we should stop now.
317 * Use: Adds groups to a groups array.
320 static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
325 for (i = 0; i < na; i++) {
327 /* --- Ensure this group isn't already in the list --- */
329 for (j = 0; j < ng; j++) {
334 /* --- See if there's room for more --- */
336 if (ng >= NGROUPS_MAX) {
337 moan("too many groups (system limit exceeded) -- some have been lost");
342 /* --- Add the group --- */
352 /* --- @bc__banner@ --- *
354 * Arguments: @FILE *fp@ = stream to write on
358 * Use: Writes a banner containing copyright information.
361 static void bc__banner(FILE *fp)
363 bc__write(fp, "$ version " VERSION "\n");
366 /* --- @bc__usage@ --- *
368 * Arguments: @FILE *fp@ = stream to write on
372 * Use: Writes a terse reminder of command line syntax.
375 static void bc__usage(FILE *fp)
379 " $ -c <shell-command> <user>\n"
380 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
382 " $ -d [-p <port>] [-f <config-file>]\n"
387 /* --- @bc__help@ --- *
389 * Arguments: @FILE *fp@ = stream to write on
390 * @int suid@ = whether we're running set-uid
394 * Use: Displays a help message for this excellent piece of software.
397 static void bc__help(FILE *fp, int suid)
404 "The `$' program allows you to run a process as another user.\n"
405 "If a command name is given, this is the process executed. If the `-c'\n"
406 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
407 "given, your default login shell is used.\n"
409 "Your user id, the user id you wish to become, the name of the process\n"
410 "you wish to run, and the identity of the current host are looked up to\n"
411 "ensure that you have permission to do this.\n"
413 "Note that logs are kept of all uses of this program.\n"
415 "Options available are:\n"
417 "-h, --help Display this help text\n"
418 "-u, --usage Display a short usage summary\n"
419 "-v, --version Display $'s version number\n"
421 "-e, --preserve-environment Try to preserve the current environment\n"
422 "-s, --su, --set-user Set environment variables to reflect USER\n"
423 "-l, --login Really log in as USER\n"
425 #if DEFAULT_LOGIN_STYLE == l_preserve
426 "preserve-environment"
427 #elif DEFAULT_LOGIN_STYLE == l_setuser
429 #elif DEFAULT_LOGIN_STYLE == l_login
435 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
436 #ifdef HAVE_SETGROUPS
437 "-k, --keep-groups Keep your current set of groups\n"
438 "-m, --merge-groups Merge the lists of groups\n"
439 "-r, --replace-groups Replace the list of groups\n"
442 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
445 "-d, --daemon Start a daemon\n"
446 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
447 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
454 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
457 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
458 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
464 * Arguments: @int argc@ = number of command line arguments
465 * @char *argv[]@ = pointer to the various arguments
467 * Returns: Zero if successful.
469 * Use: Allows a user to change UID.
472 int main(int argc, char *argv[])
474 /* --- Request block setup parameters --- */
476 request rq; /* Request buffer to build */
477 char *cmd = 0; /* Shell command to execute */
478 char *binary = "/bin/sh"; /* Default binary to execute */
479 char **env = environ; /* Default environment to pass */
480 char **todo = 0; /* Pointer to argument list */
481 char *who = 0; /* Who we're meant to become */
482 struct passwd *from_pw = 0; /* User we are right now */
483 struct passwd *to_pw = 0; /* User we want to become */
485 /* --- Become server setup parameters --- */
488 char *conffile = file_RULES; /* Default config file for daemon */
489 int port = 0; /* Default port for daemon */
492 /* --- Miscellanous shared variables --- */
494 unsigned flags = 0; /* Various useful flags */
495 int style = DEFAULT_LOGIN_STYLE; /* Login style */
496 gid_t group = -1; /* Default group to set */
497 int gstyle = g_unset; /* No group style set yet */
499 #ifdef HAVE_SETGROUPS
500 gid_t groups[NGROUPS_MAX]; /* Set of groups */
501 int ngroups; /* Number of groups in the set */
504 /* --- Default argument list executes a shell command --- */
506 static char *shell[] = {
507 "/bin/sh", /* Bourne shell */
508 "-c", /* Read from command line */
509 0, /* Pointer to shell command */
513 /* --- Definitions for the various flags --- */
516 f_daemon = 1, /* Start up in daemon mode */
517 f_duff = 2, /* Fault in arguments */
518 f_shell = 4, /* Run a default shell */
519 f_dummy = 8, /* Don't actually do anything */
520 f_setuid = 16, /* We're running setuid */
521 f_havegroup = 32 /* Set a default group */
524 /* --- Set up the program name --- */
528 if (getuid() != geteuid())
531 /* --- Read the environment into a hashtable --- */
536 sym_createTable(&bc__env);
537 for (p = environ; *p; p++)
538 bc__putenv(0, *p, 0, 0);
541 /* --- Parse some command line arguments --- */
545 static struct option opts[] = {
547 /* --- Asking for help --- */
549 { "help", 0, 0, 'h' },
550 { "usage", 0, 0, 'u' },
551 { "version", 0, 0, 'v' },
553 /* --- Login style options --- */
555 { "preserve-environment", 0, 0, 'e' },
557 { "set-user", 0, 0, 's' },
558 { "login", 0, 0, 'l' },
560 /* --- Group style options --- */
562 { "group", gFlag_argReq, 0, 'g' },
563 #ifdef HAVE_SETGROUPS
564 { "keep-groups", 0, 0, 'k' },
565 { "merge-groups", 0, 0, 'm' },
566 { "replace-groups", 0, 0, 'r' },
569 /* --- Command to run options --- */
571 { "command", gFlag_argReq, 0, 'c' },
573 /* --- Server options --- */
576 { "daemon", 0, 0, 'd' },
577 { "port", gFlag_argReq, 0, 'p' },
578 { "config-file", gFlag_argReq, 0, 'f' },
581 /* --- Tracing options --- */
584 { "impersonate", gFlag_argReq, 0, 'I' },
585 { "trace", gFlag_argOpt, 0, 'T' },
586 { "trace-level", gFlag_argOpt, 0, 'L' },
592 i = mdwopt(argc, argv,
593 "-" /* Return non-options as options */
594 "huv" /* Asking for help */
595 "esl" /* Login style options */
596 #ifdef HAVE_SETGROUPS
597 "g:kmr" /* Group style options */
599 "g:" /* Group (without @setgroups@) */
601 "c:" /* Command to run options */
603 "dp:f:" /* Server options */
606 "I:T::L::" /* Tracing options */
609 opts, 0, 0, gFlag_envVar);
615 /* --- Asking for help --- */
618 bc__help(stdout, flags & f_setuid);
630 /* --- Login style options --- */
642 /* --- Group style options --- */
645 if (isdigit((unsigned char)optarg[0]))
646 group = atoi(optarg);
648 struct group *gr = getgrnam(optarg);
650 die("unknown group `%s'", optarg);
653 flags |= f_havegroup;
666 /* --- Command to run options --- */
672 /* --- Server options --- */
676 if (isdigit((unsigned char)optarg[0]))
677 port = htons(atoi(optarg));
679 struct servent *s = getservbyname(optarg, "udp");
681 die("unknown service name `%s'", optarg);
693 /* --- Pretend to be a different user --- *
695 * There are all sorts of nasty implications for this option. Don't
696 * allow it if we're running setuid. Disable the actual login anyway.
702 if (flags & f_setuid)
703 moan("shan't allow impersonation while running setuid");
706 if (isdigit((unsigned char)optarg[0]))
707 pw = getpwuid(atoi(optarg));
709 pw = getpwnam(optarg);
711 die("can't impersonate unknown user `%s'", optarg);
712 from_pw = userdb_copyUser(pw);
713 rq.from = from_pw->pw_uid;
720 /* --- Tracing support --- *
722 * Be careful not to zap a file I wouldn't normally be allowed to write
731 if (optarg == 0 || strcmp(optarg, "-") == 0)
734 uid_t eu = geteuid(), ru = getuid();
737 if (setreuid(eu, ru))
742 die("couldn't temporarily give up privileges: %s",
746 if ((fp = fopen(optarg, "w")) == 0) {
747 die("couldn't open trace file `%s' for writing: %s",
748 optarg, strerror(errno));
752 if (setreuid(ru, eu))
756 die("couldn't regain privileges: %s", strerror(errno));
758 traceon(fp, TRACE_DFL);
759 trace(TRACE_MISC, "become: tracing enabled");
764 /* --- Setting trace levels --- */
770 unsigned int lvl = 0, l;
771 const char *p = optarg;
773 /* --- Table of tracing facilities --- */
781 static tr lvltbl[] = {
782 { 'm', TRACE_MISC, "miscellaneous messages" },
783 { 's', TRACE_SETUP, "building the request block" },
784 { 'r', TRACE_RULE, "ruleset scanning" },
785 { 'c', TRACE_CHECK, "request checking" },
787 { 'd', TRACE_DAEMON, "server process" },
788 { 'l', TRACE_CLIENT, "client process" },
789 { 'R', TRACE_RAND, "random number generator" },
790 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
792 { 'y', TRACE_YACC, "parsing configuration file" },
793 { 'D', TRACE_DFL, "default tracing options" },
794 { 'A', TRACE_ALL, "all tracing options" },
799 /* --- Output some help if there's no arguemnt --- */
807 for (tp = lvltbl; tp->l; tp++) {
808 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
809 printf("%c -- %s\n", tp->ch, tp->help);
813 "Also, `+' and `-' options are recognised to turn on and off various\n"
814 "tracing options. For example, `A-r' enables everything except ruleset\n"
815 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
827 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
830 if (flags & f_setuid)
833 lvl = sense ? (lvl | l) : (lvl & ~l);
835 moan("unknown trace option `%c'", *p);
841 yydebug = ((lvl & TRACE_YACC) != 0);
846 /* --- Something that wasn't an option --- *
848 * The following nasties are supported:
850 * * NAME=VALUE -- preserve NAME, and give it a VALUE
851 * * NAME= -- preserve NAME, and give it an empty value
852 * * NAME- -- delete NAME
853 * * NAME! -- preserve NAME with existing value
855 * Anything else is either the user name (which is OK) or the start of
856 * the command (in which case I stop and read the rest of the command).
860 size_t sz = strcspn(optarg, "=-!");
863 /* --- None of the above --- */
865 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
875 /* --- Do the appropriate thing --- */
877 switch (optarg[sz]) {
879 bc__putenv(0, optarg, envFlag_preserve, 1);
883 bc__putenv(optarg, 0, 0, 1);
887 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
888 e->f |= envFlag_preserve;
893 /* --- Something I didn't understand has occurred --- */
902 if (flags & f_duff) {
907 /* --- Switch to daemon mode if requested --- */
910 if (flags & f_daemon) {
911 T( trace(TRACE_MISC, "become: daemon mode requested"); )
912 daemon_init(conffile, port);
917 /* --- Open a syslog --- */
919 openlog(quis(), 0, LOG_AUTH);
921 /* --- Pick out the uid --- */
931 if (isdigit((unsigned char)who[0]))
932 pw = getpwuid(atoi(who));
936 die("unknown user `%s'", who);
937 to_pw = userdb_copyUser(pw);
941 /* --- Fill in the easy bits of the request --- */
947 pw = getpwuid(rq.from);
949 die("who are you? (can't find user %li)", (long)rq.from);
950 from_pw = userdb_copyUser(pw);
953 /* --- Find the local host address --- */
959 if ((he = gethostbyname(u.nodename)) == 0)
960 die("who am I? (can't resolve `%s')", u.nodename);
961 memcpy(&rq.host, he->h_addr, sizeof(rq.host));
964 /* --- Fiddle with group ownerships a bit --- */
967 #ifdef HAVE_SETGROUPS
968 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
972 /* --- Set the default login group, if there is one --- */
974 if (~flags & f_havegroup)
975 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
977 #ifndef HAVE_SETGROUPS
979 /* --- Check that it's valid --- */
981 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
982 die("invalid default group");
986 /* --- Set the default group style --- */
988 if (gstyle == g_unset)
989 gstyle = (style == l_login) ? g_replace : g_merge;
991 /* --- Read in my current set of groups --- */
993 n_fgr = getgroups(NGROUPS_MAX, from_gr);
995 /* --- Now read the groups for the target user --- *
997 * Do this even if I'm using the @g_keep@ style -- I'll use it for
998 * checking that @group@ is valid.
1006 to_gr[n_tgr++] = to_pw->pw_gid;
1009 while ((gr = getgrent()) != 0) {
1010 if (gr->gr_gid == to_gr[0])
1012 for (p = gr->gr_mem; *p; p++) {
1013 if (strcmp(to_pw->pw_name, *p) == 0) {
1014 to_gr[n_tgr++] = gr->gr_gid;
1015 if (n_tgr >= NGROUPS_MAX)
1026 /* --- Check that @group@ is reasonable --- */
1031 if (group == getgid() || group == from_pw->pw_gid)
1033 for (i = 0; i < n_fgr; i++) {
1034 if (group == from_gr[i])
1037 for (i = 0; i < n_tgr; i++) {
1038 if (group == to_gr[i])
1041 die("invalid default group");
1045 /* --- Phew. Now comes the hard bit --- */
1053 if (gstyle & g_keep) {
1055 ga[i++] = from_pw->pw_gid;
1057 if (gstyle & g_replace)
1058 ga[i++] = to_pw->pw_gid;
1060 /* --- Style authorities will become apoplectic if shown this --- *
1062 * As far as I can see, it's the neatest way of writing it.
1066 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1067 ((gstyle & g_keep) &&
1068 bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
1069 ((gstyle & g_replace) &&
1070 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1075 /* --- Trace the results of all this --- */
1077 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1079 #ifdef HAVE_SETGROUPS
1080 IF_TRACING(TRACE_SETUP, {
1083 for (i = 1; i < ngroups; i++)
1084 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1089 /* --- Shell commands are easy --- */
1096 /* --- A command given on the command line isn't too hard --- */
1098 else if (optind < argc) {
1099 todo = argv + optind;
1108 /* --- An unadorned becoming requires little work --- */
1111 shell[0] = getenv("SHELL");
1113 shell[0] = from_pw->pw_shell;
1119 /* --- An su-like login needs slightly less effort --- */
1122 shell[0] = to_pw->pw_shell;
1128 /* --- A login request needs a little bit of work --- */
1131 const char *p = strrchr(to_pw->pw_shell, '/');
1136 p = to_pw->pw_shell;
1137 shell[0] = xmalloc(strlen(p) + 2);
1139 strcpy(shell[0] + 1, p);
1142 binary = to_pw->pw_shell;
1147 /* --- Mangle the environment --- *
1149 * This keeps getting more complicated all the time. (How true. Now I've
1150 * got all sorts of nasty environment mangling to do.)
1152 * The environment stuff now happens in seven phases:
1154 * 1. Mark very special variables to be preserved. Currently only TERM
1155 * and DISPLAY are treated in this way.
1157 * 2. Set and preserve Become's own environment variables.
1159 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1160 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1162 * 4. If we're preserving the environment or being `su'-like, process the
1163 * PATH variable a little. Otherwise reset it to something
1166 * 5. If we're being `login'-like, expunge all unpreserved variables.
1168 * 6. Expunge any security-critical variables.
1170 * 7. Build a new environment table to pass to child processes.
1174 /* --- Variables to be preserved always --- *
1176 * A user can explicitly expunge a variable in this list, in which case
1177 * we never get to see it here.
1180 static char *preserve[] = {
1181 "TERM", "DISPLAY", "TZ", 0
1184 /* --- Variables to be expunged --- *
1186 * Any environment string which has one of the following as a prefix will
1187 * be expunged from the environment passed to the called process. The
1188 * first line lists variables which have been used to list search paths
1189 * for shared libraries: by manipulating these, an attacker could replace
1190 * a standard library with one of his own. The second line lists other
1191 * well-known dangerous environment variables.
1194 static char *banned[] = {
1195 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1196 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1200 /* --- Other useful variables --- */
1209 /* --- Stage one. Preserve display-specific variables --- */
1211 for (pp = preserve; *pp; pp++) {
1212 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1213 e->f |= envFlag_preserve;
1216 /* --- Stage two. Set Become's own variables --- */
1218 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1220 bc__setenv(e, from_pw->pw_name);
1221 e->f |= envFlag_preserve;
1223 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1225 bc__setenv(e, from_pw->pw_dir);
1226 e->f |= envFlag_preserve;
1228 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1229 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1230 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1231 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
1233 /* --- Stage three. Set user identity --- */
1237 static char *maildirs[] = {
1238 "/var/spool/mail", "/var/mail",
1239 "/usr/spool/mail", "/usr/mail",
1245 for (pp = maildirs; *pp; pp++) {
1246 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1247 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1248 bc__putenv("MAIL", b, envFlag_preserve, 0);
1252 } /* Fall through */
1255 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1256 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1257 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1258 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1262 /* --- Stage four. Set the user's PATH properly --- */
1265 /* --- Find an existing path --- *
1267 * If there's no path, or this is a login, then set a default path,
1268 * unless we're meant to preserve the existing one. Whew!
1271 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1273 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1275 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1276 envFlag_preserve, 0);
1279 /* --- Find the string --- */
1281 e->f = envFlag_preserve;
1282 p = strchr(e->val, '=') + 1;
1285 /* --- Write the new version to a dynamically allocated buffer --- */
1287 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1288 strcpy(e->val, "PATH=");
1291 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
1306 /* --- Stages five and six. Expunge variables and count numbers --- *
1308 * Folded together, so I only need one pass through the table. Also
1309 * count the number of variables needed at this time.
1314 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
1316 /* --- Login style expunges all unpreserved variables --- */
1318 if (style == l_login && ~e->f & envFlag_preserve)
1321 /* --- Otherwise just check the name against the list --- */
1323 for (pp = banned; *pp; pp++) {
1326 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1328 } else if (strcmp(e->_base.name, *pp) == 0)
1336 sym_remove(&bc__env, e);
1339 /* --- Stage seven. Build the new environment block --- */
1341 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1343 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1348 /* --- Trace the command --- */
1350 IF_TRACING(TRACE_SETUP, {
1353 trace(TRACE_SETUP, "setup: from user %s to user %s",
1354 from_pw->pw_name, to_pw->pw_name);
1355 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1356 for (i = 0; todo[i]; i++)
1357 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1358 for (i = 0; env[i]; i++)
1359 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1362 /* --- If necessary, resolve the path to the command --- */
1364 if (!strchr(binary, '/')) {
1368 if ((p = getenv("PATH")) == 0)
1369 p = "/bin:/usr/bin";
1372 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1374 /* --- Check length of string before copying --- */
1376 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1379 /* --- Now build the pathname and check it --- */
1381 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1382 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
1383 st.st_mode & 0111 && /* Check it's executable */
1384 S_ISREG(st.st_mode)) /* Check it's a file */
1389 die("couldn't find `%s' in path", todo[0]);
1393 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1395 /* --- Canonicalise the path string, if necessary --- */
1402 /* --- Insert current directory name if path not absolute --- */
1407 if (!getcwd(b, sizeof(b)))
1408 die("couldn't read current directory: %s", strerror(errno));
1413 /* --- Now copy over characters from the path string --- */
1417 /* --- Check for buffer overflows here --- *
1419 * I write at most one byte per iteration so this is OK. Remember to
1420 * allow one for the null byte.
1423 if (p >= b + sizeof(b) - 1)
1424 die("internal error: buffer overflow while canonifying path");
1426 /* --- Reduce multiple slashes to just one --- */
1434 /* --- Handle dots in filenames --- *
1436 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1437 * we've just stuck the current directory on the end of the buffer,
1438 * or we've just put something else on the end.
1441 else if (*q == '.' && p[-1] == '/') {
1443 /* --- A simple `./' just gets removed --- */
1445 if (q[1] == 0 || q[1] == '/') {
1451 /* --- A `../' needs to be peeled back to the previous `/' --- */
1453 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1456 while (p > b && p[-1] != '/')
1469 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1471 /* --- Run the check --- *
1473 * If the user is already what she wants to be, then print a warning.
1474 * Then, if I was just going to spawn a shell, quit, to reduce user
1475 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1476 * checking if we're already root -- root can do anything anyway, and at
1477 * least this way we get some logging done, and offer a more friendly
1481 if (rq.from == rq.to) {
1482 moan("you already are `%s'!", to_pw->pw_name);
1483 if (flags & f_shell) {
1484 moan("(to prevent confusion, I'm not spawning a shell)");
1488 int a = (rq.from == 0) || check(&rq);
1491 "permission %s for %s to become %s to run `%s'",
1492 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1496 die("permission denied");
1499 /* --- Now do the job --- */
1501 T( trace(TRACE_MISC, "become: permission granted"); )
1503 if (flags & f_dummy) {
1504 puts("permission granted");
1508 #ifdef HAVE_SETGROUPS
1509 if (setgroups(ngroups, groups) < 0)
1510 die("couldn't set groups: %s", strerror(errno));
1513 if (setgid(group) < 0)
1514 die("couldn't set default group: %s", strerror(errno));
1515 if (setuid(rq.to) < 0)
1516 die("couldn't set uid: %s", strerror(errno));
1518 /* --- If this was a login, change current directory --- */
1520 if ((flags & f_shell) &&
1522 chdir(to_pw->pw_dir) < 0) {
1523 moan("couldn't change directory to `%s': %s",
1524 to_pw->pw_dir, strerror(errno));
1527 /* --- Finally, call the program --- */
1531 execve(rq.cmd, todo, env);
1532 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1536 /*----- That's all, folks -------------------------------------------------*/