chiark / gitweb /
5af2c4fc4128b4608405247488e02b36e9f94645
[become] / src / become.c
1 /* -*-c-*-
2  *
3  * $Id: become.c,v 1.6 1997/09/05 13:47:44 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.6  1997/09/05 13:47:44  mdw
33  * Make the `-L' (trace-level) option's argument optional, like the long
34  * version is.
35  *
36  * Revision 1.5  1997/09/05  11:45:19  mdw
37  * Add support for different login styles, and environment variable
38  * manipulation in a safe and useful way.
39  *
40  * Revision 1.4  1997/08/20  16:15:13  mdw
41  * Overhaul of environment handling.  Fix daft bug in path search code.
42  *
43  * Revision 1.3  1997/08/07 16:28:59  mdw
44  * Do something useful when users attempt to become themselves.
45  *
46  * Revision 1.2  1997/08/04 10:24:20  mdw
47  * Sources placed under CVS control.
48  *
49  * Revision 1.1  1997/07/21  13:47:54  mdw
50  * Initial revision
51  *
52  */
53
54 /*----- Header files ------------------------------------------------------*/
55
56 /* --- ANSI headers --- */
57
58 #include <ctype.h>
59 #include <errno.h>
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <time.h>
64
65 /* --- Unix headers --- */
66
67 #include <sys/types.h>
68 #include <sys/stat.h>
69 #include <sys/socket.h>
70 #include <sys/utsname.h>
71
72 #include <netinet/in.h>
73
74 #include <arpa/inet.h>
75
76 #include <netdb.h>
77 #include <pwd.h>
78 #include <syslog.h>
79 #include <unistd.h>
80
81 extern char **environ;
82
83 /* --- Local headers --- */
84
85 #include "become.h"
86 #include "config.h"
87 #include "check.h"
88 #include "daemon.h"
89 #include "lexer.h"
90 #include "mdwopt.h"
91 #include "name.h"
92 #include "parser.h"
93 #include "rule.h"
94 #include "sym.h"
95 #include "utils.h"
96 #include "userdb.h"
97
98 /*----- Type definitions --------------------------------------------------*/
99
100 /* --- Symbol table entry for an environment variable --- */
101
102 typedef struct sym_env {
103   sym_base _base;                       /* Symbol table information */
104   unsigned f;                           /* Flags word (see below) */
105   char *val;                            /* Pointer to variable value */
106 } sym_env;
107
108 /* --- Environment variable flags --- */
109
110 enum {
111   envFlag_preserve = 1
112 };
113
114 /* --- Login behaviour types --- */
115
116 enum {
117   l_preserve,                           /* Preserve the environment */
118   l_user,                               /* Update who I am */
119   l_login                               /* Do a full login */
120 };
121
122 /*----- Static variables --------------------------------------------------*/
123
124 static sym_table bc__env;
125
126 /*----- Main code ---------------------------------------------------------*/
127
128 /* --- @bc__write@ --- *
129  *
130  * Arguments:   @FILE *fp@ = pointer to a stream to write on
131  *              @const char *p@ = pointer to a string
132  *
133  * Returns:     ---
134  *
135  * Use:         Writes the string to the stream, substituting the program
136  *              name (as returned by @quis@) for each occurrence of the
137  *              character `$'.
138  */
139
140 static void bc__write(FILE *fp, const char *p)
141 {
142   const char *n = quis();
143   size_t l;
144   size_t nl = strlen(n);
145
146   /* --- Try to be a little efficient --- *
147    *
148    * Gather up non-`$' characters using @strcspn@ and spew them out really
149    * quickly.
150    */
151
152   for (;;) {
153     l = strcspn(p, "$");
154     if (l)
155       fwrite(p, l, 1, fp);
156     p += l;
157     if (!*p)
158       break;
159     fwrite(n, nl, 1, fp);
160     p++;
161   }
162 }
163
164 /* --- @bc__setenv@ --- *
165  *
166  * Arguments:   @sym_env *e@ = pointer to environment variable block
167  *              @const char *val@ = value to set
168  *
169  * Returns:     ---
170  *
171  * Use:         Sets an environment variable block to the right value.
172  */
173
174 static void bc__setenv(sym_env *e, const char *val)
175 {
176   e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
177   sprintf(e->val, "%s=%s", e->_base.name, val);
178 }
179
180 /* --- @bc__putenv@ --- *
181  *
182  * Arguments:   @const char *var@ = name of the variable to set, or 0 if
183  *                      this is embedded in the value string
184  *              @const char *val@ = value to set, or 0 if the variable must
185  *                      be removed
186  *              @unsigned int fl@ = flags to set
187  *              @unsigned int force@ = force overwrite of preserved variables
188  *
189  * Returns:     Pointer to symbol block, or zero if it was deleted.
190  *
191  * Use:         Puts an item into the environment.
192  */
193
194 static sym_env *bc__putenv(const char *var, const char *val,
195                            unsigned int fl, unsigned int force)
196 {
197   unsigned int f;
198   sym_env *e;
199   char *q = 0;
200   size_t sz;
201
202   /* --- Sort out embedded variable names --- */
203
204   if (!var) {
205     const char *p = strchr(val, '=');
206
207     if (p) {
208       sz = p - val;
209       q = xmalloc(sz + 1);
210       memcpy(q, val, sz);
211       q[sz] = 0;
212       var = q;
213       val = p + 1;
214     } else {
215       var = val;
216       val = 0;
217     }
218   }
219
220   /* --- Find the variable block --- */
221
222   if (val) {
223     e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
224     if (~e->f & envFlag_preserve || force) {
225       if (f)
226         free(e->val);
227       bc__setenv(e, val);
228     }
229   } else {
230     e = sym_find(&bc__env, var, -1, 0, 0);
231     if (e && (force || ~e->f & envFlag_preserve))
232       sym_remove(&bc__env, e);
233     e = 0;
234   }
235
236   /* --- Tidy up and return --- */
237
238   if (q)
239     free(q);
240   if (e)
241     e->f = fl;
242   return (e);
243 }
244
245 /* --- @bc__banner@ --- *
246  *
247  * Arguments:   @FILE *fp@ = stream to write on
248  *
249  * Returns:     ---
250  *
251  * Use:         Writes a banner containing copyright information.
252  */
253
254 static void bc__banner(FILE *fp)
255 {
256   bc__write(fp, "$ version " VERSION "\n");
257 }
258
259 /* --- @bc__usage@ --- *
260  *
261  * Arguments:   @FILE *fp@ = stream to write on
262  *
263  * Returns:     ---
264  *
265  * Use:         Writes a terse reminder of command line syntax.
266  */
267
268 static void bc__usage(FILE *fp)
269 {
270   bc__write(fp,
271             "Usage: \n"
272             "   $ -c <shell-command> <user>\n"
273             "   $ [<env-var>] <user> [<command> [<arguments>]...]\n"
274             "   $ -d [-p <port>] [-f <config-file>]\n");
275 }
276
277 /* --- @bc__help@ --- *
278  *
279  * Arguments:   @FILE *fp@ = stream to write on
280  *              @int suid@ = whether we're running set-uid
281  *
282  * Returns:     ---
283  *
284  * Use:         Displays a help message for this excellent piece of software.
285  */
286
287 static void bc__help(FILE *fp, int suid)
288 {
289   bc__banner(fp);
290   putc('\n', fp);
291   bc__usage(fp);
292   putc('\n', fp);
293   bc__write(fp,
294 "The `$' program allows you to run a process as another user.\n"
295 "If a command name is given, this is the process executed.  If the `-c'\n"
296 "option is used, the process is assumed to be `/bin/sh'.  If no command is\n"
297 "given, your default login shell is used.\n"
298 "\n"
299 "Your user id, the user id you wish to become, the name of the process\n"
300 "you wish to run, and the identity of the current host are looked up to\n"
301 "ensure that you have permission to do this.\n"
302 "\n"
303 "Note that logs are kept of all uses of this program.\n"
304 "\n"
305 "Options available are:\n"
306 "\n"
307 "-h, --help                     Display this help text\n"
308 "-u, --usage                    Display a short usage summary\n"
309 "-v, --version                  Display $'s version number\n"
310 "\n"
311 "-e, --preserve-environment     Try to preserve the current environment\n"
312 "-s, --su, --set-user           Set environment variables to reflect USER\n"
313 "-l, --login                    Really log in as USER\n"
314 "\n"
315 "-c CMD, --command=CMD          Run the (Bourne) shell command CMD\n"
316 "\n"
317 "-d, --daemon                   Start a daemon\n"
318 "-p PORT, --port=PORT           In daemon mode, listen on PORT\n"
319 "-f FILE, --config-file=FILE    In daemon mode, read config from FILE\n");
320 #ifdef TRACING
321   bc__write(fp, "\n");
322   if (!suid) {
323     bc__write(fp,
324 "-I USER, --impersonate=USER    Claim to be USER when asking the server\n");
325   }
326   bc__write(fp,
327 "-T FILE, --trace=FILE          Dump trace information to FILE (boring)\n"
328 "-L OPTS, --trace-level=OPTS    Set level of tracing information\n");
329 #endif
330 }
331
332 /* --- @main@ --- *
333  *
334  * Arguments:   @int argc@ = number of command line arguments
335  *              @char *argv[]@ = pointer to the various arguments
336  *
337  * Returns:     Zero if successful.
338  *
339  * Use:         Allows a user to change UID.
340  */
341
342 int main(int argc, char *argv[])
343 {
344   /* --- Request block setup parameters --- */
345
346   request rq;                           /* Request buffer to build */
347   char *cmd = 0;                        /* Shell command to execute */
348   char *binary = "/bin/sh";             /* Default binary to execute */
349   char **env = environ;                 /* Default environment to pass */
350   char **todo = 0;                      /* Pointer to argument list */
351   char *who = 0;                        /* Who we're meant to become */
352   struct passwd *from_pw = 0;           /* User we are right now */
353   struct passwd *to_pw = 0;             /* User we want to become */
354
355   /* --- Become server setup parameters --- */
356
357   char *conffile = file_RULES;          /* Default config file for daemon */
358   int port = -1;                        /* Default port for daemon */
359
360   /* --- Miscellanous shared variables --- */
361
362   unsigned flags = 0;                   /* Various useful flags */
363   int style = DEFAULT_LOGIN_STYLE;      /* Login style */
364
365   /* --- Default argument list executes a shell command --- */
366
367   static char *shell[] = {
368     "/bin/sh",                          /* Bourne shell */
369     "-c",                               /* Read from command line */
370     0,                                  /* Pointer to shell command */
371     0                                   /* Terminator */
372   };
373
374   /* --- Definitions for the various flags --- */
375
376   enum {
377     f_daemon = 1,                       /* Start up in daemon mode */
378     f_duff = 2,                         /* Fault in arguments */
379     f_login = 4,                        /* Execute as a login shell */
380     f_dummy = 8,                        /* Don't actually do anything */
381     f_setuid = 16                       /* We're running setuid */
382   };
383
384   /* --- Set up the program name --- */
385
386   ego(argv[0]);
387   clock();
388   if (getuid() != geteuid())
389     flags |= f_setuid;
390
391   /* --- Read the environment into a hashtable --- */
392
393   {
394     char **p;
395
396     sym_createTable(&bc__env);
397     for (p = environ; *p; p++)
398       bc__putenv(0, *p, 0, 0);
399   }
400
401   /* --- Parse some command line arguments --- */
402
403   for (;;) {
404     int i;
405     static struct option opts[] = {
406
407       /* --- Asking for help --- */
408
409       { "help",         0,              0,      'h' },
410       { "usage",        0,              0,      'u' },
411       { "version",      0,              0,      'v' },
412
413       /* --- Login style options --- */
414
415       { "preserve-environment", 0,      0,      'e' },
416       { "su",           0,              0,      's' },
417       { "set-user",     0,              0,      's' },
418       { "login",        0,              0,      'l' },
419
420       /* --- Command to run options --- */
421
422       { "command",      gFlag_argReq,   0,      'c' },
423
424       /* --- Server options --- */
425
426       { "daemon",       0,              0,      'd' },
427       { "port",         gFlag_argReq,   0,      'p' },
428       { "config-file",  gFlag_argReq,   0,      'f' },
429
430       /* --- Tracing options --- */
431
432 #ifdef TRACING
433       { "impersonate",  gFlag_argReq,   0,      'I' },
434       { "trace",        gFlag_argOpt,   0,      'T' },
435       { "trace-level",  gFlag_argOpt,   0,      'L' },
436 #endif
437
438       { 0,              0,              0,      0 }
439     };
440
441     i = mdwopt(argc, argv,
442                "-" "huv" "esl" "c:" "dp:f:" T("I:T::L::"),
443                opts, 0, 0, gFlag_envVar);
444     if (i < 0)
445       goto done_options;
446
447     switch (i) {
448
449       /* --- Asking for help --- */
450
451       case 'h':
452         bc__help(stdout, flags & f_setuid);
453         exit(0);
454         break;
455       case 'u':
456         bc__usage(stdout);
457         exit(0);
458         break;
459       case 'v':
460         bc__banner(stdout);
461         exit(0);
462         break;
463
464       /* --- Login style options --- */
465
466       case 'e':
467         style = l_preserve;
468         break;
469       case 's':
470         style = l_user;
471         break;
472       case 'l':
473         style = l_login;
474         break;
475
476       /* --- Command to run options --- */
477
478       case 'c':
479         cmd = optarg;
480         break;
481
482       /* --- Server options --- */
483
484       case 'p':
485         port = atoi(optarg);
486         break;
487       case 'd':
488         flags |= f_daemon;
489         break;
490       case 'f':
491         conffile = optarg;
492         break;
493
494       /* --- Pretend to be a different user --- *
495        *
496        * There are all sorts of nasty implications for this option.  Don't
497        * allow it if we're running setuid.  Disable the actual login anyway.
498        */
499
500 #ifdef TRACING
501
502       case 'I':
503         if (flags & f_setuid)
504           moan("shan't allow impersonation while running setuid");
505         else {
506           struct passwd *pw;
507           if (isdigit((unsigned char)optarg[0]))
508             pw = getpwuid(atoi(optarg));
509           else
510             pw = getpwnam(optarg);
511           if (!pw)
512             die("can't impersonate unknown user `%s'", optarg);
513           from_pw = userdb_copyUser(pw);
514           rq.from = from_pw->pw_uid;
515           flags |= f_dummy;
516         }
517         break;
518
519 #endif
520
521       /* --- Tracing support --- *
522        *
523        * Be careful not to zap a file I wouldn't normally be allowed to write
524        * to!
525        */
526
527 #ifdef TRACING
528
529       case 'T': {
530         FILE *fp;
531
532         if (optarg == 0 || strcmp(optarg, "-") == 0)
533           fp = stdout;
534         else {
535           if ((flags & f_setuid) && access(optarg, W_OK)) {
536             die("no write permission for trace file file `%s': %s",
537                 optarg, strerror(errno));
538           }
539           if ((fp = fopen(optarg, "w")) == 0) {
540             die("couldn't open trace file `%s' for writing: %s",
541                 optarg, strerror(errno));
542           }
543         }
544         traceon(fp, TRACE_DFL);
545         trace(TRACE_MISC, "become: tracing enabled");
546       } break;
547
548 #endif
549
550       /* --- Setting trace levels --- */
551
552 #ifdef TRACING
553
554       case 'L': {
555         int sense = 1;
556         unsigned int lvl = 0, l;
557         const char *p = optarg;
558
559         /* --- Table of tracing facilities --- */
560
561         typedef struct tr {
562           char ch;
563           unsigned int l;
564           const char *help;
565         } tr;
566
567         static tr lvltbl[] = {
568           { 'm', TRACE_MISC,    "miscellaneous messages" },
569           { 's', TRACE_SETUP,   "building the request block" },
570           { 'd', TRACE_DAEMON,  "server process" },
571           { 'r', TRACE_RULE,    "ruleset scanning" },
572           { 'c', TRACE_CHECK,   "request checking" },
573           { 'l', TRACE_CLIENT,  "client process" },
574           { 'R', TRACE_RAND,    "random number generator" },
575           { 'C', TRACE_CRYPTO,  "cryptographic processing of requests" },
576           { 'y', TRACE_YACC,    "parsing configuration file" },
577           { 'D', TRACE_DFL,     "default tracing options" },
578           { 'A', TRACE_ALL,     "all tracing options" },
579           { 0,   0,             0 }
580         };
581         tr *tp;
582
583         /* --- Output some help if there's no arguemnt --- */
584
585         if (!optarg) {
586           bc__banner(stdout);
587           bc__write(stdout,
588                     "\n"
589                     "Tracing options:\n"
590                     "\n");
591           for (tp = lvltbl; tp->l; tp++) {
592             if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
593               printf("%c -- %s\n", tp->ch, tp->help);
594           }
595           bc__write(stdout,
596 "\n"
597 "Also, `+' and `-' options are recognised to turn on and off vavrious\n"
598 "tracing options.  For example, `A-r' enables everything except ruleset\n"
599 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
600 "check tracing.\n"
601 );
602           exit(0);
603         }
604
605         while (*p) {
606           if (*p == '+')
607             sense = 1;
608           else if (*p == '-')
609             sense = 0;
610           else {
611             for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
612               ;
613             l = tp->l;
614             if (flags & f_setuid)
615               l &= ~TRACE_PRIV;
616             if (l)
617               lvl = sense ? (lvl | l) : (lvl & ~l);
618             else
619               moan("unknown trace option `%c'", *p);
620           }
621           p++;
622         }
623
624         tracesetlvl(lvl);
625         yydebug = ((lvl & TRACE_YACC) != 0);
626       } break;
627
628 #endif
629
630       /* --- Something that wasn't an option --- *
631        *
632        * The following nasties are supported:
633        *
634        *   * NAME=VALUE         -- preserve NAME, and give it a VALUE
635        *   * NAME=              -- preserve NAME, and give it an empty value
636        *   * NAME-              -- delete NAME
637        *   * NAME!              -- preserve NAME with existing value
638        *
639        * Anything else is either the user name (which is OK) or the start of
640        * the command (in which case I stop and read the rest of the command).
641        */
642
643       case 0: {
644         size_t sz = strcspn(optarg, "=-!");
645         sym_env *e;
646
647         /* --- None of the above --- */
648
649         if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
650           if (who == 0)
651             who = optarg;
652           else {
653             optind--;
654             goto done_options;
655           }
656         }
657
658         /* --- Do the appropriate thing --- */
659
660         switch (optarg[sz]) {
661           case '=':
662             bc__putenv(0, optarg, envFlag_preserve, 1);
663             break;
664           case '-':
665             optarg[sz] = 0;
666             bc__putenv(optarg, 0, 0, 1);
667             break;
668           case '!':
669             optarg[sz] = 0;
670             if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
671               e->f |= envFlag_preserve;
672             break;
673         }
674       } break;
675
676       /* --- Something I didn't understand has occurred --- */
677
678       case '?':
679         flags |= f_duff;
680         break;
681     }
682   }
683
684 done_options:
685   if (flags & f_duff) {
686     bc__usage(stderr);
687     exit(1);
688   }
689
690   /* --- Switch to daemon mode if requested --- */
691
692   if (flags & f_daemon) {
693     T( trace(TRACE_MISC, "become: daemon mode requested"); )
694     daemon_init(conffile, port);
695     exit(0);
696   }
697
698   /* --- Open a syslog --- */
699
700   openlog(quis(), 0, LOG_AUTH);
701
702   /* --- Pick out the uid --- */
703
704   {
705     struct passwd *pw;
706
707     if (!who) {
708       bc__usage(stderr);
709       exit(1);
710     }
711
712     if (isdigit((unsigned char)who[0]))
713       pw = getpwuid(atoi(who));
714     else
715       pw = getpwnam(who);
716     if (!pw)
717       die("unknown user `%s'", who);
718     to_pw = userdb_copyUser(pw);
719     rq.to = pw->pw_uid;
720   }
721
722   /* --- Fill in the easy bits of the request --- */
723
724   if (!from_pw) {
725     struct passwd *pw;
726
727     rq.from = getuid();
728     pw = getpwuid(rq.from);
729     if (!pw)
730       die("who are you? (can't find user %li)", (long)rq.from);
731     from_pw = userdb_copyUser(pw);
732   }
733
734   /* --- Find the local host address --- */
735
736   {
737     struct utsname u;
738     struct hostent *he;
739     uname(&u);
740     if ((he = gethostbyname(u.nodename)) == 0)
741       die("who am I? (can't resolve `%s')", u.nodename);
742     memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
743   }
744
745   /* --- Shell commands are easy --- */
746
747   if (cmd) {
748     shell[2] = cmd;
749     todo = shell;
750   }
751
752   /* --- A command given on the command line isn't too hard --- */
753
754   else if (optind < argc) {
755     todo = argv + optind;
756     binary = todo[0];
757   }
758
759   else switch (style) {
760
761     /* --- An unadorned becoming requires little work --- */
762
763     case l_preserve:
764       shell[0] = getenv("SHELL");
765       if (!shell[0])
766         shell[0] = from_pw->pw_shell;
767       shell[1] = 0;
768       todo = shell;
769       binary = todo[0];
770       break;
771
772     /* --- An su-like login needs slightly less effort --- */
773
774     case l_user:
775       shell[0] = to_pw->pw_shell;
776       shell[1] = 0;
777       todo = shell;
778       binary = todo[0];
779       break;
780
781     /* --- A login request needs a little bit of work --- */
782
783     case l_login: {
784       const char *p = strrchr(to_pw->pw_shell, '/');
785
786       if (p)
787         p++;
788       else
789         p = to_pw->pw_shell;
790       shell[0] = xmalloc(strlen(p) + 2);
791       shell[0][0] = '-';
792       strcpy(shell[0] + 1, p);
793       shell[1] = 0;
794       todo = shell;
795       binary = to_pw->pw_shell;
796       chdir(to_pw->pw_dir);
797     } break;
798   }
799
800   /* --- Mangle the environment --- *
801    *
802    * This keeps getting more complicated all the time.  (How true.  Now I've
803    * got all sorts of nasty environment mangling to do.)
804    *
805    * The environment stuff now happens in seven phases:
806    *
807    *   1. Mark very special variables to be preserved.  Currently only TERM
808    *      and DISPLAY are treated in this way.
809    *
810    *   2. Set and preserve Become's own environment variables.
811    *
812    *   3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
813    *      SHELL and MAIL) if we're being `su'-like or `login'-like.
814    *
815    *   4. If we're preserving the environment or being `su'-like, process the
816    *      PATH variable a little.  Otherwise reset it to something
817    *      appropriate.
818    *
819    *   5. If we're being `login'-like, expunge all unpreserved variables.
820    *
821    *   6. Expunge any security-critical variables.
822    *
823    *   7. Build a new environment table to pass to child processes.
824    */
825
826   {
827     /* --- Variables to be preserved always --- *
828      *
829      * A user can explicitly expunge a variable in this list, in which case
830      * we never get to see it here.
831      */
832
833     static char *preserve[] = {
834       "TERM", "DISPLAY", 0
835     };
836     
837     /* --- Variables to be expunged --- *
838      *
839      * Any environment string which has one of the following as a prefix
840      * will be expunged from the environment passed to the called process.
841      * The first line lists variables which have been used to list search
842      * paths for shared libraries: by manipulating these, an attacker could
843      * replace a standard library with one of his own.  The second line lists
844      * other well-known dangerous environment variables.
845      */
846
847     static char *banned[] = {
848       "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
849       "IFS", "ENV", "BASH_ENV", "KRB_CONF",
850       0
851     };
852
853     /* --- Other useful variables --- */
854
855     sym_env *e;
856     char *p, *q, *r;
857     char **pp, **qq;
858     size_t sz;
859     unsigned f;
860     sym_iter i;
861       
862     /* --- Stage one.  Preserve display-specific variables --- */
863
864     for (pp = preserve; *pp; pp++) {
865       if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
866         e->f |= envFlag_preserve;
867     }
868
869     /* --- Stage two.  Set Become's own variables --- */
870
871     e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
872     if (!f)
873       bc__setenv(e, from_pw->pw_name);
874     e->f |= envFlag_preserve;
875
876     e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
877     if (!f)
878       bc__setenv(e, from_pw->pw_dir);
879     e->f |= envFlag_preserve;
880
881     bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
882     bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
883     bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
884     bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
885
886     /* --- Stage three.  Set user identity --- */
887
888     switch (style) {
889       case l_login: {
890         static char *maildirs[] = {
891           "/var/spool/mail", "/var/mail",
892           "/usr/spool/mail", "/usr/mail",
893           0
894         };
895         struct stat s;
896         char b[128];
897
898         for (pp = maildirs; *pp; pp++) {
899           if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
900             sprintf(b, "%s/%s", *pp, to_pw->pw_name);
901             bc__putenv("MAIL", b, envFlag_preserve, 0);
902             break;
903           }
904         }
905       } /* Fall through */
906
907       case l_user:
908         bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
909         bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
910         bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
911         bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
912         break;
913     }
914
915     /* --- Stage four.  Set the user's PATH properly --- */
916
917     {
918       /* --- Find an existing path --- *
919        *
920        * If there's no path, or this is a login, then set a default path,
921        * unless we're meant to preserve the existing one.  Whew!
922        */
923
924       e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
925
926       if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
927         bc__putenv("PATH",
928                    rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
929                    envFlag_preserve, 0);
930       } else {
931
932         /* --- Find the string --- */
933
934         e->f = envFlag_preserve;
935         p = strchr(e->val, '=') + 1;
936         r = e->val;
937
938         /* --- Write the new version to a dynamically allocated buffer --- */
939
940         e->val = xmalloc(4 + 1 + strlen(p) + 1);
941         strcpy(e->val, "PATH=");
942         q = e->val + 5;
943
944         for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
945           if (p[0] == '.')
946             continue;
947           while (*p)
948             *q++ = *p++;
949           *q++ = ':';
950         }
951         q[-1] = 0;
952
953         /* --- Done! --- */
954
955         free(r);
956       }
957     }
958
959     /* --- Stages five and six.  Expunge variables and count numbers --- *
960      *
961      * Folded together, so I only need one pass through the table.  Also
962      * count the number of variables needed at this time.
963      */
964
965     sz = 0;
966
967     for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
968
969       /* --- Login style expunges all unpreserved variables --- */
970
971       if (style == l_login && ~e->f & envFlag_preserve)
972         goto expunge;
973
974       /* --- Otherwise just check the name against the list --- */
975
976       for (pp = banned; *pp; pp++) {
977         if (**pp == '-') {
978           p = *pp + 1;
979           if (memcmp(e->_base.name, p, strlen(p)) == 0)
980             goto expunge;
981         } else if (strcmp(e->_base.name, p) == 0)
982           goto expunge;
983       }
984
985       sz++;
986       continue;
987
988     expunge:
989       sym_remove(&bc__env, e);
990     }
991
992     /* --- Stage seven.  Build the new environment block --- */
993
994     env = qq = xmalloc((sz + 1) * sizeof(*qq));
995
996     for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
997       *qq++ = e->val;
998     *qq++ = 0;
999   }
1000
1001   /* --- Trace the command --- */
1002
1003   IF_TRACING(TRACE_SETUP, {
1004     int i;
1005
1006     trace(TRACE_SETUP, "setup: from user %s to user %s",
1007           from_pw->pw_name, to_pw->pw_name);
1008     trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1009     for (i = 0; todo[i]; i++)
1010       trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1011     for (i = 0; env[i]; i++)
1012       trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1013   })
1014
1015   /* --- If necessary, resolve the path to the command --- */
1016
1017   if (!strchr(binary, '/')) {
1018     char *path, *p;
1019     struct stat st;
1020
1021     if ((p = getenv("PATH")) == 0)
1022       p = "/bin:/usr/bin";
1023     path = xstrdup(p);
1024
1025     for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
1026
1027       /* --- Check length of string before copying --- */
1028
1029       if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
1030         continue;
1031
1032       /* --- Now build the pathname and check it --- */
1033
1034       sprintf(rq.cmd, "%s/%s", p, todo[0]);
1035       if (stat(rq.cmd, &st) == 0 &&     /* Check it exists */
1036           st.st_mode & 0111 &&          /* Check it's executable */
1037           (st.st_mode & S_IFMT) == S_IFREG) /* Check it's a file */
1038         break;
1039     }
1040
1041     if (!p)
1042       die("couldn't find `%s' in path", todo[0]);
1043     binary = rq.cmd;
1044     free(path);
1045   }
1046   T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
1047
1048   /* --- Canonicalise the path string, if necessary --- */
1049
1050   {
1051     char b[CMDLEN_MAX];
1052     char *p;
1053     const char *q;
1054
1055     /* --- Insert current directory name if path not absolute --- */
1056
1057     p = b;
1058     q = binary;
1059     if (*q != '/') {
1060       if (!getcwd(b, sizeof(b)))
1061         die("couldn't read current directory: %s", strerror(errno));
1062       p += strlen(p);
1063       *p++ = '/';
1064     }
1065
1066     /* --- Now copy over characters from the path string --- */
1067
1068     while (*q) {
1069
1070       /* --- Check for buffer overflows here --- *
1071        *
1072        * I write at most one byte per iteration so this is OK.  Remember to
1073        * allow one for the null byte.
1074        */
1075
1076       if (p >= b + sizeof(b) - 1)
1077         die("internal error: buffer overflow while canonifying path");
1078
1079       /* --- Reduce multiple slashes to just one --- */
1080
1081       if (*q == '/') {
1082         while (*q == '/')
1083           q++;
1084         *p++ = '/';
1085       }
1086
1087       /* --- Handle dots in filenames --- *
1088        *
1089        * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1090        * we've just stuck the current directory on the end of the buffer,
1091        * or we've just put something else on the end.
1092        */
1093
1094       else if (*q == '.' && p[-1] == '/') {
1095
1096         /* --- A simple `./' just gets removed --- */
1097
1098         if (q[1] == 0 || q[1] == '/') {
1099           q++;
1100           p--;
1101           continue;
1102         }
1103
1104         /* --- A `../' needs to be peeled back to the previous `/' --- */
1105
1106         if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1107           q += 2;
1108           p--;
1109           while (p > b && p[-1] != '/')
1110             p--;
1111           if (p > b)
1112             p--;
1113           continue;
1114         }
1115       } else
1116         *p++ = *q++;
1117     }
1118
1119     *p++ = 0;
1120     strcpy(rq.cmd, b);
1121   }
1122   T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
1123
1124   /* --- Run the check --- *
1125    *
1126    * If the user is already what she wants to be, then print a warning.
1127    * Then, if I was just going to spawn a shell, quit, to reduce user
1128    * confusion.  Otherwise, do what was wanted anyway.
1129    */
1130
1131   if (rq.from == rq.to) {
1132     moan("you already are `%s'!", to_pw->pw_name);
1133     if (!cmd && todo == shell) {
1134       moan("(to prevent confusion, I'm not spawning a shell)");
1135       exit(0);
1136     }
1137   } else {
1138     int a = check(&rq);
1139
1140     syslog(LOG_INFO,
1141            "permission %s for %s to become %s to run `%s'",
1142            a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1143            rq.cmd);
1144
1145     if (!a)
1146       die("permission denied");
1147   }
1148
1149   /* --- Now do the job --- */
1150
1151   T( trace(TRACE_MISC, "become: permission granted"); )
1152
1153   if (flags & f_dummy) {
1154     puts("permission granted");
1155     return (0);
1156   } else {
1157     if (setuid(rq.to) == -1)
1158       die("couldn't set uid: %s", strerror(errno));
1159     execve(rq.cmd, todo, env);
1160     die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1161     return (127);
1162   }
1163 }
1164
1165 /*----- That's all, folks -------------------------------------------------*/