root/usr/src/lib/libnsl/saf/doconfig.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include "mt.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <ulimit.h>
#include <wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stropts.h>
#include <ctype.h>
#include <sys/conf.h>
#include <errno.h>
#include <signal.h>
#include "sac.h"

#define COMMENT '#'
#define NOWAIT  0
#define WAIT    1

static char     *eatwhite(char *);
static int      doassign(char *);
static int      dopush(int, char *);
static int      dopop(int, char *);
static int      dorun(char *, int);

/*
 * doconfig - the configuration script interpreter, if all is ok,
 *            return 0.  If there is a "system" error, return -1.
 *            If there is an error performing a command, or there
 *            is a syntax error, return the line number in error.
 *
 *      args:   fd - file descriptor to push and pop from
 *              script - name of the configuration script
 *              rflag - restriction flag to determine what "commands"
 *                      can be run
 */

int
doconfig(int fd, char *script, long rflag)
{
        int line;               /* line counter */
        struct stat statbuf;    /* place for stat */
        FILE *fp;               /* file pointer for config script */
        char buf[BUFSIZ + 1];   /* scratch buffer */
        char *bp;               /* scratch pointer */
        char *p;                /* scratch pointer */

        /* if the script does not exist, then there is nothing to do */
        if (stat(script, &statbuf) < 0)
                return (0);

        fp = fopen(script, "rF");
        if (fp == NULL)
                return (-1);

        line = 0;
        while (fgets(buf, BUFSIZ, fp)) {
                line++;
                p = strchr(buf, '\n');
                /* if no \n, then line is too long */
                if (p == NULL) {
                        (void) fclose(fp);
                        return (line);
                }
                *p = '\0';

                /* remove comments */
                p = strchr(buf, COMMENT);
                if (p)
                        *p = '\0';

                /* remove leading whitespace */
                bp = eatwhite(buf);
                /* see if anything is left */
                if (*bp == '\0')
                        continue;

                /* remove trailing whitespace */
                p = &buf[strlen(buf) - 1];
                while (*p && isspace(*p))
                        *p-- = '\0';

                /* get the command */
                p = bp;
                while (*p && !isspace(*p))
                        p++;
                if (*p)
                        *p++ = '\0';
                /* skip any whitespace here too (between command and args) */
                p = eatwhite(p);

                if (strcmp(bp, "assign") == 0) {
                        if ((rflag & NOASSIGN) || doassign(p)) {
                                (void) fclose(fp);
                                return (line);
                        }
                } else if (strcmp(bp, "push") == 0) {
                        if (dopush(fd, p)) {
                                (void) fclose(fp);
                                return (line);
                        }
                } else if (strcmp(bp, "pop") == 0) {
                        if (dopop(fd, p)) {
                                (void) fclose(fp);
                                return (line);
                        }
                } else if (strcmp(bp, "run") == 0) {
                        if ((rflag & NORUN) || dorun(p, NOWAIT)) {
                                (void) fclose(fp);
                                return (line);
                        }
                } else if (strcmp(bp, "runwait") == 0) {
                        if ((rflag & NORUN) || dorun(p, WAIT)) {
                                (void) fclose(fp);
                                return (line);
                        }
                } else {
                        /* unknown command */
                        (void) fclose(fp);
                        return (line);
                }
        }
        if (!feof(fp)) {
                (void) fclose(fp);
                return (-1);
        }
        (void) fclose(fp);
        return (0);
}


/*
 * doassign - handle an `assign' command
 *
 *      args:   p - assignment string
 */


