3 * $Id: become.c,v 1.17 1998/06/18 15:06:59 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.17 1998/06/18 15:06:59 mdw
33 * Close log before execing program to avoid leaving a socket open.
35 * Revision 1.16 1998/04/23 13:21:04 mdw
36 * Small tweaks. Support no-network configuration option, and rearrange
37 * the help text a little.
39 * Revision 1.15 1998/01/13 11:10:44 mdw
40 * Add `TZ' to the list of variables to be preserved.
42 * Revision 1.14 1998/01/12 16:45:39 mdw
45 * Revision 1.13 1997/09/26 09:14:57 mdw
46 * Merged blowfish branch into trunk.
48 * Revision 1.12 1997/09/25 16:04:48 mdw
49 * Change directory after becoming someone else, instead of before. This
50 * avoids problems with root-squashed NFS mounts.
52 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
53 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
54 * because I prefer Blowfish (without any particularly strong evidence) but
55 * mainly because IDEA is patented and Blowfish isn't.
57 * Revision 1.11 1997/09/24 09:48:45 mdw
58 * Fix (scary) overrun bug in group allocation stuff.
60 * Revision 1.10 1997/09/17 10:14:10 mdw
61 * Fix a typo. Support service names in `--port' option.
63 * Revision 1.9 1997/09/10 10:28:05 mdw
64 * Allow default port to be given as a service name or port number. Handle
65 * groups properly (lots of options here).
67 * Revision 1.8 1997/09/08 13:56:24 mdw
68 * Change criteria for expunging items from the user's PATH: instead of
69 * removing things starting with `.', remove things not starting with `/'.
71 * Revision 1.7 1997/09/08 13:43:20 mdw
72 * Change userid when creating tracefiles rather than fiddling with
73 * `access': it works rather better. Also, insert some stdio buffer
74 * flushing to ensure tracedumps are completely written.
76 * Revision 1.6 1997/09/05 13:47:44 mdw
77 * Make the `-L' (trace-level) option's argument optional, like the long
80 * Revision 1.5 1997/09/05 11:45:19 mdw
81 * Add support for different login styles, and environment variable
82 * manipulation in a safe and useful way.
84 * Revision 1.4 1997/08/20 16:15:13 mdw
85 * Overhaul of environment handling. Fix daft bug in path search code.
87 * Revision 1.3 1997/08/07 16:28:59 mdw
88 * Do something useful when users attempt to become themselves.
90 * Revision 1.2 1997/08/04 10:24:20 mdw
91 * Sources placed under CVS control.
93 * Revision 1.1 1997/07/21 13:47:54 mdw
98 /*----- Header files ------------------------------------------------------*/
100 /* --- ANSI headers --- */
110 /* --- Unix headers --- */
112 #include <sys/types.h>
113 #include <sys/stat.h>
114 #include <sys/socket.h>
115 #include <sys/utsname.h>
117 #include <netinet/in.h>
119 #include <arpa/inet.h>
127 extern char **environ
;
129 /* --- Local headers --- */
144 /*----- Type definitions --------------------------------------------------*/
146 /* --- Symbol table entry for an environment variable --- */
148 typedef struct sym_env
{
149 sym_base _base
; /* Symbol table information */
150 unsigned f
; /* Flags word (see below) */
151 char *val
; /* Pointer to variable value */
154 /* --- Environment variable flags --- */
160 /* --- Login behaviour types --- */
162 #define l_preserve 0 /* Preserve the environment */
163 #define l_setuser 1 /* Update who I am */
164 #define l_login 2 /* Do a full login */
166 /* --- Group behaviour types --- *
168 * Note that these make a handy bitfield.
171 #ifdef HAVE_SETGROUPS
174 g_unset
= 0, /* Nobody's set a preference */
175 g_keep
= 1, /* Leave the group memberships */
176 g_replace
= 2, /* Replace group memberships */
177 g_merge
= (g_keep
| g_replace
) /* Merge the group memberships */
182 /*----- Static variables --------------------------------------------------*/
184 static sym_table bc__env
;
186 /*----- Main code ---------------------------------------------------------*/
188 /* --- @bc__write@ --- *
190 * Arguments: @FILE *fp@ = pointer to a stream to write on
191 * @const char *p@ = pointer to a string
195 * Use: Writes the string to the stream, substituting the program
196 * name (as returned by @quis@) for each occurrence of the
200 static void bc__write(FILE *fp
, const char *p
)
202 const char *n
= quis();
204 size_t nl
= strlen(n
);
206 /* --- Try to be a little efficient --- *
208 * Gather up non-`$' characters using @strcspn@ and spew them out really
219 fwrite(n
, nl
, 1, fp
);
224 /* --- @bc__setenv@ --- *
226 * Arguments: @sym_env *e@ = pointer to environment variable block
227 * @const char *val@ = value to set
231 * Use: Sets an environment variable block to the right value.
234 static void bc__setenv(sym_env
*e
, const char *val
)
236 e
->val
= xmalloc(strlen(e
->_base
.name
) + 1 + strlen(val
) + 1);
237 sprintf(e
->val
, "%s=%s", e
->_base
.name
, val
);
240 /* --- @bc__putenv@ --- *
242 * Arguments: @const char *var@ = name of the variable to set, or 0 if
243 * this is embedded in the value string
244 * @const char *val@ = value to set, or 0 if the variable must
246 * @unsigned int fl@ = flags to set
247 * @unsigned int force@ = force overwrite of preserved variables
249 * Returns: Pointer to symbol block, or zero if it was deleted.
251 * Use: Puts an item into the environment.
254 static sym_env
*bc__putenv(const char *var
, const char *val
,
255 unsigned int fl
, unsigned int force
)
262 /* --- Sort out embedded variable names --- */
265 const char *p
= strchr(val
, '=');
280 /* --- Find the variable block --- */
283 e
= sym_find(&bc__env
, var
, -1, sizeof(*e
), &f
);
284 if (!f
|| ~e
->f
& envFlag_preserve
|| force
) {
290 e
= sym_find(&bc__env
, var
, -1, 0, 0);
291 if (e
&& (force
|| ~e
->f
& envFlag_preserve
))
292 sym_remove(&bc__env
, e
);
296 /* --- Tidy up and return --- */
305 /* --- @bc__addGroups@ --- *
307 * Arguments: @gid_t *g@ = pointer to a group array
308 * @int *png@ = pointer to number of entries in the array
309 * @const gid_t *a@ = pointer to groups to add
310 * @int na@ = number of groups to add
312 * Returns: Zero if it was OK, nonzero if we should stop now.
314 * Use: Adds groups to a groups array.
317 static int bc__addGroups(gid_t
*g
, int *png
, const gid_t
*a
, int na
)
322 for (i
= 0; i
< na
; i
++) {
324 /* --- Ensure this group isn't already in the list --- */
326 for (j
= 0; j
< ng
; j
++) {
331 /* --- See if there's room for more --- */
333 if (ng
>= NGROUPS_MAX
) {
334 moan("too many groups (system limit exceeded) -- some have been lost");
339 /* --- Add the group --- */
349 /* --- @bc__banner@ --- *
351 * Arguments: @FILE *fp@ = stream to write on
355 * Use: Writes a banner containing copyright information.
358 static void bc__banner(FILE *fp
)
360 bc__write(fp
, "$ version " VERSION
"\n");
363 /* --- @bc__usage@ --- *
365 * Arguments: @FILE *fp@ = stream to write on
369 * Use: Writes a terse reminder of command line syntax.
372 static void bc__usage(FILE *fp
)
376 " $ -c <shell-command> <user>\n"
377 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
379 " $ -d [-p <port>] [-f <config-file>]\n"
384 /* --- @bc__help@ --- *
386 * Arguments: @FILE *fp@ = stream to write on
387 * @int suid@ = whether we're running set-uid
391 * Use: Displays a help message for this excellent piece of software.
394 static void bc__help(FILE *fp
, int suid
)
401 "The `$' program allows you to run a process as another user.\n"
402 "If a command name is given, this is the process executed. If the `-c'\n"
403 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
404 "given, your default login shell is used.\n"
406 "Your user id, the user id you wish to become, the name of the process\n"
407 "you wish to run, and the identity of the current host are looked up to\n"
408 "ensure that you have permission to do this.\n"
410 "Note that logs are kept of all uses of this program.\n"
412 "Options available are:\n"
414 "-h, --help Display this help text\n"
415 "-u, --usage Display a short usage summary\n"
416 "-v, --version Display $'s version number\n"
418 "-e, --preserve-environment Try to preserve the current environment\n"
419 "-s, --su, --set-user Set environment variables to reflect USER\n"
420 "-l, --login Really log in as USER\n"
422 #if DEFAULT_LOGIN_STYLE == l_preserve
423 "preserve-environment"
424 #elif DEFAULT_LOGIN_STYLE == l_setuser
426 #elif DEFAULT_LOGIN_STYLE == l_login
432 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
433 #ifdef HAVE_SETGROUPS
434 "-k, --keep-groups Keep your current set of groups\n"
435 "-m, --merge-groups Merge the lists of groups\n"
436 "-r, --replace-groups Replace the list of groups\n"
439 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
442 "-d, --daemon Start a daemon\n"
443 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
444 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
451 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
454 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
455 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
461 * Arguments: @int argc@ = number of command line arguments
462 * @char *argv[]@ = pointer to the various arguments
464 * Returns: Zero if successful.
466 * Use: Allows a user to change UID.
469 int main(int argc
, char *argv
[])
471 /* --- Request block setup parameters --- */
473 request rq
; /* Request buffer to build */
474 char *cmd
= 0; /* Shell command to execute */
475 char *binary
= "/bin/sh"; /* Default binary to execute */
476 char **env
= environ
; /* Default environment to pass */
477 char **todo
= 0; /* Pointer to argument list */
478 char *who
= 0; /* Who we're meant to become */
479 struct passwd
*from_pw
= 0; /* User we are right now */
480 struct passwd
*to_pw
= 0; /* User we want to become */
482 /* --- Become server setup parameters --- */
485 char *conffile
= file_RULES
; /* Default config file for daemon */
486 int port
= 0; /* Default port for daemon */
489 /* --- Miscellanous shared variables --- */
491 unsigned flags
= 0; /* Various useful flags */
492 int style
= DEFAULT_LOGIN_STYLE
; /* Login style */
493 gid_t group
= -1; /* Default group to set */
494 int gstyle
= g_unset
; /* No group style set yet */
496 #ifdef HAVE_SETGROUPS
497 gid_t groups
[NGROUPS_MAX
]; /* Set of groups */
498 int ngroups
; /* Number of groups in the set */
501 /* --- Default argument list executes a shell command --- */
503 static char *shell
[] = {
504 "/bin/sh", /* Bourne shell */
505 "-c", /* Read from command line */
506 0, /* Pointer to shell command */
510 /* --- Definitions for the various flags --- */
513 f_daemon
= 1, /* Start up in daemon mode */
514 f_duff
= 2, /* Fault in arguments */
515 f_shell
= 4, /* Run a default shell */
516 f_dummy
= 8, /* Don't actually do anything */
517 f_setuid
= 16, /* We're running setuid */
518 f_havegroup
= 32 /* Set a default group */
521 /* --- Set up the program name --- */
525 if (getuid() != geteuid())
528 /* --- Read the environment into a hashtable --- */
533 sym_createTable(&bc__env
);
534 for (p
= environ
; *p
; p
++)
535 bc__putenv(0, *p
, 0, 0);
538 /* --- Parse some command line arguments --- */
542 static struct option opts
[] = {
544 /* --- Asking for help --- */
546 { "help", 0, 0, 'h' },
547 { "usage", 0, 0, 'u' },
548 { "version", 0, 0, 'v' },
550 /* --- Login style options --- */
552 { "preserve-environment", 0, 0, 'e' },
554 { "set-user", 0, 0, 's' },
555 { "login", 0, 0, 'l' },
557 /* --- Group style options --- */
559 { "group", gFlag_argReq
, 0, 'g' },
560 #ifdef HAVE_SETGROUPS
561 { "keep-groups", 0, 0, 'k' },
562 { "merge-groups", 0, 0, 'm' },
563 { "replace-groups", 0, 0, 'r' },
566 /* --- Command to run options --- */
568 { "command", gFlag_argReq
, 0, 'c' },
570 /* --- Server options --- */
573 { "daemon", 0, 0, 'd' },
574 { "port", gFlag_argReq
, 0, 'p' },
575 { "config-file", gFlag_argReq
, 0, 'f' },
578 /* --- Tracing options --- */
581 { "impersonate", gFlag_argReq
, 0, 'I' },
582 { "trace", gFlag_argOpt
, 0, 'T' },
583 { "trace-level", gFlag_argOpt
, 0, 'L' },
589 i
= mdwopt(argc
, argv
,
590 "-" /* Return non-options as options */
591 "huv" /* Asking for help */
592 "esl" /* Login style options */
593 #ifdef HAVE_SETGROUPS
594 "g:kmr" /* Group style options */
596 "g:" /* Group (without @setgroups@) */
598 "c:" /* Command to run options */
600 "dp:f:" /* Server options */
603 "I:T::L::" /* Tracing options */
606 opts
, 0, 0, gFlag_envVar
);
612 /* --- Asking for help --- */
615 bc__help(stdout
, flags
& f_setuid
);
627 /* --- Login style options --- */
639 /* --- Group style options --- */
642 if (isdigit((unsigned char)optarg
[0]))
643 group
= atoi(optarg
);
645 struct group
*gr
= getgrnam(optarg
);
647 die("unknown group `%s'", optarg
);
650 flags
|= f_havegroup
;
663 /* --- Command to run options --- */
669 /* --- Server options --- */
673 if (isdigit((unsigned char)optarg
[0]))
674 port
= htons(atoi(optarg
));
676 struct servent
*s
= getservbyname(optarg
, "udp");
678 die("unknown service name `%s'", optarg
);
690 /* --- Pretend to be a different user --- *
692 * There are all sorts of nasty implications for this option. Don't
693 * allow it if we're running setuid. Disable the actual login anyway.
699 if (flags
& f_setuid
)
700 moan("shan't allow impersonation while running setuid");
703 if (isdigit((unsigned char)optarg
[0]))
704 pw
= getpwuid(atoi(optarg
));
706 pw
= getpwnam(optarg
);
708 die("can't impersonate unknown user `%s'", optarg
);
709 from_pw
= userdb_copyUser(pw
);
710 rq
.from
= from_pw
->pw_uid
;
717 /* --- Tracing support --- *
719 * Be careful not to zap a file I wouldn't normally be allowed to write
728 if (optarg
== 0 || strcmp(optarg
, "-") == 0)
731 uid_t eu
= geteuid(), ru
= getuid();
734 if (setreuid(eu
, ru
))
739 die("couldn't temporarily give up privileges: %s",
743 if ((fp
= fopen(optarg
, "w")) == 0) {
744 die("couldn't open trace file `%s' for writing: %s",
745 optarg
, strerror(errno
));
749 if (setreuid(ru
, eu
))
753 die("couldn't regain privileges: %s", strerror(errno
));
755 traceon(fp
, TRACE_DFL
);
756 trace(TRACE_MISC
, "become: tracing enabled");
761 /* --- Setting trace levels --- */
767 unsigned int lvl
= 0, l
;
768 const char *p
= optarg
;
770 /* --- Table of tracing facilities --- */
778 static tr lvltbl
[] = {
779 { 'm', TRACE_MISC
, "miscellaneous messages" },
780 { 's', TRACE_SETUP
, "building the request block" },
781 { 'r', TRACE_RULE
, "ruleset scanning" },
782 { 'c', TRACE_CHECK
, "request checking" },
784 { 'd', TRACE_DAEMON
, "server process" },
785 { 'l', TRACE_CLIENT
, "client process" },
786 { 'R', TRACE_RAND
, "random number generator" },
787 { 'C', TRACE_CRYPTO
, "cryptographic processing of requests" },
789 { 'y', TRACE_YACC
, "parsing configuration file" },
790 { 'D', TRACE_DFL
, "default tracing options" },
791 { 'A', TRACE_ALL
, "all tracing options" },
796 /* --- Output some help if there's no arguemnt --- */
804 for (tp
= lvltbl
; tp
->l
; tp
++) {
805 if ((flags
& f_setuid
) == 0 || tp
->l
& ~TRACE_PRIV
)
806 printf("%c -- %s\n", tp
->ch
, tp
->help
);
810 "Also, `+' and `-' options are recognised to turn on and off various\n"
811 "tracing options. For example, `A-r' enables everything except ruleset\n"
812 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
824 for (tp
= lvltbl
; tp
->l
&& *p
!= tp
->ch
; tp
++)
827 if (flags
& f_setuid
)
830 lvl
= sense ?
(lvl
| l
) : (lvl
& ~l
);
832 moan("unknown trace option `%c'", *p
);
838 yydebug
= ((lvl
& TRACE_YACC
) != 0);
843 /* --- Something that wasn't an option --- *
845 * The following nasties are supported:
847 * * NAME=VALUE -- preserve NAME, and give it a VALUE
848 * * NAME= -- preserve NAME, and give it an empty value
849 * * NAME- -- delete NAME
850 * * NAME! -- preserve NAME with existing value
852 * Anything else is either the user name (which is OK) or the start of
853 * the command (in which case I stop and read the rest of the command).
857 size_t sz
= strcspn(optarg
, "=-!");
860 /* --- None of the above --- */
862 if (optarg
[sz
] == 0 || (optarg
[sz
] != '=' && optarg
[sz
+ 1] != 0)) {
872 /* --- Do the appropriate thing --- */
874 switch (optarg
[sz
]) {
876 bc__putenv(0, optarg
, envFlag_preserve
, 1);
880 bc__putenv(optarg
, 0, 0, 1);
884 if ((e
= sym_find(&bc__env
, optarg
, -1, 0, 0)) != 0)
885 e
->f
|= envFlag_preserve
;
890 /* --- Something I didn't understand has occurred --- */
899 if (flags
& f_duff
) {
904 /* --- Switch to daemon mode if requested --- */
907 if (flags
& f_daemon
) {
908 T( trace(TRACE_MISC
, "become: daemon mode requested"); )
909 daemon_init(conffile
, port
);
914 /* --- Open a syslog --- */
916 openlog(quis(), 0, LOG_AUTH
);
918 /* --- Pick out the uid --- */
928 if (isdigit((unsigned char)who
[0]))
929 pw
= getpwuid(atoi(who
));
933 die("unknown user `%s'", who
);
934 to_pw
= userdb_copyUser(pw
);
938 /* --- Fill in the easy bits of the request --- */
944 pw
= getpwuid(rq
.from
);
946 die("who are you? (can't find user %li)", (long)rq
.from
);
947 from_pw
= userdb_copyUser(pw
);
950 /* --- Find the local host address --- */
956 if ((he
= gethostbyname(u
.nodename
)) == 0)
957 die("who am I? (can't resolve `%s')", u
.nodename
);
958 memcpy(&rq
.host
, he
->h_addr
, sizeof(struct in_addr
));
961 /* --- Fiddle with group ownerships a bit --- */
964 #ifdef HAVE_SETGROUPS
965 gid_t from_gr
[NGROUPS_MAX
], to_gr
[NGROUPS_MAX
];
969 /* --- Set the default login group, if there is one --- */
971 if (~flags
& f_havegroup
)
972 group
= (style
== l_preserve
) ? from_pw
->pw_gid
: to_pw
->pw_gid
;
974 #ifndef HAVE_SETGROUPS
976 /* --- Check that it's valid --- */
978 if (group
!= from_pw
->pw_gid
&& group
!= to_pw
->pw_gid
)
979 die("invalid default group");
983 /* --- Set the default group style --- */
985 if (gstyle
== g_unset
)
986 gstyle
= (style
== l_login
) ? g_replace
: g_merge
;
988 /* --- Read in my current set of groups --- */
990 n_fgr
= getgroups(NGROUPS_MAX
, from_gr
);
992 /* --- Now read the groups for the target user --- *
994 * Do this even if I'm using the @g_keep@ style -- I'll use it for
995 * checking that @group@ is valid.
1003 to_gr
[n_tgr
++] = to_pw
->pw_gid
;
1006 while ((gr
= getgrent()) != 0) {
1007 if (gr
->gr_gid
== to_gr
[0])
1009 for (p
= gr
->gr_mem
; *p
; p
++) {
1010 if (strcmp(to_pw
->pw_name
, *p
) == 0) {
1011 to_gr
[n_tgr
++] = gr
->gr_gid
;
1012 if (n_tgr
>= NGROUPS_MAX
)
1023 /* --- Check that @group@ is reasonable --- */
1028 if (group
== getgid() || group
== from_pw
->pw_gid
)
1030 for (i
= 0; i
< n_fgr
; i
++) {
1031 if (group
== from_gr
[i
])
1034 for (i
= 0; i
< n_tgr
; i
++) {
1035 if (group
== to_gr
[i
])
1038 die("invalid default group");
1042 /* --- Phew. Now comes the hard bit --- */
1050 if (gstyle
& g_keep
) {
1052 ga
[i
++] = from_pw
->pw_gid
;
1054 if (gstyle
& g_replace
)
1055 ga
[i
++] = to_pw
->pw_gid
;
1057 /* --- Style authorities will become apoplectic if shown this --- *
1059 * As far as I can see, it's the neatest way of writing it.
1063 (void)(bc__addGroups(groups
, &ngroups
, ga
, i
) ||
1064 ((gstyle
& g_keep
) &&
1065 bc__addGroups(groups
, &ngroups
, from_gr
, n_fgr
)) ||
1066 ((gstyle
& g_replace
) &&
1067 bc__addGroups(groups
, &ngroups
, to_gr
, n_tgr
)));
1072 /* --- Trace the results of all this --- */
1074 T( trace(TRACE_SETUP
, "setup: default group == %i", (int)group
); )
1076 #ifdef HAVE_SETGROUPS
1077 IF_TRACING(TRACE_SETUP
, {
1080 for (i
= 1; i
< ngroups
; i
++)
1081 trace(TRACE_SETUP
, "setup: subsidiary group %i", (int)groups
[i
]);
1086 /* --- Shell commands are easy --- */
1093 /* --- A command given on the command line isn't too hard --- */
1095 else if (optind
< argc
) {
1096 todo
= argv
+ optind
;
1105 /* --- An unadorned becoming requires little work --- */
1108 shell
[0] = getenv("SHELL");
1110 shell
[0] = from_pw
->pw_shell
;
1116 /* --- An su-like login needs slightly less effort --- */
1119 shell
[0] = to_pw
->pw_shell
;
1125 /* --- A login request needs a little bit of work --- */
1128 const char *p
= strrchr(to_pw
->pw_shell
, '/');
1133 p
= to_pw
->pw_shell
;
1134 shell
[0] = xmalloc(strlen(p
) + 2);
1136 strcpy(shell
[0] + 1, p
);
1139 binary
= to_pw
->pw_shell
;
1144 /* --- Mangle the environment --- *
1146 * This keeps getting more complicated all the time. (How true. Now I've
1147 * got all sorts of nasty environment mangling to do.)
1149 * The environment stuff now happens in seven phases:
1151 * 1. Mark very special variables to be preserved. Currently only TERM
1152 * and DISPLAY are treated in this way.
1154 * 2. Set and preserve Become's own environment variables.
1156 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1157 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1159 * 4. If we're preserving the environment or being `su'-like, process the
1160 * PATH variable a little. Otherwise reset it to something
1163 * 5. If we're being `login'-like, expunge all unpreserved variables.
1165 * 6. Expunge any security-critical variables.
1167 * 7. Build a new environment table to pass to child processes.
1171 /* --- Variables to be preserved always --- *
1173 * A user can explicitly expunge a variable in this list, in which case
1174 * we never get to see it here.
1177 static char *preserve
[] = {
1178 "TERM", "DISPLAY", "TZ", 0
1181 /* --- Variables to be expunged --- *
1183 * Any environment string which has one of the following as a prefix will
1184 * be expunged from the environment passed to the called process. The
1185 * first line lists variables which have been used to list search paths
1186 * for shared libraries: by manipulating these, an attacker could replace
1187 * a standard library with one of his own. The second line lists other
1188 * well-known dangerous environment variables.
1191 static char *banned
[] = {
1192 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1193 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1197 /* --- Other useful variables --- */
1206 /* --- Stage one. Preserve display-specific variables --- */
1208 for (pp
= preserve
; *pp
; pp
++) {
1209 if ((e
= sym_find(&bc__env
, *pp
, -1, 0, 0)) != 0)
1210 e
->f
|= envFlag_preserve
;
1213 /* --- Stage two. Set Become's own variables --- */
1215 e
= sym_find(&bc__env
, "BECOME_ORIGINAL_USER", -1, sizeof(*e
), &f
);
1217 bc__setenv(e
, from_pw
->pw_name
);
1218 e
->f
|= envFlag_preserve
;
1220 e
= sym_find(&bc__env
, "BECOME_ORIGINAL_HOME", -1, sizeof(*e
), &f
);
1222 bc__setenv(e
, from_pw
->pw_dir
);
1223 e
->f
|= envFlag_preserve
;
1225 bc__putenv("BECOME_OLD_USER", from_pw
->pw_name
, envFlag_preserve
, 0);
1226 bc__putenv("BECOME_OLD_HOME", from_pw
->pw_dir
, envFlag_preserve
, 0);
1227 bc__putenv("BECOME_USER", to_pw
->pw_name
, envFlag_preserve
, 0);
1228 bc__putenv("BECOME_HOME", to_pw
->pw_dir
, envFlag_preserve
, 0);
1230 /* --- Stage three. Set user identity --- */
1234 static char *maildirs
[] = {
1235 "/var/spool/mail", "/var/mail",
1236 "/usr/spool/mail", "/usr/mail",
1242 for (pp
= maildirs
; *pp
; pp
++) {
1243 if (stat(*pp
, &s
) == 0 && S_ISDIR(s
.st_mode
)) {
1244 sprintf(b
, "%s/%s", *pp
, to_pw
->pw_name
);
1245 bc__putenv("MAIL", b
, envFlag_preserve
, 0);
1249 } /* Fall through */
1252 bc__putenv("USER", to_pw
->pw_name
, envFlag_preserve
, 0);
1253 bc__putenv("LOGNAME", to_pw
->pw_name
, envFlag_preserve
, 0);
1254 bc__putenv("HOME", to_pw
->pw_dir
, envFlag_preserve
, 0);
1255 bc__putenv("SHELL", to_pw
->pw_shell
, envFlag_preserve
, 0);
1259 /* --- Stage four. Set the user's PATH properly --- */
1262 /* --- Find an existing path --- *
1264 * If there's no path, or this is a login, then set a default path,
1265 * unless we're meant to preserve the existing one. Whew!
1268 e
= sym_find(&bc__env
, "PATH", -1, sizeof(*e
), &f
);
1270 if (!f
|| (style
== l_login
&& ~e
->f
& envFlag_preserve
)) {
1272 rq
.to ?
"/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1273 envFlag_preserve
, 0);
1276 /* --- Find the string --- */
1278 e
->f
= envFlag_preserve
;
1279 p
= strchr(e
->val
, '=') + 1;
1282 /* --- Write the new version to a dynamically allocated buffer --- */
1284 e
->val
= xmalloc(4 + 1 + strlen(p
) + 1);
1285 strcpy(e
->val
, "PATH=");
1288 for (p
= strtok(p
, ":"); p
; p
= strtok(0, ":")) {
1303 /* --- Stages five and six. Expunge variables and count numbers --- *
1305 * Folded together, so I only need one pass through the table. Also
1306 * count the number of variables needed at this time.
1311 for (sym_createIter(&i
, &bc__env
); (e
= sym_next(&i
)) != 0; ) {
1313 /* --- Login style expunges all unpreserved variables --- */
1315 if (style
== l_login
&& ~e
->f
& envFlag_preserve
)
1318 /* --- Otherwise just check the name against the list --- */
1320 for (pp
= banned
; *pp
; pp
++) {
1323 if (memcmp(e
->_base
.name
, p
, strlen(p
)) == 0)
1325 } else if (strcmp(e
->_base
.name
, *pp
) == 0)
1333 sym_remove(&bc__env
, e
);
1336 /* --- Stage seven. Build the new environment block --- */
1338 env
= qq
= xmalloc((sz
+ 1) * sizeof(*qq
));
1340 for (sym_createIter(&i
, &bc__env
); (e
= sym_next(&i
)) != 0; )
1345 /* --- Trace the command --- */
1347 IF_TRACING(TRACE_SETUP
, {
1350 trace(TRACE_SETUP
, "setup: from user %s to user %s",
1351 from_pw
->pw_name
, to_pw
->pw_name
);
1352 trace(TRACE_SETUP
, "setup: binary == `%s'", binary
);
1353 for (i
= 0; todo
[i
]; i
++)
1354 trace(TRACE_SETUP
, "setup: arg %i == `%s'", i
, todo
[i
]);
1355 for (i
= 0; env
[i
]; i
++)
1356 trace(TRACE_SETUP
, "setup: env %i == `%s'", i
, env
[i
]);
1359 /* --- If necessary, resolve the path to the command --- */
1361 if (!strchr(binary
, '/')) {
1365 if ((p
= getenv("PATH")) == 0)
1366 p
= "/bin:/usr/bin";
1369 for (p
= strtok(path
, ":"); p
; p
= strtok(0, ":")) {
1371 /* --- Check length of string before copying --- */
1373 if (strlen(p
) + strlen(binary
) + 2 > sizeof(rq
.cmd
))
1376 /* --- Now build the pathname and check it --- */
1378 sprintf(rq
.cmd
, "%s/%s", p
, todo
[0]);
1379 if (stat(rq
.cmd
, &st
) == 0 && /* Check it exists */
1380 st
.st_mode
& 0111 && /* Check it's executable */
1381 S_ISREG(st
.st_mode
)) /* Check it's a file */
1386 die("couldn't find `%s' in path", todo
[0]);
1390 T( trace(TRACE_SETUP
, "setup: resolve binary to `%s'", binary
); )
1392 /* --- Canonicalise the path string, if necessary --- */
1399 /* --- Insert current directory name if path not absolute --- */
1404 if (!getcwd(b
, sizeof(b
)))
1405 die("couldn't read current directory: %s", strerror(errno
));
1410 /* --- Now copy over characters from the path string --- */
1414 /* --- Check for buffer overflows here --- *
1416 * I write at most one byte per iteration so this is OK. Remember to
1417 * allow one for the null byte.
1420 if (p
>= b
+ sizeof(b
) - 1)
1421 die("internal error: buffer overflow while canonifying path");
1423 /* --- Reduce multiple slashes to just one --- */
1431 /* --- Handle dots in filenames --- *
1433 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1434 * we've just stuck the current directory on the end of the buffer,
1435 * or we've just put something else on the end.
1438 else if (*q
== '.' && p
[-1] == '/') {
1440 /* --- A simple `./' just gets removed --- */
1442 if (q
[1] == 0 || q
[1] == '/') {
1448 /* --- A `../' needs to be peeled back to the previous `/' --- */
1450 if (q
[1] == '.' && (q
[2] == 0 || q
[2] == '/')) {
1453 while (p
> b
&& p
[-1] != '/')
1466 T( trace(TRACE_SETUP
, "setup: canonify binary to `%s'", rq
.cmd
); )
1468 /* --- Run the check --- *
1470 * If the user is already what she wants to be, then print a warning.
1471 * Then, if I was just going to spawn a shell, quit, to reduce user
1472 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1473 * checking if we're already root -- root can do anything anyway, and at
1474 * least this way we get some logging done, and offer a more friendly
1478 if (rq
.from
== rq
.to
) {
1479 moan("you already are `%s'!", to_pw
->pw_name
);
1480 if (flags
& f_shell
) {
1481 moan("(to prevent confusion, I'm not spawning a shell)");
1485 int a
= (rq
.from
== 0) || check(&rq
);
1488 "permission %s for %s to become %s to run `%s'",
1489 a ?
"granted" : "denied", from_pw
->pw_name
, to_pw
->pw_name
,
1493 die("permission denied");
1496 /* --- Now do the job --- */
1498 T( trace(TRACE_MISC
, "become: permission granted"); )
1500 if (flags
& f_dummy
) {
1501 puts("permission granted");
1505 #ifdef HAVE_SETGROUPS
1506 if (setgroups(ngroups
, groups
) < 0)
1507 die("couldn't set groups: %s", strerror(errno
));
1510 if (setgid(group
) < 0)
1511 die("couldn't set default group: %s", strerror(errno
));
1512 if (setuid(rq
.to
) < 0)
1513 die("couldn't set uid: %s", strerror(errno
));
1515 /* --- If this was a login, change current directory --- */
1517 if ((flags
& f_shell
) &&
1519 chdir(to_pw
->pw_dir
) < 0) {
1520 moan("couldn't change directory to `%s': %s",
1521 to_pw
->pw_dir
, strerror(errno
));
1524 /* --- Finally, call the program --- */
1528 execve(rq
.cmd
, todo
, env
);
1529 die("couldn't exec `%s': %s", rq
.cmd
, strerror(errno
));
1533 /*----- That's all, folks -------------------------------------------------*/