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