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