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