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