root/usr/src/cmd/bnu/sysfiles.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

#include "uucp.h"

#include <unistd.h>
#include "sysfiles.h"
#include <sys/stropts.h>

/*
 * manage systems files (Systems, Devices, and Dialcodes families).
 *
 * also manage new file Devconfig, allows per-device setup.
 * present use is to specify what streams modules to push/pop for
 * AT&T TLI/streams network.
 *
 * TODO:
 *    call bsfix()?
 *    combine the 3 versions of everything (sys, dev, and dial) into one.
 *    allow arbitrary classes of service.
 *    need verifysys() for uucheck.
 *    nameserver interface?
 *    pass sysname (or 0) to getsysline().  (might want reg. exp. or NS processing
 */

/* private variables */
static void tokenize(), nameparse(), setfile(), setioctl(),
        scansys(), scancfg(), setconfig();
static int namematch(), nextdialers(), nextdevices(), nextsystems(), getaline();

/* pointer arrays might be dynamically allocated */
static char *Systems[64] = {0}; /* list of Systems files */
static char *Devices[64] = {0}; /* list of Devices files */
static char *Dialers[64] = {0}; /* list of Dialers files */
static char *Pops[64] = {0};    /* list of STREAMS modules to be popped */
static char *Pushes[64] = {0};  /* list of STREAMS modules to be pushed */

static int nsystems;            /* index into list of Systems files */
static int ndevices;            /* index into list of Devices files */
static int ndialers;            /* index into list of Dialers files */
static int npops;               /* index into list of STREAMS modules */
                                                        /*to be popped */
static int npushes;             /* index into list of STREAMS modules */
                                                        /*to be pushed */

GLOBAL unsigned connecttime = CONNECTTIME;
GLOBAL unsigned expecttime = EXPECTTIME;
GLOBAL unsigned msgtime = MSGTIME;

static FILE *fsystems;
static FILE *fdevices;
static FILE *fdialers;

static char errformat[BUFSIZ];

/* this might be dynamically allocated */
#define NTOKENS 16
static char *tokens[NTOKENS], **tokptr;

/* export these */
EXTERN void sysreset(), devreset(), dialreset(), setdevcfg(), setservice();
EXTERN char *strsave();

/* import these */
extern char *strcpy(), *strtok(), *strchr(), *strsave();
EXTERN int eaccess();

/*
 * setservice init's Systems, Devices, Dialers lists from Sysfiles
 */
GLOBAL void
setservice(service)
char *service;
{
        char    *prev = _uu_setlocale(LC_ALL, "C");

        setconfig();
        scansys(service);
        (void) _uu_resetlocale(LC_ALL, prev);
        return;
}

/*
 * setdevcfg init's Pops, Pushes lists from Devconfig
 */

GLOBAL void
setdevcfg(service, device)
char *service, *device;
{
        char    *prev = _uu_setlocale(LC_ALL, "C");

        scancfg(service, device);
        (void) _uu_resetlocale(LC_ALL, prev);
        return;
}

/*      administrative files access */
GLOBAL int
sysaccess(type)
int type;
{
        switch (type) {

        case ACCESS_SYSTEMS:
                return(access(Systems[nsystems], R_OK));
        case ACCESS_DEVICES:
                return(access(Devices[ndevices], R_OK));
        case ACCESS_DIALERS:
                return(access(Dialers[ndialers], R_OK));
        case EACCESS_SYSTEMS:
                return(eaccess(Systems[nsystems], R_OK));
        case EACCESS_DEVICES:
                return(eaccess(Devices[ndevices], R_OK));
        case EACCESS_DIALERS:
                return(eaccess(Dialers[ndialers], R_OK));
        default:
                (void)sprintf(errformat, "bad access type %d", type);
                logent(errformat, "sysaccess");
                return(FAIL);
        }
}


/*
 * read Sysfiles, set up lists of Systems/Devices/Dialers file names.
 * allow multiple entries for a given service, allow a service
 * type to describe resources more than once, e.g., systems=foo:baz systems=bar.
 */
