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