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