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