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