3 * $Id: become.c,v 1.2 1997/08/04 10:24:20 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.2 1997/08/04 10:24:20 mdw
33 * Sources placed under CVS control.
35 * Revision 1.1 1997/07/21 13:47:54 mdw
40 /*----- Header files ------------------------------------------------------*/
42 /* --- ANSI headers --- */
51 /* --- Unix headers --- */
53 #include <sys/types.h>
55 #include <sys/socket.h>
56 #include <sys/utsname.h>
58 #include <netinet/in.h>
60 #include <arpa/inet.h>
67 extern char **environ
;
69 /* --- Local headers --- */
83 /*----- Main code ---------------------------------------------------------*/
85 /* --- @bc__write@ --- *
87 * Arguments: @FILE *fp@ = pointer to a stream to write on
88 * @const char *p@ = pointer to a string
92 * Use: Writes the string to the stream, substituting the program
93 * name (as returned by @quis@) for each occurrence of the
97 static void bc__write(FILE *fp
, const char *p
)
99 const char *n
= quis();
101 size_t nl
= strlen(n
);
103 /* --- Try to be a little efficient --- *
105 * Gather up non-`$' characters using @strcpn@ and spew them out really
116 fwrite(n
, nl
, 1, fp
);
121 /* --- @bc__banner@ --- *
123 * Arguments: @FILE *fp@ = stream to write on
127 * Use: Writes a banner containing copyright information.
130 static void bc__banner(FILE *fp
)
132 bc__write(fp
, "$ version " VERSION
"\n");
135 /* --- @bc__usage@ --- *
137 * Arguments: @FILE *fp@ = stream to write on
141 * Use: Writes a terse reminder of command line syntax.
144 static void bc__usage(FILE *fp
)
148 " $ -c <shell-command> <user>\n"
149 " $ <user> [<command> [<arguments>]...]\n"
150 " $ -d [-p <port>] [-f <config-file>]\n");
153 /* --- @bc__makeEnv@ --- *
155 * Arguments: @const char *var@ = name of an environment variable
156 * @const char *value@ = value of the variable
158 * Returns: A pointer to an allocated block assigning the value to the
161 * Use: Constructs environment mappings.
164 static char *bc__makeEnv(const char *var
, const char *value
)
166 char *p
= xmalloc(strlen(var
) + strlen(value
) + 2);
167 sprintf(p
, "%s=%s", var
, value
);
171 /* --- @bc__help@ --- *
173 * Arguments: @FILE *fp@ = stream to write on
177 * Use: Displays a help message for this excellent piece of software.
180 static void bc__help(FILE *fp
)
187 "The `$' program allows you to run a process as another user.\n"
188 "If a command name is given, this is the process executed. If the `-c'\n"
189 "option is used, the process is assumed to be `/bin/sh'. If no command is\n"
190 "given, your default login shell is used.\n"
192 "Your user id, the user id you wish to become, the name of the process\n"
193 "you wish to run, and the identity of the current host are looked up to\n"
194 "ensure that you have permission to do this.\n"
196 "Note that logs are kept of all uses of this program.\n"
198 "Options available are:\n"
200 "-h, --help Display this help text\n"
201 "-v, --version Display the version number of this copy of $\n"
202 "-l, --login Really log in as the user\n"
203 "-c, --command=CMD Run the (Bourne) shell command CMD\n"
204 "-d, --daemon Start up a daemon, to accept requests from clients\n"
205 "-p, --port=PORT In daemon mode, listen on PORT\n"
206 "-f, --config-file=FILE In daemon mode, read config from FILE\n"
208 "--impersonate=USER Claim to be USER when asking the server\n"
209 "--trace=FILE Dump trace information to FILE (boring)\n"
210 "--trace-level=OPTS Set level of tracing information\n"
217 * Arguments: @int argc@ = number of command line arguments
218 * @char *argv[]@ = pointer to the various arguments
220 * Returns: Zero if successful.
222 * Use: Allows a user to change UID.
225 int main(int argc
, char *argv
[])
227 /* --- Request block setup parameters --- */
229 request rq
; /* Request buffer to build */
230 char *cmd
= 0; /* Shell command to execute */
231 char *binary
= "/bin/sh"; /* Default binary to execute */
232 char **env
= environ
; /* Default environment to pass */
233 char **todo
; /* Pointer to argument list */
234 struct passwd
*from_pw
= 0; /* User we are right now */
235 struct passwd
*to_pw
= 0; /* User we want to become */
237 /* --- Become server setup parameters --- */
239 char *conffile
= file_RULES
; /* Default config file for daemon */
240 int port
= -1; /* Default port for daemon */
242 /* --- Miscellanous shared variables --- */
244 unsigned flags
= 0; /* Various useful flags */
246 /* --- Default argument list executes a shell command --- */
248 static char *shell
[] = {
249 "/bin/sh", /* Bourne shell */
250 "-c", /* Read from command line */
251 0, /* Pointer to shell command */
255 /* --- Login request replaces the environment with this --- */
257 static char *mangled_env
[] = {
258 "PATH=/bin:/usr/bin", /* Default `clean' path*/
259 0, /* User's name (`USER') */
260 0, /* User's name (`LOGNAME') */
261 0, /* Home directory (`HOME') */
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 { "version", 0, 0, 'v' },
289 { "login", 0, 0, 'l' },
290 { "command", gFlag_argReq
, 0, 'c' },
291 { "daemon", 0, 0, 'd' },
292 { "port", gFlag_argReq
, 0, 'p' },
293 { "config-file", gFlag_argReq
, 0, 'f' },
295 { "impersonate", gFlag_argReq
, 0, 'I' },
296 { "trace", gFlag_argOpt
, 0, 'T' },
297 { "trace-level", gFlag_argOpt
, 0, 'L' },
302 i
= mdwopt(argc
, argv
, "+hvlc:p:df:", opts
, 0, 0, 0);
308 /* --- Standard help and version options --- */
319 /* --- Various simple options --- */
337 /* --- Pretend to be a different user --- *
339 * There are all sorts of nasty implications for this option. Don't
340 * allow it if we're running setuid. Disable the actual login anyway.
345 if (flags
& f_setuid
)
346 moan("shan't allow impersonation while running setuid");
349 if (isdigit((unsigned char)optarg
[0]))
350 pw
= getpwuid(atoi(optarg
));
352 pw
= getpwnam(optarg
);
354 die("can't impersonate unknown user `%s'", optarg
);
355 from_pw
= userdb_copyUser(pw
);
356 rq
.from
= from_pw
->pw_uid
;
362 /* --- Tracing support --- *
364 * Be careful not to zap a file I wouldn't normally be allowed to write
373 if (optarg
== 0 || strcmp(optarg
, "-") == 0)
376 if ((flags
& f_setuid
) && access(optarg
, W_OK
)) {
377 die("no write permission for trace file file `%s': %s",
378 optarg
, strerror(errno
));
380 if ((fp
= fopen(optarg
, "w")) == 0) {
381 die("couldn't open trace file `%s' for writing: %s",
382 optarg
, strerror(errno
));
385 traceon(fp
, TRACE_DFL
);
386 trace(TRACE_MISC
, "become: tracing enabled");
391 /* --- Setting trace levels --- */
397 unsigned int lvl
= 0, l
;
398 const char *p
= optarg
;
400 /* --- Table of tracing facilities --- */
408 static tr lvltbl
[] = {
409 { 'm', TRACE_MISC
, "miscellaneous messages" },
410 { 's', TRACE_SETUP
, "building the request block" },
411 { 'd', TRACE_DAEMON
, "server process" },
412 { 'r', TRACE_RULE
, "ruleset scanning" },
413 { 'c', TRACE_CHECK
, "request checking" },
414 { 'l', TRACE_CLIENT
, "client process" },
415 { 'R', TRACE_RAND
, "random number generator" },
416 { 'C', TRACE_CRYPTO
, "cryptographic processing of requests" },
417 { 'y', TRACE_YACC
, "parsing configuration file" },
418 { 'D', TRACE_DFL
, "default tracing options" },
419 { 'A', TRACE_ALL
, "all tracing options" },
424 /* --- Output some help if there's no arguemnt --- */
432 for (tp
= lvltbl
; tp
->l
; tp
++) {
433 if ((flags
& f_setuid
) == 0 || tp
->l
& ~TRACE_PRIV
)
434 printf("%c -- %s\n", tp
->ch
, tp
->help
);
438 "Also, `+' and `-' options are recognised to turn on and off vavrious\n"
439 "tracing options. For example, `A-r' enables everything except ruleset\n"
440 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
452 for (tp
= lvltbl
; tp
->l
&& *p
!= tp
->ch
; tp
++)
455 if (flags
& f_setuid
)
458 lvl
= sense ?
(lvl
| l
) : (lvl
& ~l
);
460 moan("unknown trace option `%c'", *p
);
466 yydebug
= ((lvl
& TRACE_YACC
) != 0);
471 /* --- Something I didn't understand has occurred --- */
478 if (flags
& f_duff
) {
483 /* --- Switch to daemon mode if requested --- */
485 if (flags
& f_daemon
) {
486 T( trace(TRACE_MISC
, "become: daemon mode requested"); )
487 daemon_init(conffile
, port
);
491 /* --- Open a syslog --- */
493 openlog(quis(), 0, LOG_AUTH
);
495 /* --- Pick out the uid --- */
501 if (optind
>= argc
) {
507 if (isdigit((unsigned char)u
[0]))
508 pw
= getpwuid(atoi(u
));
512 die("unknown user `%s'", u
);
513 to_pw
= userdb_copyUser(pw
);
517 /* --- Fill in the easy bits of the request --- */
523 pw
= getpwuid(rq
.from
);
525 die("who are you? (can't find user %li)", (long)rq
.from
);
526 from_pw
= userdb_copyUser(pw
);
529 /* --- Find the local host address --- */
535 if ((he
= gethostbyname(u
.nodename
)) == 0)
536 die("who am I? (can't resolve `%s')", u
.nodename
);
537 memcpy(&rq
.host
, he
->h_addr
, sizeof(struct in_addr
));
540 /* --- Shell commands are easy --- */
547 /* --- A command given on the command line isn't too hard --- */
549 else if (optind
< argc
) {
550 todo
= argv
+ optind
;
554 /* --- A login request needs a little bit of work --- */
556 else if (flags
& f_login
) {
557 const char *p
= strrchr(to_pw
->pw_shell
, '/');
562 shell
[0] = xmalloc(strlen(p
) + 2);
564 strcpy(shell
[0] + 1, p
);
567 binary
= to_pw
->pw_shell
;
570 /* --- An unadorned becoming requires little work --- */
573 shell
[0] = from_pw
->pw_shell
;
579 /* --- Mangle the environment if login flag given --- */
581 if (flags
& f_login
) {
583 env
[1] = bc__makeEnv("USER", to_pw
->pw_name
);
584 env
[2] = bc__makeEnv("LOGNAME", to_pw
->pw_name
);
585 env
[3] = bc__makeEnv("HOME", to_pw
->pw_dir
);
588 /* --- Trace the command --- */
590 IF_TRACING(TRACE_SETUP
, {
593 trace(TRACE_SETUP
, "setup: from user %s to user %s",
594 from_pw
->pw_name
, to_pw
->pw_name
);
595 trace(TRACE_SETUP
, "setup: binary == `%s'", binary
);
596 for (i
= 0; todo
[i
]; i
++)
597 trace(TRACE_SETUP
, "setup: arg %i == `%s'", i
, todo
[i
]);
598 for (i
= 0; env
[i
]; i
++)
599 trace(TRACE_SETUP
, "setup: env %i == `%s'", i
, env
[i
]);
602 /* --- If necessary, resolve the path to the command --- */
604 if (!strchr(binary
, '/')) {
608 if ((p
= getenv("PATH")) == 0)
612 for (p
= strtok(path
, ":"); (p
= strtok(0, ":")) != 0; ) {
614 /* --- SECURITY: check length of string before copying --- */
616 if (strlen(p
) + strlen(binary
) + 2 > sizeof(rq
.cmd
))
619 /* --- Now build the pathname and check it --- */
621 sprintf(rq
.cmd
, "%s/%s", p
, todo
[0]);
622 if (stat(rq
.cmd
, &st
) == 0 && /* Check it exists */
623 st
.st_mode
& 0111 && /* Check it's executable */
624 (st
.st_mode
& S_IFMT
) == S_IFREG
) /* Check it's a file */
629 die("couldn't find `%s' in path", todo
[0]);
633 T( trace(TRACE_SETUP
, "setup: resolve binary to `%s'", binary
); )
635 /* --- Canonicalise the path string, if necessary --- */
642 /* --- Insert current directory name if path not absolute --- */
647 if (!getcwd(b
, sizeof(b
)))
648 die("couldn't read current directory: %s", strerror(errno
));
653 /* --- Now copy over characters from the path string --- */
657 /* --- SECURITY: check for buffer overflows here --- *
659 * I write at most one byte per iteration so this is OK. Remember to
660 * allow one for the null byte.
663 if (p
>= b
+ sizeof(b
) - 1)
664 die("internal error: buffer overflow while canonifying path");
666 /* --- Reduce multiple slashes to just one --- */
674 /* --- Handle dots in filenames --- *
676 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
677 * we've just stuck the current directory on the end of the buffer,
678 * or we've just put something else on the end.
681 else if (*q
== '.' && p
[-1] == '/') {
683 /* --- A simple `./' just gets removed --- */
685 if (q
[1] == 0 || q
[1] == '/') {
691 /* --- A `../' needs to be peeled back to the previous `/' --- */
693 if (q
[1] == '.' && (q
[2] == 0 || q
[2] == '/')) {
696 while (p
> b
&& p
[-1] != '/')
709 T( trace(TRACE_SETUP
, "setup: canonify binary to `%s'", rq
.cmd
); )
711 /* --- Run the check --- */
717 "permission %s for %s to become %s to run `%s'",
718 a ?
"granted" : "denied", from_pw
->pw_name
, to_pw
->pw_name
,
722 die("permission denied");
725 /* --- Now do the job --- */
727 T( trace(TRACE_MISC
, "become: permission granted"); )
729 if (flags
& f_dummy
) {
730 puts("permission granted");
733 if (setuid(rq
.to
) == -1 || seteuid(rq
.to
) == -1)
734 die("couldn't set uid: %s", strerror(errno
));
735 execve(rq
.cmd
, todo
, env
);
736 die("couldn't exec `%s': %s", rq
.cmd
, strerror(errno
));
741 /*----- That's all, folks -------------------------------------------------*/