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