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