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