627eda56fb117938003bf0e9f02b202d26d374e9
[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 -------------------------------------------------*/