# 2.11BSD#431 - /usr/src/libexec/tcpd/options.c

##### Functions

    process_options   X

##### Variables

    dry_run           X

##### Macros

    EXPAND_ARG        X
    MAXPATHNAMELEN    X
    NEED_ARG          X
    OPT_ARG           X
    USE_LAST          X
    expand_arg        X
    maybe_dup2        X
    need_arg          X
    opt_arg           X
    permit_arg        X
    use_last          X
    whitespace        X

##### struct's

    option            X
    syslog_names      X

       1:  /*
       2:   * General skeleton for adding options to the access control language. The
       3:   * features offered by this module are documented in the hosts_options(5)
       4:   * manual page (source file: hosts_options.5, "nroff -man" format).
       5:   *
       6:   * Notes and warnings for those who want to add features:
       7:   *
       8:   * In case of errors, abort options processing and deny access. There are too
       9:   * many irreversible side effects to make error recovery feasible. For
      10:   * example, it makes no sense to continue after we have already changed the
      11:   * userid.
      12:   *
      13:   * In case of errors, do not terminate the process: the routines might be
      14:   * called from a long-running daemon that should run forever. Instead, call
      15:   * tcpd_jump() which does a non-local goto back into the hosts_access()
      16:   * routine.
      17:   *
      18:   * In case of severe errors, use clean_exit() instead of directly calling
      19:   * exit(), or the inetd may loop on an UDP request.
      20:   *
      21:   * In verification mode (for example, with the "tcpdmatch" command) the
      22:   * "dry_run" flag is set. In this mode, an option function should just "say"
      23:   * what it is going to do instead of really doing it.
      24:   *
      25:   * Some option functions do not return (for example, the twist option passes
      26:   * control to another program). In verification mode (dry_run flag is set)
      27:   * such options should clear the "dry_run" flag to inform the caller of this
      28:   * course of action.
      29:   */
      30:
      31: #ifndef lint
      32: static char sccsid[] = "@(#) options.c 1.16 95/01/08 17:00:28";
      33: #endif
      34:
      35: /* System libraries. */
      36:
      37: #include <sys/types.h>
      38: #include <sys/param.h>
      39: #include <sys/socket.h>
      40: #include <sys/stat.h>
      41: #include <netinet/in.h>
      42: #include <netdb.h>
      43: #include <stdio.h>
      44: #include <syslog.h>
      45: #include <pwd.h>
      46: #include <grp.h>
      47: #include <ctype.h>
      48: #include <setjmp.h>
      49: #include <string.h>
      50:
      51: #ifndef MAXPATHNAMELEN
      52: #define MAXPATHNAMELEN  BUFSIZ
      53: #endif
      54:
      55: /* Local stuff. */
      56:
      57: #include "tcpd.h"
      58:
      59: /* Options runtime support. */
      60:
      61: int     dry_run = 0;            /* flag set in verification mode */
      62: extern jmp_buf tcpd_buf;        /* tcpd_jump() support */
      63:
      64: /* Options parser support. */
      65:
      66: static char whitespace_eq[] = "= \t\r\n";
      67: #define whitespace (whitespace_eq + 1)
      68:
      69: static char *get_field();       /* chew :-delimited field off string */
      70: static char *chop_string();     /* strip leading and trailing blanks */
      71:
      72: /* List of functions that implement the options. Add yours here. */
      73:
      74: static void user_option();      /* execute "user name.group" option */
      75: static void group_option();     /* execute "group name" option */
      76: static void umask_option();     /* execute "umask mask" option */
      77: static void linger_option();        /* execute "linger time" option */
      78: static void keepalive_option();     /* execute "keepalive" option */
      79: static void spawn_option();     /* execute "spawn command" option */
      80: static void twist_option();     /* execute "twist command" option */
      81: static void rfc931_option();        /* execute "rfc931" option */
      82: static void setenv_option();        /* execute "setenv name value" */
      83: static void nice_option();      /* execute "nice" option */
      84: static void severity_option();      /* execute "severity value" */
      85: static void allow_option();     /* execute "allow" option */
      86: static void deny_option();      /* execute "deny" option */
      87: static void banners_option();       /* execute "banners path" option */
      88:
      89: /* Structure of the options table. */
      90:
      91: struct option {
      92:     char   *name;           /* keyword name, case is ignored */
      93:     void  (*func) ();           /* function that does the real work */
      94:     int     flags;          /* see below... */
      95: };
      96:
      97: #define NEED_ARG    (1<<1)      /* option requires argument */
      98: #define USE_LAST    (1<<2)      /* option must be last */
      99: #define OPT_ARG     (1<<3)      /* option has optional argument */
     100: #define EXPAND_ARG  (1<<4)      /* do %x expansion on argument */
     101:
     102: #define need_arg(o) ((o)->flags & NEED_ARG)
     103: #define opt_arg(o)  ((o)->flags & OPT_ARG)
     104: #define permit_arg(o)   ((o)->flags & (NEED_ARG | OPT_ARG))
     105: #define use_last(o) ((o)->flags & USE_LAST)
     106: #define expand_arg(o)   ((o)->flags & EXPAND_ARG)
     107:
     108: /* List of known keywords. Add yours here. */
     109:
     110: static struct option option_table[] = {
     111:     "user", user_option, NEED_ARG,
     112:     "group", group_option, NEED_ARG,
     113:     "umask", umask_option, NEED_ARG,
     114:     "linger", linger_option, NEED_ARG,
     115:     "keepalive", keepalive_option, 0,
     116:     "spawn", spawn_option, NEED_ARG | EXPAND_ARG,
     117:     "twist", twist_option, NEED_ARG | EXPAND_ARG | USE_LAST,
     118:     "rfc931", rfc931_option, OPT_ARG,
     119:     "setenv", setenv_option, NEED_ARG | EXPAND_ARG,
     120:     "nice", nice_option, OPT_ARG,
     121:     "severity", severity_option, NEED_ARG,
     122:     "allow", allow_option, USE_LAST,
     123:     "deny", deny_option, USE_LAST,
     124:     "banners", banners_option, NEED_ARG,
     125:     0,
     126: };
     127:
     128: /* process_options - process access control options */
     129:
     130: void    process_options(options, request)
     131: char   *options;
     132: struct request_info *request;
     133: {
     134:     char   *key;
     135:     char   *value;
     136:     char   *curr_opt;
     137:     char   *next_opt;
     138:     struct option *op;
     139:     char    bf[BUFSIZ];
     140:
     141:     for (curr_opt = get_field(options); curr_opt; curr_opt = next_opt) {
     142:     next_opt = get_field((char *) 0);
     143:
     144:     /*
     145: 	 * Separate the option into name and value parts. For backwards
     146: 	 * compatibility we ignore exactly one '=' between name and value.
     147: 	 */
     148:     curr_opt = chop_string(curr_opt);
     149:     if (*(value = curr_opt + strcspn(curr_opt, whitespace_eq))) {
     150:         if (*value != '=') {
     151:         *value++ = 0;
     152:         value += strspn(value, whitespace);
     153:         }
     154:         if (*value == '=') {
     155:         *value++ = 0;
     156:         value += strspn(value, whitespace);
     157:         }
     158:     }
     159:     if (*value == 0)
     160:         value = 0;
     161:     key = curr_opt;
     162:
     163:     /*
     164: 	 * Disallow missing option names (and empty option fields).
     165: 	 */
     166:     if (*key == 0)
     167:         tcpd_jump("missing option name");
     168:
     169:     /*
     170: 	 * Lookup the option-specific info and do some common error checks.
     171: 	 * Delegate option-specific processing to the specific functions.
     172: 	 */
     173:
     174:     for (op = option_table; op->name && STR_NE(op->name, key); op++)
     175:          /* VOID */ ;
     176:     if (op->name == 0)
     177:         tcpd_jump("bad option name: \"%s\"", key);
     178:     if (!value && need_arg(op))
     179:         tcpd_jump("option \"%s\" requires value", key);
     180:     if (value && !permit_arg(op))
     181:         tcpd_jump("option \"%s\" requires no value", key);
     182:     if (next_opt && use_last(op))
     183:         tcpd_jump("option \"%s\" must be at end", key);
     184:     if (value && expand_arg(op))
     185:         value = chop_string(percent_x(bf, sizeof(bf), value, request));
     186:     if (hosts_access_verbose)
     187:         syslog(LOG_DEBUG, "option:   %s %s", key, value ? value : "");
     188:     (*(op->func)) (value, request);
     189:     }
     190: }
     191:
     192: /* allow_option - grant access */
     193:
     194: /* ARGSUSED */
     195:
     196: static void allow_option(value, request)
     197: char   *value;
     198: struct request_info *request;
     199: {
     200:     longjmp(tcpd_buf, AC_PERMIT);
     201: }
     202:
     203: /* deny_option - deny access */
     204:
     205: /* ARGSUSED */
     206:
     207: static void deny_option(value, request)
     208: char   *value;
     209: struct request_info *request;
     210: {
     211:     longjmp(tcpd_buf, AC_DENY);
     212: }
     213:
     214: /* banners_option - expand %<char>, terminate each line with CRLF */
     215:
     216: static void banners_option(value, request)
     217: char   *value;
     218: struct request_info *request;
     219: {
     220:     char    path[MAXPATHNAMELEN];
     221:     char    ibuf[BUFSIZ];
     222:     char    obuf[2 * BUFSIZ];
     223:     int     ch;
     224:     FILE   *fp;
     225:
     226:     sprintf(path, "%s/%s", value, eval_daemon(request));
     227:     if ((fp = fopen(path, "r")) != 0) {
     228:     while ((ch = fgetc(fp)) == 0)
     229:         write(request->fd, "", 1);
     230:     ungetc(ch, fp);
     231:     while (fgets(ibuf, sizeof(ibuf) - 1, fp)) {
     232:         if (split_at(ibuf, '\n'))
     233:         strcat(ibuf, "\r\n");
     234:         percent_x(obuf, sizeof(obuf), ibuf, request);
     235:         write(request->fd, obuf, strlen(obuf));
     236:     }
     237:     fclose(fp);
     238:     }
     239: }
     240:
     241: /* group_option - switch group id */
     242:
     243: /* ARGSUSED */
     244:
     245: static void group_option(value, request)
     246: char   *value;
     247: struct request_info *request;
     248: {
     249:     struct group *grp;
     250:     struct group *getgrnam();
     251:
     252:     if ((grp = getgrnam(value)) == 0)
     253:     tcpd_jump("unknown group: \"%s\"", value);
     254:     endgrent();
     255:
     256:     if (dry_run == 0 && setgid(grp->gr_gid))
     257:     tcpd_jump("setgid(%s): %m", value);
     258: }
     259:
     260: /* user_option - switch user id */
     261:
     262: /* ARGSUSED */
     263:
     264: static void user_option(value, request)
     265: char   *value;
     266: struct request_info *request;
     267: {
     268:     struct passwd *pwd;
     269:     struct passwd *getpwnam();
     270:     char   *group;
     271:
     272:     if ((group = split_at(value, '.')) != 0)
     273:     group_option(group, request);
     274:     if ((pwd = getpwnam(value)) == 0)
     275:     tcpd_jump("unknown user: \"%s\"", value);
     276:     endpwent();
     277:
     278:     if (dry_run == 0 && setuid(pwd->pw_uid))
     279:     tcpd_jump("setuid(%s): %m", value);
     280: }
     281:
     282: /* umask_option - set file creation mask */
     283:
     284: /* ARGSUSED */
     285:
     286: static void umask_option(value, request)
     287: char   *value;
     288: struct request_info *request;
     289: {
     290:     unsigned mask;
     291:     char    junk;
     292:
     293:     if (sscanf(value, "%o%c", &mask, &junk) != 1 || (mask & 0777) != mask)
     294:     tcpd_jump("bad umask value: \"%s\"", value);
     295:     (void) umask(mask);
     296: }
     297:
     298: /* spawn_option - spawn a shell command and wait */
     299:
     300: /* ARGSUSED */
     301:
     302: static void spawn_option(value, request)
     303: char   *value;
     304: struct request_info *request;
     305: {
     306:     if (dry_run == 0)
     307:     shell_cmd(value);
     308: }
     309:
     310: /* linger_option - set the socket linger time (Marc Boucher <marc@cam.org>) */
     311:
     312: /* ARGSUSED */
     313:
     314: static void linger_option(value, request)
     315: char   *value;
     316: struct request_info *request;
     317: {
     318:     struct linger linger;
     319:     char    junk;
     320:
     321:     if (sscanf(value, "%d%c", &linger.l_linger, &junk) != 1
     322:     || linger.l_linger < 0)
     323:     tcpd_jump("bad linger value: \"%s\"", value);
     324:     if (dry_run == 0) {
     325:     linger.l_onoff = (linger.l_linger != 0);
     326:     if (setsockopt(request->fd, SOL_SOCKET, SO_LINGER, (char *) &linger,
     327:                sizeof(linger)) < 0)
     328:         tcpd_warn("setsockopt SO_LINGER %d: %m", linger.l_linger);
     329:     }
     330: }
     331:
     332: /* keepalive_option - set the socket keepalive option */
     333:
     334: /* ARGSUSED */
     335:
     336: static void keepalive_option(value, request)
     337: char   *value;
     338: struct request_info *request;
     339: {
     340:     static int on = 1;
     341:
     342:     if (dry_run == 0 && setsockopt(request->fd, SOL_SOCKET, SO_KEEPALIVE,
     343:                    (char *) &on, sizeof(on)) < 0)
     344:     tcpd_warn("setsockopt SO_KEEPALIVE: %m");
     345: }
     346:
     347: /* nice_option - set nice value */
     348:
     349: /* ARGSUSED */
     350:
     351: static void nice_option(value, request)
     352: char   *value;
     353: struct request_info *request;
     354: {
     355:     int     niceval = 10;
     356:     char    junk;
     357:
     358:     if (value != 0 && sscanf(value, "%d%c", &niceval, &junk) != 1)
     359:     tcpd_jump("bad nice value: \"%s\"", value);
     360:     if (dry_run == 0 && nice(niceval) < 0)
     361:     tcpd_warn("nice(%d): %m", niceval);
     362: }
     363:
     364: /* twist_option - replace process by shell command */
     365:
     366: static void twist_option(value, request)
     367: char   *value;
     368: struct request_info *request;
     369: {
     370:     char   *error;
     371:
     372:     if (dry_run != 0) {
     373:     dry_run = 0;
     374:     } else {
     375:     syslog(deny_severity, "twist %s to %s", eval_client(request), value);
     376:
     377:     /* Before switching to the shell, set up stdin, stdout and stderr. */
     378:
     379: #define maybe_dup2(from, to) ((from == to) ? to : (close(to), dup(from)))
     380:
     381:     if (maybe_dup2(request->fd, 0) != 0 ||
     382:         maybe_dup2(request->fd, 1) != 1 ||
     383:         maybe_dup2(request->fd, 2) != 2) {
     384:         error = "twist_option: dup: %m";
     385:     } else {
     386:         if (request->fd > 2)
     387:         close(request->fd);
     388:         (void) execl("/bin/sh", "sh", "-c", value, (char *) 0);
     389:         error = "twist_option: /bin/sh: %m";
     390:     }
     391:
     392:     /* Something went wrong: we MUST terminate the process. */
     393:
     394:     tcpd_warn(error);
     395:     clean_exit(request);
     396:     }
     397: }
     398:
     399: /* rfc931_option - look up remote user name */
     400:
     401: static void rfc931_option(value, request)
     402: char   *value;
     403: struct request_info *request;
     404: {
     405:     int     timeout;
     406:     char    junk;
     407:
     408:     if (value != 0) {
     409:     if (sscanf(value, "%d%c", &timeout, &junk) != 1 || timeout <= 0)
     410:         tcpd_jump("bad rfc931 timeout: \"%s\"", value);
     411:     rfc931_timeout = timeout;
     412:     }
     413:     (void) eval_user(request);
     414: }
     415:
     416: /* setenv_option - set environment variable */
     417:
     418: /* ARGSUSED */
     419:
     420: static void setenv_option(value, request)
     421: char   *value;
     422: struct request_info *request;
     423: {
     424:     char   *var_value;
     425:
     426:     if (*(var_value = value + strcspn(value, whitespace)))
     427:     *var_value++ = 0;
     428:     if (setenv(chop_string(value), chop_string(var_value), 1))
     429:     tcpd_jump("memory allocation failure");
     430: }
     431:
     432:  /*
     433:   * The severity option goes last because it comes with a huge amount of ugly
     434:   * #ifdefs and tables.
     435:   */
     436:
     437: struct syslog_names {
     438:     char   *name;
     439:     int     value;
     440: };
     441:
     442: static struct syslog_names log_fac[] = {
     443: #ifdef LOG_KERN
     444:     "kern", LOG_KERN,
     445: #endif
     446: #ifdef LOG_USER
     447:     "user", LOG_USER,
     448: #endif
     449: #ifdef LOG_MAIL
     450:     "mail", LOG_MAIL,
     451: #endif
     452: #ifdef LOG_DAEMON
     453:     "daemon", LOG_DAEMON,
     454: #endif
     455: #ifdef LOG_AUTH
     456:     "auth", LOG_AUTH,
     457: #endif
     458: #ifdef LOG_LPR
     459:     "lpr", LOG_LPR,
     460: #endif
     461: #ifdef LOG_NEWS
     462:     "news", LOG_NEWS,
     463: #endif
     464: #ifdef LOG_UUCP
     465:     "uucp", LOG_UUCP,
     466: #endif
     467: #ifdef LOG_CRON
     468:     "cron", LOG_CRON,
     469: #endif
     470: #ifdef LOG_LOCAL0
     471:     "local0", LOG_LOCAL0,
     472: #endif
     473: #ifdef LOG_LOCAL1
     474:     "local1", LOG_LOCAL1,
     475: #endif
     476: #ifdef LOG_LOCAL2
     477:     "local2", LOG_LOCAL2,
     478: #endif
     479: #ifdef LOG_LOCAL3
     480:     "local3", LOG_LOCAL3,
     481: #endif
     482: #ifdef LOG_LOCAL4
     483:     "local4", LOG_LOCAL4,
     484: #endif
     485: #ifdef LOG_LOCAL5
     486:     "local5", LOG_LOCAL5,
     487: #endif
     488: #ifdef LOG_LOCAL6
     489:     "local6", LOG_LOCAL6,
     490: #endif
     491: #ifdef LOG_LOCAL7
     492:     "local7", LOG_LOCAL7,
     493: #endif
     494:     0,
     495: };
     496:
     497: static struct syslog_names log_sev[] = {
     498: #ifdef LOG_EMERG
     499:     "emerg", LOG_EMERG,
     500: #endif
     501: #ifdef LOG_ALERT
     502:     "alert", LOG_ALERT,
     503: #endif
     504: #ifdef LOG_CRIT
     505:     "crit", LOG_CRIT,
     506: #endif
     507: #ifdef LOG_ERR
     508:     "err", LOG_ERR,
     509: #endif
     510: #ifdef LOG_WARNING
     511:     "warning", LOG_WARNING,
     512: #endif
     513: #ifdef LOG_NOTICE
     514:     "notice", LOG_NOTICE,
     515: #endif
     516: #ifdef LOG_INFO
     517:     "info", LOG_INFO,
     518: #endif
     519: #ifdef LOG_DEBUG
     520:     "debug", LOG_DEBUG,
     521: #endif
     522:     0,
     523: };
     524:
     525: /* severity_map - lookup facility or severity value */
     526:
     527: static int severity_map(table, name)
     528: struct syslog_names *table;
     529: char   *name;
     530: {
     531:     struct syslog_names *t;
     532:
     533:     for (t = table; t->name; t++)
     534:     if (STR_EQ(t->name, name))
     535:         return (t->value);
     536:     tcpd_jump("bad syslog facility or severity: \"%s\"", name);
     537:     /* NOTREACHED */
     538: }
     539:
     540: /* severity_option - change logging severity for this event (Dave Mitchell) */
     541:
     542: /* ARGSUSED */
     543:
     544: static void severity_option(value, request)
     545: char   *value;
     546: struct request_info *request;
     547: {
     548:     char   *level = split_at(value, '.');
     549:
     550:     allow_severity = deny_severity = level ?
     551:     severity_map(log_fac, value) | severity_map(log_sev, level) :
     552:     severity_map(log_sev, value);
     553: }
     554:
     555: /* get_field - return pointer to next field in string */
     556:
     557: static char *get_field(string)
     558: char   *string;
     559: {
     560:     static char *last = "";
     561:     char   *src;
     562:     char   *dst;
     563:     char   *ret;
     564:     int     ch;
     565:
     566:     /*
     567:      * This function returns pointers to successive fields within a given
     568:      * string. ":" is the field separator; warn if the rule ends in one. It
     569:      * replaces a "\:" sequence by ":", without treating the result of
     570:      * substitution as field terminator. A null argument means resume search
     571:      * where the previous call terminated. This function destroys its
     572:      * argument.
     573:      *
     574:      * Work from explicit source or from memory. While processing \: we
     575:      * overwrite the input. This way we do not have to maintain buffers for
     576:      * copies of input fields.
     577:      */
     578:
     579:     src = dst = ret = (string ? string : last);
     580:     if (src[0] == 0)
     581:     return (0);
     582:
     583:     while (ch = *src) {
     584:     if (ch == ':') {
     585:         if (*++src == 0)
     586:         tcpd_warn("rule ends in \":\"");
     587:         break;
     588:     }
     589:     if (ch == '\\' && src[1] == ':')
     590:         src++;
     591:     *dst++ = *src++;
     592:     }
     593:     last = src;
     594:     *dst = 0;
     595:     return (ret);
     596: }
     597:
     598: /* chop_string - strip leading and trailing blanks from string */
     599:
     600: static char *chop_string(string)
     601: register char *string;
     602: {
     603:     char   *start = 0;
     604:     char   *end;
     605:     char   *cp;
     606:
     607:     for (cp = string; *cp; cp++) {
     608:     if (!isspace(*cp)) {
     609:         if (start == 0)
     610:         start = cp;
     611:         end = cp;
     612:     }
     613:     }
     614:     return (start ? (end[1] = 0, start) : cp);
     615: }

