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