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