3 * $Id: become.c,v 1.4 1997/08/20 16:15:13 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.4 1997/08/20 16:15:13 mdw
33 * Overhaul of environment handling. Fix daft bug in path search code.
35 * Revision 1.3 1997/08/07 16:28:59 mdw
36 * Do something useful when users attempt to become themselves.
38 * Revision 1.2 1997/08/04 10:24:20 mdw
39 * Sources placed under CVS control.
41 * Revision 1.1 1997/07/21 13:47:54 mdw
46 /*----- Header files ------------------------------------------------------*/
48 /* --- ANSI headers --- */
57 /* --- Unix headers --- */
59 #include <sys/types.h>
61 #include <sys/socket.h>
62 #include <sys/utsname.h>
64 #include <netinet/in.h>
66 #include <arpa/inet.h>
73 extern char **environ
;
75 /* --- Local headers --- */
89 /*----- Main code ---------------------------------------------------------*/
91 /* --- @bc__write@ --- *
93 * Arguments: @FILE *fp@ = pointer to a stream to write on
94 * @const char *p@ = pointer to a string
98 * Use: Writes the string to the stream, substituting the program
99 * name (as returned by @quis@) for each occurrence of the
103 static void bc__write(FILE *fp
, const char *p
)
105 const char *n
= quis();
107 size_t nl
= strlen(n
);
109 /* --- Try to be a little efficient --- *
111 * Gather up non-`$' characters using @strcspn@ and spew them out really
122 fwrite(n
, nl
, 1, fp
);
127 /* --- @bc__banner@ --- *
129 * Arguments: @FILE *fp@ = stream to write on
133 * Use: Writes a banner containing copyright information.
136 static void bc__banner(FILE *fp
)
138 bc__write(fp
, "$ version " VERSION
"\n");
141 /* --- @bc__usage@ --- *
143 * Arguments: @FILE *fp@ = stream to write on
147 * Use: Writes a terse reminder of command line syntax.
150 static void bc__usage(FILE *fp
)
154 " $ -c <shell-command> <user>\n"
155 " $ <user> [<command> [<arguments>]...]\n"
156 " $ -d [-p <port>] [-f <config-file>]\n");
159 /* --- @bc__makeEnv@ --- *
161 * Arguments: @const char *var@ = name of an environment variable
162 * @const char *value@ = value of the variable
164 * Returns: A pointer to an allocated block assigning the value to the
167 * Use: Constructs environment mappings.
170 static char *bc__makeEnv(const char *var
, const char *value
)
172 char *p
= xmalloc(strlen(var
) + strlen(value
) + 2);
173 sprintf(p
, "%s=%s", var
, value
);
177 /* --- @bc__help@ --- *
179 * Arguments: @FILE *fp@ = stream to write on
180 * @int suid@ = whether we're running set-uid
184 * Use: Displays a help message for this excellent piece of software.
187 static void bc__help(FILE *fp
, int suid
)
194 "The `$' program allows you to run a process as another user.\n"
195 "If a command name is given, this is the process executed. If the `-c'\n"
196 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
197 "given, your default login shell is used.\n"
199 "Your user id, the user id you wish to become, the name of the process\n"
200 "you wish to run, and the identity of the current host are looked up to\n"
201 "ensure that you have permission to do this.\n"
203 "Note that logs are kept of all uses of this program.\n"
205 "Options available are:\n"
207 "-h, --help Display this help text\n"
208 "-v, --version Display the version number of this copy of $\n"
209 "-l, --login Really log in as the user\n"
210 "-c, --command=CMD Run the (Bourne) shell command CMD\n"
211 "-d, --daemon Start up a daemon, to accept requests from clients\n"
212 "-p, --port=PORT In daemon mode, listen on PORT\n"
213 "-f, --config-file=FILE In daemon mode, read config from FILE\n");
217 "--impersonate=USER Claim to be USER when asking the server\n");
220 "--trace=FILE Dump trace information to FILE (boring)\n"
221 "--trace-level=OPTS Set level of tracing information\n");
227 * Arguments: @int argc@ = number of command line arguments
228 * @char *argv[]@ = pointer to the various arguments
230 * Returns: Zero if successful.
232 * Use: Allows a user to change UID.
235 int main(int argc
, char *argv
[])
237 /* --- Request block setup parameters --- */
239 request rq
; /* Request buffer to build */
240 char *cmd
= 0; /* Shell command to execute */
241 char *binary
= "/bin/sh"; /* Default binary to execute */
242 char **env
= environ
; /* Default environment to pass */
243 char **todo
; /* Pointer to argument list */
244 struct passwd
*from_pw
= 0; /* User we are right now */
245 struct passwd
*to_pw
= 0; /* User we want to become */
247 /* --- Become server setup parameters --- */
249 char *conffile
= file_RULES
; /* Default config file for daemon */
250 int port
= -1; /* Default port for daemon */
252 /* --- Miscellanous shared variables --- */
254 unsigned flags
= 0; /* Various useful flags */
256 /* --- Default argument list executes a shell command --- */
258 static char *shell
[] = {
259 "/bin/sh", /* Bourne shell */
260 "-c", /* Read from command line */
261 0, /* Pointer to shell command */
265 /* --- Definitions for the various flags --- */
268 f_daemon
= 1, /* Start up in daemon mode */
269 f_duff
= 2, /* Fault in arguments */
270 f_login
= 4, /* Execute as a login shell */
271 f_dummy
= 8, /* Don't actually do anything */
272 f_setuid
= 16 /* We're running setuid */
275 /* --- Set up the program name --- */
279 if (getuid() != geteuid())
282 /* --- Parse some command line arguments --- */
286 static struct option opts
[] = {
287 { "help", 0, 0, 'h' },
288 { "usage", 0, 0, 'U' },
289 { "version", 0, 0, 'v' },
290 { "login", 0, 0, 'l' },
291 { "command", gFlag_argReq
, 0, 'c' },
292 { "daemon", 0, 0, 'd' },
293 { "port", gFlag_argReq
, 0, 'p' },
294 { "config-file", gFlag_argReq
, 0, 'f' },
296 { "impersonate", gFlag_argReq
, 0, 'I' },
297 { "trace", gFlag_argOpt
, 0, 'T' },
298 { "trace-level", gFlag_argOpt
, 0, 'L' },
303 i
= mdwopt(argc
, argv
, "+hvlc:p:df:", opts
, 0, 0, 0);
309 /* --- Standard help and version options --- */
312 bc__help(stdout
, flags
& f_setuid
);
324 /* --- Various simple options --- */
342 /* --- Pretend to be a different user --- *
344 * There are all sorts of nasty implications for this option. Don't
345 * allow it if we're running setuid. Disable the actual login anyway.
351 if (flags
& f_setuid
)
352 moan("shan't allow impersonation while running setuid");
355 if (isdigit((unsigned char)optarg
[0]))
356 pw
= getpwuid(atoi(optarg
));
358 pw
= getpwnam(optarg
);
360 die("can't impersonate unknown user `%s'", optarg
);
361 from_pw
= userdb_copyUser(pw
);
362 rq
.from
= from_pw
->pw_uid
;
369 /* --- Tracing support --- *
371 * Be careful not to zap a file I wouldn't normally be allowed to write
380 if (optarg
== 0 || strcmp(optarg
, "-") == 0)
383 if ((flags
& f_setuid
) && access(optarg
, W_OK
)) {
384 die("no write permission for trace file file `%s': %s",
385 optarg
, strerror(errno
));
387 if ((fp
= fopen(optarg
, "w")) == 0) {
388 die("couldn't open trace file `%s' for writing: %s",
389 optarg
, strerror(errno
));
392 traceon(fp
, TRACE_DFL
);
393 trace(TRACE_MISC
, "become: tracing enabled");
398 /* --- Setting trace levels --- */
404 unsigned int lvl
= 0, l
;
405 const char *p
= optarg
;
407 /* --- Table of tracing facilities --- */
415 static tr lvltbl
[] = {
416 { 'm', TRACE_MISC
, "miscellaneous messages" },
417 { 's', TRACE_SETUP
, "building the request block" },
418 { 'd', TRACE_DAEMON
, "server process" },
419 { 'r', TRACE_RULE
, "ruleset scanning" },
420 { 'c', TRACE_CHECK
, "request checking" },
421 { 'l', TRACE_CLIENT
, "client process" },
422 { 'R', TRACE_RAND
, "random number generator" },
423 { 'C', TRACE_CRYPTO
, "cryptographic processing of requests" },
424 { 'y', TRACE_YACC
, "parsing configuration file" },
425 { 'D', TRACE_DFL
, "default tracing options" },
426 { 'A', TRACE_ALL
, "all tracing options" },
431 /* --- Output some help if there's no arguemnt --- */
439 for (tp
= lvltbl
; tp
->l
; tp
++) {
440 if ((flags
& f_setuid
) == 0 || tp
->l
& ~TRACE_PRIV
)
441 printf("%c -- %s\n", tp
->ch
, tp
->help
);
445 "Also, `+' and `-' options are recognised to turn on and off vavrious\n"
446 "tracing options. For example, `A-r' enables everything except ruleset\n"
447 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
459 for (tp
= lvltbl
; tp
->l
&& *p
!= tp
->ch
; tp
++)
462 if (flags
& f_setuid
)
465 lvl
= sense ?
(lvl
| l
) : (lvl
& ~l
);
467 moan("unknown trace option `%c'", *p
);
473 yydebug
= ((lvl
& TRACE_YACC
) != 0);
478 /* --- Something I didn't understand has occurred --- */
485 if (flags
& f_duff
) {
490 /* --- Switch to daemon mode if requested --- */
492 if (flags
& f_daemon
) {
493 T( trace(TRACE_MISC
, "become: daemon mode requested"); )
494 daemon_init(conffile
, port
);
498 /* --- Open a syslog --- */
500 openlog(quis(), 0, LOG_AUTH
);
502 /* --- Pick out the uid --- */
508 if (optind
>= argc
) {
514 if (isdigit((unsigned char)u
[0]))
515 pw
= getpwuid(atoi(u
));
519 die("unknown user `%s'", u
);
520 to_pw
= userdb_copyUser(pw
);
524 /* --- Fill in the easy bits of the request --- */
530 pw
= getpwuid(rq
.from
);
532 die("who are you? (can't find user %li)", (long)rq
.from
);
533 from_pw
= userdb_copyUser(pw
);
536 /* --- Find the local host address --- */
542 if ((he
= gethostbyname(u
.nodename
)) == 0)
543 die("who am I? (can't resolve `%s')", u
.nodename
);
544 memcpy(&rq
.host
, he
->h_addr
, sizeof(struct in_addr
));
547 /* --- Shell commands are easy --- */
554 /* --- A command given on the command line isn't too hard --- */
556 else if (optind
< argc
) {
557 todo
= argv
+ optind
;
561 /* --- A login request needs a little bit of work --- */
563 else if (flags
& f_login
) {
564 const char *p
= strrchr(to_pw
->pw_shell
, '/');
569 shell
[0] = xmalloc(strlen(p
) + 2);
571 strcpy(shell
[0] + 1, p
);
574 binary
= to_pw
->pw_shell
;
577 /* --- An unadorned becoming requires little work --- */
580 shell
[0] = getenv("SHELL");
582 shell
[0] = from_pw
->pw_shell
;
588 /* --- Mangle the environment --- *
590 * This keeps getting more complicated all the time.
597 /* --- Expunge some environment variables --- *
599 * Any environment string which has one of the following as a prefix
600 * will be expunged from the environment passed to the called process.
601 * The first line lists variables which have been used to list search
602 * paths for shared libraries: by manipulating these, an attacker could
603 * replace a standard library with one of his own. The second line lists
604 * other well-known dangerous environment variables.
607 static const char *banned
[] = {
608 "LD_", "SHLIB_PATH=", "LIBPATH=", "_RLD_",
609 "IFS=", "ENV=", "BASH_ENV=", "KRB_CONF=",
613 /* --- Do this in two passes --- *
615 * The first pass works out how big the environment block needs to be.
616 * The second actually fills it.
619 for (pass
= 0; pass
< 2; pass
++) {
622 if (flags
& f_login
) {
624 /* --- This is a login request --- *
626 * Erase the existing environment and build a new one.
632 env
[i
++] = "PATH=/usr/bin:/bin";
633 env
[i
++] = bc__makeEnv("USER", to_pw
->pw_name
);
634 env
[i
++] = bc__makeEnv("LOGNAME", to_pw
->pw_name
);
635 env
[i
++] = bc__makeEnv("HOME", to_pw
->pw_dir
);
639 /* --- Normal request --- *
641 * Remove dangerous variables from the list.
644 for (j
= 0; environ
[j
]; j
++) {
645 for (b
= 0; banned
[b
]; b
++) {
646 if (memcmp(environ
[j
], banned
[b
], strlen(banned
[b
])) == 0)
656 /* --- Now add our own variables --- *
658 * The following are supplied only to help people construct startup
659 * scripts. Anyone who relies on them being accurate for
660 * authentication purposes will get exactly what they deserve.
666 env
[i
++] = bc__makeEnv("BECOME_OLDUSER", from_pw
->pw_name
);
667 env
[i
++] = bc__makeEnv("BECOME_OLDHOME", from_pw
->pw_dir
);
668 env
[i
++] = bc__makeEnv("BECOME_USER", to_pw
->pw_name
);
669 env
[i
++] = bc__makeEnv("BECOME_HOME", to_pw
->pw_dir
);
672 /* --- Allocate memory after the first pass is complete --- */
679 env
= xmalloc(i
* sizeof(env
[0]));
683 /* --- Trace the command --- */
685 IF_TRACING(TRACE_SETUP
, {
688 trace(TRACE_SETUP
, "setup: from user %s to user %s",
689 from_pw
->pw_name
, to_pw
->pw_name
);
690 trace(TRACE_SETUP
, "setup: binary == `%s'", binary
);
691 for (i
= 0; todo
[i
]; i
++)
692 trace(TRACE_SETUP
, "setup: arg %i == `%s'", i
, todo
[i
]);
693 for (i
= 0; env
[i
]; i
++)
694 trace(TRACE_SETUP
, "setup: env %i == `%s'", i
, env
[i
]);
697 /* --- If necessary, resolve the path to the command --- */
699 if (!strchr(binary
, '/')) {
703 if ((p
= getenv("PATH")) == 0)
707 for (p
= strtok(path
, ":"); p
; p
= strtok(0, ":")) {
709 /* --- Check length of string before copying --- */
711 if (strlen(p
) + strlen(binary
) + 2 > sizeof(rq
.cmd
))
714 /* --- Now build the pathname and check it --- */
716 sprintf(rq
.cmd
, "%s/%s", p
, todo
[0]);
717 if (stat(rq
.cmd
, &st
) == 0 && /* Check it exists */
718 st
.st_mode
& 0111 && /* Check it's executable */
719 (st
.st_mode
& S_IFMT
) == S_IFREG
) /* Check it's a file */
724 die("couldn't find `%s' in path", todo
[0]);
728 T( trace(TRACE_SETUP
, "setup: resolve binary to `%s'", binary
); )
730 /* --- Canonicalise the path string, if necessary --- */
737 /* --- Insert current directory name if path not absolute --- */
742 if (!getcwd(b
, sizeof(b
)))
743 die("couldn't read current directory: %s", strerror(errno
));
748 /* --- Now copy over characters from the path string --- */
752 /* --- Check for buffer overflows here --- *
754 * I write at most one byte per iteration so this is OK. Remember to
755 * allow one for the null byte.
758 if (p
>= b
+ sizeof(b
) - 1)
759 die("internal error: buffer overflow while canonifying path");
761 /* --- Reduce multiple slashes to just one --- */
769 /* --- Handle dots in filenames --- *
771 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
772 * we've just stuck the current directory on the end of the buffer,
773 * or we've just put something else on the end.
776 else if (*q
== '.' && p
[-1] == '/') {
778 /* --- A simple `./' just gets removed --- */
780 if (q
[1] == 0 || q
[1] == '/') {
786 /* --- A `../' needs to be peeled back to the previous `/' --- */
788 if (q
[1] == '.' && (q
[2] == 0 || q
[2] == '/')) {
791 while (p
> b
&& p
[-1] != '/')
804 T( trace(TRACE_SETUP
, "setup: canonify binary to `%s'", rq
.cmd
); )
806 /* --- Run the check --- *
808 * If the user is already what she wants to be, then print a warning.
809 * Then, if I was just going to spawn a shell, quit, to reduce user
810 * confusion. Otherwise, do what was wanted anyway.
813 if (rq
.from
== rq
.to
) {
814 moan("you already are `%s'!", to_pw
->pw_name
);
815 if (!cmd
&& todo
== shell
) {
816 moan("(to prevent confusion, I'm not spawning a shell)");
823 "permission %s for %s to become %s to run `%s'",
824 a ?
"granted" : "denied", from_pw
->pw_name
, to_pw
->pw_name
,
828 die("permission denied");
831 /* --- Now do the job --- */
833 T( trace(TRACE_MISC
, "become: permission granted"); )
835 if (flags
& f_dummy
) {
836 puts("permission granted");
839 if (setuid(rq
.to
) == -1)
840 die("couldn't set uid: %s", strerror(errno
));
841 execve(rq
.cmd
, todo
, env
);
842 die("couldn't exec `%s': %s", rq
.cmd
, strerror(errno
));
847 /*----- That's all, folks -------------------------------------------------*/