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