New file: simplifies the job of getting a newly checked-out source tree
[become] / src / become.c
CommitLineData
c4f2d992 1/* -*-c-*-
2 *
59f147b0 3 * $Id: become.c,v 1.19 1998/06/29 13:10:41 mdw Exp $
c4f2d992 4 *
5 * Main code for `become'
6 *
c758e654 7 * (c) 1998 EBI
c4f2d992 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 $
59f147b0 32 * Revision 1.19 1998/06/29 13:10:41 mdw
33 * Add some commentary regarding an issue in `sudo' which affects `become';
34 * I'm not fixing it yet because I don't think it's important.
35 *
36 * Fixed the PATH lookup code to use the right binary name: `binary' rather
37 * than `todo[0]'. The two only differ when `style' is `l_login', in which
38 * case `binary' has an initial `/' anyway, and the PATH lookup code is
39 * never invoked. The name is used in a buffer-overflow precheck, though,
40 * and auditing is easier if the naming is consistent.
41 *
7df38e1e 42 * Revision 1.18 1998/06/26 10:32:54 mdw
43 * Cosmetic change: use sizeof(destination) in memcpy.
44 *
46a5b3c6 45 * Revision 1.17 1998/06/18 15:06:59 mdw
46 * Close log before execing program to avoid leaving a socket open.
47 *
f175e71b 48 * Revision 1.16 1998/04/23 13:21:04 mdw
49 * Small tweaks. Support no-network configuration option, and rearrange
50 * the help text a little.
51 *
94455fbb 52 * Revision 1.15 1998/01/13 11:10:44 mdw
53 * Add `TZ' to the list of variables to be preserved.
54 *
c758e654 55 * Revision 1.14 1998/01/12 16:45:39 mdw
56 * Fix copyright date.
57 *
9e5602f0 58 * Revision 1.13 1997/09/26 09:14:57 mdw
59 * Merged blowfish branch into trunk.
60 *
45df69ab 61 * Revision 1.12 1997/09/25 16:04:48 mdw
62 * Change directory after becoming someone else, instead of before. This
63 * avoids problems with root-squashed NFS mounts.
64 *
9e5602f0 65 * Revision 1.11.2.1 1997/09/26 09:07:58 mdw
66 * Use the Blowfish encryption algorithm instead of IDEA. This is partly
67 * because I prefer Blowfish (without any particularly strong evidence) but
68 * mainly because IDEA is patented and Blowfish isn't.
69 *
45df69ab 70 * Revision 1.11 1997/09/24 09:48:45 mdw
505f2736 71 * Fix (scary) overrun bug in group allocation stuff.
72 *
73 * Revision 1.10 1997/09/17 10:14:10 mdw
9df0c75d 74 * Fix a typo. Support service names in `--port' option.
75 *
a5089273 76 * Revision 1.9 1997/09/10 10:28:05 mdw
77 * Allow default port to be given as a service name or port number. Handle
78 * groups properly (lots of options here).
79 *
80 * Revision 1.8 1997/09/08 13:56:24 mdw
6a4ec25c 81 * Change criteria for expunging items from the user's PATH: instead of
82 * removing things starting with `.', remove things not starting with `/'.
83 *
84 * Revision 1.7 1997/09/08 13:43:20 mdw
c6885d5f 85 * Change userid when creating tracefiles rather than fiddling with
86 * `access': it works rather better. Also, insert some stdio buffer
87 * flushing to ensure tracedumps are completely written.
88 *
89 * Revision 1.6 1997/09/05 13:47:44 mdw
d6683a48 90 * Make the `-L' (trace-level) option's argument optional, like the long
91 * version is.
92 *
93 * Revision 1.5 1997/09/05 11:45:19 mdw
387501ae 94 * Add support for different login styles, and environment variable
95 * manipulation in a safe and useful way.
96 *
97 * Revision 1.4 1997/08/20 16:15:13 mdw
569b8891 98 * Overhaul of environment handling. Fix daft bug in path search code.
99 *
88e486d5 100 * Revision 1.3 1997/08/07 16:28:59 mdw
101 * Do something useful when users attempt to become themselves.
102 *
03f996bd 103 * Revision 1.2 1997/08/04 10:24:20 mdw
104 * Sources placed under CVS control.
105 *
106 * Revision 1.1 1997/07/21 13:47:54 mdw
c4f2d992 107 * Initial revision
108 *
109 */
110
111/*----- Header files ------------------------------------------------------*/
112
113/* --- ANSI headers --- */
114
115#include <ctype.h>
116#include <errno.h>
a5089273 117#include <limits.h>
c4f2d992 118#include <stdio.h>
119#include <stdlib.h>
120#include <string.h>
03f996bd 121#include <time.h>
c4f2d992 122
123/* --- Unix headers --- */
124
125#include <sys/types.h>
126#include <sys/stat.h>
127#include <sys/socket.h>
128#include <sys/utsname.h>
129
130#include <netinet/in.h>
131
132#include <arpa/inet.h>
133
134#include <netdb.h>
a5089273 135#include <grp.h>
c4f2d992 136#include <pwd.h>
137#include <syslog.h>
138#include <unistd.h>
139
03f996bd 140extern char **environ;
141
c4f2d992 142/* --- Local headers --- */
143
144#include "become.h"
145#include "config.h"
146#include "check.h"
147#include "daemon.h"
148#include "lexer.h"
149#include "mdwopt.h"
150#include "name.h"
151#include "parser.h"
152#include "rule.h"
387501ae 153#include "sym.h"
c4f2d992 154#include "utils.h"
03f996bd 155#include "userdb.h"
c4f2d992 156
387501ae 157/*----- Type definitions --------------------------------------------------*/
158
159/* --- Symbol table entry for an environment variable --- */
160
161typedef struct sym_env {
162 sym_base _base; /* Symbol table information */
163 unsigned f; /* Flags word (see below) */
164 char *val; /* Pointer to variable value */
165} sym_env;
166
167/* --- Environment variable flags --- */
168
169enum {
170 envFlag_preserve = 1
171};
172
173/* --- Login behaviour types --- */
174
f175e71b 175#define l_preserve 0 /* Preserve the environment */
176#define l_setuser 1 /* Update who I am */
177#define l_login 2 /* Do a full login */
387501ae 178
a5089273 179/* --- Group behaviour types --- *
180 *
181 * Note that these make a handy bitfield.
182 */
183
184#ifdef HAVE_SETGROUPS
185
186enum {
187 g_unset = 0, /* Nobody's set a preference */
188 g_keep = 1, /* Leave the group memberships */
189 g_replace = 2, /* Replace group memberships */
190 g_merge = (g_keep | g_replace) /* Merge the group memberships */
191};
192
193#endif
194
387501ae 195/*----- Static variables --------------------------------------------------*/
196
197static sym_table bc__env;
198
c4f2d992 199/*----- Main code ---------------------------------------------------------*/
200
201/* --- @bc__write@ --- *
202 *
203 * Arguments: @FILE *fp@ = pointer to a stream to write on
204 * @const char *p@ = pointer to a string
205 *
206 * Returns: ---
207 *
208 * Use: Writes the string to the stream, substituting the program
209 * name (as returned by @quis@) for each occurrence of the
210 * character `$'.
211 */
212
213static void bc__write(FILE *fp, const char *p)
214{
215 const char *n = quis();
216 size_t l;
217 size_t nl = strlen(n);
218
219 /* --- Try to be a little efficient --- *
220 *
569b8891 221 * Gather up non-`$' characters using @strcspn@ and spew them out really
c4f2d992 222 * quickly.
223 */
224
225 for (;;) {
226 l = strcspn(p, "$");
227 if (l)
228 fwrite(p, l, 1, fp);
229 p += l;
230 if (!*p)
231 break;
232 fwrite(n, nl, 1, fp);
233 p++;
234 }
235}
236
387501ae 237/* --- @bc__setenv@ --- *
238 *
239 * Arguments: @sym_env *e@ = pointer to environment variable block
240 * @const char *val@ = value to set
241 *
242 * Returns: ---
243 *
244 * Use: Sets an environment variable block to the right value.
245 */
246
247static void bc__setenv(sym_env *e, const char *val)
248{
249 e->val = xmalloc(strlen(e->_base.name) + 1 + strlen(val) + 1);
250 sprintf(e->val, "%s=%s", e->_base.name, val);
251}
252
253/* --- @bc__putenv@ --- *
254 *
255 * Arguments: @const char *var@ = name of the variable to set, or 0 if
256 * this is embedded in the value string
257 * @const char *val@ = value to set, or 0 if the variable must
258 * be removed
259 * @unsigned int fl@ = flags to set
260 * @unsigned int force@ = force overwrite of preserved variables
261 *
262 * Returns: Pointer to symbol block, or zero if it was deleted.
263 *
264 * Use: Puts an item into the environment.
265 */
266
267static sym_env *bc__putenv(const char *var, const char *val,
268 unsigned int fl, unsigned int force)
269{
270 unsigned int f;
271 sym_env *e;
272 char *q = 0;
273 size_t sz;
274
275 /* --- Sort out embedded variable names --- */
276
277 if (!var) {
278 const char *p = strchr(val, '=');
279
280 if (p) {
281 sz = p - val;
282 q = xmalloc(sz + 1);
283 memcpy(q, val, sz);
284 q[sz] = 0;
285 var = q;
286 val = p + 1;
287 } else {
288 var = val;
289 val = 0;
290 }
291 }
292
293 /* --- Find the variable block --- */
294
295 if (val) {
296 e = sym_find(&bc__env, var, -1, sizeof(*e), &f);
a5089273 297 if (!f || ~e->f & envFlag_preserve || force) {
387501ae 298 if (f)
299 free(e->val);
300 bc__setenv(e, val);
301 }
302 } else {
303 e = sym_find(&bc__env, var, -1, 0, 0);
304 if (e && (force || ~e->f & envFlag_preserve))
305 sym_remove(&bc__env, e);
306 e = 0;
307 }
308
309 /* --- Tidy up and return --- */
310
311 if (q)
312 free(q);
313 if (e)
314 e->f = fl;
315 return (e);
316}
317
a5089273 318/* --- @bc__addGroups@ --- *
319 *
320 * Arguments: @gid_t *g@ = pointer to a group array
321 * @int *png@ = pointer to number of entries in the array
322 * @const gid_t *a@ = pointer to groups to add
323 * @int na@ = number of groups to add
324 *
325 * Returns: Zero if it was OK, nonzero if we should stop now.
326 *
327 * Use: Adds groups to a groups array.
328 */
329
330static int bc__addGroups(gid_t *g, int *png, const gid_t *a, int na)
331{
332 int i, j;
333 int ng = *png;
334
335 for (i = 0; i < na; i++) {
336
337 /* --- Ensure this group isn't already in the list --- */
338
339 for (j = 0; j < ng; j++) {
340 if (a[i] == g[j])
341 goto next_group;
342 }
343
344 /* --- See if there's room for more --- */
345
505f2736 346 if (ng >= NGROUPS_MAX) {
347 moan("too many groups (system limit exceeded) -- some have been lost");
a5089273 348 *png = ng;
349 return (-1);
350 }
351
352 /* --- Add the group --- */
353
354 g[ng++] = a[i];
355 next_group:;
356 }
357
358 *png = ng;
359 return (0);
360}
361
c4f2d992 362/* --- @bc__banner@ --- *
363 *
364 * Arguments: @FILE *fp@ = stream to write on
365 *
366 * Returns: ---
367 *
368 * Use: Writes a banner containing copyright information.
369 */
370
371static void bc__banner(FILE *fp)
372{
373 bc__write(fp, "$ version " VERSION "\n");
374}
375
376/* --- @bc__usage@ --- *
377 *
378 * Arguments: @FILE *fp@ = stream to write on
379 *
380 * Returns: ---
381 *
382 * Use: Writes a terse reminder of command line syntax.
383 */
384
385static void bc__usage(FILE *fp)
386{
387 bc__write(fp,
388 "Usage: \n"
389 " $ -c <shell-command> <user>\n"
387501ae 390 " $ [<env-var>] <user> [<command> [<arguments>]...]\n"
f175e71b 391#ifndef NONETWORK
392 " $ -d [-p <port>] [-f <config-file>]\n"
393#endif
394 );
c4f2d992 395}
396
397/* --- @bc__help@ --- *
398 *
399 * Arguments: @FILE *fp@ = stream to write on
569b8891 400 * @int suid@ = whether we're running set-uid
c4f2d992 401 *
402 * Returns: ---
403 *
404 * Use: Displays a help message for this excellent piece of software.
405 */
406
569b8891 407static void bc__help(FILE *fp, int suid)
c4f2d992 408{
409 bc__banner(fp);
410 putc('\n', fp);
411 bc__usage(fp);
412 putc('\n', fp);
413 bc__write(fp,
414"The `$' program allows you to run a process as another user.\n"
415"If a command name is given, this is the process executed. If the `-c'\n"
416"option is used, the process is assumed to be `/bin/sh'. If no command is\n"
417"given, your default login shell is used.\n"
418"\n"
419"Your user id, the user id you wish to become, the name of the process\n"
420"you wish to run, and the identity of the current host are looked up to\n"
421"ensure that you have permission to do this.\n"
422"\n"
423"Note that logs are kept of all uses of this program.\n"
424"\n"
425"Options available are:\n"
426"\n"
387501ae 427"-h, --help Display this help text\n"
428"-u, --usage Display a short usage summary\n"
429"-v, --version Display $'s version number\n"
430"\n"
431"-e, --preserve-environment Try to preserve the current environment\n"
432"-s, --su, --set-user Set environment variables to reflect USER\n"
433"-l, --login Really log in as USER\n"
f175e71b 434" [Default is "
435#if DEFAULT_LOGIN_STYLE == l_preserve
436 "preserve-environment"
437#elif DEFAULT_LOGIN_STYLE == l_setuser
438 "set-user"
439#elif DEFAULT_LOGIN_STYLE == l_login
440 "login"
441#else
442 "poorly configured"
443#endif
444"]\n\n"
a5089273 445"-g GROUP, --group=GROUP Set primary group-id to be GROUP\n"
446#ifdef HAVE_SETGROUPS
447"-k, --keep-groups Keep your current set of groups\n"
448"-m, --merge-groups Merge the lists of groups\n"
449"-r, --replace-groups Replace the list of groups\n"
450#endif
451"\n"
387501ae 452"-c CMD, --command=CMD Run the (Bourne) shell command CMD\n"
f175e71b 453#ifndef NONETWORK
387501ae 454"\n"
455"-d, --daemon Start a daemon\n"
456"-p PORT, --port=PORT In daemon mode, listen on PORT\n"
f175e71b 457"-f FILE, --config-file=FILE In daemon mode, read config from FILE\n"
458#endif
459 );
03f996bd 460#ifdef TRACING
387501ae 461 bc__write(fp, "\n");
569b8891 462 if (!suid) {
463 bc__write(fp,
387501ae 464"-I USER, --impersonate=USER Claim to be USER when asking the server\n");
569b8891 465 }
466 bc__write(fp,
387501ae 467"-T FILE, --trace=FILE Dump trace information to FILE (boring)\n"
468"-L OPTS, --trace-level=OPTS Set level of tracing information\n");
03f996bd 469#endif
c4f2d992 470}
471
472/* --- @main@ --- *
473 *
474 * Arguments: @int argc@ = number of command line arguments
475 * @char *argv[]@ = pointer to the various arguments
476 *
477 * Returns: Zero if successful.
478 *
479 * Use: Allows a user to change UID.
480 */
481
482int main(int argc, char *argv[])
483{
03f996bd 484 /* --- Request block setup parameters --- */
c4f2d992 485
03f996bd 486 request rq; /* Request buffer to build */
487 char *cmd = 0; /* Shell command to execute */
488 char *binary = "/bin/sh"; /* Default binary to execute */
489 char **env = environ; /* Default environment to pass */
387501ae 490 char **todo = 0; /* Pointer to argument list */
491 char *who = 0; /* Who we're meant to become */
03f996bd 492 struct passwd *from_pw = 0; /* User we are right now */
493 struct passwd *to_pw = 0; /* User we want to become */
494
495 /* --- Become server setup parameters --- */
496
f175e71b 497#ifndef NONETWORK
03f996bd 498 char *conffile = file_RULES; /* Default config file for daemon */
9df0c75d 499 int port = 0; /* Default port for daemon */
f175e71b 500#endif
03f996bd 501
502 /* --- Miscellanous shared variables --- */
503
504 unsigned flags = 0; /* Various useful flags */
387501ae 505 int style = DEFAULT_LOGIN_STYLE; /* Login style */
a5089273 506 gid_t group = -1; /* Default group to set */
507 int gstyle = g_unset; /* No group style set yet */
508
509#ifdef HAVE_SETGROUPS
510 gid_t groups[NGROUPS_MAX]; /* Set of groups */
511 int ngroups; /* Number of groups in the set */
512#endif
03f996bd 513
514 /* --- Default argument list executes a shell command --- */
515
516 static char *shell[] = {
517 "/bin/sh", /* Bourne shell */
518 "-c", /* Read from command line */
519 0, /* Pointer to shell command */
520 0 /* Terminator */
c4f2d992 521 };
522
03f996bd 523 /* --- Definitions for the various flags --- */
524
525 enum {
526 f_daemon = 1, /* Start up in daemon mode */
527 f_duff = 2, /* Fault in arguments */
45df69ab 528 f_shell = 4, /* Run a default shell */
03f996bd 529 f_dummy = 8, /* Don't actually do anything */
a5089273 530 f_setuid = 16, /* We're running setuid */
531 f_havegroup = 32 /* Set a default group */
03f996bd 532 };
c4f2d992 533
534 /* --- Set up the program name --- */
535
536 ego(argv[0]);
03f996bd 537 clock();
538 if (getuid() != geteuid())
539 flags |= f_setuid;
c4f2d992 540
387501ae 541 /* --- Read the environment into a hashtable --- */
542
543 {
544 char **p;
545
546 sym_createTable(&bc__env);
547 for (p = environ; *p; p++)
548 bc__putenv(0, *p, 0, 0);
549 }
550
c4f2d992 551 /* --- Parse some command line arguments --- */
552
553 for (;;) {
554 int i;
03f996bd 555 static struct option opts[] = {
387501ae 556
557 /* --- Asking for help --- */
558
c4f2d992 559 { "help", 0, 0, 'h' },
387501ae 560 { "usage", 0, 0, 'u' },
c4f2d992 561 { "version", 0, 0, 'v' },
387501ae 562
563 /* --- Login style options --- */
564
565 { "preserve-environment", 0, 0, 'e' },
566 { "su", 0, 0, 's' },
567 { "set-user", 0, 0, 's' },
03f996bd 568 { "login", 0, 0, 'l' },
387501ae 569
a5089273 570 /* --- Group style options --- */
571
572 { "group", gFlag_argReq, 0, 'g' },
573#ifdef HAVE_SETGROUPS
574 { "keep-groups", 0, 0, 'k' },
575 { "merge-groups", 0, 0, 'm' },
576 { "replace-groups", 0, 0, 'r' },
577#endif
578
387501ae 579 /* --- Command to run options --- */
580
c4f2d992 581 { "command", gFlag_argReq, 0, 'c' },
387501ae 582
583 /* --- Server options --- */
584
f175e71b 585#ifndef NONETWORK
c4f2d992 586 { "daemon", 0, 0, 'd' },
587 { "port", gFlag_argReq, 0, 'p' },
588 { "config-file", gFlag_argReq, 0, 'f' },
f175e71b 589#endif
387501ae 590
591 /* --- Tracing options --- */
592
03f996bd 593#ifdef TRACING
594 { "impersonate", gFlag_argReq, 0, 'I' },
595 { "trace", gFlag_argOpt, 0, 'T' },
596 { "trace-level", gFlag_argOpt, 0, 'L' },
597#endif
387501ae 598
c4f2d992 599 { 0, 0, 0, 0 }
600 };
601
387501ae 602 i = mdwopt(argc, argv,
a5089273 603 "-" /* Return non-options as options */
604 "huv" /* Asking for help */
605 "esl" /* Login style options */
606#ifdef HAVE_SETGROUPS
607 "g:kmr" /* Group style options */
608#else
609 "g:" /* Group (without @setgroups@) */
610#endif
611 "c:" /* Command to run options */
f175e71b 612#ifndef NONETWORK
a5089273 613 "dp:f:" /* Server options */
f175e71b 614#endif
a5089273 615#ifdef TRACING
616 "I:T::L::" /* Tracing options */
617#endif
618 ,
387501ae 619 opts, 0, 0, gFlag_envVar);
c4f2d992 620 if (i < 0)
387501ae 621 goto done_options;
c4f2d992 622
623 switch (i) {
03f996bd 624
387501ae 625 /* --- Asking for help --- */
03f996bd 626
c4f2d992 627 case 'h':
569b8891 628 bc__help(stdout, flags & f_setuid);
629 exit(0);
630 break;
387501ae 631 case 'u':
569b8891 632 bc__usage(stdout);
c4f2d992 633 exit(0);
634 break;
635 case 'v':
636 bc__banner(stdout);
637 exit(0);
638 break;
03f996bd 639
387501ae 640 /* --- Login style options --- */
03f996bd 641
387501ae 642 case 'e':
643 style = l_preserve;
644 break;
645 case 's':
a5089273 646 style = l_setuser;
387501ae 647 break;
03f996bd 648 case 'l':
387501ae 649 style = l_login;
03f996bd 650 break;
387501ae 651
a5089273 652 /* --- Group style options --- */
653
654 case 'g':
655 if (isdigit((unsigned char)optarg[0]))
656 group = atoi(optarg);
657 else {
658 struct group *gr = getgrnam(optarg);
659 if (!gr)
660 die("unknown group `%s'", optarg);
661 group = gr->gr_gid;
662 }
663 flags |= f_havegroup;
664 break;
665
666 case 'k':
667 gstyle = g_keep;
668 break;
669 case 'm':
670 gstyle = g_merge;
671 break;
672 case 'r':
673 gstyle = g_replace;
674 break;
675
387501ae 676 /* --- Command to run options --- */
677
c4f2d992 678 case 'c':
679 cmd = optarg;
680 break;
387501ae 681
682 /* --- Server options --- */
683
f175e71b 684#ifndef NONETWORK
c4f2d992 685 case 'p':
a5089273 686 if (isdigit((unsigned char)optarg[0]))
687 port = htons(atoi(optarg));
688 else {
689 struct servent *s = getservbyname(optarg, "udp");
690 if (!s)
691 die("unknown service name `%s'", optarg);
692 port = s->s_port;
693 }
c4f2d992 694 break;
695 case 'd':
696 flags |= f_daemon;
697 break;
698 case 'f':
699 conffile = optarg;
700 break;
f175e71b 701#endif
03f996bd 702
703 /* --- Pretend to be a different user --- *
704 *
705 * There are all sorts of nasty implications for this option. Don't
706 * allow it if we're running setuid. Disable the actual login anyway.
707 */
708
709#ifdef TRACING
569b8891 710
03f996bd 711 case 'I':
712 if (flags & f_setuid)
713 moan("shan't allow impersonation while running setuid");
714 else {
715 struct passwd *pw;
716 if (isdigit((unsigned char)optarg[0]))
717 pw = getpwuid(atoi(optarg));
718 else
719 pw = getpwnam(optarg);
720 if (!pw)
721 die("can't impersonate unknown user `%s'", optarg);
722 from_pw = userdb_copyUser(pw);
723 rq.from = from_pw->pw_uid;
724 flags |= f_dummy;
725 }
c4f2d992 726 break;
569b8891 727
03f996bd 728#endif
729
730 /* --- Tracing support --- *
731 *
732 * Be careful not to zap a file I wouldn't normally be allowed to write
733 * to!
734 */
735
736#ifdef TRACING
737
738 case 'T': {
739 FILE *fp;
740
741 if (optarg == 0 || strcmp(optarg, "-") == 0)
742 fp = stdout;
743 else {
c6885d5f 744 uid_t eu = geteuid(), ru = getuid();
745
746#ifdef HAVE_SETREUID
747 if (setreuid(eu, ru))
748#else
749 if (seteuid(ru))
750#endif
751 {
752 die("couldn't temporarily give up privileges: %s",
753 strerror(errno));
03f996bd 754 }
c6885d5f 755
03f996bd 756 if ((fp = fopen(optarg, "w")) == 0) {
757 die("couldn't open trace file `%s' for writing: %s",
758 optarg, strerror(errno));
759 }
c6885d5f 760
761#ifdef HAVE_SETREUID
762 if (setreuid(ru, eu))
763#else
764 if (seteuid(eu))
765#endif
766 die("couldn't regain privileges: %s", strerror(errno));
03f996bd 767 }
768 traceon(fp, TRACE_DFL);
769 trace(TRACE_MISC, "become: tracing enabled");
770 } break;
771
772#endif
773
774 /* --- Setting trace levels --- */
775
776#ifdef TRACING
777
778 case 'L': {
779 int sense = 1;
780 unsigned int lvl = 0, l;
781 const char *p = optarg;
782
783 /* --- Table of tracing facilities --- */
784
785 typedef struct tr {
786 char ch;
787 unsigned int l;
788 const char *help;
789 } tr;
790
791 static tr lvltbl[] = {
792 { 'm', TRACE_MISC, "miscellaneous messages" },
793 { 's', TRACE_SETUP, "building the request block" },
03f996bd 794 { 'r', TRACE_RULE, "ruleset scanning" },
795 { 'c', TRACE_CHECK, "request checking" },
f175e71b 796#ifndef NONETWORK
797 { 'd', TRACE_DAEMON, "server process" },
03f996bd 798 { 'l', TRACE_CLIENT, "client process" },
799 { 'R', TRACE_RAND, "random number generator" },
800 { 'C', TRACE_CRYPTO, "cryptographic processing of requests" },
f175e71b 801#endif
03f996bd 802 { 'y', TRACE_YACC, "parsing configuration file" },
803 { 'D', TRACE_DFL, "default tracing options" },
804 { 'A', TRACE_ALL, "all tracing options" },
805 { 0, 0, 0 }
806 };
807 tr *tp;
808
809 /* --- Output some help if there's no arguemnt --- */
810
811 if (!optarg) {
812 bc__banner(stdout);
813 bc__write(stdout,
814 "\n"
815 "Tracing options:\n"
816 "\n");
817 for (tp = lvltbl; tp->l; tp++) {
818 if ((flags & f_setuid) == 0 || tp->l & ~TRACE_PRIV)
819 printf("%c -- %s\n", tp->ch, tp->help);
820 }
821 bc__write(stdout,
822"\n"
9df0c75d 823"Also, `+' and `-' options are recognised to turn on and off various\n"
03f996bd 824"tracing options. For example, `A-r' enables everything except ruleset\n"
825"tracing, and `A-D+c' is everything except the defaults, but with request\n"
826"check tracing.\n"
827);
828 exit(0);
829 }
830
831 while (*p) {
832 if (*p == '+')
833 sense = 1;
834 else if (*p == '-')
835 sense = 0;
836 else {
837 for (tp = lvltbl; tp->l && *p != tp->ch; tp++)
838 ;
839 l = tp->l;
840 if (flags & f_setuid)
841 l &= ~TRACE_PRIV;
842 if (l)
843 lvl = sense ? (lvl | l) : (lvl & ~l);
844 else
845 moan("unknown trace option `%c'", *p);
846 }
847 p++;
848 }
849
850 tracesetlvl(lvl);
851 yydebug = ((lvl & TRACE_YACC) != 0);
852 } break;
853
854#endif
855
387501ae 856 /* --- Something that wasn't an option --- *
857 *
858 * The following nasties are supported:
859 *
860 * * NAME=VALUE -- preserve NAME, and give it a VALUE
861 * * NAME= -- preserve NAME, and give it an empty value
862 * * NAME- -- delete NAME
863 * * NAME! -- preserve NAME with existing value
864 *
865 * Anything else is either the user name (which is OK) or the start of
866 * the command (in which case I stop and read the rest of the command).
867 */
868
869 case 0: {
870 size_t sz = strcspn(optarg, "=-!");
871 sym_env *e;
872
873 /* --- None of the above --- */
874
875 if (optarg[sz] == 0 || (optarg[sz] != '=' && optarg[sz + 1] != 0)) {
9df0c75d 876 if (!who) {
387501ae 877 who = optarg;
9df0c75d 878 break;
879 } else {
387501ae 880 optind--;
881 goto done_options;
882 }
883 }
884
885 /* --- Do the appropriate thing --- */
886
887 switch (optarg[sz]) {
888 case '=':
889 bc__putenv(0, optarg, envFlag_preserve, 1);
890 break;
891 case '-':
892 optarg[sz] = 0;
893 bc__putenv(optarg, 0, 0, 1);
894 break;
895 case '!':
896 optarg[sz] = 0;
897 if ((e = sym_find(&bc__env, optarg, -1, 0, 0)) != 0)
898 e->f |= envFlag_preserve;
899 break;
900 }
901 } break;
902
03f996bd 903 /* --- Something I didn't understand has occurred --- */
904
c4f2d992 905 case '?':
906 flags |= f_duff;
907 break;
908 }
909 }
387501ae 910
911done_options:
c4f2d992 912 if (flags & f_duff) {
913 bc__usage(stderr);
914 exit(1);
915 }
916
917 /* --- Switch to daemon mode if requested --- */
918
f175e71b 919#ifndef NONETWORK
c4f2d992 920 if (flags & f_daemon) {
03f996bd 921 T( trace(TRACE_MISC, "become: daemon mode requested"); )
c4f2d992 922 daemon_init(conffile, port);
923 exit(0);
924 }
f175e71b 925#endif
c4f2d992 926
927 /* --- Open a syslog --- */
928
929 openlog(quis(), 0, LOG_AUTH);
930
931 /* --- Pick out the uid --- */
932
933 {
c4f2d992 934 struct passwd *pw;
03f996bd 935
387501ae 936 if (!who) {
c4f2d992 937 bc__usage(stderr);
938 exit(1);
939 }
03f996bd 940
387501ae 941 if (isdigit((unsigned char)who[0]))
942 pw = getpwuid(atoi(who));
03f996bd 943 else
387501ae 944 pw = getpwnam(who);
c4f2d992 945 if (!pw)
387501ae 946 die("unknown user `%s'", who);
03f996bd 947 to_pw = userdb_copyUser(pw);
c4f2d992 948 rq.to = pw->pw_uid;
949 }
950
951 /* --- Fill in the easy bits of the request --- */
952
03f996bd 953 if (!from_pw) {
954 struct passwd *pw;
955
956 rq.from = getuid();
957 pw = getpwuid(rq.from);
958 if (!pw)
959 die("who are you? (can't find user %li)", (long)rq.from);
960 from_pw = userdb_copyUser(pw);
961 }
c4f2d992 962
963 /* --- Find the local host address --- */
964
965 {
966 struct utsname u;
967 struct hostent *he;
968 uname(&u);
969 if ((he = gethostbyname(u.nodename)) == 0)
970 die("who am I? (can't resolve `%s')", u.nodename);
7df38e1e 971 memcpy(&rq.host, he->h_addr, sizeof(rq.host));
c4f2d992 972 }
973
a5089273 974 /* --- Fiddle with group ownerships a bit --- */
975
976 {
977#ifdef HAVE_SETGROUPS
978 gid_t from_gr[NGROUPS_MAX], to_gr[NGROUPS_MAX];
979 int n_fgr, n_tgr;
980#endif
981
982 /* --- Set the default login group, if there is one --- */
983
984 if (~flags & f_havegroup)
985 group = (style == l_preserve) ? from_pw->pw_gid : to_pw->pw_gid;
986
987#ifndef HAVE_SETGROUPS
988
989 /* --- Check that it's valid --- */
990
991 if (group != from_pw->pw_gid && group != to_pw->pw_gid)
992 die("invalid default group");
993
994#else
995
996 /* --- Set the default group style --- */
997
998 if (gstyle == g_unset)
999 gstyle = (style == l_login) ? g_replace : g_merge;
1000
1001 /* --- Read in my current set of groups --- */
1002
1003 n_fgr = getgroups(NGROUPS_MAX, from_gr);
1004
1005 /* --- Now read the groups for the target user --- *
1006 *
1007 * Do this even if I'm using the @g_keep@ style -- I'll use it for
1008 * checking that @group@ is valid.
1009 */
1010
1011 {
1012 struct group *gr;
1013 char **p;
1014
1015 n_tgr = 0;
1016 to_gr[n_tgr++] = to_pw->pw_gid;
1017
1018 setgrent();
1019 while ((gr = getgrent()) != 0) {
1020 if (gr->gr_gid == to_gr[0])
1021 continue;
1022 for (p = gr->gr_mem; *p; p++) {
1023 if (strcmp(to_pw->pw_name, *p) == 0) {
1024 to_gr[n_tgr++] = gr->gr_gid;
1025 if (n_tgr >= NGROUPS_MAX)
1026 goto done_groups;
1027 break;
1028 }
1029 }
1030 }
1031
1032 done_groups:
1033 endgrent();
1034 }
1035
1036 /* --- Check that @group@ is reasonable --- */
1037
1038 {
1039 int i;
1040
1041 if (group == getgid() || group == from_pw->pw_gid)
1042 goto group_ok;
1043 for (i = 0; i < n_fgr; i++) {
1044 if (group == from_gr[i])
1045 goto group_ok;
1046 }
1047 for (i = 0; i < n_tgr; i++) {
1048 if (group == to_gr[i])
1049 goto group_ok;
1050 }
1051 die("invalid default group");
1052 group_ok:;
1053 }
1054
1055 /* --- Phew. Now comes the hard bit --- */
1056
1057 {
1058 gid_t ga[4];
1059 int i;
1060
1061 i = 0;
1062 ga[i++] = group;
1063 if (gstyle & g_keep) {
1064 ga[i++] = getgid();
1065 ga[i++] = from_pw->pw_gid;
1066 }
1067 if (gstyle & g_replace)
1068 ga[i++] = to_pw->pw_gid;
1069
1070 /* --- Style authorities will become apoplectic if shown this --- *
1071 *
1072 * As far as I can see, it's the neatest way of writing it.
1073 */
1074
1075 ngroups = 0;
1076 (void)(bc__addGroups(groups, &ngroups, ga, i) ||
1077 ((gstyle & g_keep) &&
f175e71b 1078 bc__addGroups(groups, &ngroups, from_gr, n_fgr)) ||
a5089273 1079 ((gstyle & g_replace) &&
1080 bc__addGroups(groups, &ngroups, to_gr, n_tgr)));
1081 }
1082
1083#endif
1084
1085 /* --- Trace the results of all this --- */
1086
1087 T( trace(TRACE_SETUP, "setup: default group == %i", (int)group); )
1088
1089#ifdef HAVE_SETGROUPS
1090 IF_TRACING(TRACE_SETUP, {
1091 int i;
1092
1093 for (i = 1; i < ngroups; i++)
1094 trace(TRACE_SETUP, "setup: subsidiary group %i", (int)groups[i]);
1095 })
1096#endif
1097 }
1098
03f996bd 1099 /* --- Shell commands are easy --- */
c4f2d992 1100
1101 if (cmd) {
1102 shell[2] = cmd;
1103 todo = shell;
03f996bd 1104 }
1105
1106 /* --- A command given on the command line isn't too hard --- */
1107
1108 else if (optind < argc) {
c4f2d992 1109 todo = argv + optind;
03f996bd 1110 binary = todo[0];
1111 }
1112
45df69ab 1113 else {
1114 flags |= f_shell;
03f996bd 1115
45df69ab 1116 switch (style) {
c4f2d992 1117
45df69ab 1118 /* --- An unadorned becoming requires little work --- */
03f996bd 1119
45df69ab 1120 case l_preserve:
1121 shell[0] = getenv("SHELL");
1122 if (!shell[0])
1123 shell[0] = from_pw->pw_shell;
1124 shell[1] = 0;
1125 todo = shell;
1126 binary = todo[0];
1127 break;
387501ae 1128
45df69ab 1129 /* --- An su-like login needs slightly less effort --- */
1130
1131 case l_setuser:
1132 shell[0] = to_pw->pw_shell;
1133 shell[1] = 0;
1134 todo = shell;
1135 binary = todo[0];
1136 break;
1137
1138 /* --- A login request needs a little bit of work --- */
387501ae 1139
45df69ab 1140 case l_login: {
1141 const char *p = strrchr(to_pw->pw_shell, '/');
1142
1143 if (p)
1144 p++;
1145 else
1146 p = to_pw->pw_shell;
1147 shell[0] = xmalloc(strlen(p) + 2);
1148 shell[0][0] = '-';
1149 strcpy(shell[0] + 1, p);
1150 shell[1] = 0;
1151 todo = shell;
1152 binary = to_pw->pw_shell;
1153 } break;
1154 }
03f996bd 1155 }
1156
569b8891 1157 /* --- Mangle the environment --- *
1158 *
387501ae 1159 * This keeps getting more complicated all the time. (How true. Now I've
1160 * got all sorts of nasty environment mangling to do.)
1161 *
1162 * The environment stuff now happens in seven phases:
1163 *
1164 * 1. Mark very special variables to be preserved. Currently only TERM
1165 * and DISPLAY are treated in this way.
1166 *
1167 * 2. Set and preserve Become's own environment variables.
1168 *
1169 * 3. Set and preserve the user identity variables (USER, LOGNAME, HOME,
1170 * SHELL and MAIL) if we're being `su'-like or `login'-like.
1171 *
1172 * 4. If we're preserving the environment or being `su'-like, process the
1173 * PATH variable a little. Otherwise reset it to something
1174 * appropriate.
1175 *
1176 * 5. If we're being `login'-like, expunge all unpreserved variables.
1177 *
1178 * 6. Expunge any security-critical variables.
1179 *
1180 * 7. Build a new environment table to pass to child processes.
569b8891 1181 */
1182
1183 {
387501ae 1184 /* --- Variables to be preserved always --- *
1185 *
1186 * A user can explicitly expunge a variable in this list, in which case
1187 * we never get to see it here.
1188 */
569b8891 1189
387501ae 1190 static char *preserve[] = {
94455fbb 1191 "TERM", "DISPLAY", "TZ", 0
387501ae 1192 };
c6885d5f 1193
387501ae 1194 /* --- Variables to be expunged --- *
569b8891 1195 *
c6885d5f 1196 * Any environment string which has one of the following as a prefix will
1197 * be expunged from the environment passed to the called process. The
1198 * first line lists variables which have been used to list search paths
1199 * for shared libraries: by manipulating these, an attacker could replace
1200 * a standard library with one of his own. The second line lists other
1201 * well-known dangerous environment variables.
569b8891 1202 */
1203
387501ae 1204 static char *banned[] = {
1205 "-LD_", "SHLIB_PATH", "LIBPATH", "-_RLD_",
1206 "IFS", "ENV", "BASH_ENV", "KRB_CONF",
569b8891 1207 0
1208 };
1209
387501ae 1210 /* --- Other useful variables --- */
03f996bd 1211
387501ae 1212 sym_env *e;
1213 char *p, *q, *r;
1214 char **pp, **qq;
1215 size_t sz;
1216 unsigned f;
1217 sym_iter i;
c6885d5f 1218
387501ae 1219 /* --- Stage one. Preserve display-specific variables --- */
569b8891 1220
387501ae 1221 for (pp = preserve; *pp; pp++) {
1222 if ((e = sym_find(&bc__env, *pp, -1, 0, 0)) != 0)
1223 e->f |= envFlag_preserve;
1224 }
569b8891 1225
387501ae 1226 /* --- Stage two. Set Become's own variables --- */
569b8891 1227
387501ae 1228 e = sym_find(&bc__env, "BECOME_ORIGINAL_USER", -1, sizeof(*e), &f);
1229 if (!f)
1230 bc__setenv(e, from_pw->pw_name);
1231 e->f |= envFlag_preserve;
1232
1233 e = sym_find(&bc__env, "BECOME_ORIGINAL_HOME", -1, sizeof(*e), &f);
1234 if (!f)
1235 bc__setenv(e, from_pw->pw_dir);
1236 e->f |= envFlag_preserve;
569b8891 1237
387501ae 1238 bc__putenv("BECOME_OLD_USER", from_pw->pw_name, envFlag_preserve, 0);
1239 bc__putenv("BECOME_OLD_HOME", from_pw->pw_dir, envFlag_preserve, 0);
1240 bc__putenv("BECOME_USER", to_pw->pw_name, envFlag_preserve, 0);
1241 bc__putenv("BECOME_HOME", to_pw->pw_dir, envFlag_preserve, 0);
569b8891 1242
387501ae 1243 /* --- Stage three. Set user identity --- */
1244
1245 switch (style) {
1246 case l_login: {
1247 static char *maildirs[] = {
1248 "/var/spool/mail", "/var/mail",
1249 "/usr/spool/mail", "/usr/mail",
1250 0
1251 };
1252 struct stat s;
1253 char b[128];
1254
1255 for (pp = maildirs; *pp; pp++) {
1256 if (stat(*pp, &s) == 0 && S_ISDIR(s.st_mode)) {
1257 sprintf(b, "%s/%s", *pp, to_pw->pw_name);
1258 bc__putenv("MAIL", b, envFlag_preserve, 0);
1259 break;
569b8891 1260 }
569b8891 1261 }
387501ae 1262 } /* Fall through */
1263
a5089273 1264 case l_setuser:
387501ae 1265 bc__putenv("USER", to_pw->pw_name, envFlag_preserve, 0);
1266 bc__putenv("LOGNAME", to_pw->pw_name, envFlag_preserve, 0);
1267 bc__putenv("HOME", to_pw->pw_dir, envFlag_preserve, 0);
1268 bc__putenv("SHELL", to_pw->pw_shell, envFlag_preserve, 0);
1269 break;
1270 }
1271
1272 /* --- Stage four. Set the user's PATH properly --- */
569b8891 1273
387501ae 1274 {
1275 /* --- Find an existing path --- *
569b8891 1276 *
387501ae 1277 * If there's no path, or this is a login, then set a default path,
1278 * unless we're meant to preserve the existing one. Whew!
569b8891 1279 */
1280
387501ae 1281 e = sym_find(&bc__env, "PATH", -1, sizeof(*e), &f);
1282
1283 if (!f || (style == l_login && ~e->f & envFlag_preserve)) {
1284 bc__putenv("PATH",
1285 rq.to ? "/usr/bin:/bin" : "/usr/bin:/usr/sbin:/bin:/sbin",
1286 envFlag_preserve, 0);
1287 } else {
1288
1289 /* --- Find the string --- */
1290
1291 e->f = envFlag_preserve;
1292 p = strchr(e->val, '=') + 1;
1293 r = e->val;
1294
1295 /* --- Write the new version to a dynamically allocated buffer --- */
1296
1297 e->val = xmalloc(4 + 1 + strlen(p) + 1);
1298 strcpy(e->val, "PATH=");
1299 q = e->val + 5;
1300
1301 for (p = strtok(p, ":"); p; p = strtok(0, ":")) {
6a4ec25c 1302 if (p[0] != '/')
387501ae 1303 continue;
1304 while (*p)
1305 *q++ = *p++;
1306 *q++ = ':';
1307 }
1308 q[-1] = 0;
1309
1310 /* --- Done! --- */
1311
1312 free(r);
569b8891 1313 }
387501ae 1314 }
1315
1316 /* --- Stages five and six. Expunge variables and count numbers --- *
1317 *
1318 * Folded together, so I only need one pass through the table. Also
1319 * count the number of variables needed at this time.
1320 */
1321
1322 sz = 0;
569b8891 1323
387501ae 1324 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; ) {
569b8891 1325
387501ae 1326 /* --- Login style expunges all unpreserved variables --- */
569b8891 1327
387501ae 1328 if (style == l_login && ~e->f & envFlag_preserve)
1329 goto expunge;
1330
1331 /* --- Otherwise just check the name against the list --- */
1332
1333 for (pp = banned; *pp; pp++) {
1334 if (**pp == '-') {
1335 p = *pp + 1;
1336 if (memcmp(e->_base.name, p, strlen(p)) == 0)
1337 goto expunge;
a5089273 1338 } else if (strcmp(e->_base.name, *pp) == 0)
387501ae 1339 goto expunge;
1340 }
1341
1342 sz++;
1343 continue;
1344
1345 expunge:
1346 sym_remove(&bc__env, e);
569b8891 1347 }
387501ae 1348
1349 /* --- Stage seven. Build the new environment block --- */
1350
1351 env = qq = xmalloc((sz + 1) * sizeof(*qq));
1352
1353 for (sym_createIter(&i, &bc__env); (e = sym_next(&i)) != 0; )
1354 *qq++ = e->val;
1355 *qq++ = 0;
03f996bd 1356 }
1357
1358 /* --- Trace the command --- */
1359
1360 IF_TRACING(TRACE_SETUP, {
1361 int i;
1362
1363 trace(TRACE_SETUP, "setup: from user %s to user %s",
1364 from_pw->pw_name, to_pw->pw_name);
1365 trace(TRACE_SETUP, "setup: binary == `%s'", binary);
1366 for (i = 0; todo[i]; i++)
1367 trace(TRACE_SETUP, "setup: arg %i == `%s'", i, todo[i]);
1368 for (i = 0; env[i]; i++)
1369 trace(TRACE_SETUP, "setup: env %i == `%s'", i, env[i]);
1370 })
1371
c4f2d992 1372 /* --- If necessary, resolve the path to the command --- */
1373
03f996bd 1374 if (!strchr(binary, '/')) {
1375 char *path, *p;
c4f2d992 1376 struct stat st;
c4f2d992 1377
1378 if ((p = getenv("PATH")) == 0)
1379 p = "/bin:/usr/bin";
03f996bd 1380 path = xstrdup(p);
c4f2d992 1381
569b8891 1382 for (p = strtok(path, ":"); p; p = strtok(0, ":")) {
c4f2d992 1383
569b8891 1384 /* --- Check length of string before copying --- */
c4f2d992 1385
03f996bd 1386 if (strlen(p) + strlen(binary) + 2 > sizeof(rq.cmd))
c4f2d992 1387 continue;
1388
59f147b0 1389 /* --- Now build the pathname and check it --- *
1390 *
1391 * Issue: user can take advantage of these privileges to decide whether
1392 * a program with a given name exists. I'm not sure that's
1393 * particularly significant: it only works on regular files with
1394 * execute permissions, and if you're relying on the names of these
1395 * being secret to keep your security up, then you're doing something
1396 * deeply wrong anyway. On the other hand, it's useful to allow people
1397 * to be able to execute programs and scripts which they wouldn't
1398 * otherwise have access to. [This problem was brought up on
1399 * Bugtraq, as a complaint against sudo.]
1400 */
c4f2d992 1401
59f147b0 1402 sprintf(rq.cmd, "%s/%s", p, binary);
03f996bd 1403 if (stat(rq.cmd, &st) == 0 && /* Check it exists */
c4f2d992 1404 st.st_mode & 0111 && /* Check it's executable */
c6885d5f 1405 S_ISREG(st.st_mode)) /* Check it's a file */
c4f2d992 1406 break;
1407 }
1408
1409 if (!p)
1410 die("couldn't find `%s' in path", todo[0]);
03f996bd 1411 binary = rq.cmd;
c4f2d992 1412 free(path);
1413 }
03f996bd 1414 T( trace(TRACE_SETUP, "setup: resolve binary to `%s'", binary); )
c4f2d992 1415
1416 /* --- Canonicalise the path string, if necessary --- */
1417
1418 {
1419 char b[CMDLEN_MAX];
1420 char *p;
1421 const char *q;
1422
1423 /* --- Insert current directory name if path not absolute --- */
1424
1425 p = b;
03f996bd 1426 q = binary;
c4f2d992 1427 if (*q != '/') {
1428 if (!getcwd(b, sizeof(b)))
1429 die("couldn't read current directory: %s", strerror(errno));
1430 p += strlen(p);
1431 *p++ = '/';
1432 }
1433
1434 /* --- Now copy over characters from the path string --- */
1435
1436 while (*q) {
1437
569b8891 1438 /* --- Check for buffer overflows here --- *
c4f2d992 1439 *
1440 * I write at most one byte per iteration so this is OK. Remember to
1441 * allow one for the null byte.
1442 */
1443
1444 if (p >= b + sizeof(b) - 1)
03f996bd 1445 die("internal error: buffer overflow while canonifying path");
c4f2d992 1446
1447 /* --- Reduce multiple slashes to just one --- */
1448
1449 if (*q == '/') {
1450 while (*q == '/')
1451 q++;
1452 *p++ = '/';
1453 }
1454
1455 /* --- Handle dots in filenames --- *
1456 *
1457 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
1458 * we've just stuck the current directory on the end of the buffer,
1459 * or we've just put something else on the end.
1460 */
1461
1462 else if (*q == '.' && p[-1] == '/') {
1463
1464 /* --- A simple `./' just gets removed --- */
1465
1466 if (q[1] == 0 || q[1] == '/') {
1467 q++;
1468 p--;
1469 continue;
1470 }
1471
1472 /* --- A `../' needs to be peeled back to the previous `/' --- */
1473
1474 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
1475 q += 2;
1476 p--;
1477 while (p > b && p[-1] != '/')
1478 p--;
1479 if (p > b)
1480 p--;
1481 continue;
1482 }
1483 } else
1484 *p++ = *q++;
1485 }
1486
1487 *p++ = 0;
1488 strcpy(rq.cmd, b);
1489 }
03f996bd 1490 T( trace(TRACE_SETUP, "setup: canonify binary to `%s'", rq.cmd); )
c4f2d992 1491
88e486d5 1492 /* --- Run the check --- *
1493 *
1494 * If the user is already what she wants to be, then print a warning.
1495 * Then, if I was just going to spawn a shell, quit, to reduce user
a5089273 1496 * confusion. Otherwise, do what was wanted anyway. Also, don't bother
1497 * checking if we're already root -- root can do anything anyway, and at
1498 * least this way we get some logging done, and offer a more friendly
1499 * front-end.
88e486d5 1500 */
c4f2d992 1501
88e486d5 1502 if (rq.from == rq.to) {
1503 moan("you already are `%s'!", to_pw->pw_name);
45df69ab 1504 if (flags & f_shell) {
88e486d5 1505 moan("(to prevent confusion, I'm not spawning a shell)");
1506 exit(0);
1507 }
1508 } else {
a5089273 1509 int a = (rq.from == 0) || check(&rq);
c4f2d992 1510
1511 syslog(LOG_INFO,
1512 "permission %s for %s to become %s to run `%s'",
03f996bd 1513 a ? "granted" : "denied", from_pw->pw_name, to_pw->pw_name,
1514 rq.cmd);
c4f2d992 1515
1516 if (!a)
1517 die("permission denied");
1518 }
1519
1520 /* --- Now do the job --- */
1521
03f996bd 1522 T( trace(TRACE_MISC, "become: permission granted"); )
1523
1524 if (flags & f_dummy) {
1525 puts("permission granted");
1526 return (0);
03f996bd 1527 }
a5089273 1528
1529#ifdef HAVE_SETGROUPS
1530 if (setgroups(ngroups, groups) < 0)
1531 die("couldn't set groups: %s", strerror(errno));
1532#endif
1533
1534 if (setgid(group) < 0)
1535 die("couldn't set default group: %s", strerror(errno));
1536 if (setuid(rq.to) < 0)
1537 die("couldn't set uid: %s", strerror(errno));
1538
45df69ab 1539 /* --- If this was a login, change current directory --- */
1540
f175e71b 1541 if ((flags & f_shell) &&
1542 style == l_login &&
1543 chdir(to_pw->pw_dir) < 0) {
45df69ab 1544 moan("couldn't change directory to `%s': %s",
1545 to_pw->pw_dir, strerror(errno));
1546 }
1547
a5089273 1548 /* --- Finally, call the program --- */
1549
1550 fflush(0);
46a5b3c6 1551 closelog();
a5089273 1552 execve(rq.cmd, todo, env);
1553 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
1554 return (127);
c4f2d992 1555}
1556
1557/*----- That's all, folks -------------------------------------------------*/