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