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