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