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