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