Do something useful when users attempt to become themselves.
[become] / src / become.c
CommitLineData
c4f2d992 1/* -*-c-*-
2 *
88e486d5 3 * $Id: become.c,v 1.3 1997/08/07 16:28:59 mdw Exp $
c4f2d992 4 *
5 * Main code for `become'
6 *
7 * (c) 1997 EBI
8 */
9
03f996bd 10/*----- Licensing notice --------------------------------------------------*
c4f2d992 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
03f996bd 25 * along with `become'; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
c4f2d992 27 */
28
29/*----- Revision history --------------------------------------------------*
30 *
31 * $Log: become.c,v $
88e486d5 32 * Revision 1.3 1997/08/07 16:28:59 mdw
33 * Do something useful when users attempt to become themselves.
34 *
03f996bd 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
c4f2d992 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>
03f996bd 52#include <time.h>
c4f2d992 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
03f996bd 70extern char **environ;
71
c4f2d992 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"
03f996bd 84#include "userdb.h"
c4f2d992 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
100static 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
133static 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
147static 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
03f996bd 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
167static 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
c4f2d992 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
183static 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"
03f996bd 205"-l, --login Really log in as the user\n"
c4f2d992 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"
03f996bd 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);
c4f2d992 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
228int main(int argc, char *argv[])
229{
03f996bd 230 /* --- Request block setup parameters --- */
c4f2d992 231
03f996bd 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 */
c4f2d992 256 };
257
03f996bd 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 };
c4f2d992 277
278 /* --- Set up the program name --- */
279
280 ego(argv[0]);
03f996bd 281 clock();
282 if (getuid() != geteuid())
283 flags |= f_setuid;
c4f2d992 284
285 /* --- Parse some command line arguments --- */
286
287 for (;;) {
288 int i;
03f996bd 289 static struct option opts[] = {
c4f2d992 290 { "help", 0, 0, 'h' },
291 { "version", 0, 0, 'v' },
03f996bd 292 { "login", 0, 0, 'l' },
c4f2d992 293 { "command", gFlag_argReq, 0, 'c' },
294 { "daemon", 0, 0, 'd' },
295 { "port", gFlag_argReq, 0, 'p' },
296 { "config-file", gFlag_argReq, 0, 'f' },
03f996bd 297#ifdef TRACING
298 { "impersonate", gFlag_argReq, 0, 'I' },
299 { "trace", gFlag_argOpt, 0, 'T' },
300 { "trace-level", gFlag_argOpt, 0, 'L' },
301#endif
c4f2d992 302 { 0, 0, 0, 0 }
303 };
304
03f996bd 305 i = mdwopt(argc, argv, "+hvlc:p:df:", opts, 0, 0, 0);
c4f2d992 306 if (i < 0)
307 break;
308
309 switch (i) {
03f996bd 310
311 /* --- Standard help and version options --- */
312
c4f2d992 313 case 'h':
314 bc__help(stdout);
315 exit(0);
316 break;
317 case 'v':
318 bc__banner(stdout);
319 exit(0);
320 break;
03f996bd 321
322 /* --- Various simple options --- */
323
324 case 'l':
325 flags |= f_login;
326 break;
c4f2d992 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;
03f996bd 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 }
c4f2d992 362 break;
03f996bd 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
c4f2d992 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) {
03f996bd 489 T( trace(TRACE_MISC, "become: daemon mode requested"); )
c4f2d992 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;
03f996bd 503
c4f2d992 504 if (optind >= argc) {
505 bc__usage(stderr);
506 exit(1);
507 }
508 u = argv[optind++];
03f996bd 509
510 if (isdigit((unsigned char)u[0]))
c4f2d992 511 pw = getpwuid(atoi(u));
03f996bd 512 else
513 pw = getpwnam(u);
c4f2d992 514 if (!pw)
515 die("unknown user `%s'", u);
03f996bd 516 to_pw = userdb_copyUser(pw);
c4f2d992 517 rq.to = pw->pw_uid;
518 }
519
520 /* --- Fill in the easy bits of the request --- */
521
03f996bd 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 }
c4f2d992 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
03f996bd 543 /* --- Shell commands are easy --- */
c4f2d992 544
545 if (cmd) {
546 shell[2] = cmd;
547 todo = shell;
03f996bd 548 }
549
550 /* --- A command given on the command line isn't too hard --- */
551
552 else if (optind < argc) {
c4f2d992 553 todo = argv + optind;
03f996bd 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);
c4f2d992 568 shell[1] = 0;
569 todo = shell;
03f996bd 570 binary = to_pw->pw_shell;
c4f2d992 571 }
572
03f996bd 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
c4f2d992 605 /* --- If necessary, resolve the path to the command --- */
606
03f996bd 607 if (!strchr(binary, '/')) {
608 char *path, *p;
c4f2d992 609 struct stat st;
c4f2d992 610
611 if ((p = getenv("PATH")) == 0)
612 p = "/bin:/usr/bin";
03f996bd 613 path = xstrdup(p);
c4f2d992 614
615 for (p = strtok(path, ":"); (p = strtok(0, ":")) != 0; ) {
616
617 /* --- SECURITY: check length of string before copying --- */
618
03f996bd 619 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
c4f2d992 620 continue;
621
622 /* --- Now build the pathname and check it --- */
623
03f996bd 624 sprintf(rq.cmd, "%s/%s", p, todo[0]);
625 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
c4f2d992 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]);
03f996bd 633 binary = rq.cmd;
c4f2d992 634 free(path);
635 }
03f996bd 636 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
c4f2d992 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;
03f996bd 648 q = binary;
c4f2d992 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)
03f996bd 667 die("internal error: buffer overflow while canonifying path");
c4f2d992 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 }
03f996bd 712 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
c4f2d992 713
88e486d5 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 */
c4f2d992 720
88e486d5 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 {
c4f2d992 728 int a = check(&rq);
c4f2d992 729
730 syslog(LOG_INFO,
731 "permission %s for %s to become %s to run `%s'",
03f996bd 732 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
733 rq.cmd);
c4f2d992 734
735 if (!a)
736 die("permission denied");
737 }
738
739 /* --- Now do the job --- */
740
03f996bd 741 T( trace(TRACE_MISC, "become: permission granted"); )
742
743 if (flags & f_dummy) {
744 puts("permission granted");
745 return (0);
746 } else {
88e486d5 747 if (setuid(rq.to) == -1)
03f996bd 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 }
c4f2d992 753}
754
755/*----- That's all, folks -------------------------------------------------*/