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