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