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