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