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