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