static void
scansys(service)
char *service;
{       FILE *f;
        char *tok, buf[BUFSIZ];

        Systems[0] = Devices[0] = Dialers[0] = NULL;
        if ((f = fopen(SYSFILES, "r")) != 0) {
                while (getaline(f, buf) > 0) {
                        /* got a (logical) line from Sysfiles */
                        /* strtok's of this buf continue in tokenize() */
                        tok = strtok(buf, " \t");
                        if (namematch("service=", tok, service)) {
                                tokenize();
                                nameparse();
                        }
                }
                (void) fclose(f);
        }

        /* if didn't find entries in Sysfiles, use defaults */
        if (Systems[0] == NULL) {
                Systems[0] = strsave(SYSTEMS);
                ASSERT(Systems[0] != NULL, Ct_ALLOCATE, "scansys: Systems", 0);
                Systems[1] = NULL;
        }
        if (Devices[0] == NULL) {
                Devices[0] = strsave(DEVICES);
                ASSERT(Devices[0] != NULL, Ct_ALLOCATE, "scansys: Devices", 0);
                Devices[1] = NULL;
        }
        if (Dialers[0] == NULL) {
                Dialers[0] = strsave(DIALERS);
                ASSERT(Dialers[0] != NULL, Ct_ALLOCATE, "scansys: Dialers", 0);
                Dialers[1] = NULL;
        }
        return;
}


/*
 * read Devconfig.  allow multiple entries for a given service, allow a service
 * type to describe resources more than once, e.g., push=foo:baz push=bar.
 */
static void
scancfg(service, device)
char *service, *device;
{       FILE *f;
        char *tok, buf[BUFSIZ];

        /* (re)initialize device-specific information */

        npops = npushes = 0;
        Pops[0] = Pushes[0] = NULL;
        connecttime = CONNECTTIME;
        expecttime = EXPECTTIME;
        msgtime = MSGTIME;

        if ((f = fopen(DEVCONFIG, "r")) != 0) {
                while (getaline(f, buf) > 0) {
                        /* got a (logical) line from Devconfig */
                        /* strtok's of this buf continue in tokenize() */
                        tok = strtok(buf, " \t");
                        if (namematch("service=", tok, service)) {
                                tok = strtok((char *)0, " \t");
                                if ( namematch("device=", tok, device)) {
                                        tokenize();
                                        nameparse();
                                }
                        }
                }
                (void) fclose(f);
        }

        return;

}

/*
 *  given a file pointer and buffer, construct logical line in buffer
 *  (i.e., concatenate lines ending in '\').  return length of line
 *  ASSUMES that buffer is BUFSIZ long!
 */

static int
getaline(f, line)
FILE *f;
char *line;
{       char *lptr, *lend;

        lptr = line;
        while (fgets(lptr, (line + BUFSIZ) - lptr, f) != NULL) {
                lend = lptr + strlen(lptr);
                if (lend == lptr || lend[-1] != '\n')
                        /* empty buf or line too long! */
                        break;
                *--lend = '\0'; /* lop off ending '\n' */
                if ( lend == line ) /* empty line - ignore */
                        continue;
                lptr = lend;
                if (lend[-1] != '\\')
                        break;
                /* continuation */
                lend[-1] = ' ';
        }
        return(lptr - line);
}

/*
 * given a label (e.g., "service=", "device="), a name ("cu", "uucico"),
 *  and a line:  if line begins with the label and if the name appears
 * in a colon-separated list of names following the label, return true;
 * else return false
 */
static int
namematch(label, line, name)
char *label, *line, *name;
{       char *lend;

        if (strncmp(label, line, strlen(label)) != SAME) {
                return(FALSE);  /* probably a comment line */
        }
        line += strlen(label);
        if (*line == '\0')
                return(FALSE);
        /*
         * can't use strtok() in the following because scansys(),
         * scancfg() do an initializing call to strtok() before
         * coming here and then CONTINUE calling strtok() in tokenize(),
         * after returning from namematch().
         */
        while ((lend = strchr(line, ':')) != NULL) {
                *lend = '\0';
                if (strcmp(line, name) == SAME)
                        return(TRUE);
                line = lend+1;
        }
        return(strcmp(line, name) == SAME);
}

/*
 * tokenize() continues pulling tokens out of a buffer -- the
 * initializing call to strtok must have been made before calling
 * tokenize() -- and starts stuffing 'em into tokptr.
 */
static void
tokenize()
{       char *tok;

        tokptr = tokens;
        while ((tok = strtok((char *) NULL, " \t")) != NULL) {
                *tokptr++ = tok;
                if (tokptr - tokens >= NTOKENS)
                        break;
        }
        *tokptr = NULL;
        return;
}

