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