#### Defined functions

allow_option defined in line 196; used 2 times

- in line 85, 122

banners_option defined in line 216; used 2 times

- in line 87, 124

chop_string defined in line 600; used 5 times

- in line 70, 148, 185, 428(2)

deny_option defined in line 207; used 2 times

- in line 86, 123

get_field defined in line 557; used 3 times

- in line 69, 141-142(2)

group_option defined in line 245; used 3 times

- in line 75, 112, 273

keepalive_option defined in line 336; used 2 times

- in line 78, 115

linger_option defined in line 314; used 2 times

- in line 77, 114

nice_option defined in line 351; used 2 times

- in line 83, 120

process_options defined in line 130; used 3 times

- in /usr/src/libexec/tcpd/hosts_access.c line 163
- in /usr/src/libexec/tcpd/tcpd.h line 180
- in /usr/src/libexec/tcpd/tcpdchk.c line 229

rfc931_option defined in line 401; used 2 times

- in line 81, 118

setenv_option defined in line 420; used 2 times

- in line 82, 119

severity_map defined in line 527; used 3 times

- in line 551-552(3)

severity_option defined in line 544; used 2 times

- in line 84, 121

spawn_option defined in line 302; used 2 times

- in line 79, 116

twist_option defined in line 366; used 2 times

