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