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