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