3 * $Id: become.c,v 1.10 1997/09/17 10:14:10 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.10 1997/09/17 10:14:10 mdw
33 * Fix a typo. Support service names in `--port' option.
35 * Revision 1.9 1997/09/10 10:28:05 mdw
36 * Allow default port to be given as a service name or port number. Handle
37 * groups properly (lots of options here).
39 * Revision 1.8 1997/09/08 13:56:24 mdw
40 * Change criteria for expunging items from the user's PATH: instead of
41 * removing things starting with `.', remove things not starting with `/'.
43 * Revision 1.7 1997/09/08 13:43:20 mdw
44 * Change userid when creating tracefiles rather than fiddling with
45 * `access': it works rather better. Also, insert some stdio buffer
46 * flushing to ensure tracedumps are completely written.
48 * Revision 1.6 1997/09/05 13:47:44 mdw
49 * Make the `-L' (trace-level) option's argument optional, like the long
52 * Revision 1.5 1997/09/05 11:45:19 mdw
53 * Add support for different login styles, and environment variable
54 * manipulation in a safe and useful way.
56 * Revision 1.4 1997/08/20 16:15:13 mdw
57 * Overhaul of environment handling. Fix daft bug in path search code.
59 * Revision 1.3 1997/08/07 16:28:59 mdw
60 * Do something useful when users attempt to become themselves.
62 * Revision 1.2 1997/08/04 10:24:20 mdw
63 * Sources placed under CVS control.
65 * Revision 1.1 1997/07/21 13:47:54 mdw
70 /*----- Header files ------------------------------------------------------*/
72 /* --- ANSI headers --- */
82 /* --- Unix headers --- */
84 #include <sys/types.h>
86 #include <sys/socket.h>
87 #include <sys/utsname.h>
89 #include <netinet/in.h>
91 #include <arpa/inet.h>
99 extern char **environ
;
101 /* --- Local headers --- */
116 /*----- Type definitions --------------------------------------------------*/
118 /* --- Symbol table entry for an environment variable --- */
120 typedef struct sym_env
{
121 sym_base _base
; /* Symbol table information */
122 unsigned f
; /* Flags word (see below) */
123 char *val
; /* Pointer to variable value */
126 /* --- Environment variable flags --- */
132 /* --- Login behaviour types --- */
135 l_preserve
, /* Preserve the environment */
136 l_setuser
, /* Update who I am */
137 l_login
/* Do a full login */
140 /* --- Group behaviour types --- *
142 * Note that these make a handy bitfield.
145 #ifdef HAVE_SETGROUPS
148 g_unset
= 0, /* Nobody's set a preference */
149 g_keep
= 1, /* Leave the group memberships */
150 g_replace
= 2, /* Replace group memberships */
151 g_merge
= (g_keep
| g_replace
) /* Merge the group memberships */
156 /*----- Static variables --------------------------------------------------*/
158 static sym_table bc__env
;
160 /*----- Main code ---------------------------------------------------------*/
162 /* --- @bc__write@ --- *
164 * Arguments: @FILE *fp@ = pointer to a stream to write on
165 * @const char *p@ = pointer to a string
169 * Use: Writes the string to the stream, substituting the program
170 * name (as returned by @quis@) for each occurrence of the
174 static void bc__write(FILE *fp
, const char *p
)
176 const char *n
= quis();
178 size_t nl
= strlen(n
);
180 /* --- Try to be a little efficient --- *
182 * Gather up non-`$' characters using @strcspn@ and spew them out really
193 fwrite(n
, nl
, 1, fp
);
198 /* --- @bc__setenv@ --- *
200 * Arguments: @sym_env *e@ = pointer to environment variable block
201 * @const char *val@ = value to set
205 * Use: Sets an environment variable block to the right value.
208 static void bc__setenv(sym_env
*e
, const char *val
)
210 e
->val
= xmalloc(strlen(e
->_base
.name
) + 1 + strlen(val
) + 1);
211 sprintf(e
->val
, "%s=%s", e
->_base
.name
, val
);
214 /* --- @bc__putenv@ --- *
216 * Arguments: @const char *var@ = name of the variable to set, or 0 if
217 * this is embedded in the value string
218 * @const char *val@ = value to set, or 0 if the variable must
220 * @unsigned int fl@ = flags to set
221 * @unsigned int force@ = force overwrite of preserved variables
223 * Returns: Pointer to symbol block, or zero if it was deleted.
225 * Use: Puts an item into the environment.
228 static sym_env
*bc__putenv(const char *var
, const char *val
,
229 unsigned int fl
, unsigned int force
)
236 /* --- Sort out embedded variable names --- */
239 const char *p
= strchr(val
, '=');
254 /* --- Find the variable block --- */
257 e
= sym_find(&bc__env
, var
, -1, sizeof(*e
), &f
);
258 if (!f
|| ~e
->f
& envFlag_preserve
|| force
) {
264 e
= sym_find(&bc__env
, var
, -1, 0, 0);
265 if (e
&& (force
|| ~e
->f
& envFlag_preserve
))
266 sym_remove(&bc__env
, e
);
270 /* --- Tidy up and return --- */
279 /* --- @bc__addGroups@ --- *
281 * Arguments: @gid_t *g@ = pointer to a group array
282 * @int *png@ = pointer to number of entries in the array
283 * @const gid_t *a@ = pointer to groups to add
284 * @int na@ = number of groups to add
286 * Returns: Zero if it was OK, nonzero if we should stop now.
288 * Use: Adds groups to a groups array.
291 static int bc__addGroups(gid_t
*g
, int *png
, const gid_t
*a
, int na
)
296 for (i
= 0; i
< na
; i
++) {
298 /* --- Ensure this group isn't already in the list --- */
300 for (j
= 0; j
< ng
; j
++) {
305 /* --- See if there's room for more --- */
307 if (ng
> NGROUPS_MAX
) {
308 moan("too many groups (system limit exceeded -- some have been lost");
313 /* --- Add the group --- */
323 /* --- @bc__banner@ --- *
325 * Arguments: @FILE *fp@ = stream to write on
329 * Use: Writes a banner containing copyright information.
332 static void bc__banner(FILE *fp
)
334 bc__write(fp
, "$ version " VERSION
"\n");
337 /* --- @bc__usage@ --- *
339 * Arguments: @FILE *fp@ = stream to write on
343 * Use: Writes a terse reminder of command line syntax.
346 static void bc__usage(FILE *fp
)
350 " $ -c <shell-command> <user>\n"
351 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
352 " $ -d [-p <port>] [-f <config-file>]\n");
355 /* --- @bc__help@ --- *
357 * Arguments: @FILE *fp@ = stream to write on
358 * @int suid@ = whether we're running set-uid
362 * Use: Displays a help message for this excellent piece of software.
365 static void bc__help(FILE *fp
, int suid
)
372 "The `$' program allows you to run a process as another user.\n"
373 "If a command name is given, this is the process executed. If the `-c'\n"
374 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
375 "given, your default login shell is used.\n"
377 "Your user id, the user id you wish to become, the name of the process\n"
378 "you wish to run, and the identity of the current host are looked up to\n"
379 "ensure that you have permission to do this.\n"
381 "Note that logs are kept of all uses of this program.\n"
383 "Options available are:\n"
385 "-h, --help Display this help text\n"
386 "-u, --usage Display a short usage summary\n"
387 "-v, --version Display $'s version number\n"
389 "-e, --preserve-environment Try to preserve the current environment\n"
390 "-s, --su, --set-user Set environment variables to reflect USER\n"
391 "-l, --login Really log in as USER\n"
393 "-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
394 #ifdef HAVE_SETGROUPS
395 "-k, --keep-groups Keep your current set of groups\n"
396 "-m, --merge-groups Merge the lists of groups\n"
397 "-r, --replace-groups Replace the list of groups\n"
400 "-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
402 "-d, --daemon Start a daemon\n"
403 "-p PORT, --port=PORT In daemon mode, listen on PORT\n"
404 "-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
409 "-I USER, --impersonate=USER Claim to be USER when asking the server\n");
412 "-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
413 "-L OPTS, --trace-level=OPTS Set level of tracing information\n");
419 * Arguments: @int argc@ = number of command line arguments
420 * @char *argv[]@ = pointer to the various arguments
422 * Returns: Zero if successful.
424 * Use: Allows a user to change UID.
427 int main(int argc
, char *argv
[])
429 /* --- Request block setup parameters --- */
431 request rq
; /* Request buffer to build */
432 char *cmd
= 0; /* Shell command to execute */
433 char *binary
= "/bin/sh"; /* Default binary to execute */
434 char **env
= environ
; /* Default environment to pass */
435 char **todo
= 0; /* Pointer to argument list */
436 char *who
= 0; /* Who we're meant to become */
437 struct passwd
*from_pw
= 0; /* User we are right now */
438 struct passwd
*to_pw
= 0; /* User we want to become */
440 /* --- Become server setup parameters --- */
442 char *conffile
= file_RULES
; /* Default config file for daemon */
443 int port
= 0; /* Default port for daemon */
445 /* --- Miscellanous shared variables --- */
447 unsigned flags
= 0; /* Various useful flags */
448 int style
= DEFAULT_LOGIN_STYLE
; /* Login style */
449 gid_t group
= -1; /* Default group to set */
450 int gstyle
= g_unset
; /* No group style set yet */
452 #ifdef HAVE_SETGROUPS
453 gid_t groups
[NGROUPS_MAX
]; /* Set of groups */
454 int ngroups
; /* Number of groups in the set */
457 /* --- Default argument list executes a shell command --- */
459 static char *shell
[] = {
460 "/bin/sh", /* Bourne shell */
461 "-c", /* Read from command line */
462 0, /* Pointer to shell command */
466 /* --- Definitions for the various flags --- */
469 f_daemon
= 1, /* Start up in daemon mode */
470 f_duff
= 2, /* Fault in arguments */
471 f_login
= 4, /* Execute as a login shell */
472 f_dummy
= 8, /* Don't actually do anything */
473 f_setuid
= 16, /* We're running setuid */
474 f_havegroup
= 32 /* Set a default group */
477 /* --- Set up the program name --- */
481 if (getuid() != geteuid())
484 /* --- Read the environment into a hashtable --- */
489 sym_createTable(&bc__env
);
490 for (p
= environ
; *p
; p
++)
491 bc__putenv(0, *p
, 0, 0);
494 /* --- Parse some command line arguments --- */
498 static struct option opts
[] = {
500 /* --- Asking for help --- */
502 { "help", 0, 0, 'h' },
503 { "usage", 0, 0, 'u' },
504 { "version", 0, 0, 'v' },
506 /* --- Login style options --- */
508 { "preserve-environment", 0, 0, 'e' },
510 { "set-user", 0, 0, 's' },
511 { "login", 0, 0, 'l' },
513 /* --- Group style options --- */
515 { "group", gFlag_argReq
, 0, 'g' },
516 #ifdef HAVE_SETGROUPS
517 { "keep-groups", 0, 0, 'k' },
518 { "merge-groups", 0, 0, 'm' },
519 { "replace-groups", 0, 0, 'r' },
522 /* --- Command to run options --- */
524 { "command", gFlag_argReq
, 0, 'c' },
526 /* --- Server options --- */
528 { "daemon", 0, 0, 'd' },
529 { "port", gFlag_argReq
, 0, 'p' },
530 { "config-file", gFlag_argReq
, 0, 'f' },
532 /* --- Tracing options --- */
535 { "impersonate", gFlag_argReq
, 0, 'I' },
536 { "trace", gFlag_argOpt
, 0, 'T' },
537 { "trace-level", gFlag_argOpt
, 0, 'L' },
543 i
= mdwopt(argc
, argv
,
544 "-" /* Return non-options as options */
545 "huv" /* Asking for help */
546 "esl" /* Login style options */
547 #ifdef HAVE_SETGROUPS
548 "g:kmr" /* Group style options */
550 "g:" /* Group (without @setgroups@) */
552 "c:" /* Command to run options */
553 "dp:f:" /* Server options */
555 "I:T::L::" /* Tracing options */
558 opts
, 0, 0, gFlag_envVar
);
564 /* --- Asking for help --- */
567 bc__help(stdout
, flags
& f_setuid
);
579 /* --- Login style options --- */
591 /* --- Group style options --- */
594 if (isdigit((unsigned char)optarg
[0]))
595 group
= atoi(optarg
);
597 struct group
*gr
= getgrnam(optarg
);
599 die("unknown group `%s'", optarg
);
602 flags
|= f_havegroup
;
615 /* --- Command to run options --- */
621 /* --- Server options --- */
624 if (isdigit((unsigned char)optarg
[0]))
625 port
= htons(atoi(optarg
));
627 struct servent
*s
= getservbyname(optarg
, "udp");
629 die("unknown service name `%s'", optarg
);
640 /* --- Pretend to be a different user --- *
642 * There are all sorts of nasty implications for this option. Don't
643 * allow it if we're running setuid. Disable the actual login anyway.
649 if (flags
& f_setuid
)
650 moan("shan't allow impersonation while running setuid");
653 if (isdigit((unsigned char)optarg
[0]))
654 pw
= getpwuid(atoi(optarg
));
656 pw
= getpwnam(optarg
);
658 die("can't impersonate unknown user `%s'", optarg
);
659 from_pw
= userdb_copyUser(pw
);
660 rq
.from
= from_pw
->pw_uid
;
667 /* --- Tracing support --- *
669 * Be careful not to zap a file I wouldn't normally be allowed to write
678 if (optarg
== 0 || strcmp(optarg
, "-") == 0)
681 uid_t eu
= geteuid(), ru
= getuid();
684 if (setreuid(eu
, ru
))
689 die("couldn't temporarily give up privileges: %s",
693 if ((fp
= fopen(optarg
, "w")) == 0) {
694 die("couldn't open trace file `%s' for writing: %s",
695 optarg
, strerror(errno
));
699 if (setreuid(ru
, eu
))
703 die("couldn't regain privileges: %s", strerror(errno
));
705 traceon(fp
, TRACE_DFL
);
706 trace(TRACE_MISC
, "become: tracing enabled");
711 /* --- Setting trace levels --- */
717 unsigned int lvl
= 0, l
;
718 const char *p
= optarg
;
720 /* --- Table of tracing facilities --- */
728 static tr lvltbl
[] = {
729 { 'm', TRACE_MISC
, "miscellaneous messages" },
730 { 's', TRACE_SETUP
, "building the request block" },
731 { 'd', TRACE_DAEMON
, "server process" },
732 { 'r', TRACE_RULE
, "ruleset scanning" },
733 { 'c', TRACE_CHECK
, "request checking" },
734 { 'l', TRACE_CLIENT
, "client process" },
735 { 'R', TRACE_RAND
, "random number generator" },
736 { 'C', TRACE_CRYPTO
, "cryptographic processing of requests" },
737 { 'y', TRACE_YACC
, "parsing configuration file" },
738 { 'D', TRACE_DFL
, "default tracing options" },
739 { 'A', TRACE_ALL
, "all tracing options" },
744 /* --- Output some help if there's no arguemnt --- */
752 for (tp
= lvltbl
; tp
->l
; tp
++) {
753 if ((flags
& f_setuid
) == 0 || tp
->l
& ~TRACE_PRIV
)
754 printf("%c -- %s\n", tp
->ch
, tp
->help
);
758 "Also, `+' and `-' options are recognised to turn on and off various\n"
759 "tracing options. For example, `A-r' enables everything except ruleset\n"
760 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
772 for (tp
= lvltbl
; tp
->l
&& *p
!= tp
->ch
; tp
++)
775 if (flags
& f_setuid
)
778 lvl
= sense ?
(lvl
| l
) : (lvl
& ~l
);
780 moan("unknown trace option `%c'", *p
);
786 yydebug
= ((lvl
& TRACE_YACC
) != 0);
791 /* --- Something that wasn't an option --- *
793 * The following nasties are supported:
795 * * NAME=VALUE -- preserve NAME, and give it a VALUE
796 * * NAME= -- preserve NAME, and give it an empty value
797 * * NAME- -- delete NAME
798 * * NAME! -- preserve NAME with existing value
800 * Anything else is either the user name (which is OK) or the start of
801 * the command (in which case I stop and read the rest of the command).
805 size_t sz
= strcspn(optarg
, "=-!");
808 /* --- None of the above --- */
810 if (optarg
[sz
] == 0 || (optarg
[sz
] != '=' && optarg
[sz
+ 1] != 0)) {
820 /* --- Do the appropriate thing --- */
822 switch (optarg
[sz
]) {
824 bc__putenv(0, optarg
, envFlag_preserve
, 1);
828 bc__putenv(optarg
, 0, 0, 1);
832 if ((e
= sym_find(&bc__env
, optarg
, -1, 0, 0)) != 0)
833 e
->f
|= envFlag_preserve
;
838 /* --- Something I didn't understand has occurred --- */
847 if (flags
& f_duff
) {
852 /* --- Switch to daemon mode if requested --- */
854 if (flags
& f_daemon
) {
855 T( trace(TRACE_MISC
, "become: daemon mode requested"); )
856 daemon_init(conffile
, port
);
860 /* --- Open a syslog --- */
862 openlog(quis(), 0, LOG_AUTH
);
864 /* --- Pick out the uid --- */
874 if (isdigit((unsigned char)who
[0]))
875 pw
= getpwuid(atoi(who
));
879 die("unknown user `%s'", who
);
880 to_pw
= userdb_copyUser(pw
);
884 /* --- Fill in the easy bits of the request --- */
890 pw
= getpwuid(rq
.from
);
892 die("who are you? (can't find user %li)", (long)rq
.from
);
893 from_pw
= userdb_copyUser(pw
);
896 /* --- Find the local host address --- */
902 if ((he
= gethostbyname(u
.nodename
)) == 0)
903 die("who am I? (can't resolve `%s')", u
.nodename
);
904 memcpy(&rq
.host
, he
->h_addr
, sizeof(struct in_addr
));
907 /* --- Fiddle with group ownerships a bit --- */
910 #ifdef HAVE_SETGROUPS
911 gid_t from_gr
[NGROUPS_MAX
], to_gr
[NGROUPS_MAX
];
915 /* --- Set the default login group, if there is one --- */
917 if (~flags
& f_havegroup
)
918 group
= (style
== l_preserve
) ? from_pw
->pw_gid
: to_pw
->pw_gid
;
920 #ifndef HAVE_SETGROUPS
922 /* --- Check that it's valid --- */
924 if (group
!= from_pw
->pw_gid
&& group
!= to_pw
->pw_gid
)
925 die("invalid default group");
929 /* --- Set the default group style --- */
931 if (gstyle
== g_unset
)
932 gstyle
= (style
== l_login
) ? g_replace
: g_merge
;
934 /* --- Read in my current set of groups --- */
936 n_fgr
= getgroups(NGROUPS_MAX
, from_gr
);
938 /* --- Now read the groups for the target user --- *
940 * Do this even if I'm using the @g_keep@ style -- I'll use it for
941 * checking that @group@ is valid.
949 to_gr
[n_tgr
++] = to_pw
->pw_gid
;
952 while ((gr
= getgrent()) != 0) {
953 if (gr
->gr_gid
== to_gr
[0])
955 for (p
= gr
->gr_mem
; *p
; p
++) {
956 if (strcmp(to_pw
->pw_name
, *p
) == 0) {
957 to_gr
[n_tgr
++] = gr
->gr_gid
;
958 if (n_tgr
>= NGROUPS_MAX
)
969 /* --- Check that @group@ is reasonable --- */
974 if (group
== getgid() || group
== from_pw
->pw_gid
)
976 for (i
= 0; i
< n_fgr
; i
++) {
977 if (group
== from_gr
[i
])
980 for (i
= 0; i
< n_tgr
; i
++) {
981 if (group
== to_gr
[i
])
984 die("invalid default group");
988 /* --- Phew. Now comes the hard bit --- */
996 if (gstyle
& g_keep
) {
998 ga
[i
++] = from_pw
->pw_gid
;
1000 if (gstyle
& g_replace
)
1001 ga
[i
++] = to_pw
->pw_gid
;
1003 /* --- Style authorities will become apoplectic if shown this --- *
1005 * As far as I can see, it's the neatest way of writing it.
1009 (void)(bc__addGroups(groups
, &ngroups
, ga
, i
) ||
1010 ((gstyle
& g_keep
) &&
1011 bc__addGroups(groups
, &ngroups
, from_gr
,n_fgr
)) ||
1012 ((gstyle
& g_replace
) &&
1013 bc__addGroups(groups
, &ngroups
, to_gr
, n_tgr
)));
1018 /* --- Trace the results of all this --- */
1020 T( trace(TRACE_SETUP
, "setup: default group == %i", (int)group
); )
1022 #ifdef HAVE_SETGROUPS
1023 IF_TRACING(TRACE_SETUP
, {
1026 for (i
= 1; i
< ngroups
; i
++)
1027 trace(TRACE_SETUP
, "setup: subsidiary group %i", (int)groups
[i
]);
1032 /* --- Shell commands are easy --- */
1039 /* --- A command given on the command line isn't too hard --- */
1041 else if (optind
< argc
) {
1042 todo
= argv
+ optind
;
1046 else switch (style
) {
1048 /* --- An unadorned becoming requires little work --- */
1051 shell
[0] = getenv("SHELL");
1053 shell
[0] = from_pw
->pw_shell
;
1059 /* --- An su-like login needs slightly less effort --- */
1062 shell
[0] = to_pw
->pw_shell
;
1068 /* --- A login request needs a little bit of work --- */
1071 const char *p
= strrchr(to_pw
->pw_shell
, '/');
1076 p
= to_pw
->pw_shell
;
1077 shell
[0] = xmalloc(strlen(p
) + 2);
1079 strcpy(shell
[0] + 1, p
);
1082 binary
= to_pw
->pw_shell
;
1083 chdir(to_pw
->pw_dir
);
1087 /* --- Mangle the environment --- *
1089 * This keeps getting more complicated all the time. (How true. Now I've
1090 * got all sorts of nasty environment mangling to do.)
1092 * The environment stuff now happens in seven phases:
1094 * 1. Mark very special variables to be preserved. Currently only TERM
1095 * and DISPLAY are treated in this way.
1097 * 2. Set and preserve Become's own environment variables.
1099 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1100 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1102 * 4. If we're preserving the environment or being `su'-like, process the
1103 * PATH variable a little. Otherwise reset it to something
1106 * 5. If we're being `login'-like, expunge all unpreserved variables.
1108 * 6. Expunge any security-critical variables.
1110 * 7. Build a new environment table to pass to child processes.
1114 /* --- Variables to be preserved always --- *
1116 * A user can explicitly expunge a variable in this list, in which case
1117 * we never get to see it here.
1120 static char *preserve
[] = {
1121 "TERM", "DISPLAY", 0
1124 /* --- Variables to be expunged --- *
1126 * Any environment string which has one of the following as a prefix will
1127 * be expunged from the environment passed to the called process. The
1128 * first line lists variables which have been used to list search paths
1129 * for shared libraries: by manipulating these, an attacker could replace
1130 * a standard library with one of his own. The second line lists other
1131 * well-known dangerous environment variables.
1134 static char *banned
[] = {
1135 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1136 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
1140 /* --- Other useful variables --- */
1149 /* --- Stage one. Preserve display-specific variables --- */
1151 for (pp
= preserve
; *pp
; pp
++) {
1152 if ((e
= sym_find(&bc__env
, *pp
, -1, 0, 0)) != 0)
1153 e
->f
|= envFlag_preserve
;
1156 /* --- Stage two. Set Become's own variables --- */
1158 e
= sym_find(&bc__env
, "BECOME_ORIGINAL_USER", -1, sizeof(*e
), &f
);
1160 bc__setenv(e
, from_pw
->pw_name
);
1161 e
->f
|= envFlag_preserve
;
1163 e
= sym_find(&bc__env
, "BECOME_ORIGINAL_HOME", -1, sizeof(*e
), &f
);
1165 bc__setenv(e
, from_pw
->pw_dir
);
1166 e
->f
|= envFlag_preserve
;
1168 bc__putenv("BECOME_OLD_USER", from_pw
->pw_name
, envFlag_preserve
, 0);
1169 bc__putenv("BECOME_OLD_HOME", from_pw
->pw_dir
, envFlag_preserve
, 0);
1170 bc__putenv("BECOME_USER", to_pw
->pw_name
, envFlag_preserve
, 0);
1171 bc__putenv("BECOME_HOME", to_pw
->pw_dir
, envFlag_preserve
, 0);
1173 /* --- Stage three. Set user identity --- */
1177 static char *maildirs
[] = {
1178 "/var/spool/mail", "/var/mail",
1179 "/usr/spool/mail", "/usr/mail",
1185 for (pp
= maildirs
; *pp
; pp
++) {
1186 if (stat(*pp
, &s
) == 0 && S_ISDIR(s
.st_mode
)) {
1187 sprintf(b
, "%s/%s", *pp
, to_pw
->pw_name
);
1188 bc__putenv("MAIL", b
, envFlag_preserve
, 0);
1192 } /* Fall through */
1195 bc__putenv("USER", to_pw
->pw_name
, envFlag_preserve
, 0);
1196 bc__putenv("LOGNAME", to_pw
->pw_name
, envFlag_preserve
, 0);
1197 bc__putenv("HOME", to_pw
->pw_dir
, envFlag_preserve
, 0);
1198 bc__putenv("SHELL", to_pw
->pw_shell
, envFlag_preserve
, 0);
1202 /* --- Stage four. Set the user's PATH properly --- */
1205 /* --- Find an existing path --- *
1207 * If there's no path, or this is a login, then set a default path,
1208 * unless we're meant to preserve the existing one. Whew!
1211 e
= sym_find(&bc__env
, "PATH", -1, sizeof(*e
), &f
);
1213 if (!f
|| (style
== l_login
&& ~e
->f
& envFlag_preserve
)) {
1215 rq
.to ?
"/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1216 envFlag_preserve
, 0);
1219 /* --- Find the string --- */
1221 e
->f
= envFlag_preserve
;
1222 p
= strchr(e
->val
, '=') + 1;
1225 /* --- Write the new version to a dynamically allocated buffer --- */
1227 e
->val
= xmalloc(4 + 1 + strlen(p
) + 1);
1228 strcpy(e
->val
, "PATH=");
1231 for (p
= strtok(p
, ":"); p
; p
= strtok(0, ":")) {
1246 /* --- Stages five and six. Expunge variables and count numbers --- *
1248 * Folded together, so I only need one pass through the table. Also
1249 * count the number of variables needed at this time.
1254 for (sym_createIter(&i
, &bc__env
); (e
= sym_next(&i
)) != 0; ) {
1256 /* --- Login style expunges all unpreserved variables --- */
1258 if (style
== l_login
&& ~e
->f
& envFlag_preserve
)
1261 /* --- Otherwise just check the name against the list --- */
1263 for (pp
= banned
; *pp
; pp
++) {
1266 if (memcmp(e
->_base
.name
, p
, strlen(p
)) == 0)
1268 } else if (strcmp(e
->_base
.name
, *pp
) == 0)
1276 sym_remove(&bc__env
, e
);
1279 /* --- Stage seven. Build the new environment block --- */
1281 env
= qq
= xmalloc((sz
+ 1) * sizeof(*qq
));
1283 for (sym_createIter(&i
, &bc__env
); (e
= sym_next(&i
)) != 0; )
1288 /* --- Trace the command --- */
1290 IF_TRACING(TRACE_SETUP
, {
1293 trace(TRACE_SETUP
, "setup: from user %s to user %s",
1294 from_pw
->pw_name
, to_pw
->pw_name
);
1295 trace(TRACE_SETUP
, "setup: binary == `%s'", binary
);
1296 for (i
= 0; todo
[i
]; i
++)
1297 trace(TRACE_SETUP
, "setup: arg %i == `%s'", i
, todo
[i
]);
1298 for (i
= 0; env
[i
]; i
++)
1299 trace(TRACE_SETUP
, "setup: env %i == `%s'", i
, env
[i
]);
1302 /* --- If necessary, resolve the path to the command --- */
1304 if (!strchr(binary
, '/')) {
1308 if ((p
= getenv("PATH")) == 0)
1309 p
= "/bin:/usr/bin";
1312 for (p
= strtok(path
, ":"); p
; p
= strtok(0, ":")) {
1314 /* --- Check length of string before copying --- */
1316 if (strlen(p
) + strlen(binary
) + 2 > sizeof(rq
.cmd
))
1319 /* --- Now build the pathname and check it --- */
1321 sprintf(rq
.cmd
, "%s/%s", p
, todo
[0]);
1322 if (stat(rq
.cmd
, &st
) == 0 && /* Check it exists */
1323 st
.st_mode
& 0111 && /* Check it's executable */
1324 S_ISREG(st
.st_mode
)) /* Check it's a file */
1329 die("couldn't find `%s' in path", todo
[0]);
1333 T( trace(TRACE_SETUP
, "setup: resolve binary to `%s'", binary
); )
1335 /* --- Canonicalise the path string, if necessary --- */
1342 /* --- Insert current directory name if path not absolute --- */
1347 if (!getcwd(b
, sizeof(b
)))
1348 die("couldn't read current directory: %s", strerror(errno
));
1353 /* --- Now copy over characters from the path string --- */
1357 /* --- Check for buffer overflows here --- *
1359 * I write at most one byte per iteration so this is OK. Remember to
1360 * allow one for the null byte.
1363 if (p
>= b
+ sizeof(b
) - 1)
1364 die("internal error: buffer overflow while canonifying path");
1366 /* --- Reduce multiple slashes to just one --- */
1374 /* --- Handle dots in filenames --- *
1376 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1377 * we've just stuck the current directory on the end of the buffer,
1378 * or we've just put something else on the end.
1381 else if (*q
== '.' && p
[-1] == '/') {
1383 /* --- A simple `./' just gets removed --- */
1385 if (q
[1] == 0 || q
[1] == '/') {
1391 /* --- A `../' needs to be peeled back to the previous `/' --- */
1393 if (q
[1] == '.' && (q
[2] == 0 || q
[2] == '/')) {
1396 while (p
> b
&& p
[-1] != '/')
1409 T( trace(TRACE_SETUP
, "setup: canonify binary to `%s'", rq
.cmd
); )
1411 /* --- Run the check --- *
1413 * If the user is already what she wants to be, then print a warning.
1414 * Then, if I was just going to spawn a shell, quit, to reduce user
1415 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1416 * checking if we're already root -- root can do anything anyway, and at
1417 * least this way we get some logging done, and offer a more friendly
1421 if (rq
.from
== rq
.to
) {
1422 moan("you already are `%s'!", to_pw
->pw_name
);
1423 if (!cmd
&& todo
== shell
) {
1424 moan("(to prevent confusion, I'm not spawning a shell)");
1428 int a
= (rq
.from
== 0) || check(&rq
);
1431 "permission %s for %s to become %s to run `%s'",
1432 a ?
"granted" : "denied", from_pw
->pw_name
, to_pw
->pw_name
,
1436 die("permission denied");
1439 /* --- Now do the job --- */
1441 T( trace(TRACE_MISC
, "become: permission granted"); )
1443 if (flags
& f_dummy
) {
1444 puts("permission granted");
1448 #ifdef HAVE_SETGROUPS
1449 if (setgroups(ngroups
, groups
) < 0)
1450 die("couldn't set groups: %s", strerror(errno
));
1453 if (setgid(group
) < 0)
1454 die("couldn't set default group: %s", strerror(errno
));
1455 if (setuid(rq
.to
) < 0)
1456 die("couldn't set uid: %s", strerror(errno
));
1458 /* --- Finally, call the program --- */
1461 execve(rq
.cmd
, todo
, env
);
1462 die("couldn't exec `%s': %s", rq
.cmd
, strerror(errno
));
1466 /*----- That's all, folks -------------------------------------------------*/