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