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