Change directory after becoming someone else, instead of before. This
[become] / src / become.c
CommitLineData
c4f2d992 1/* -*-c-*-
2 *
45df69ab 3 * $Id: become.c,v 1.12 1997/09/25 16:04:48 mdw Exp $
c4f2d992 4 *
5 * Main code for `become'
6 *
7 * (c) 1997 EBI
8 */
9
03f996bd 10/*----- Licensing notice --------------------------------------------------*
c4f2d992 11 *
12 * This file is part of `become'
13 *
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.
18 *
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.
23 *
24 * You should have received a copy of the GNU General Public License
03f996bd 25 * along with `become'; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
c4f2d992 27 */
28
29/*----- Revision history --------------------------------------------------*
30 *
31 * $Log: become.c,v $
45df69ab 32 * Revision 1.12 1997/09/25 16:04:48 mdw
33 * Change directory after becoming someone else, instead of before. This
34 * avoids problems with root-squashed NFS mounts.
35 *
36 * Revision 1.11 1997/09/24 09:48:45 mdw
505f2736 37 * Fix (scary) overrun bug in group allocation stuff.
38 *
39 * Revision 1.10 1997/09/17 10:14:10 mdw
9df0c75d 40 * Fix a typo. Support service names in `--port' option.
41 *
a5089273 42 * Revision 1.9 1997/09/10 10:28:05 mdw
43 * Allow default port to be given as a service name or port number. Handle
44 * groups properly (lots of options here).
45 *
46 * Revision 1.8 1997/09/08 13:56:24 mdw
6a4ec25c 47 * Change criteria for expunging items from the user's PATH: instead of
48 * removing things starting with `.', remove things not starting with `/'.
49 *
50 * Revision 1.7 1997/09/08 13:43:20 mdw
c6885d5f 51 * Change userid when creating tracefiles rather than fiddling with
52 * `access': it works rather better. Also, insert some stdio buffer
53 * flushing to ensure tracedumps are completely written.
54 *
55 * Revision 1.6 1997/09/05 13:47:44 mdw
d6683a48 56 * Make the `-L' (trace-level) option's argument optional, like the long
57 * version is.
58 *
59 * Revision 1.5 1997/09/05 11:45:19 mdw
387501ae 60 * Add support for different login styles, and environment variable
61 * manipulation in a safe and useful way.
62 *
63 * Revision 1.4 1997/08/20 16:15:13 mdw
569b8891 64 * Overhaul of environment handling. Fix daft bug in path search code.
65 *
88e486d5 66 * Revision 1.3 1997/08/07 16:28:59 mdw
67 * Do something useful when users attempt to become themselves.
68 *
03f996bd 69 * Revision 1.2 1997/08/04 10:24:20 mdw
70 * Sources placed under CVS control.
71 *
72 * Revision 1.1 1997/07/21 13:47:54 mdw
c4f2d992 73 * Initial revision
74 *
75 */
76
77/*----- Header files ------------------------------------------------------*/
78
79/* --- ANSI headers --- */
80
81#include <ctype.h>
82#include <errno.h>
a5089273 83#include <limits.h>
c4f2d992 84#include <stdio.h>
85#include <stdlib.h>
86#include <string.h>
03f996bd 87#include <time.h>
c4f2d992 88
89/* --- Unix headers --- */
90
91#include <sys/types.h>
92#include <sys/stat.h>
93#include <sys/socket.h>
94#include <sys/utsname.h>
95
96#include <netinet/in.h>
97
98#include <arpa/inet.h>
99
100#include <netdb.h>
a5089273 101#include <grp.h>
c4f2d992 102#include <pwd.h>
103#include <syslog.h>
104#include <unistd.h>
105
03f996bd 106extern char **environ;
107
c4f2d992 108/* --- Local headers --- */
109
110#include "become.h"
111#include "config.h"
112#include "check.h"
113#include "daemon.h"
114#include "lexer.h"
115#include "mdwopt.h"
116#include "name.h"
117#include "parser.h"
118#include "rule.h"
387501ae 119#include "sym.h"
c4f2d992 120#include "utils.h"
03f996bd 121#include "userdb.h"
c4f2d992 122
387501ae 123/*----- Type definitions --------------------------------------------------*/
124
125/* --- Symbol table entry for an environment variable --- */
126
127typedef struct sym_env {
128 sym_base _base; /* Symbol table information */
129 unsigned f; /* Flags word (see below) */
130 char *val; /* Pointer to variable value */
131} sym_env;
132
133/* --- Environment variable flags --- */
134
135enum {
136 envFlag_preserve = 1
137};
138
139/* --- Login behaviour types --- */
140
141enum {
142 l_preserve, /* Preserve the environment */
a5089273 143 l_setuser, /* Update who I am */
387501ae 144 l_login /* Do a full login */
145};
146
a5089273 147/* --- Group behaviour types --- *
148 *
149 * Note that these make a handy bitfield.
150 */
151
152#ifdef HAVE_SETGROUPS
153
154enum {
155 g_unset = 0, /* Nobody's set a preference */
156 g_keep = 1, /* Leave the group memberships */
157 g_replace = 2, /* Replace group memberships */
158 g_merge = (g_keep | g_replace) /* Merge the group memberships */
159};
160
161#endif
162
387501ae 163/*----- Static variables --------------------------------------------------*/
164
165static sym_table bc__env;
166
c4f2d992 167/*----- Main code ---------------------------------------------------------*/
168
169/* --- @bc__write@ --- *
170 *
171 * Arguments: @FILE *fp@ = pointer to a stream to write on
172 * @const char *p@ = pointer to a string
173 *
174 * Returns: ---
175 *
176 * Use: Writes the string to the stream, substituting the program
177 * name (as returned by @quis@) for each occurrence of the
178 * character `$'.
179 */
180
181static void bc__write(FILE *fp, const char *p)
182{
183 const char *n = quis();
184 size_t l;
185 size_t nl = strlen(n);
186
187 /* --- Try to be a little efficient --- *
188 *
569b8891 189 * Gather up non-`$' characters using @strcspn@ and spew them out really
c4f2d992 190 * quickly.
191 */
192
193 for (;;) {
194 l = strcspn(p, "$");
195 if (l)
196 fwrite(p, l, 1, fp);
197 p += l;
198 if (!*p)
199 break;
200 fwrite(n, nl, 1, fp);
201 p++;
202 }
203}
204
387501ae 205/* --- @bc__setenv@ --- *
206 *
207 * Arguments: @sym_env *e@ = pointer to environment variable block
208 * @const char *val@ = value to set
209 *
210 * Returns: ---
211 *
212 * Use: Sets an environment variable block to the right value.
213 */
214
215static void bc__setenv(sym_env *e, const char *val)
216{
217 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
218 sprintf(e->val, "%s=%s", e->_base.name, val);
219}
220
221/* --- @bc__putenv@ --- *
222 *
223 * Arguments: @const char *var@ = name of the variable to set, or 0 if
224 * this is embedded in the value string
225 * @const char *val@ = value to set, or 0 if the variable must
226 * be removed
227 * @unsigned int fl@ = flags to set
228 * @unsigned int force@ = force overwrite of preserved variables
229 *
230 * Returns: Pointer to symbol block, or zero if it was deleted.
231 *
232 * Use: Puts an item into the environment.
233 */
234
235static sym_env *bc__putenv(const char *var, const char *val,
236 unsigned int fl, unsigned int force)
237{
238 unsigned int f;
239 sym_env *e;
240 char *q = 0;
241 size_t sz;
242
243 /* --- Sort out embedded variable names --- */
244
245 if (!var) {
246 const char *p = strchr(val, '=');
247
248 if (p) {
249 sz = p - val;
250 q = xmalloc(sz + 1);
251 memcpy(q, val, sz);
252 q[sz] = 0;
253 var = q;
254 val = p + 1;
255 } else {
256 var = val;
257 val = 0;
258 }
259 }
260
261 /* --- Find the variable block --- */
262
263 if (val) {
264 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
a5089273 265 if (!f || ~e->f & envFlag_preserve || force) {
387501ae 266 if (f)
267 free(e->val);
268 bc__setenv(e, val);
269 }
270 } else {
271 e = sym_find(&bc__env, var, -1, 0, 0);
272 if (e && (force || ~e->f & envFlag_preserve))
273 sym_remove(&bc__env, e);
274 e = 0;
275 }
276
277 /* --- Tidy up and return --- */
278
279 if (q)
280 free(q);
281 if (e)
282 e->f = fl;
283 return (e);
284}
285
a5089273 286/* --- @bc__addGroups@ --- *
287 *
288 * Arguments: @gid_t *g@ = pointer to a group array
289 * @int *png@ = pointer to number of entries in the array
290 * @const gid_t *a@ = pointer to groups to add
291 * @int na@ = number of groups to add
292 *
293 * Returns: Zero if it was OK, nonzero if we should stop now.
294 *
295 * Use: Adds groups to a groups array.
296 */
297
298static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
299{
300 int i, j;
301 int ng = *png;
302
303 for (i = 0; i < na; i++) {
304
305 /* --- Ensure this group isn't already in the list --- */
306
307 for (j = 0; j < ng; j++) {
308 if (a[i] == g[j])
309 goto next_group;
310 }
311
312 /* --- See if there's room for more --- */
313
505f2736 314 if (ng >= NGROUPS_MAX) {
315 moan("too many groups (system limit exceeded) -- some have been lost");
a5089273 316 *png = ng;
317 return (-1);
318 }
319
320 /* --- Add the group --- */
321
322 g[ng++] = a[i];
323 next_group:;
324 }
325
326 *png = ng;
327 return (0);
328}
329
c4f2d992 330/* --- @bc__banner@ --- *
331 *
332 * Arguments: @FILE *fp@ = stream to write on
333 *
334 * Returns: ---
335 *
336 * Use: Writes a banner containing copyright information.
337 */
338
339static void bc__banner(FILE *fp)
340{
341 bc__write(fp, "$ version " VERSION "\n");
342}
343
344/* --- @bc__usage@ --- *
345 *
346 * Arguments: @FILE *fp@ = stream to write on
347 *
348 * Returns: ---
349 *
350 * Use: Writes a terse reminder of command line syntax.
351 */
352
353static void bc__usage(FILE *fp)
354{
355 bc__write(fp,
356 "Usage: \n"
357 " $ -c <shell-command> <user>\n"
387501ae 358 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
c4f2d992 359 " $ -d [-p <port>] [-f <config-file>]\n");
360}
361
362/* --- @bc__help@ --- *
363 *
364 * Arguments: @FILE *fp@ = stream to write on
569b8891 365 * @int suid@ = whether we're running set-uid
c4f2d992 366 *
367 * Returns: ---
368 *
369 * Use: Displays a help message for this excellent piece of software.
370 */
371
569b8891 372static void bc__help(FILE *fp, int suid)
c4f2d992 373{
374 bc__banner(fp);
375 putc('\n', fp);
376 bc__usage(fp);
377 putc('\n', fp);
378 bc__write(fp,
379"The `$' program allows you to run a process as another user.\n"
380"If a command name is given, this is the process executed. If the `-c'\n"
381"option is used, the process is assumed to be `/bin/sh'. If no command is\n"
382"given, your default login shell is used.\n"
383"\n"
384"Your user id, the user id you wish to become, the name of the process\n"
385"you wish to run, and the identity of the current host are looked up to\n"
386"ensure that you have permission to do this.\n"
387"\n"
388"Note that logs are kept of all uses of this program.\n"
389"\n"
390"Options available are:\n"
391"\n"
387501ae 392"-h, --help Display this help text\n"
393"-u, --usage Display a short usage summary\n"
394"-v, --version Display $'s version number\n"
395"\n"
396"-e, --preserve-environment Try to preserve the current environment\n"
397"-s, --su, --set-user Set environment variables to reflect USER\n"
398"-l, --login Really log in as USER\n"
399"\n"
a5089273 400"-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
401#ifdef HAVE_SETGROUPS
402"-k, --keep-groups Keep your current set of groups\n"
403"-m, --merge-groups Merge the lists of groups\n"
404"-r, --replace-groups Replace the list of groups\n"
405#endif
406"\n"
387501ae 407"-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
408"\n"
409"-d, --daemon Start a daemon\n"
410"-p PORT, --port=PORT In daemon mode, listen on PORT\n"
411"-f FILE, --config-file=FILE In daemon mode, read config from FILE\n");
03f996bd 412#ifdef TRACING
387501ae 413 bc__write(fp, "\n");
569b8891 414 if (!suid) {
415 bc__write(fp,
387501ae 416"-I USER, --impersonate=USER Claim to be USER when asking the server\n");
569b8891 417 }
418 bc__write(fp,
387501ae 419"-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
420"-L OPTS, --trace-level=OPTS Set level of tracing information\n");
03f996bd 421#endif
c4f2d992 422}
423
424/* --- @main@ --- *
425 *
426 * Arguments: @int argc@ = number of command line arguments
427 * @char *argv[]@ = pointer to the various arguments
428 *
429 * Returns: Zero if successful.
430 *
431 * Use: Allows a user to change UID.
432 */
433
434int main(int argc, char *argv[])
435{
03f996bd 436 /* --- Request block setup parameters --- */
c4f2d992 437
03f996bd 438 request rq; /* Request buffer to build */
439 char *cmd = 0; /* Shell command to execute */
440 char *binary = "/bin/sh"; /* Default binary to execute */
441 char **env = environ; /* Default environment to pass */
387501ae 442 char **todo = 0; /* Pointer to argument list */
443 char *who = 0; /* Who we're meant to become */
03f996bd 444 struct passwd *from_pw = 0; /* User we are right now */
445 struct passwd *to_pw = 0; /* User we want to become */
446
447 /* --- Become server setup parameters --- */
448
449 char *conffile = file_RULES; /* Default config file for daemon */
9df0c75d 450 int port = 0; /* Default port for daemon */
03f996bd 451
452 /* --- Miscellanous shared variables --- */
453
454 unsigned flags = 0; /* Various useful flags */
387501ae 455 int style = DEFAULT_LOGIN_STYLE; /* Login style */
a5089273 456 gid_t group = -1; /* Default group to set */
457 int gstyle = g_unset; /* No group style set yet */
458
459#ifdef HAVE_SETGROUPS
460 gid_t groups[NGROUPS_MAX]; /* Set of groups */
461 int ngroups; /* Number of groups in the set */
462#endif
03f996bd 463
464 /* --- Default argument list executes a shell command --- */
465
466 static char *shell[] = {
467 "/bin/sh", /* Bourne shell */
468 "-c", /* Read from command line */
469 0, /* Pointer to shell command */
470 0 /* Terminator */
c4f2d992 471 };
472
03f996bd 473 /* --- Definitions for the various flags --- */
474
475 enum {
476 f_daemon = 1, /* Start up in daemon mode */
477 f_duff = 2, /* Fault in arguments */
45df69ab 478 f_shell = 4, /* Run a default shell */
03f996bd 479 f_dummy = 8, /* Don't actually do anything */
a5089273 480 f_setuid = 16, /* We're running setuid */
481 f_havegroup = 32 /* Set a default group */
03f996bd 482 };
c4f2d992 483
484 /* --- Set up the program name --- */
485
486 ego(argv[0]);
03f996bd 487 clock();
488 if (getuid() != geteuid())
489 flags |= f_setuid;
c4f2d992 490
387501ae 491 /* --- Read the environment into a hashtable --- */
492
493 {
494 char **p;
495
496 sym_createTable(&bc__env);
497 for (p = environ; *p; p++)
498 bc__putenv(0, *p, 0, 0);
499 }
500
c4f2d992 501 /* --- Parse some command line arguments --- */
502
503 for (;;) {
504 int i;
03f996bd 505 static struct option opts[] = {
387501ae 506
507 /* --- Asking for help --- */
508
c4f2d992 509 { "help", 0, 0, 'h' },
387501ae 510 { "usage", 0, 0, 'u' },
c4f2d992 511 { "version", 0, 0, 'v' },
387501ae 512
513 /* --- Login style options --- */
514
515 { "preserve-environment", 0, 0, 'e' },
516 { "su", 0, 0, 's' },
517 { "set-user", 0, 0, 's' },
03f996bd 518 { "login", 0, 0, 'l' },
387501ae 519
a5089273 520 /* --- Group style options --- */
521
522 { "group", gFlag_argReq, 0, 'g' },
523#ifdef HAVE_SETGROUPS
524 { "keep-groups", 0, 0, 'k' },
525 { "merge-groups", 0, 0, 'm' },
526 { "replace-groups", 0, 0, 'r' },
527#endif
528
387501ae 529 /* --- Command to run options --- */
530
c4f2d992 531 { "command", gFlag_argReq, 0, 'c' },
387501ae 532
533 /* --- Server options --- */
534
c4f2d992 535 { "daemon", 0, 0, 'd' },
536 { "port", gFlag_argReq, 0, 'p' },
537 { "config-file", gFlag_argReq, 0, 'f' },
387501ae 538
539 /* --- Tracing options --- */
540
03f996bd 541#ifdef TRACING
542 { "impersonate", gFlag_argReq, 0, 'I' },
543 { "trace", gFlag_argOpt, 0, 'T' },
544 { "trace-level", gFlag_argOpt, 0, 'L' },
545#endif
387501ae 546
c4f2d992 547 { 0, 0, 0, 0 }
548 };
549
387501ae 550 i = mdwopt(argc, argv,
a5089273 551 "-" /* Return non-options as options */
552 "huv" /* Asking for help */
553 "esl" /* Login style options */
554#ifdef HAVE_SETGROUPS
555 "g:kmr" /* Group style options */
556#else
557 "g:" /* Group (without @setgroups@) */
558#endif
559 "c:" /* Command to run options */
560 "dp:f:" /* Server options */
561#ifdef TRACING
562 "I:T::L::" /* Tracing options */
563#endif
564 ,
387501ae 565 opts, 0, 0, gFlag_envVar);
c4f2d992 566 if (i < 0)
387501ae 567 goto done_options;
c4f2d992 568
569 switch (i) {
03f996bd 570
387501ae 571 /* --- Asking for help --- */
03f996bd 572
c4f2d992 573 case 'h':
569b8891 574 bc__help(stdout, flags & f_setuid);
575 exit(0);
576 break;
387501ae 577 case 'u':
569b8891 578 bc__usage(stdout);
c4f2d992 579 exit(0);
580 break;
581 case 'v':
582 bc__banner(stdout);
583 exit(0);
584 break;
03f996bd 585
387501ae 586 /* --- Login style options --- */
03f996bd 587
387501ae 588 case 'e':
589 style = l_preserve;
590 break;
591 case 's':
a5089273 592 style = l_setuser;
387501ae 593 break;
03f996bd 594 case 'l':
387501ae 595 style = l_login;
03f996bd 596 break;
387501ae 597
a5089273 598 /* --- Group style options --- */
599
600 case 'g':
601 if (isdigit((unsigned char)optarg[0]))
602 group = atoi(optarg);
603 else {
604 struct group *gr = getgrnam(optarg);
605 if (!gr)
606 die("unknown group `%s'", optarg);
607 group = gr->gr_gid;
608 }
609 flags |= f_havegroup;
610 break;
611
612 case 'k':
613 gstyle = g_keep;
614 break;
615 case 'm':
616 gstyle = g_merge;
617 break;
618 case 'r':
619 gstyle = g_replace;
620 break;
621
387501ae 622 /* --- Command to run options --- */
623
c4f2d992 624 case 'c':
625 cmd = optarg;
626 break;
387501ae 627
628 /* --- Server options --- */
629
c4f2d992 630 case 'p':
a5089273 631 if (isdigit((unsigned char)optarg[0]))
632 port = htons(atoi(optarg));
633 else {
634 struct servent *s = getservbyname(optarg, "udp");
635 if (!s)
636 die("unknown service name `%s'", optarg);
637 port = s->s_port;
638 }
c4f2d992 639 break;
640 case 'd':
641 flags |= f_daemon;
642 break;
643 case 'f':
644 conffile = optarg;
645 break;
03f996bd 646
647 /* --- Pretend to be a different user --- *
648 *
649 * There are all sorts of nasty implications for this option. Don't
650 * allow it if we're running setuid. Disable the actual login anyway.
651 */
652
653#ifdef TRACING
569b8891 654
03f996bd 655 case 'I':
656 if (flags & f_setuid)
657 moan("shan't allow impersonation while running setuid");
658 else {
659 struct passwd *pw;
660 if (isdigit((unsigned char)optarg[0]))
661 pw = getpwuid(atoi(optarg));
662 else
663 pw = getpwnam(optarg);
664 if (!pw)
665 die("can't impersonate unknown user `%s'", optarg);
666 from_pw = userdb_copyUser(pw);
667 rq.from = from_pw->pw_uid;
668 flags |= f_dummy;
669 }
c4f2d992 670 break;
569b8891 671
03f996bd 672#endif
673
674 /* --- Tracing support --- *
675 *
676 * Be careful not to zap a file I wouldn't normally be allowed to write
677 * to!
678 */
679
680#ifdef TRACING
681
682 case 'T': {
683 FILE *fp;
684
685 if (optarg == 0 || strcmp(optarg, "-") == 0)
686 fp = stdout;
687 else {
c6885d5f 688 uid_t eu = geteuid(), ru = getuid();
689
690#ifdef HAVE_SETREUID
691 if (setreuid(eu, ru))
692#else
693 if (seteuid(ru))
694#endif
695 {
696 die("couldn't temporarily give up privileges: %s",
697 strerror(errno));
03f996bd 698 }
c6885d5f 699
03f996bd 700 if ((fp = fopen(optarg, "w")) == 0) {
701 die("couldn't open trace file `%s' for writing: %s",
702 optarg, strerror(errno));
703 }
c6885d5f 704
705#ifdef HAVE_SETREUID
706 if (setreuid(ru, eu))
707#else
708 if (seteuid(eu))
709#endif
710 die("couldn't regain privileges: %s", strerror(errno));
03f996bd 711 }
712 traceon(fp, TRACE_DFL);
713 trace(TRACE_MISC, "become: tracing enabled");
714 } break;
715
716#endif
717
718 /* --- Setting trace levels --- */
719
720#ifdef TRACING
721
722 case 'L': {
723 int sense = 1;
724 unsigned int lvl = 0, l;
725 const char *p = optarg;
726
727 /* --- Table of tracing facilities --- */
728
729 typedef struct tr {
730 char ch;
731 unsigned int l;
732 const char *help;
733 } tr;
734
735 static tr lvltbl[] = {
736 { 'm', TRACE_MISC, "miscellaneous messages" },
737 { 's', TRACE_SETUP, "building the request block" },
738 { 'd', TRACE_DAEMON, "server process" },
739 { 'r', TRACE_RULE, "ruleset scanning" },
740 { 'c', TRACE_CHECK, "request checking" },
741 { 'l', TRACE_CLIENT, "client process" },
742 { 'R', TRACE_RAND, "random number generator" },
743 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
744 { 'y', TRACE_YACC, "parsing configuration file" },
745 { 'D', TRACE_DFL, "default tracing options" },
746 { 'A', TRACE_ALL, "all tracing options" },
747 { 0, 0, 0 }
748 };
749 tr *tp;
750
751 /* --- Output some help if there's no arguemnt --- */
752
753 if (!optarg) {
754 bc__banner(stdout);
755 bc__write(stdout,
756 "\n"
757 "Tracing options:\n"
758 "\n");
759 for (tp = lvltbl; tp->l; tp++) {
760 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
761 printf("%c -- %s\n", tp->ch, tp->help);
762 }
763 bc__write(stdout,
764"\n"
9df0c75d 765"Also, `+' and `-' options are recognised to turn on and off various\n"
03f996bd 766"tracing options. For example, `A-r' enables everything except ruleset\n"
767"tracing, and `A-D+c' is everything except the defaults, but with request\n"
768"check tracing.\n"
769);
770 exit(0);
771 }
772
773 while (*p) {
774 if (*p == '+')
775 sense = 1;
776 else if (*p == '-')
777 sense = 0;
778 else {
779 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
780 ;
781 l = tp->l;
782 if (flags & f_setuid)
783 l &= ~TRACE_PRIV;
784 if (l)
785 lvl = sense ? (lvl | l) : (lvl & ~l);
786 else
787 moan("unknown trace option `%c'", *p);
788 }
789 p++;
790 }
791
792 tracesetlvl(lvl);
793 yydebug = ((lvl & TRACE_YACC) != 0);
794 } break;
795
796#endif
797
387501ae 798 /* --- Something that wasn't an option --- *
799 *
800 * The following nasties are supported:
801 *
802 * * NAME=VALUE -- preserve NAME, and give it a VALUE
803 * * NAME= -- preserve NAME, and give it an empty value
804 * * NAME- -- delete NAME
805 * * NAME! -- preserve NAME with existing value
806 *
807 * Anything else is either the user name (which is OK) or the start of
808 * the command (in which case I stop and read the rest of the command).
809 */
810
811 case 0: {
812 size_t sz = strcspn(optarg, "=-!");
813 sym_env *e;
814
815 /* --- None of the above --- */
816
817 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
9df0c75d 818 if (!who) {
387501ae 819 who = optarg;
9df0c75d 820 break;
821 } else {
387501ae 822 optind--;
823 goto done_options;
824 }
825 }
826
827 /* --- Do the appropriate thing --- */
828
829 switch (optarg[sz]) {
830 case '=':
831 bc__putenv(0, optarg, envFlag_preserve, 1);
832 break;
833 case '-':
834 optarg[sz] = 0;
835 bc__putenv(optarg, 0, 0, 1);
836 break;
837 case '!':
838 optarg[sz] = 0;
839 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
840 e->f |= envFlag_preserve;
841 break;
842 }
843 } break;
844
03f996bd 845 /* --- Something I didn't understand has occurred --- */
846
c4f2d992 847 case '?':
848 flags |= f_duff;
849 break;
850 }
851 }
387501ae 852
853done_options:
c4f2d992 854 if (flags & f_duff) {
855 bc__usage(stderr);
856 exit(1);
857 }
858
859 /* --- Switch to daemon mode if requested --- */
860
861 if (flags & f_daemon) {
03f996bd 862 T( trace(TRACE_MISC, "become: daemon mode requested"); )
c4f2d992 863 daemon_init(conffile, port);
864 exit(0);
865 }
866
867 /* --- Open a syslog --- */
868
869 openlog(quis(), 0, LOG_AUTH);
870
871 /* --- Pick out the uid --- */
872
873 {
c4f2d992 874 struct passwd *pw;
03f996bd 875
387501ae 876 if (!who) {
c4f2d992 877 bc__usage(stderr);
878 exit(1);
879 }
03f996bd 880
387501ae 881 if (isdigit((unsigned char)who[0]))
882 pw = getpwuid(atoi(who));
03f996bd 883 else
387501ae 884 pw = getpwnam(who);
c4f2d992 885 if (!pw)
387501ae 886 die("unknown user `%s'", who);
03f996bd 887 to_pw = userdb_copyUser(pw);
c4f2d992 888 rq.to = pw->pw_uid;
889 }
890
891 /* --- Fill in the easy bits of the request --- */
892
03f996bd 893 if (!from_pw) {
894 struct passwd *pw;
895
896 rq.from = getuid();
897 pw = getpwuid(rq.from);
898 if (!pw)
899 die("who are you? (can't find user %li)", (long)rq.from);
900 from_pw = userdb_copyUser(pw);
901 }
c4f2d992 902
903 /* --- Find the local host address --- */
904
905 {
906 struct utsname u;
907 struct hostent *he;
908 uname(&u);
909 if ((he = gethostbyname(u.nodename)) == 0)
910 die("who am I? (can't resolve `%s')", u.nodename);
911 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
912 }
913
a5089273 914 /* --- Fiddle with group ownerships a bit --- */
915
916 {
917#ifdef HAVE_SETGROUPS
918 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
919 int n_fgr, n_tgr;
920#endif
921
922 /* --- Set the default login group, if there is one --- */
923
924 if (~flags & f_havegroup)
925 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
926
927#ifndef HAVE_SETGROUPS
928
929 /* --- Check that it's valid --- */
930
931 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
932 die("invalid default group");
933
934#else
935
936 /* --- Set the default group style --- */
937
938 if (gstyle == g_unset)
939 gstyle = (style == l_login) ? g_replace : g_merge;
940
941 /* --- Read in my current set of groups --- */
942
943 n_fgr = getgroups(NGROUPS_MAX, from_gr);
944
945 /* --- Now read the groups for the target user --- *
946 *
947 * Do this even if I'm using the @g_keep@ style -- I'll use it for
948 * checking that @group@ is valid.
949 */
950
951 {
952 struct group *gr;
953 char **p;
954
955 n_tgr = 0;
956 to_gr[n_tgr++] = to_pw->pw_gid;
957
958 setgrent();
959 while ((gr = getgrent()) != 0) {
960 if (gr->gr_gid == to_gr[0])
961 continue;
962 for (p = gr->gr_mem; *p; p++) {
963 if (strcmp(to_pw->pw_name, *p) == 0) {
964 to_gr[n_tgr++] = gr->gr_gid;
965 if (n_tgr >= NGROUPS_MAX)
966 goto done_groups;
967 break;
968 }
969 }
970 }
971
972 done_groups:
973 endgrent();
974 }
975
976 /* --- Check that @group@ is reasonable --- */
977
978 {
979 int i;
980
981 if (group == getgid() || group == from_pw->pw_gid)
982 goto group_ok;
983 for (i = 0; i < n_fgr; i++) {
984 if (group == from_gr[i])
985 goto group_ok;
986 }
987 for (i = 0; i < n_tgr; i++) {
988 if (group == to_gr[i])
989 goto group_ok;
990 }
991 die("invalid default group");
992 group_ok:;
993 }
994
995 /* --- Phew. Now comes the hard bit --- */
996
997 {
998 gid_t ga[4];
999 int i;
1000
1001 i = 0;
1002 ga[i++] = group;
1003 if (gstyle & g_keep) {
1004 ga[i++] = getgid();
1005 ga[i++] = from_pw->pw_gid;
1006 }
1007 if (gstyle & g_replace)
1008 ga[i++] = to_pw->pw_gid;
1009
1010 /* --- Style authorities will become apoplectic if shown this --- *
1011 *
1012 * As far as I can see, it's the neatest way of writing it.
1013 */
1014
1015 ngroups = 0;
1016 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1017 ((gstyle & g_keep) &&
1018 bc__addGroups(groups, &ngroups, from_gr,n_fgr)) ||
1019 ((gstyle & g_replace) &&
1020 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1021 }
1022
1023#endif
1024
1025 /* --- Trace the results of all this --- */
1026
1027 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1028
1029#ifdef HAVE_SETGROUPS
1030 IF_TRACING(TRACE_SETUP, {
1031 int i;
1032
1033 for (i = 1; i < ngroups; i++)
1034 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1035 })
1036#endif
1037 }
1038
03f996bd 1039 /* --- Shell commands are easy --- */
c4f2d992 1040
1041 if (cmd) {
1042 shell[2] = cmd;
1043 todo = shell;
03f996bd 1044 }
1045
1046 /* --- A command given on the command line isn't too hard --- */
1047
1048 else if (optind < argc) {
c4f2d992 1049 todo = argv + optind;
03f996bd 1050 binary = todo[0];
1051 }
1052
45df69ab 1053 else {
1054 flags |= f_shell;
03f996bd 1055
45df69ab 1056 switch (style) {
c4f2d992 1057
45df69ab 1058 /* --- An unadorned becoming requires little work --- */
03f996bd 1059
45df69ab 1060 case l_preserve:
1061 shell[0] = getenv("SHELL");
1062 if (!shell[0])
1063 shell[0] = from_pw->pw_shell;
1064 shell[1] = 0;
1065 todo = shell;
1066 binary = todo[0];
1067 break;
387501ae 1068
45df69ab 1069 /* --- An su-like login needs slightly less effort --- */
1070
1071 case l_setuser:
1072 shell[0] = to_pw->pw_shell;
1073 shell[1] = 0;
1074 todo = shell;
1075 binary = todo[0];
1076 break;
1077
1078 /* --- A login request needs a little bit of work --- */
387501ae 1079
45df69ab 1080 case l_login: {
1081 const char *p = strrchr(to_pw->pw_shell, '/');
1082
1083 if (p)
1084 p++;
1085 else
1086 p = to_pw->pw_shell;
1087 shell[0] = xmalloc(strlen(p) + 2);
1088 shell[0][0] = '-';
1089 strcpy(shell[0] + 1, p);
1090 shell[1] = 0;
1091 todo = shell;
1092 binary = to_pw->pw_shell;
1093 } break;
1094 }
03f996bd 1095 }
1096
569b8891 1097 /* --- Mangle the environment --- *
1098 *
387501ae 1099 * This keeps getting more complicated all the time. (How true. Now I've
1100 * got all sorts of nasty environment mangling to do.)
1101 *
1102 * The environment stuff now happens in seven phases:
1103 *
1104 * 1. Mark very special variables to be preserved. Currently only TERM
1105 * and DISPLAY are treated in this way.
1106 *
1107 * 2. Set and preserve Become's own environment variables.
1108 *
1109 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1110 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1111 *
1112 * 4. If we're preserving the environment or being `su'-like, process the
1113 * PATH variable a little. Otherwise reset it to something
1114 * appropriate.
1115 *
1116 * 5. If we're being `login'-like, expunge all unpreserved variables.
1117 *
1118 * 6. Expunge any security-critical variables.
1119 *
1120 * 7. Build a new environment table to pass to child processes.
569b8891 1121 */
1122
1123 {
387501ae 1124 /* --- Variables to be preserved always --- *
1125 *
1126 * A user can explicitly expunge a variable in this list, in which case
1127 * we never get to see it here.
1128 */
569b8891 1129
387501ae 1130 static char *preserve[] = {
1131 "TERM", "DISPLAY", 0
1132 };
c6885d5f 1133
387501ae 1134 /* --- Variables to be expunged --- *
569b8891 1135 *
c6885d5f 1136 * Any environment string which has one of the following as a prefix will
1137 * be expunged from the environment passed to the called process. The
1138 * first line lists variables which have been used to list search paths
1139 * for shared libraries: by manipulating these, an attacker could replace
1140 * a standard library with one of his own. The second line lists other
1141 * well-known dangerous environment variables.
569b8891 1142 */
1143
387501ae 1144 static char *banned[] = {
1145 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1146 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
569b8891 1147 0
1148 };
1149
387501ae 1150 /* --- Other useful variables --- */
03f996bd 1151
387501ae 1152 sym_env *e;
1153 char *p, *q, *r;
1154 char **pp, **qq;
1155 size_t sz;
1156 unsigned f;
1157 sym_iter i;
c6885d5f 1158
387501ae 1159 /* --- Stage one. Preserve display-specific variables --- */
569b8891 1160
387501ae 1161 for (pp = preserve; *pp; pp++) {
1162 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1163 e->f |= envFlag_preserve;
1164 }
569b8891 1165
387501ae 1166 /* --- Stage two. Set Become's own variables --- */
569b8891 1167
387501ae 1168 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1169 if (!f)
1170 bc__setenv(e, from_pw->pw_name);
1171 e->f |= envFlag_preserve;
1172
1173 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1174 if (!f)
1175 bc__setenv(e, from_pw->pw_dir);
1176 e->f |= envFlag_preserve;
569b8891 1177
387501ae 1178 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1179 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1180 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1181 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
569b8891 1182
387501ae 1183 /* --- Stage three. Set user identity --- */
1184
1185 switch (style) {
1186 case l_login: {
1187 static char *maildirs[] = {
1188 "/var/spool/mail", "/var/mail",
1189 "/usr/spool/mail", "/usr/mail",
1190 0
1191 };
1192 struct stat s;
1193 char b[128];
1194
1195 for (pp = maildirs; *pp; pp++) {
1196 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1197 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1198 bc__putenv("MAIL", b, envFlag_preserve, 0);
1199 break;
569b8891 1200 }
569b8891 1201 }
387501ae 1202 } /* Fall through */
1203
a5089273 1204 case l_setuser:
387501ae 1205 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1206 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1207 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1208 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1209 break;
1210 }
1211
1212 /* --- Stage four. Set the user's PATH properly --- */
569b8891 1213
387501ae 1214 {
1215 /* --- Find an existing path --- *
569b8891 1216 *
387501ae 1217 * If there's no path, or this is a login, then set a default path,
1218 * unless we're meant to preserve the existing one. Whew!
569b8891 1219 */
1220
387501ae 1221 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1222
1223 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1224 bc__putenv("PATH",
1225 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1226 envFlag_preserve, 0);
1227 } else {
1228
1229 /* --- Find the string --- */
1230
1231 e->f = envFlag_preserve;
1232 p = strchr(e->val, '=') + 1;
1233 r = e->val;
1234
1235 /* --- Write the new version to a dynamically allocated buffer --- */
1236
1237 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1238 strcpy(e->val, "PATH=");
1239 q = e->val + 5;
1240
1241 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
6a4ec25c 1242 if (p[0] != '/')
387501ae 1243 continue;
1244 while (*p)
1245 *q++ = *p++;
1246 *q++ = ':';
1247 }
1248 q[-1] = 0;
1249
1250 /* --- Done! --- */
1251
1252 free(r);
569b8891 1253 }
387501ae 1254 }
1255
1256 /* --- Stages five and six. Expunge variables and count numbers --- *
1257 *
1258 * Folded together, so I only need one pass through the table. Also
1259 * count the number of variables needed at this time.
1260 */
1261
1262 sz = 0;
569b8891 1263
387501ae 1264 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
569b8891 1265
387501ae 1266 /* --- Login style expunges all unpreserved variables --- */
569b8891 1267
387501ae 1268 if (style == l_login && ~e->f & envFlag_preserve)
1269 goto expunge;
1270
1271 /* --- Otherwise just check the name against the list --- */
1272
1273 for (pp = banned; *pp; pp++) {
1274 if (**pp == '-') {
1275 p = *pp + 1;
1276 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1277 goto expunge;
a5089273 1278 } else if (strcmp(e->_base.name, *pp) == 0)
387501ae 1279 goto expunge;
1280 }
1281
1282 sz++;
1283 continue;
1284
1285 expunge:
1286 sym_remove(&bc__env, e);
569b8891 1287 }
387501ae 1288
1289 /* --- Stage seven. Build the new environment block --- */
1290
1291 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1292
1293 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1294 *qq++ = e->val;
1295 *qq++ = 0;
03f996bd 1296 }
1297
1298 /* --- Trace the command --- */
1299
1300 IF_TRACING(TRACE_SETUP, {
1301 int i;
1302
1303 trace(TRACE_SETUP, "setup: from user %s to user %s",
1304 from_pw->pw_name, to_pw->pw_name);
1305 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1306 for (i = 0; todo[i]; i++)
1307 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1308 for (i = 0; env[i]; i++)
1309 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1310 })
1311
c4f2d992 1312 /* --- If necessary, resolve the path to the command --- */
1313
03f996bd 1314 if (!strchr(binary, '/')) {
1315 char *path, *p;
c4f2d992 1316 struct stat st;
c4f2d992 1317
1318 if ((p = getenv("PATH")) == 0)
1319 p = "/bin:/usr/bin";
03f996bd 1320 path = xstrdup(p);
c4f2d992 1321
569b8891 1322 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
c4f2d992 1323
569b8891 1324 /* --- Check length of string before copying --- */
c4f2d992 1325
03f996bd 1326 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
c4f2d992 1327 continue;
1328
1329 /* --- Now build the pathname and check it --- */
1330
03f996bd 1331 sprintf(rq.cmd, "%s/%s", p, todo[0]);
1332 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
c4f2d992 1333 st.st_mode & 0111 && /* Check it's executable */
c6885d5f 1334 S_ISREG(st.st_mode)) /* Check it's a file */
c4f2d992 1335 break;
1336 }
1337
1338 if (!p)
1339 die("couldn't find `%s' in path", todo[0]);
03f996bd 1340 binary = rq.cmd;
c4f2d992 1341 free(path);
1342 }
03f996bd 1343 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
c4f2d992 1344
1345 /* --- Canonicalise the path string, if necessary --- */
1346
1347 {
1348 char b[CMDLEN_MAX];
1349 char *p;
1350 const char *q;
1351
1352 /* --- Insert current directory name if path not absolute --- */
1353
1354 p = b;
03f996bd 1355 q = binary;
c4f2d992 1356 if (*q != '/') {
1357 if (!getcwd(b, sizeof(b)))
1358 die("couldn't read current directory: %s", strerror(errno));
1359 p += strlen(p);
1360 *p++ = '/';
1361 }
1362
1363 /* --- Now copy over characters from the path string --- */
1364
1365 while (*q) {
1366
569b8891 1367 /* --- Check for buffer overflows here --- *
c4f2d992 1368 *
1369 * I write at most one byte per iteration so this is OK. Remember to
1370 * allow one for the null byte.
1371 */
1372
1373 if (p >= b + sizeof(b) - 1)
03f996bd 1374 die("internal error: buffer overflow while canonifying path");
c4f2d992 1375
1376 /* --- Reduce multiple slashes to just one --- */
1377
1378 if (*q == '/') {
1379 while (*q == '/')
1380 q++;
1381 *p++ = '/';
1382 }
1383
1384 /* --- Handle dots in filenames --- *
1385 *
1386 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1387 * we've just stuck the current directory on the end of the buffer,
1388 * or we've just put something else on the end.
1389 */
1390
1391 else if (*q == '.' && p[-1] == '/') {
1392
1393 /* --- A simple `./' just gets removed --- */
1394
1395 if (q[1] == 0 || q[1] == '/') {
1396 q++;
1397 p--;
1398 continue;
1399 }
1400
1401 /* --- A `../' needs to be peeled back to the previous `/' --- */
1402
1403 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1404 q += 2;
1405 p--;
1406 while (p > b && p[-1] != '/')
1407 p--;
1408 if (p > b)
1409 p--;
1410 continue;
1411 }
1412 } else
1413 *p++ = *q++;
1414 }
1415
1416 *p++ = 0;
1417 strcpy(rq.cmd, b);
1418 }
03f996bd 1419 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
c4f2d992 1420
88e486d5 1421 /* --- Run the check --- *
1422 *
1423 * If the user is already what she wants to be, then print a warning.
1424 * Then, if I was just going to spawn a shell, quit, to reduce user
a5089273 1425 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1426 * checking if we're already root -- root can do anything anyway, and at
1427 * least this way we get some logging done, and offer a more friendly
1428 * front-end.
88e486d5 1429 */
c4f2d992 1430
88e486d5 1431 if (rq.from == rq.to) {
1432 moan("you already are `%s'!", to_pw->pw_name);
45df69ab 1433 if (flags & f_shell) {
88e486d5 1434 moan("(to prevent confusion, I'm not spawning a shell)");
1435 exit(0);
1436 }
1437 } else {
a5089273 1438 int a = (rq.from == 0) || check(&rq);
c4f2d992 1439
1440 syslog(LOG_INFO,
1441 "permission %s for %s to become %s to run `%s'",
03f996bd 1442 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1443 rq.cmd);
c4f2d992 1444
1445 if (!a)
1446 die("permission denied");
1447 }
1448
1449 /* --- Now do the job --- */
1450
03f996bd 1451 T( trace(TRACE_MISC, "become: permission granted"); )
1452
1453 if (flags & f_dummy) {
1454 puts("permission granted");
1455 return (0);
03f996bd 1456 }
a5089273 1457
1458#ifdef HAVE_SETGROUPS
1459 if (setgroups(ngroups, groups) < 0)
1460 die("couldn't set groups: %s", strerror(errno));
1461#endif
1462
1463 if (setgid(group) < 0)
1464 die("couldn't set default group: %s", strerror(errno));
1465 if (setuid(rq.to) < 0)
1466 die("couldn't set uid: %s", strerror(errno));
1467
45df69ab 1468 /* --- If this was a login, change current directory --- */
1469
1470 if (flags & f_shell && style == l_login && chdir(to_pw->pw_dir) < 0) {
1471 moan("couldn't change directory to `%s': %s",
1472 to_pw->pw_dir, strerror(errno));
1473 }
1474
a5089273 1475 /* --- Finally, call the program --- */
1476
1477 fflush(0);
1478 execve(rq.cmd, todo, env);
1479 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1480 return (127);
c4f2d992 1481}
1482
1483/*----- That's all, folks -------------------------------------------------*/