3 * $Id: become.c,v 1.3 1997/08/07 16:28: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.3 1997/08/07 16:28:59 mdw
33 * Do something useful when users attempt to become themselves.
35 * Revision 1.2 1997/08/04 10:24:20 mdw
36 * Sources placed under CVS control.
38 * Revision 1.1 1997/07/21 13:47:54 mdw
43 /*----- Header files ------------------------------------------------------*/
45 /* --- ANSI headers --- */
54 /* --- Unix headers --- */
56 #include <sys/types.h>
58 #include <sys/socket.h>
59 #include <sys/utsname.h>
61 #include <netinet/in.h>
63 #include <arpa/inet.h>
70 extern char **environ
;
72 /* --- Local headers --- */
86 /*----- Main code ---------------------------------------------------------*/
88 /* --- @bc__write@ --- *
90 * Arguments: @FILE *fp@ = pointer to a stream to write on
91 * @const char *p@ = pointer to a string
95 * Use: Writes the string to the stream, substituting the program
96 * name (as returned by @quis@) for each occurrence of the
100 static void bc__write(FILE *fp
, const char *p
)
102 const char *n
= quis();
104 size_t nl
= strlen(n
);
106 /* --- Try to be a little efficient --- *
108 * Gather up non-`$' characters using @strcpn@ and spew them out really
119 fwrite(n
, nl
, 1, fp
);
124 /* --- @bc__banner@ --- *
126 * Arguments: @FILE *fp@ = stream to write on
130 * Use: Writes a banner containing copyright information.
133 static void bc__banner(FILE *fp
)
135 bc__write(fp
, "$ version " VERSION
"\n");
138 /* --- @bc__usage@ --- *
140 * Arguments: @FILE *fp@ = stream to write on
144 * Use: Writes a terse reminder of command line syntax.
147 static void bc__usage(FILE *fp
)
151 " $ -c <shell-command> <user>\n"
152 " $ <user> [<command> [<arguments>]...]\n"
153 " $ -d [-p <port>] [-f <config-file>]\n");
156 /* --- @bc__makeEnv@ --- *
158 * Arguments: @const char *var@ = name of an environment variable
159 * @const char *value@ = value of the variable
161 * Returns: A pointer to an allocated block assigning the value to the
164 * Use: Constructs environment mappings.
167 static char *bc__makeEnv(const char *var
, const char *value
)
169 char *p
= xmalloc(strlen(var
) + strlen(value
) + 2);
170 sprintf(p
, "%s=%s", var
, value
);
174 /* --- @bc__help@ --- *
176 * Arguments: @FILE *fp@ = stream to write on
180 * Use: Displays a help message for this excellent piece of software.
183 static void bc__help(FILE *fp
)
190 "The `$' program allows you to run a process as another user.\n"
191 "If a command name is given, this is the process executed. If the `-c'\n"
192 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
193 "given, your default login shell is used.\n"
195 "Your user id, the user id you wish to become, the name of the process\n"
196 "you wish to run, and the identity of the current host are looked up to\n"
197 "ensure that you have permission to do this.\n"
199 "Note that logs are kept of all uses of this program.\n"
201 "Options available are:\n"
203 "-h, --help Display this help text\n"
204 "-v, --version Display the version number of this copy of $\n"
205 "-l, --login Really log in as the user\n"
206 "-c, --command=CMD Run the (Bourne) shell command CMD\n"
207 "-d, --daemon Start up a daemon, to accept requests from clients\n"
208 "-p, --port=PORT In daemon mode, listen on PORT\n"
209 "-f, --config-file=FILE In daemon mode, read config from FILE\n"
211 "--impersonate=USER Claim to be USER when asking the server\n"
212 "--trace=FILE Dump trace information to FILE (boring)\n"
213 "--trace-level=OPTS Set level of tracing information\n"
220 * Arguments: @int argc@ = number of command line arguments
221 * @char *argv[]@ = pointer to the various arguments
223 * Returns: Zero if successful.
225 * Use: Allows a user to change UID.
228 int main(int argc
, char *argv
[])
230 /* --- Request block setup parameters --- */
232 request rq
; /* Request buffer to build */
233 char *cmd
= 0; /* Shell command to execute */
234 char *binary
= "/bin/sh"; /* Default binary to execute */
235 char **env
= environ
; /* Default environment to pass */
236 char **todo
; /* Pointer to argument list */
237 struct passwd
*from_pw
= 0; /* User we are right now */
238 struct passwd
*to_pw
= 0; /* User we want to become */
240 /* --- Become server setup parameters --- */
242 char *conffile
= file_RULES
; /* Default config file for daemon */
243 int port
= -1; /* Default port for daemon */
245 /* --- Miscellanous shared variables --- */
247 unsigned flags
= 0; /* Various useful flags */
249 /* --- Default argument list executes a shell command --- */
251 static char *shell
[] = {
252 "/bin/sh", /* Bourne shell */
253 "-c", /* Read from command line */
254 0, /* Pointer to shell command */
258 /* --- Login request replaces the environment with this --- */
260 static char *mangled_env
[] = {
261 "PATH=/bin:/usr/bin", /* Default `clean' path*/
262 0, /* User's name (`USER') */
263 0, /* User's name (`LOGNAME') */
264 0, /* Home directory (`HOME') */
268 /* --- Definitions for the various flags --- */
271 f_daemon
= 1, /* Start up in daemon mode */
272 f_duff
= 2, /* Fault in arguments */
273 f_login
= 4, /* Execute as a login shell */
274 f_dummy
= 8, /* Don't actually do anything */
275 f_setuid
= 16 /* We're running setuid */
278 /* --- Set up the program name --- */
282 if (getuid() != geteuid())
285 /* --- Parse some command line arguments --- */
289 static struct option opts
[] = {
290 { "help", 0, 0, 'h' },
291 { "version", 0, 0, 'v' },
292 { "login", 0, 0, 'l' },
293 { "command", gFlag_argReq
, 0, 'c' },
294 { "daemon", 0, 0, 'd' },
295 { "port", gFlag_argReq
, 0, 'p' },
296 { "config-file", gFlag_argReq
, 0, 'f' },
298 { "impersonate", gFlag_argReq
, 0, 'I' },
299 { "trace", gFlag_argOpt
, 0, 'T' },
300 { "trace-level", gFlag_argOpt
, 0, 'L' },
305 i
= mdwopt(argc
, argv
, "+hvlc:p:df:", opts
, 0, 0, 0);
311 /* --- Standard help and version options --- */
322 /* --- Various simple options --- */
340 /* --- Pretend to be a different user --- *
342 * There are all sorts of nasty implications for this option. Don't
343 * allow it if we're running setuid. Disable the actual login anyway.
348 if (flags
& f_setuid
)
349 moan("shan't allow impersonation while running setuid");
352 if (isdigit((unsigned char)optarg
[0]))
353 pw
= getpwuid(atoi(optarg
));
355 pw
= getpwnam(optarg
);
357 die("can't impersonate unknown user `%s'", optarg
);
358 from_pw
= userdb_copyUser(pw
);
359 rq
.from
= from_pw
->pw_uid
;
365 /* --- Tracing support --- *
367 * Be careful not to zap a file I wouldn't normally be allowed to write
376 if (optarg
== 0 || strcmp(optarg
, "-") == 0)
379 if ((flags
& f_setuid
) && access(optarg
, W_OK
)) {
380 die("no write permission for trace file file `%s': %s",
381 optarg
, strerror(errno
));
383 if ((fp
= fopen(optarg
, "w")) == 0) {
384 die("couldn't open trace file `%s' for writing: %s",
385 optarg
, strerror(errno
));
388 traceon(fp
, TRACE_DFL
);
389 trace(TRACE_MISC
, "become: tracing enabled");
394 /* --- Setting trace levels --- */
400 unsigned int lvl
= 0, l
;
401 const char *p
= optarg
;
403 /* --- Table of tracing facilities --- */
411 static tr lvltbl
[] = {
412 { 'm', TRACE_MISC
, "miscellaneous messages" },
413 { 's', TRACE_SETUP
, "building the request block" },
414 { 'd', TRACE_DAEMON
, "server process" },
415 { 'r', TRACE_RULE
, "ruleset scanning" },
416 { 'c', TRACE_CHECK
, "request checking" },
417 { 'l', TRACE_CLIENT
, "client process" },
418 { 'R', TRACE_RAND
, "random number generator" },
419 { 'C', TRACE_CRYPTO
, "cryptographic processing of requests" },
420 { 'y', TRACE_YACC
, "parsing configuration file" },
421 { 'D', TRACE_DFL
, "default tracing options" },
422 { 'A', TRACE_ALL
, "all tracing options" },
427 /* --- Output some help if there's no arguemnt --- */
435 for (tp
= lvltbl
; tp
->l
; tp
++) {
436 if ((flags
& f_setuid
) == 0 || tp
->l
& ~TRACE_PRIV
)
437 printf("%c -- %s\n", tp
->ch
, tp
->help
);
441 "Also, `+' and `-' options are recognised to turn on and off vavrious\n"
442 "tracing options. For example, `A-r' enables everything except ruleset\n"
443 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
455 for (tp
= lvltbl
; tp
->l
&& *p
!= tp
->ch
; tp
++)
458 if (flags
& f_setuid
)
461 lvl
= sense ?
(lvl
| l
) : (lvl
& ~l
);
463 moan("unknown trace option `%c'", *p
);
469 yydebug
= ((lvl
& TRACE_YACC
) != 0);
474 /* --- Something I didn't understand has occurred --- */
481 if (flags
& f_duff
) {
486 /* --- Switch to daemon mode if requested --- */
488 if (flags
& f_daemon
) {
489 T( trace(TRACE_MISC
, "become: daemon mode requested"); )
490 daemon_init(conffile
, port
);
494 /* --- Open a syslog --- */
496 openlog(quis(), 0, LOG_AUTH
);
498 /* --- Pick out the uid --- */
504 if (optind
>= argc
) {
510 if (isdigit((unsigned char)u
[0]))
511 pw
= getpwuid(atoi(u
));
515 die("unknown user `%s'", u
);
516 to_pw
= userdb_copyUser(pw
);
520 /* --- Fill in the easy bits of the request --- */
526 pw
= getpwuid(rq
.from
);
528 die("who are you? (can't find user %li)", (long)rq
.from
);
529 from_pw
= userdb_copyUser(pw
);
532 /* --- Find the local host address --- */
538 if ((he
= gethostbyname(u
.nodename
)) == 0)
539 die("who am I? (can't resolve `%s')", u
.nodename
);
540 memcpy(&rq
.host
, he
->h_addr
, sizeof(struct in_addr
));
543 /* --- Shell commands are easy --- */
550 /* --- A command given on the command line isn't too hard --- */
552 else if (optind
< argc
) {
553 todo
= argv
+ optind
;
557 /* --- A login request needs a little bit of work --- */
559 else if (flags
& f_login
) {
560 const char *p
= strrchr(to_pw
->pw_shell
, '/');
565 shell
[0] = xmalloc(strlen(p
) + 2);
567 strcpy(shell
[0] + 1, p
);
570 binary
= to_pw
->pw_shell
;
573 /* --- An unadorned becoming requires little work --- */
576 shell
[0] = from_pw
->pw_shell
;
582 /* --- Mangle the environment if login flag given --- */
584 if (flags
& f_login
) {
586 env
[1] = bc__makeEnv("USER", to_pw
->pw_name
);
587 env
[2] = bc__makeEnv("LOGNAME", to_pw
->pw_name
);
588 env
[3] = bc__makeEnv("HOME", to_pw
->pw_dir
);
591 /* --- Trace the command --- */
593 IF_TRACING(TRACE_SETUP
, {
596 trace(TRACE_SETUP
, "setup: from user %s to user %s",
597 from_pw
->pw_name
, to_pw
->pw_name
);
598 trace(TRACE_SETUP
, "setup: binary == `%s'", binary
);
599 for (i
= 0; todo
[i
]; i
++)
600 trace(TRACE_SETUP
, "setup: arg %i == `%s'", i
, todo
[i
]);
601 for (i
= 0; env
[i
]; i
++)
602 trace(TRACE_SETUP
, "setup: env %i == `%s'", i
, env
[i
]);
605 /* --- If necessary, resolve the path to the command --- */
607 if (!strchr(binary
, '/')) {
611 if ((p
= getenv("PATH")) == 0)
615 for (p
= strtok(path
, ":"); (p
= strtok(0, ":")) != 0; ) {
617 /* --- SECURITY: check length of string before copying --- */
619 if (strlen(p
) + strlen(binary
) + 2 > sizeof(rq
.cmd
))
622 /* --- Now build the pathname and check it --- */
624 sprintf(rq
.cmd
, "%s/%s", p
, todo
[0]);
625 if (stat(rq
.cmd
, &st
) == 0 && /* Check it exists */
626 st
.st_mode
& 0111 && /* Check it's executable */
627 (st
.st_mode
& S_IFMT
) == S_IFREG
) /* Check it's a file */
632 die("couldn't find `%s' in path", todo
[0]);
636 T( trace(TRACE_SETUP
, "setup: resolve binary to `%s'", binary
); )
638 /* --- Canonicalise the path string, if necessary --- */
645 /* --- Insert current directory name if path not absolute --- */
650 if (!getcwd(b
, sizeof(b
)))
651 die("couldn't read current directory: %s", strerror(errno
));
656 /* --- Now copy over characters from the path string --- */
660 /* --- SECURITY: check for buffer overflows here --- *
662 * I write at most one byte per iteration so this is OK. Remember to
663 * allow one for the null byte.
666 if (p
>= b
+ sizeof(b
) - 1)
667 die("internal error: buffer overflow while canonifying path");
669 /* --- Reduce multiple slashes to just one --- */
677 /* --- Handle dots in filenames --- *
679 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
680 * we've just stuck the current directory on the end of the buffer,
681 * or we've just put something else on the end.
684 else if (*q
== '.' && p
[-1] == '/') {
686 /* --- A simple `./' just gets removed --- */
688 if (q
[1] == 0 || q
[1] == '/') {
694 /* --- A `../' needs to be peeled back to the previous `/' --- */
696 if (q
[1] == '.' && (q
[2] == 0 || q
[2] == '/')) {
699 while (p
> b
&& p
[-1] != '/')
712 T( trace(TRACE_SETUP
, "setup: canonify binary to `%s'", rq
.cmd
); )
714 /* --- Run the check --- *
716 * If the user is already what she wants to be, then print a warning.
717 * Then, if I was just going to spawn a shell, quit, to reduce user
718 * confusion. Otherwise, do what was wanted anyway.
721 if (rq
.from
== rq
.to
) {
722 moan("you already are `%s'!", to_pw
->pw_name
);
723 if (!cmd
&& todo
== shell
) {
724 moan("(to prevent confusion, I'm not spawning a shell)");
731 "permission %s for %s to become %s to run `%s'",
732 a ?
"granted" : "denied", from_pw
->pw_name
, to_pw
->pw_name
,
736 die("permission denied");
739 /* --- Now do the job --- */
741 T( trace(TRACE_MISC
, "become: permission granted"); )
743 if (flags
& f_dummy
) {
744 puts("permission granted");
747 if (setuid(rq
.to
) == -1)
748 die("couldn't set uid: %s", strerror(errno
));
749 execve(rq
.cmd
, todo
, env
);
750 die("couldn't exec `%s': %s", rq
.cmd
, strerror(errno
));
755 /*----- That's all, folks -------------------------------------------------*/