- in line 80, 117

umask_option defined in line 286; used 2 times

- in line 76, 113

user_option defined in line 264; used 2 times

- in line 74, 111

#### Defined variables

dry_run defined in line 61; used 12 times

- in line 256, 278, 306, 324, 342, 360, 372-373(2)
- in /usr/src/libexec/tcpd/tcpdchk.c line 228-230(2)
- in /usr/src/libexec/tcpd/tcpdmatch.c line 295, 313

log_fac defined in line 442; used 1 times

- in line 551

log_sev defined in line 497; used 2 times

- in line 551-552(2)

option_table defined in line 110; used 1 times

- in line 174

sccsid defined in line 32; never used
whitespace_eq defined in line 66; used 2 times

- in line 67, 149

#### Defined struct's

option defined in line 91; used 4 times

- in line 110(2), 138(2)

syslog_names defined in line 437; used 8 times

- in line 442(2), 497(2), 528-531(4)

#### Defined macros

EXPAND_ARG defined in line 100; used 4 times

- in line 106, 116-119(3)

MAXPATHNAMELEN defined in line 52; used 2 times

- in line 51, 220

NEED_ARG defined in line 97; used 11 times

- in line 102-104(2), 111-124(9)

OPT_ARG defined in line 99; used 4 times

- in line 103-104(2), 118-120(2)

USE_LAST defined in line 98; used 4 times

- in line 105, 117-123(3)

expand_arg defined in line 106; used 1 times

- in line 184

maybe_dup2 defined in line 379; used 3 times

- in line 381-383(3)

need_arg defined in line 102; used 1 times

- in line 178

opt_arg defined in line 103; never used
permit_arg defined in line 104; used 1 times

- in line 180

use_last defined in line 105; used 1 times

- in line 182

whitespace defined in line 67; used 3 times

- in line 152-156(2), 426

Last modified: 1995-01-08 Generated: 2016-12-26 | Generated by src2html V0.67 page hit count: 1655 |