/*
 * look at top token in array: should be line of the form
 *      name=item1:item2:item3...
 * if name is one we recognize, then call set[file|ioctl] to set up
 * corresponding list.  otherwise, log bad name.
 */
static void
nameparse()
{       char **line, *equals;
        int temp;

#define setuint(a,b,c) a = ( ((temp = atoi(b)) <= 0) ? (c) : temp )

        for (line = tokens; (line - tokens) < NTOKENS && *line; line++) {
                equals = strchr(*line, '=');
                if (equals == NULL)
                        continue;       /* may be meaningful someday? */
                *equals = '\0';
                /* ignore entry with empty rhs */
                if (*++equals == '\0')
                        continue;
                if (strcmp(*line, "systems") == SAME)
                        setfile(Systems, equals);
                else if (strcmp(*line, "devices") == SAME)
                        setfile(Devices, equals);
                else if (strcmp(*line, "dialers") == SAME)
                        setfile(Dialers, equals);
                else if (strcmp(*line, "pop") == SAME)
                        setioctl(Pops, equals);
                else if (strcmp(*line, "push") == SAME)
                        setioctl(Pushes, equals);
                else if (strcmp(*line, "connecttime") == SAME)
                        setuint(connecttime, equals, CONNECTTIME);
                else if (strcmp(*line, "expecttime") == SAME)
                        setuint(expecttime, equals, EXPECTTIME);
                else if (strcmp(*line, "msgtime") == SAME)
                        setuint(msgtime, equals, MSGTIME);
                else {
                        (void)sprintf(errformat,"unrecognized label %s",*line);
                        logent(errformat, "Sysfiles|Devconfig");
                }
        }
        return;
}

/*
 * given the list for a particular type (systems, devices,...)
 * and a line of colon-separated files, add 'em to list
 */

static void
setfile(type, line)
char **type, *line;
{       char **tptr, *tok;
        char expandpath[BUFSIZ];

        if (*line == 0)
                return;
        tptr = type;
        while (*tptr)           /* skip over existing entries to*/
                tptr++;         /* concatenate multiple entries */

        for (tok = strtok(line, ":"); tok != NULL;
        tok = strtok((char *) NULL, ":")) {
                expandpath[0] = '\0';
                if ( *tok != '/' )
                        /* by default, file names are relative to SYSDIR */
                        sprintf(expandpath, "%s/", SYSDIR);
                strcat(expandpath, tok);
                if (eaccess(expandpath, R_OK) != 0)
                        /* if we can't read it, no point in adding to list */
                        continue;
                *tptr = strsave(expandpath);
                ASSERT(*tptr != NULL, Ct_ALLOCATE, "setfile: tptr", 0);
                tptr++;
        }
        return;
}

/*
 * given the list for a particular ioctl (push, pop)
 * and a line of colon-separated modules, add 'em to list
 */

static void
setioctl(type, line)
char **type, *line;
{       char **tptr, *tok;

        if (*line == 0)
                return;
        tptr = type;
        while (*tptr)           /* skip over existing entries to*/
                tptr++;         /* concatenate multiple entries */
        for (tok = strtok(line, ":"); tok != NULL;
        tok = strtok((char *) NULL, ":")) {
                *tptr = strsave(tok);
                ASSERT(*tptr != NULL, Ct_ALLOCATE, "setioctl: tptr", 0);
                tptr++;
        }
        return;
}

/*
 * reset Systems files
 */
GLOBAL void
sysreset()
{
        if (fsystems)
                fclose(fsystems);
        fsystems = NULL;
        nsystems = 0;
        devreset();
        return;
}

/*
 * reset Devices files
 */
GLOBAL void
devreset()
{
        if (fdevices)
                fclose(fdevices);
        fdevices = NULL;
        ndevices = 0;
        dialreset();
        return;
}

/*
 * reset Dialers files
 */
GLOBAL void
dialreset()
{
        if (fdialers)
                fclose(fdialers);
        fdialers = NULL;
        ndialers = 0;
        return;
}

/*
 * get next line from Systems file
 * return TRUE if successful, FALSE if not
 */
