Do something useful when users attempt to become themselves.
[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 -------------------------------------------------*/