static int
doassign(char *p)
{
        char *var;              /* environment variable to be assigned */
        char val[BUFSIZ];       /* and the value to be assigned to it */
        char scratch[BUFSIZ];   /* scratch buffer */
        char delim;             /* delimiter char seen (for quoted strings ) */
        char *tp;               /* scratch pointer */

        if (*p == '\0')
                return (-1);
        var = p;
        /* skip first token, but stop if we see a '=' */
        while (*p && !isspace(*p) && (*p != '='))
                p++;

        /* if we found end of string, it's an error */
        if (*p == '\0')
                return (-1);

        /* if we found a space, look for the '=', otherwise it's an error */
        if (isspace(*p)) {
                *p++ = '\0';
                while (*p && isspace(*p))
                        p++;
                if (*p == '\0')
                        return (-1);
                if (*p == '=')
                        p++;
                else
                        return (-1);
        } else {
                /* skip over '=' */
                *p = '\0';
                p++;
        }

        /* skip over any whitespace */
        p = eatwhite(p);
        if (*p == '\'' || *p == '"') {
                /* handle quoted values */
                delim = *p++;
                tp = val;
                for (;;) {
                        if (*p == '\0') {
                                return (-1);
                        } else if (*p == delim) {
                                if (*(p - 1) != '\\')
                                        break;
                                else
                                        *(tp - 1) = *p++;
                        } else
                                *tp++ = *p++;
                }
                *tp = '\0';
                /*
                 * these assignments make the comment below true
                 * (values of tp and p
                 */
                tp = ++p;
                p = val;
        } else {
                tp = p;
                /* look for end of token */
                while (*tp && !isspace(*tp))
                        tp++;
        }

/*
 * at this point, p points to the value, and tp points to the
 * end of the token.  check to make sure there is no garbage on
 * the end of the line
 */

        if (*tp)
                return (-1);
        (void) snprintf(scratch, sizeof (scratch), "%s=%s", var, p);
        /* note: need to malloc fresh space so putenv works */
        tp = malloc(strlen(scratch) + 1);
        if (tp == NULL)
                return (-1);
        (void) strcpy(tp, scratch);
        if (putenv(tp))
                return (-1);
        return (0);
}


/*
 * dopush - handle a `push' command
 *
 *      args:   fd - file descriptor to push on
 *              p - list of modules to push
 */


static int
dopush(int fd, char *p)
{
        char *tp;       /* scratch pointer */
        int i;          /* scratch variable */
        int npush;      /* count # of modules pushed */

        if (*p == '\0')
                return (-1);
        npush = 0;
        for (;;) {
                if (*p == '\0')         /* found end of line */
                        return (0);
                p = eatwhite(p);
                if (*p == '\0')
                        return (-1);
                tp = p;
                while (*tp && !isspace(*tp) && (*tp != ','))
                        tp++;
                if (*tp)
                        *tp++ = '\0';
                if (ioctl(fd, I_PUSH, p) < 0) {

/*
 * try to pop all that we've done, if pop fails it doesn't matter because
 * nothing can be done anyhow
 */

                        for (i = 0; i < npush; ++i)
                                (void) ioctl(fd, I_POP, 0);
                        return (-1);
                }
                /* count the number of modules we've pushed */
                npush++;
                p = tp;
        }
}


/*
 * dopop - handle a `pop' command
 *
 *      args:   fd - file descriptor to pop from
 *              p - name of module to pop to or ALL (null means pop top only)
 */


static int
dopop(int fd, char *p)
{
        char *modp;             /* module name from argument to pop */
        char buf[FMNAMESZ + 1]; /* scratch buffer */

        if (*p == '\0') {
                /* just a pop with no args */
                if (ioctl(fd, I_POP, 0) < 0)
                        return (-1);
                return (0);
        }

        /* skip any whitespace in between */
        p = eatwhite(p);
        modp = p;
        /* find end of module name */
        while (*p && !isspace(*p))
                p++;

        if (*p)         /* if not end of line, extra junk on line */
                return (-1);
        if (strcmp(modp, "ALL") == 0) {
                /* it's the magic name, pop them all */
                while (ioctl(fd, I_POP, 0) == 0)
                        ;
                /* After all popped, we'll get an EINVAL, which is expected */
                if (errno != EINVAL)
                        return (-1);
                return (0);
        }
        /* check to see if the named module is on the stream */
        if (ioctl(fd, I_FIND, modp) != 1)
                return (-1);

        /* pop them until the right one is on top */
        for (;;) {
                if (ioctl(fd, I_LOOK, buf) < 0)
                        return (-1);
                if (strcmp(modp, buf) == 0)
                        /* we're done */
                        return (0);
                if (ioctl(fd, I_POP, 0) < 0)
                        return (-1);
        }
        /* NOTREACHED */
}


