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