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