root/usr/src/cmd/devfsadm/devpolicy.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <priv.h>
#include <string.h>
#include <libgen.h>
#include <errno.h>
#include <libintl.h>
#include <sys/devpolicy.h>
#include <sys/modctl.h>
#include "message.h"
#include "plcysubr.h"

/* Cannot include devfsadm_impl.h because of static definitions */
#define err_print       devfsadm_errprint
extern void err_print(char *, ...);

#define PLCY_CHUNK      128

/*
 * devpolicy sort order sorts on three items to help the kernel;
 * the kernel will verify but not sort.
 *
 *      1) major number - but default major will be first in sorted output
 *      2) wildcard or not - non wildcard entries are sorted first.
 *              2a) Expanded minor numbers first (empty name sorts first).
 *              2b) Named minors.
 *      3) length of wildcard entry - longest pattern first
 *
 * The last rule allows patterns such as *ctl and * to be used both
 * unambiguously instead of current bogosities as found in /etc/minor_perm:
 *      rtvc:ctl 0644 root sys
 *      rtvc:rtvcctl* 0644 root sys
 *      rtvc:rtvc[!ctl]* 0666 root sys
 *
 * The last pattern only works by accident.
 *
 * This would simply become (in sorted order):
 *      rtvc:ctl
 *      rtvc:rtvcctl*
 *      rtvc:*
 */

static int
qcmp(const void *a, const void *b)
{
        const devplcysys_t *pa = a;
        const devplcysys_t *pb = b;
        int wilda, wildb;

        /* sort on major number, default major first in sort output */
        if (pa->dps_maj == DEVPOLICY_DFLT_MAJ)
                return (-1);
        if (pb->dps_maj == DEVPOLICY_DFLT_MAJ)
                return (1);

        if (pa->dps_maj > pb->dps_maj)
                return (1);
        else if (pa->dps_maj < pb->dps_maj)
                return (-1);

        wilda = strchr(pa->dps_minornm, '*') != NULL;
        wildb = strchr(pb->dps_minornm, '*') != NULL;

        /* sort the entry with the wildcard last */
        if (wilda != wildb)
                return (wilda - wildb);

        /* entries without wildcards compare with strcmp() */
        if (wilda == 0)
                return (strcmp(pa->dps_minornm, pb->dps_minornm));

        /* shortest wildcard last */
        return ((int)(strlen(pb->dps_minornm) - strlen(pa->dps_minornm)));
}

static int
loadprivs(const char *infile)
{
        char *line, *col;
        FILE *in;
        struct fileentry *fep;
        int res = 0;

        in = fopen(infile, "r");

        if (in == NULL)
                return (0);

        while ((fep = fgetline(in)) != NULL && fep->entry != NULL) {
                line = fep->entry;

                if (*line == '\0')
                        continue;

                line[strlen(line)-1] = '\0';

                col = strchr(line, ':');

                if (col != NULL) {
                        major_t maj;
                        *col = '\0';

                        if (modctl(MODGETMAJBIND, line, col - line + 1, &maj)
                            != 0)
                                continue;

                        line = col + 1;
                }

                if (modctl(MODALLOCPRIV, line) != 0) {
                        (void) err_print("modctl(MODALLOCPRIV, %s): %s\n",
                                line, strerror(errno));
                        res = -1;
                }
        }
        return (res);
}

static int
loadpolicy(const char *infile)
{
        char *line;
        int nalloc = 0, cnt = 0;
        char *mem = NULL;
        devplcysys_t *dp, *dflt = NULL;
        FILE *in;
        struct fileentry *fep;
        int res;

        char *maj;
        char *tok;
        char *min;

        in = fopen(infile, "r");

        if (in == NULL) {
                err_print(OPEN_FAILED, infile, strerror(errno));
                return (-1);
        }

        while ((fep = fgetline(in)) != NULL && fep->entry != NULL) {
                line = fep->entry;
                if (cnt >= nalloc) {
                        nalloc += PLCY_CHUNK;
                        mem = realloc(mem, nalloc * devplcysys_sz);
                        if (mem == NULL) {
                                err_print(MALLOC_FAILED,
                                        nalloc * devplcysys_sz);
                                return (-1);
                        }

                        /* Readjust pointer to dflt after realloc */
                        if (dflt != NULL)
                                /* LINTED: alignment */
                                dflt = (devplcysys_t *)mem;
                }
                maj = strtok(line, "\n\t ");

                if (maj == NULL)
                        continue;

                /* LINTED: alignment */
                dp = (devplcysys_t *)(mem + devplcysys_sz * cnt);

                if (strcmp(maj, "*") == 0) {
                        if (dflt != NULL) {
                                err_print(DPLCY_ONE_DFLT, infile);
                                return (-1);
                        }
                        (void) memset(dp, 0, devplcysys_sz);
                        dp->dps_maj = DEVPOLICY_DFLT_MAJ;
                        dflt = dp;
                } else {
                        if (dflt == NULL) {
                                err_print(DPLCY_FIRST, infile);
                                return (-1);
                        }

                        (void) memcpy(dp, dflt, devplcysys_sz);

                        min = strchr(maj, ':');

                        if (min != NULL) {
                                *min++ = '\0';
                                if (strchr(min, ':') != NULL) {
                                        (void) fprintf(stderr,
                                            "Too many ``:'' in entry\n");
                                        return (-1);
                                }
                        } else
                                min = "*";

                        /* Silently ignore unknown devices. */
                        if (modctl(MODGETMAJBIND, maj, strlen(maj) + 1,
                            &dp->dps_maj) != 0)
                                continue;

                        if (*min == '(') {
                                /* Numeric minor range */
                                char type;

                                if (parse_minor_range(min, &dp->dps_lomin,
                                    &dp->dps_himin, &type) == -1) {
                                        err_print(INVALID_MINOR, min);
                                        return (-1);
                                }
                                dp->dps_isblock = type == 'b';
                        } else {
                                if (strlen(min) >= sizeof (dp->dps_minornm)) {
                                        err_print(MINOR_TOO_LONG, maj, min);
                                        return (-1);
                                }
                                (void) strcpy(dp->dps_minornm, min);
                        }
                }

                while (tok = strtok(NULL, "\n\t ")) {
                        if (parse_plcy_token(tok, dp)) {
                                err_print(BAD_ENTRY, fep->startline,
                                        fep->orgentry);
                                return (-1);
                        }
                }
                cnt++;
        }
        if (fep == NULL) {
                if (feof(in))
                        err_print(UNEXPECTED_EOF, infile);
                else
                        err_print(NO_MEMORY);
                return (-1);
        }
        qsort(mem, cnt, devplcysys_sz, qcmp);

        if ((res = modctl(MODSETDEVPOLICY, cnt, devplcysys_sz, mem)) != 0)
                err_print("modctl(MODSETDEVPOLICY): %s\n", strerror(errno));

        return (res);
}

int
load_devpolicy(void)
{
        int res;

        devplcy_init();

        res = loadprivs(EXTRA_PRIVS);
        res += loadpolicy(DEV_POLICY);

        return (res);
}