/*
 * dorun - handle a `run' command
 *
 *      args:   p - command line to run
 *              waitflag - flag indicating whether a wait should be done
 */


static int
dorun(char *p, int waitflg)
{
        char *tp;               /* scratch pointer */
        char *ep;               /* scratch pointer (end of token) */
        char savech;            /* hold area */
        int status;             /* return status from wait */
        pid_t pid;              /* pid of child proc */
        pid_t rpid;             /* returned pid from wait */
        void (*func)();         /* return from signal */

        if (*p == '\0')
                return (-1);

        /*
         * get first token
         */

        for (tp = p; *tp && !isspace(*tp); ++tp)
                ;
        savech = '\0';
        if (*tp) {
                savech = *tp;
                *tp = '\0';
        }

        /*
         * look for built-in's
         */

        if (strcmp(p, "cd") == 0) {
                *tp = savech;
                tp = eatwhite(tp);
                if (*tp == '\0')
                        /* if nothing there, try to cd to $HOME */
                        tp = getenv("HOME");
                if (chdir(tp) < 0)
                        return (-1);
        } else if (strcmp(p, "ulimit") == 0) {
                *tp = savech;
                tp = eatwhite(tp);
                /* must have an argument */
                if (*tp == '\0')
                        return (-1);
                /* make sure nothing appears on line after arg */
                for (ep = tp; *ep && !isspace(*ep); ++ep)
                        ;
                ep = eatwhite(ep);
                if (*ep)
                        return (-1);
                if (!isdigit(*tp))
                        return (-1);

                if (ulimit(2, atoi(tp)) < 0)
                        return (-1);
        } else if (strcmp(p, "umask") == 0) {
                *tp = savech;
                tp = eatwhite(tp);
                /* must have an argument */
                if (*tp == '\0')
                        return (-1);
                /* make sure nothing appears on line after arg */
                for (ep = tp; *ep && !isspace(*ep); ++ep)
                        ;
                ep = eatwhite(ep);
                if (*ep)
                        return (-1);
                if (!isdigit(*tp))
                        return (-1);
                (void) umask(strtol(tp, NULL, 8));
        } else {
                /* not a built-in */
                *tp = savech;
                func = signal(SIGCLD, SIG_DFL);
                if ((pid = fork()) < 0) {
                        (void) signal(SIGCLD, func);
                        return (-1);
                }
                if (pid) {
                        if (waitflg == WAIT) {
                                status = 0;
                                rpid = -1;
                                while (rpid != pid)
                                        rpid = wait(&status);
                                if (status) {
                                        /* child failed */
                                        (void) signal(SIGCLD, func);
                                        return (-1);
                                }
                        }
                        (void) signal(SIGCLD, func);
                } else {
                        /* set IFS for security */
                        (void) putenv("IFS=\" \"");
                        /*
                         * need to close all files to prevent unauthorized
                         * access in the children.  Setup stdin, stdout,
                         * and stderr to /dev/null.
                         */
                        closefrom(0);
                        /* stdin */
                        if (open("/dev/null", O_RDWR) != 0)
                                return (-1);
                        /* stdout */
                        if (dup(0) != 1)
                                return (-1);
                        /* stderr */
                        if (dup(0) != 2)
                                return (-1);
                        (void) execl("/usr/bin/sh", "sh", "-c", p, NULL);
                        /*
                         * if we get here, there is a problem - remember that
                         * this is the child
                         */
                        exit(1);
                }
        }
        return (0);
}


/*
 * eatwhite - swallow any leading whitespace, return pointer to first
 *            non-white space character or to terminating null character
 *            if nothing else is there
 *
 *      args:   p - string to parse
 */

static char *
eatwhite(char *p)
{
        while (*p && isspace(*p))
                p++;
        return (p);
}