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