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