GLOBAL int
getsysline(char *buf, int len)
{
        char    *prev = _uu_setlocale(LC_ALL, "C");

        if (Systems[0] == NULL)
                /* not initialized via setservice() - use default */
                setservice("uucico");

        /* initialize devices and dialers whenever a new line is read */
        /* from systems */
        devreset();
        if (fsystems == NULL)
                if (nextsystems() == FALSE) {
                        (void) _uu_resetlocale(LC_ALL, prev);
                        return(FALSE);
                }

        ASSERT(len >= BUFSIZ, "BUFFER TOO SMALL", "getsysline", 0);
        for(;;) {
                while (getaline(fsystems, buf) != 0)
                    if ((*buf != '#') && (*buf != ' ') &&
                        (*buf != '\t') && (*buf != '\n')) {
                        (void) _uu_resetlocale(LC_ALL, prev);
                        return(TRUE);
                }
                if (nextsystems() == FALSE) {
                        (void) _uu_resetlocale(LC_ALL, prev);
                        return(FALSE);
                }
        }
}

/*
 * move to next systems file.  return TRUE if successful, FALSE if not
 */
static int
nextsystems()
{
        devreset();

        if (fsystems != NULL) {
                (void) fclose(fsystems);
                nsystems++;
        } else {
                nsystems = 0;
        }
        for ( ; Systems[nsystems] != NULL; nsystems++)
                if ((fsystems = fopen(Systems[nsystems], "r")) != NULL)
                        return(TRUE);
        return(FALSE);
}

/*
 * get next line from Devices file
 * return TRUE if successful, FALSE if not
 */
GLOBAL int
getdevline(char *buf, int len)
{
        char    *prev = _uu_setlocale(LC_ALL, "C");

        if (Devices[0] == NULL)
                /* not initialized via setservice() - use default */
                setservice("uucico");

        if (fdevices == NULL)
                if (nextdevices() == FALSE) {
                        (void) _uu_resetlocale(LC_ALL, prev);
                        return(FALSE);
                }
        for(;;) {
                if (fgets(buf, len, fdevices) != NULL) {
                        (void) _uu_resetlocale(LC_ALL, prev);
                        return(TRUE);
                }
                if (nextdevices() == FALSE) {
                        (void) _uu_resetlocale(LC_ALL, prev);
                        return(FALSE);
                }
        }
}

/*
 * move to next devices file.  return TRUE if successful, FALSE if not
 */
static int
nextdevices()
{
        if (fdevices != NULL) {
                (void) fclose(fdevices);
                ndevices++;
        } else {
                ndevices = 0;
        }
        for ( ; Devices[ndevices] != NULL; ndevices++)
                if ((fdevices = fopen(Devices[ndevices], "r")) != NULL)
                        return(TRUE);
        return(FALSE);
}


/*
 * get next line from Dialers file
 * return TRUE if successful, FALSE if not
 */

GLOBAL int
getdialline(char *buf, int len)
{
        char    *prev = _uu_setlocale(LC_ALL, "C");

        if (Dialers[0] == NULL)
                /* not initialized via setservice() - use default */
                setservice("uucico");

        if (fdialers == NULL)
                if (nextdialers() == FALSE) {
                        (void) _uu_resetlocale(LC_ALL, prev);
                        return(FALSE);
                }
        for(;;) {
                if (fgets(buf, len, fdialers) != NULL) {
                        (void) _uu_resetlocale(LC_ALL, prev);
                        return(TRUE);
                }
                if (nextdialers() == FALSE) {
                        (void) _uu_resetlocale(LC_ALL, prev);
                        return(FALSE);
                }
        }
}

/*
 * move to next dialers file.  return TRUE if successful, FALSE if not
 */
static int
nextdialers()
{
        if (fdialers) {
                (void) fclose(fdialers);
                ndialers++;
        } else {
                ndialers = 0;
        }

        for ( ; Dialers[ndialers] != NULL; ndialers++)
                if ((fdialers = fopen(Dialers[ndialers], "r")) != NULL)
                        return(TRUE);
        return(FALSE);
}

/*
 * get next module to be popped
 * return TRUE if successful, FALSE if not
 */
