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