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