static int
getpop(buf, len, optional)
char *buf;
int len, *optional;
{
        int slen;

        if ( Pops[0] == NULL || Pops[npops] == NULL )
                return(FALSE);

        /*      if the module name is enclosed in parentheses,  */
        /*      is optional. set flag & strip parens            */
        slen = strlen(Pops[npops]) - 1;
        if ( Pops[npops][0] == '('  && Pops[npops][slen] == ')' ) {
                *optional = 1;
                len = ( slen < len ? slen : len );
                strncpy(buf, &(Pops[npops++][1]), len);
        } else {
                *optional = 0;
                strncpy(buf, Pops[npops++], len);
        }
        buf[len-1] = '\0';
        return(TRUE);
}

/*
 * get next module to be pushed
 * return TRUE if successful, FALSE if not
 */
static int
getpush(buf, len)
char *buf;
int len;
{
        if ( Pushes[0] == NULL || Pushes[npushes] == NULL )
                return(FALSE);
        strncpy(buf, Pushes[npushes++], len);
        return(TRUE);
}

/*
 * pop/push requested modules
 * return TRUE if successful, FALSE if not
 */
GLOBAL int
pop_push(fd)
int fd;
{
    char        strmod[FMNAMESZ], onstream[FMNAMESZ];
    int         optional;
    char        *prev = _uu_setlocale(LC_ALL, "C");

    /*  check for streams modules to pop        */
    while ( getpop(strmod, sizeof(strmod), &optional) ) {
        DEBUG(5, (optional ? "pop_push: optionally POPing %s\n"
                           : "pop_push: POPing %s\n" ), strmod);
        if ( ioctl(fd, I_LOOK, onstream) == -1 ) {
            DEBUG(5, "pop_push: I_LOOK on fd %d failed ", fd);
            DEBUG(5, "errno %d\n", errno);
            (void) _uu_resetlocale(LC_ALL, prev);
            return(FALSE);
        }
        if ( strcmp(strmod, onstream) != SAME ) {
            if ( optional )
                continue;
            DEBUG(5, "pop_push: I_POP: %s not there\n", strmod);
            (void) _uu_resetlocale(LC_ALL, prev);
            return(FALSE);
        }
        if ( ioctl(fd, I_POP, 0) == -1 ) {
            DEBUG(5, "pop_push: I_POP on fd %d failed ", fd);
            DEBUG(5, "errno %d\n", errno);
            (void) _uu_resetlocale(LC_ALL, prev);
            return(FALSE);
        }
    }

    /*  check for streams modules to push       */
    while ( getpush(strmod, sizeof(strmod)) ) {
        DEBUG(5, "pop_push: PUSHing %s\n", strmod);
        if ( ioctl(fd, I_PUSH, strmod) == -1 ) {
            DEBUG(5, "pop_push: I_PUSH on fd %d failed ", fd);
            DEBUG(5, "errno %d\n", errno);
            (void) _uu_resetlocale(LC_ALL, prev);
            return(FALSE);
        }
    }
    (void) _uu_resetlocale(LC_ALL, prev);
    return(TRUE);
}

/*
 *      return name of currently open Systems file
 */
GLOBAL char *
currsys()
{
        return(Systems[nsystems]);
}

/*
 *      return name of currently open Devices file
 */
GLOBAL char *
currdev()
{
        return(Devices[ndevices]);
}

/*
 *      return name of currently open Dialers file
 */
GLOBAL char *
currdial()
{
        return(Dialers[ndialers]);
}

/*
 * set configuration parameters provided in Config file
 */
static void
setconfig()
{
    FILE *f;
    char buf[BUFSIZ];
    char *tok;
    extern char _ProtoCfg[];

    if ((f = fopen(CONFIG, "r")) != 0) {
        while (getaline(f, buf) > 0) {
            /* got a (logical) line from Config file */
            tok = strtok(buf, " \t");
            if ( (tok != NULL) && (*tok != '#') ) {
                /* got a token */

                /* this probably should be table driven when
                 * the list of configurable parameters grows.
                 */
                if (strncmp("Protocol=", tok, strlen("Protocol=")) == SAME) {
                    tok += strlen("Protocol=");
                    if ( *tok != '\0' ) {
                        if ( _ProtoCfg[0] != '\0' ) {
                            DEBUG(7, "Protocol string %s ", tok);
                            DEBUG(7, "overrides %s\n", _ProtoCfg);
                        }
                        strcpy(_ProtoCfg, tok);
                    }
                } else {
                    DEBUG(7, "Unknown configuration parameter %s\n", tok);
                }
            }
        }
    }
}