chiark / gitweb /
Build parser and lexer into the source directory. Other cosmetic
[become] / src / become.c
1 /* -*-c-*-
2  *
3  * $Id: become.c,v 1.3 1997/08/07 16:28:59 mdw Exp $
4  *
5  * Main code for `become'
6  *
7  * (c) 1997 EBI
8  */
9
10 /*----- Licensing notice --------------------------------------------------*
11  *
12  * This file is part of `become'
13  *
14  * `Become' is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * `Become' is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with `become'; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------*
30  *
31  * $Log: become.c,v $
32  * Revision 1.3  1997/08/07 16:28:59  mdw
33  * Do something useful when users attempt to become themselves.
34  *
35  * Revision 1.2  1997/08/04 10:24:20  mdw
36  * Sources placed under CVS control.
37  *
38  * Revision 1.1  1997/07/21  13:47:54  mdw
39  * Initial revision
40  *
41  */
42
43 /*----- Header files ------------------------------------------------------*/
44
45 /* --- ANSI headers --- */
46
47 #include <ctype.h>
48 #include <errno.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <time.h>
53
54 /* --- Unix headers --- */
55
56 #include <sys/types.h>
57 #include <sys/stat.h>
58 #include <sys/socket.h>
59 #include <sys/utsname.h>
60
61 #include <netinet/in.h>
62
63 #include <arpa/inet.h>
64
65 #include <netdb.h>
66 #include <pwd.h>
67 #include <syslog.h>
68 #include <unistd.h>
69
70 extern char **environ;
71
72 /* --- Local headers --- */
73
74 #include "become.h"
75 #include "config.h"
76 #include "check.h"
77 #include "daemon.h"
78 #include "lexer.h"
79 #include "mdwopt.h"
80 #include "name.h"
81 #include "parser.h"
82 #include "rule.h"
83 #include "utils.h"
84 #include "userdb.h"
85
86 /*----- Main code ---------------------------------------------------------*/
87
88 /* --- @bc__write@ --- *
89  *
90  * Arguments:   @FILE *fp@ = pointer to a stream to write on
91  *              @const char *p@ = pointer to a string
92  *
93  * Returns:     ---
94  *
95  * Use:         Writes the string to the stream, substituting the program
96  *              name (as returned by @quis@) for each occurrence of the
97  *              character `$'.
98  */
99
100 static void bc__write(FILE *fp, const char *p)
101 {
102   const char *n = quis();
103   size_t l;
104   size_t nl = strlen(n);
105
106   /* --- Try to be a little efficient --- *
107    *
108    * Gather up non-`$' characters using @strcpn@ and spew them out really
109    * quickly.
110    */
111
112   for (;;) {
113     l = strcspn(p, "$");
114     if (l)
115       fwrite(p, l, 1, fp);
116     p += l;
117     if (!*p)
118       break;
119     fwrite(n, nl, 1, fp);
120     p++;
121   }
122 }
123
124 /* --- @bc__banner@ --- *
125  *
126  * Arguments:   @FILE *fp@ = stream to write on
127  *
128  * Returns:     ---
129  *
130  * Use:         Writes a banner containing copyright information.
131  */
132
133 static void bc__banner(FILE *fp)
134 {
135   bc__write(fp, "$ version " VERSION "\n");
136 }
137
138 /* --- @bc__usage@ --- *
139  *
140  * Arguments:   @FILE *fp@ = stream to write on
141  *
142  * Returns:     ---
143  *
144  * Use:         Writes a terse reminder of command line syntax.
145  */
146
147 static void bc__usage(FILE *fp)
148 {
149   bc__write(fp,
150             "Usage: \n"
151             "   $ -c <shell-command> <user>\n"
152             "   $ <user> [<command> [<arguments>]...]\n"
153             "   $ -d [-p <port>] [-f <config-file>]\n");
154 }
155
156 /* --- @bc__makeEnv@ --- *
157  *
158  * Arguments:   @const char *var@ = name of an environment variable
159  *              @const char *value@ = value of the variable
160  *
161  * Returns:     A pointer to an allocated block assigning the value to the
162  *              name.
163  *
164  * Use:         Constructs environment mappings.
165  */
166
167 static char *bc__makeEnv(const char *var, const char *value)
168 {
169   char *p = xmalloc(strlen(var) + strlen(value) + 2);
170   sprintf(p, "%s=%s", var, value);
171   return (p);
172 }
173
174 /* --- @bc__help@ --- *
175  *
176  * Arguments:   @FILE *fp@ = stream to write on
177  *
178  * Returns:     ---
179  *
180  * Use:         Displays a help message for this excellent piece of software.
181  */
182
183 static void bc__help(FILE *fp)
184 {
185   bc__banner(fp);
186   putc('\n', fp);
187   bc__usage(fp);
188   putc('\n', fp);
189   bc__write(fp,
190 "The `$' program allows you to run a process as another user.\n"
191 "If a command name is given, this is the process executed.  If the `-c'\n"
192 "option is used, the process is assumed to be `/bin/sh'.  If no command is\n"
193 "given, your default login shell is used.\n"
194 "\n"
195 "Your user id, the user id you wish to become, the name of the process\n"
196 "you wish to run, and the identity of the current host are looked up to\n"
197 "ensure that you have permission to do this.\n"
198 "\n"
199 "Note that logs are kept of all uses of this program.\n"
200 "\n"
201 "Options available are:\n"
202 "\n"
203 "-h, --help             Display this help text\n"
204 "-v, --version          Display the version number of this copy of $\n"
205 "-l, --login            Really log in as the user\n"
206 "-c, --command=CMD      Run the (Bourne) shell command CMD\n"
207 "-d, --daemon           Start up a daemon, to accept requests from clients\n"
208 "-p, --port=PORT                In daemon mode, listen on PORT\n"
209 "-f, --config-file=FILE In daemon mode, read config from FILE\n"
210 #ifdef TRACING
211 "--impersonate=USER     Claim to be USER when asking the server\n"
212 "--trace=FILE           Dump trace information to FILE (boring)\n"
213 "--trace-level=OPTS     Set level of tracing information\n"
214 #endif
215 );
216 }
217
218 /* --- @main@ --- *
219  *
220  * Arguments:   @int argc@ = number of command line arguments
221  *              @char *argv[]@ = pointer to the various arguments
222  *
223  * Returns:     Zero if successful.
224  *
225  * Use:         Allows a user to change UID.
226  */
227
228 int main(int argc, char *argv[])
229 {
230   /* --- Request block setup parameters --- */
231
232   request rq;                           /* Request buffer to build */
233   char *cmd = 0;                        /* Shell command to execute */
234   char *binary = "/bin/sh";             /* Default binary to execute */
235   char **env = environ;                 /* Default environment to pass */
236   char **todo;                          /* Pointer to argument list */
237   struct passwd *from_pw = 0;           /* User we are right now */
238   struct passwd *to_pw = 0;             /* User we want to become */
239
240   /* --- Become server setup parameters --- */
241
242   char *conffile = file_RULES;          /* Default config file for daemon */
243   int port = -1;                        /* Default port for daemon */
244
245   /* --- Miscellanous shared variables --- */
246
247   unsigned flags = 0;                   /* Various useful flags */
248
249   /* --- Default argument list executes a shell command --- */
250
251   static char *shell[] = {
252     "/bin/sh",                          /* Bourne shell */
253     "-c",                               /* Read from command line */
254     0,                                  /* Pointer to shell command */
255     0                                   /* Terminator */
256   };
257
258   /* --- Login request replaces the environment with this --- */
259
260   static char *mangled_env[] = {
261     "PATH=/bin:/usr/bin",               /* Default `clean' path*/
262     0,                                  /* User's name (`USER') */
263     0,                                  /* User's name (`LOGNAME') */
264     0,                                  /* Home directory (`HOME') */
265     0                                   /* Terminator */
266   };
267
268   /* --- Definitions for the various flags --- */
269
270   enum {
271     f_daemon = 1,                       /* Start up in daemon mode */
272     f_duff = 2,                         /* Fault in arguments */
273     f_login = 4,                        /* Execute as a login shell */
274     f_dummy = 8,                        /* Don't actually do anything */
275     f_setuid = 16                       /* We're running setuid */
276   };
277
278   /* --- Set up the program name --- */
279
280   ego(argv[0]);
281   clock();
282   if (getuid() != geteuid())
283     flags |= f_setuid;
284
285   /* --- Parse some command line arguments --- */
286
287   for (;;) {
288     int i;
289     static struct option opts[] = {
290       { "help",         0,              0,      'h' },
291       { "version",      0,              0,      'v' },
292       { "login",        0,              0,      'l' },
293       { "command",      gFlag_argReq,   0,      'c' },
294       { "daemon",       0,              0,      'd' },
295       { "port",         gFlag_argReq,   0,      'p' },
296       { "config-file",  gFlag_argReq,   0,      'f' },
297 #ifdef TRACING
298       { "impersonate",  gFlag_argReq,   0,      'I' },
299       { "trace",        gFlag_argOpt,   0,      'T' },
300       { "trace-level",  gFlag_argOpt,   0,      'L' },
301 #endif
302       { 0,              0,              0,      0 }
303     };
304
305     i = mdwopt(argc, argv, "+hvlc:p:df:", opts, 0, 0, 0);
306     if (i < 0)
307       break;
308
309     switch (i) {
310
311       /* --- Standard help and version options --- */
312
313       case 'h':
314         bc__help(stdout);
315         exit(0);
316         break;
317       case 'v':
318         bc__banner(stdout);
319         exit(0);
320         break;
321
322       /* --- Various simple options --- */
323
324       case 'l':
325         flags |= f_login;
326         break;
327       case 'c':
328         cmd = optarg;
329         break;
330       case 'p':
331         port = atoi(optarg);
332         break;
333       case 'd':
334         flags |= f_daemon;
335         break;
336       case 'f':
337         conffile = optarg;
338         break;
339
340       /* --- Pretend to be a different user --- *
341        *
342        * There are all sorts of nasty implications for this option.  Don't
343        * allow it if we're running setuid.  Disable the actual login anyway.
344        */
345
346 #ifdef TRACING
347       case 'I':
348         if (flags & f_setuid)
349           moan("shan't allow impersonation while running setuid");
350         else {
351           struct passwd *pw;
352           if (isdigit((unsigned char)optarg[0]))
353             pw = getpwuid(atoi(optarg));
354           else
355             pw = getpwnam(optarg);
356           if (!pw)
357             die("can't impersonate unknown user `%s'", optarg);
358           from_pw = userdb_copyUser(pw);
359           rq.from = from_pw->pw_uid;
360           flags |= f_dummy;
361         }
362         break;
363 #endif
364
365       /* --- Tracing support --- *
366        *
367        * Be careful not to zap a file I wouldn't normally be allowed to write
368        * to!
369        */
370
371 #ifdef TRACING
372
373       case 'T': {
374         FILE *fp;
375
376         if (optarg == 0 || strcmp(optarg, "-") == 0)
377           fp = stdout;
378         else {
379           if ((flags & f_setuid) && access(optarg, W_OK)) {
380             die("no write permission for trace file file `%s': %s",
381                 optarg, strerror(errno));
382           }
383           if ((fp = fopen(optarg, "w")) == 0) {
384             die("couldn't open trace file `%s' for writing: %s",
385                 optarg, strerror(errno));
386           }
387         }
388         traceon(fp, TRACE_DFL);
389         trace(TRACE_MISC, "become: tracing enabled");
390       } break;
391
392 #endif
393
394       /* --- Setting trace levels --- */
395
396 #ifdef TRACING
397
398       case 'L': {
399         int sense = 1;
400         unsigned int lvl = 0, l;
401         const char *p = optarg;
402
403         /* --- Table of tracing facilities --- */
404
405         typedef struct tr {
406           char ch;
407           unsigned int l;
408           const char *help;
409         } tr;
410
411         static tr lvltbl[] = {
412           { 'm', TRACE_MISC,    "miscellaneous messages" },
413           { 's', TRACE_SETUP,   "building the request block" },
414           { 'd', TRACE_DAEMON,  "server process" },
415           { 'r', TRACE_RULE,    "ruleset scanning" },
416           { 'c', TRACE_CHECK,   "request checking" },
417           { 'l', TRACE_CLIENT,  "client process" },
418           { 'R', TRACE_RAND,    "random number generator" },
419           { 'C', TRACE_CRYPTO,  "cryptographic processing of requests" },
420           { 'y', TRACE_YACC,    "parsing configuration file" },
421           { 'D', TRACE_DFL,     "default tracing options" },
422           { 'A', TRACE_ALL,     "all tracing options" },
423           { 0,   0,             0 }
424         };
425         tr *tp;
426
427         /* --- Output some help if there's no arguemnt --- */
428
429         if (!optarg) {
430           bc__banner(stdout);
431           bc__write(stdout,
432                     "\n"
433                     "Tracing options:\n"
434                     "\n");
435           for (tp = lvltbl; tp->l; tp++) {
436             if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
437               printf("%c -- %s\n", tp->ch, tp->help);
438           }
439           bc__write(stdout,
440 "\n"
441 "Also, `+' and `-' options are recognised to turn on and off vavrious\n"
442 "tracing options.  For example, `A-r' enables everything except ruleset\n"
443 "tracing, and `A-D+c' is everything except the defaults, but with request\n"
444 "check tracing.\n"
445 );
446           exit(0);
447         }
448
449         while (*p) {
450           if (*p == '+')
451             sense = 1;
452           else if (*p == '-')
453             sense = 0;
454           else {
455             for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
456               ;
457             l = tp->l;
458             if (flags & f_setuid)
459               l &= ~TRACE_PRIV;
460             if (l)
461               lvl = sense ? (lvl | l) : (lvl & ~l);
462             else
463               moan("unknown trace option `%c'", *p);
464           }
465           p++;
466         }
467
468         tracesetlvl(lvl);
469         yydebug = ((lvl & TRACE_YACC) != 0);
470       } break;
471
472 #endif
473
474       /* --- Something I didn't understand has occurred --- */
475
476       case '?':
477         flags |= f_duff;
478         break;
479     }
480   }
481   if (flags & f_duff) {
482     bc__usage(stderr);
483     exit(1);
484   }
485
486   /* --- Switch to daemon mode if requested --- */
487
488   if (flags & f_daemon) {
489     T( trace(TRACE_MISC, "become: daemon mode requested"); )
490     daemon_init(conffile, port);
491     exit(0);
492   }
493
494   /* --- Open a syslog --- */
495
496   openlog(quis(), 0, LOG_AUTH);
497
498   /* --- Pick out the uid --- */
499
500   {
501     const char *u;
502     struct passwd *pw;
503
504     if (optind >= argc) {
505       bc__usage(stderr);
506       exit(1);
507     }
508     u = argv[optind++];
509
510     if (isdigit((unsigned char)u[0]))
511       pw = getpwuid(atoi(u));
512     else
513       pw = getpwnam(u);
514     if (!pw)
515       die("unknown user `%s'", u);
516     to_pw = userdb_copyUser(pw);
517     rq.to = pw->pw_uid;
518   }
519
520   /* --- Fill in the easy bits of the request --- */
521
522   if (!from_pw) {
523     struct passwd *pw;
524
525     rq.from = getuid();
526     pw = getpwuid(rq.from);
527     if (!pw)
528       die("who are you? (can't find user %li)", (long)rq.from);
529     from_pw = userdb_copyUser(pw);
530   }
531
532   /* --- Find the local host address --- */
533
534   {
535     struct utsname u;
536     struct hostent *he;
537     uname(&u);
538     if ((he = gethostbyname(u.nodename)) == 0)
539       die("who am I? (can't resolve `%s')", u.nodename);
540     memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
541   }
542
543   /* --- Shell commands are easy --- */
544
545   if (cmd) {
546     shell[2] = cmd;
547     todo = shell;
548   }
549
550   /* --- A command given on the command line isn't too hard --- */
551
552   else if (optind < argc) {
553     todo = argv + optind;
554     binary = todo[0];
555   }
556
557   /* --- A login request needs a little bit of work --- */
558
559   else if (flags & f_login) {
560     const char *p = strrchr(to_pw->pw_shell, '/');
561     if (p)
562       p++;
563     else
564       p = to_pw->pw_shell;
565     shell[0] = xmalloc(strlen(p) + 2);
566     shell[0][0] = '-';
567     strcpy(shell[0] + 1, p);
568     shell[1] = 0;
569     todo = shell;
570     binary = to_pw->pw_shell;
571   }
572
573   /* --- An unadorned becoming requires little work --- */
574
575   else {
576     shell[0] = from_pw->pw_shell;
577     shell[1] = 0;
578     todo = shell;
579     binary = todo[0];
580   }
581
582   /* --- Mangle the environment if login flag given --- */
583
584   if (flags & f_login) {
585     env = mangled_env;
586     env[1] = bc__makeEnv("USER", to_pw->pw_name);
587     env[2] = bc__makeEnv("LOGNAME", to_pw->pw_name);
588     env[3] = bc__makeEnv("HOME", to_pw->pw_dir);
589   }
590
591   /* --- Trace the command --- */
592
593   IF_TRACING(TRACE_SETUP, {
594     int i;
595
596     trace(TRACE_SETUP, "setup: from user %s to user %s",
597           from_pw->pw_name, to_pw->pw_name);
598     trace(TRACE_SETUP, "setup: binary == `%s'", binary);
599     for (i = 0; todo[i]; i++)
600       trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
601     for (i = 0; env[i]; i++)
602       trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
603   })
604
605   /* --- If necessary, resolve the path to the command --- */
606
607   if (!strchr(binary, '/')) {
608     char *path, *p;
609     struct stat st;
610
611     if ((p = getenv("PATH")) == 0)
612       p = "/bin:/usr/bin";
613     path = xstrdup(p);
614
615     for (p = strtok(path, ":"); (p = strtok(0, ":")) != 0; ) {
616
617       /* --- SECURITY: check length of string before copying --- */
618
619       if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
620         continue;
621
622       /* --- Now build the pathname and check it --- */
623
624       sprintf(rq.cmd, "%s/%s", p, todo[0]);
625       if (stat(rq.cmd, &st) == 0 &&     /* Check it exists */
626           st.st_mode & 0111 &&          /* Check it's executable */
627           (st.st_mode & S_IFMT) == S_IFREG) /* Check it's a file */
628         break;
629     }
630
631     if (!p)
632       die("couldn't find `%s' in path", todo[0]);
633     binary = rq.cmd;
634     free(path);
635   }
636   T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
637
638   /* --- Canonicalise the path string, if necessary --- */
639
640   {
641     char b[CMDLEN_MAX];
642     char *p;
643     const char *q;
644
645     /* --- Insert current directory name if path not absolute --- */
646
647     p = b;
648     q = binary;
649     if (*q != '/') {
650       if (!getcwd(b, sizeof(b)))
651         die("couldn't read current directory: %s", strerror(errno));
652       p += strlen(p);
653       *p++ = '/';
654     }
655
656     /* --- Now copy over characters from the path string --- */
657
658     while (*q) {
659
660       /* --- SECURITY: check for buffer overflows here --- *
661        *
662        * I write at most one byte per iteration so this is OK.  Remember to
663        * allow one for the null byte.
664        */
665
666       if (p >= b + sizeof(b) - 1)
667         die("internal error: buffer overflow while canonifying path");
668
669       /* --- Reduce multiple slashes to just one --- */
670
671       if (*q == '/') {
672         while (*q == '/')
673           q++;
674         *p++ = '/';
675       }
676
677       /* --- Handle dots in filenames --- *
678        *
679        * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
680        * we've just stuck the current directory on the end of the buffer,
681        * or we've just put something else on the end.
682        */
683
684       else if (*q == '.' && p[-1] == '/') {
685
686         /* --- A simple `./' just gets removed --- */
687
688         if (q[1] == 0 || q[1] == '/') {
689           q++;
690           p--;
691           continue;
692         }
693
694         /* --- A `../' needs to be peeled back to the previous `/' --- */
695
696         if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
697           q += 2;
698           p--;
699           while (p > b && p[-1] != '/')
700             p--;
701           if (p > b)
702             p--;
703           continue;
704         }
705       } else
706         *p++ = *q++;
707     }
708
709     *p++ = 0;
710     strcpy(rq.cmd, b);
711   }
712   T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
713
714   /* --- Run the check --- *
715    *
716    * If the user is already what she wants to be, then print a warning.
717    * Then, if I was just going to spawn a shell, quit, to reduce user
718    * confusion.  Otherwise, do what was wanted anyway.
719    */
720
721   if (rq.from == rq.to) {
722     moan("you already are `%s'!", to_pw->pw_name);
723     if (!cmd && todo == shell) {
724       moan("(to prevent confusion, I'm not spawning a shell)");
725       exit(0);
726     }
727   } else {
728     int a = check(&rq);
729
730     syslog(LOG_INFO,
731            "permission %s for %s to become %s to run `%s'",
732            a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
733            rq.cmd);
734
735     if (!a)
736       die("permission denied");
737   }
738
739   /* --- Now do the job --- */
740
741   T( trace(TRACE_MISC, "become: permission granted"); )
742
743   if (flags & f_dummy) {
744     puts("permission granted");
745     return (0);
746   } else {
747     if (setuid(rq.to) == -1)
748       die("couldn't set uid: %s", strerror(errno));
749     execve(rq.cmd, todo, env);
750     die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
751     return (127);
752   }
753 }
754
755 /*----- That's all, folks -------------------------------------------------*/