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