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