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