2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#define _POSIX_PTHREAD_SEMANTICS /* for getgrnam_r */
2N/A#ifdef lint
2N/A#define _REENTRANT /* for strtok_r */
2N/A#endif
2N/A
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <ctype.h>
2N/A#include <string.h>
2N/A#include <unistd.h>
2N/A#include <dirent.h>
2N/A#include <errno.h>
2N/A#include <grp.h>
2N/A#include <pwd.h>
2N/A#include <nss_dbdefs.h>
2N/A#include <stdarg.h>
2N/A#include <syslog.h>
2N/A#include <sys/acl.h>
2N/A#include <sys/types.h>
2N/A#include <sys/stat.h>
2N/A#include <sys/ddi.h>
2N/A#include <sys/sunddi.h>
2N/A#include <sys/devinfo_impl.h>
2N/A#include <sys/hwconf.h>
2N/A#include <sys/modctl.h>
2N/A#include <libnvpair.h>
2N/A#include <device_info.h>
2N/A#include <regex.h>
2N/A#include <strings.h>
2N/A#include <libdevinfo.h>
2N/A#include <zone.h>
2N/A#include <fcntl.h>
2N/A#include <utmpx.h>
2N/A
2N/Aextern int is_minor_node(const char *, const char **);
2N/A
2N/A#define MAX_LINELEN 256
2N/A#define LOGINDEVPERM "/etc/logindevperm"
2N/A
2N/A/*
2N/A * Constant info per dir_dev_access() invocation
2N/A */
2N/Atypedef struct devperm_info {
2N/A di_node_t cache_snapshot;
2N/A uid_t uid;
2N/A gid_t gid;
2N/A mode_t mode;
2N/A char *devperm_line;
2N/A void (*ferror)(char *);
2N/A char drvname[MAX_LINELEN];
2N/A int node_matched;
2N/A char *devfs_path;
2N/A} devperm_info_t;
2N/A
2N/Astatic int is_login_user(uid_t);
2N/Astatic int logindevperm(const char *, uid_t, gid_t, void (*)());
2N/Astatic int dir_dev_acc(char *, char *, devperm_info_t *);
2N/Astatic int setdevaccess(char *, uid_t, gid_t, mode_t, void (*)());
2N/Astatic void logerror(char *);
2N/Astatic int is_blank(char *);
2N/A
2N/A/*
2N/A * Revoke all access to a device node and make sure that there are
2N/A * no interposed streams devices attached. Must be called before a
2N/A * device is actually opened.
2N/A * When fdetach is called, the underlying device node is revealed; it
2N/A * will have the previous owner and that owner can re-attach; so we
2N/A * retry until we win.
2N/A * Ignore non-existent devices.
2N/A */
2N/Astatic int
2N/Asetdevaccess(char *dev, uid_t uid, gid_t gid, mode_t mode,
2N/A void (*ferror)(char *))
2N/A{
2N/A int err = 0, local_errno;
2N/A char errstring[MAX_LINELEN];
2N/A struct stat st;
2N/A
2N/A if (chown(dev, uid, gid) == -1) {
2N/A if (errno == ENOENT) /* no such file */
2N/A return (0);
2N/A err = -1;
2N/A local_errno = errno;
2N/A }
2N/A
2N/A /*
2N/A * don't fdetach block devices, as it will unmount them
2N/A */
2N/A if (!((stat(dev, &st) == 0) && ((st.st_mode & S_IFMT) == S_IFBLK))) {
2N/A while (fdetach(dev) == 0) {
2N/A if (chown(dev, uid, gid) == -1) {
2N/A err = -1;
2N/A local_errno = errno;
2N/A }
2N/A }
2N/A if (err && ferror) {
2N/A (void) snprintf(errstring, MAX_LINELEN,
2N/A "failed to chown device %s: %s\n",
2N/A dev, strerror(local_errno));
2N/A (*ferror)(errstring);
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * strip_acl sets an acl and changes the files owner/group
2N/A */
2N/A err = acl_strip(dev, uid, gid, mode);
2N/A
2N/A if (err != 0) {
2N/A /*
2N/A * If the file system returned ENOSYS, we know that it
2N/A * doesn't support ACLs, therefore, we must assume that
2N/A * there were no ACLs to remove in the first place.
2N/A */
2N/A err = 0;
2N/A if (errno != ENOSYS) {
2N/A err = -1;
2N/A
2N/A if (ferror) {
2N/A (void) snprintf(errstring, MAX_LINELEN,
2N/A "failed to set acl on device %s: %s\n",
2N/A dev, strerror(errno));
2N/A (*ferror)(errstring);
2N/A }
2N/A }
2N/A if (chmod(dev, mode) == -1) {
2N/A err = -1;
2N/A if (ferror) {
2N/A (void) snprintf(errstring, MAX_LINELEN,
2N/A "failed to chmod device %s: %s\n",
2N/A dev, strerror(errno));
2N/A (*ferror)(errstring);
2N/A }
2N/A }
2N/A }
2N/A
2N/A return (err);
2N/A}
2N/A
2N/A/*
2N/A * logindevperm - change owner/group/permissions of devices
2N/A * list in /etc/logindevperm.
2N/A */
2N/Astatic int
2N/Alogindevperm(const char *ttyn, uid_t uid, gid_t gid, void (*ferror)(char *))
2N/A{
2N/A int err = 0, lineno = 0;
2N/A const char *field_delims = " \t\n";
2N/A char line[MAX_LINELEN], errstring[MAX_LINELEN];
2N/A char saveline[MAX_LINELEN];
2N/A char *console;
2N/A char *mode_str;
2N/A char *dev_list;
2N/A char *device;
2N/A char *ptr;
2N/A FILE *fp;
2N/A char ttyn_path[PATH_MAX + 1];
2N/A int n;
2N/A devperm_info_t info;
2N/A
2N/A info.uid = uid;
2N/A info.gid = gid;
2N/A info.ferror = ferror;
2N/A
2N/A if ((fp = fopen(LOGINDEVPERM, "r")) == NULL) {
2N/A if (ferror) {
2N/A (void) snprintf(errstring, MAX_LINELEN,
2N/A LOGINDEVPERM ": open failed: %s\n",
2N/A strerror(errno));
2N/A (*ferror)(errstring);
2N/A }
2N/A return (-1);
2N/A }
2N/A
2N/A if ((n = resolvepath(ttyn, ttyn_path, PATH_MAX)) == -1)
2N/A return (-1);
2N/A ttyn_path[n] = '\0';
2N/A
2N/A /* may be null in which case we fall back to DINFOCPYONE */
2N/A info.cache_snapshot = di_init("/", DINFOCACHE);
2N/A
2N/A while (fgets(line, MAX_LINELEN, fp) != NULL) {
2N/A char *last;
2N/A char tmp[PATH_MAX + 1];
2N/A
2N/A lineno++;
2N/A
2N/A if ((ptr = strchr(line, '#')) != NULL)
2N/A *ptr = '\0'; /* handle comments */
2N/A
2N/A (void) strcpy(saveline, line);
2N/A
2N/A console = strtok_r(line, field_delims, &last);
2N/A if (console == NULL)
2N/A continue; /* ignore blank lines */
2N/A
2N/A if ((n = resolvepath(console, tmp, PATH_MAX)) == -1)
2N/A continue;
2N/A tmp[n] = '\0';
2N/A
2N/A if (strcmp(ttyn_path, tmp) != 0)
2N/A continue;
2N/A
2N/A mode_str = strtok_r(last, field_delims, &last);
2N/A if (mode_str == NULL) {
2N/A err = -1; /* invalid entry, skip */
2N/A if (ferror) {
2N/A (void) snprintf(errstring, MAX_LINELEN,
2N/A LOGINDEVPERM
2N/A ": line %d, invalid entry -- %s\n",
2N/A lineno, line);
2N/A (*ferror)(errstring);
2N/A }
2N/A continue;
2N/A }
2N/A
2N/A /* convert string to octal value */
2N/A info.mode = (mode_t)strtol(mode_str, &ptr, 8);
2N/A if (info.mode > 0777 || *ptr != '\0') {
2N/A err = -1; /* invalid mode, skip */
2N/A if (ferror) {
2N/A (void) snprintf(errstring, MAX_LINELEN,
2N/A LOGINDEVPERM
2N/A ": line %d, invalid mode -- %s\n",
2N/A lineno, mode_str);
2N/A (*ferror)(errstring);
2N/A }
2N/A continue;
2N/A }
2N/A
2N/A dev_list = strtok_r(last, field_delims, &last);
2N/A if (dev_list == NULL) {
2N/A err = -1; /* empty device list, skip */
2N/A if (ferror) {
2N/A (void) snprintf(errstring, MAX_LINELEN,
2N/A LOGINDEVPERM
2N/A ": line %d, empty device list -- %s\n",
2N/A lineno, line);
2N/A (*ferror)(errstring);
2N/A }
2N/A continue;
2N/A }
2N/A
2N/A /* include line from logindevperm for additional parsing */
2N/A info.devperm_line = saveline;
2N/A
2N/A device = strtok_r(dev_list, ":", &last);
2N/A while (device != NULL) {
2N/A if ((device[0] != '/') || (strlen(device) <= 1)) {
2N/A err = -1;
2N/A } else if (dir_dev_acc("/", &device[1], &info)) {
2N/A err = -1;
2N/A }
2N/A device = strtok_r(last, ":", &last);
2N/A }
2N/A }
2N/A if (info.cache_snapshot)
2N/A di_fini(info.cache_snapshot);
2N/A (void) fclose(fp);
2N/A
2N/A return (err);
2N/A}
2N/A
2N/A/*
2N/A * returns 0 if resolved, -1 otherwise.
2N/A * devpath: Absolute path to /dev link
2N/A * devfs_path: Returns malloced string: /devices path w/out "/devices"
2N/A */
2N/Aint
2N/Adevfs_resolve_link(char *devpath, char **devfs_path)
2N/A{
2N/A char contents[PATH_MAX + 1];
2N/A char stage_link[PATH_MAX + 1];
2N/A char *ptr;
2N/A int linksize;
2N/A char *slashdev = "/dev/";
2N/A
2N/A if (devfs_path) {
2N/A *devfs_path = NULL;
2N/A }
2N/A
2N/A linksize = readlink(devpath, contents, PATH_MAX);
2N/A
2N/A if (linksize <= 0) {
2N/A return (-1);
2N/A } else {
2N/A contents[linksize] = '\0';
2N/A }
2N/A
2N/A /*
2N/A * if the link contents is not a minor node assume
2N/A * that link contents is really a pointer to another
2N/A * link, and if so recurse and read its link contents.
2N/A */
2N/A if (is_minor_node((const char *)contents, (const char **)&ptr) !=
2N/A 1) {
2N/A if (strncmp(contents, slashdev, strlen(slashdev)) == 0) {
2N/A /* absolute path, starting with /dev */
2N/A (void) strcpy(stage_link, contents);
2N/A } else {
2N/A /* relative path, prefix devpath */
2N/A if ((ptr = strrchr(devpath, '/')) == NULL) {
2N/A /* invalid link */
2N/A return (-1);
2N/A }
2N/A *ptr = '\0';
2N/A (void) strcpy(stage_link, devpath);
2N/A *ptr = '/';
2N/A (void) strcat(stage_link, "/");
2N/A (void) strcat(stage_link, contents);
2N/A
2N/A }
2N/A return (devfs_resolve_link(stage_link, devfs_path));
2N/A }
2N/A
2N/A if (devfs_path) {
2N/A *devfs_path = strdup(ptr);
2N/A if (*devfs_path == NULL) {
2N/A return (-1);
2N/A }
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Awalk_nodes(di_node_t node, void *arg)
2N/A{
2N/A devperm_info_t *info = (devperm_info_t *)arg;
2N/A char *path;
2N/A int matched;
2N/A
2N/A path = di_devfs_path(node);
2N/A matched = strcmp(info->devfs_path, path);
2N/A di_devfs_path_free(path);
2N/A if (matched == 0) {
2N/A info->node_matched = 1;
2N/A (void) strncpy(info->drvname, di_driver_name(node),
2N/A sizeof (info->drvname));
2N/A return (DI_WALK_TERMINATE);
2N/A }
2N/A return (DI_WALK_CONTINUE);
2N/A}
2N/A
2N/A/*
2N/A * Use the cached devinfo snapshot to match the devfs device
2N/A * path and return the driver name for the node.
2N/A */
2N/Astatic int
2N/Agetdrvname(char *devfs_path, devperm_info_t *info)
2N/A{
2N/A di_node_t node;
2N/A int rv;
2N/A
2N/A info->devfs_path = devfs_path;
2N/A
2N/A /* first try the cached snapshot */
2N/A if (info->cache_snapshot) {
2N/A info->node_matched = 0;
2N/A rv = di_walk_node(info->cache_snapshot, DI_WALK_CLDFIRST,
2N/A info, walk_nodes);
2N/A if (rv == 0 && info->node_matched)
2N/A return (1);
2N/A }
2N/A
2N/A /* fall back to looking up this node directly */
2N/A node = di_init(devfs_path, DINFOCPYONE);
2N/A if (node == NULL)
2N/A return (0);
2N/A (void) strncpy(info->drvname, di_driver_name(node),
2N/A sizeof (info->drvname));
2N/A di_fini(node);
2N/A return (1);
2N/A}
2N/A
2N/A/*
2N/A * check a logindevperm line for a driver list and match this against
2N/A * the driver of the minor node
2N/A * returns 0 if no drivers were specified or a driver match
2N/A */
2N/Astatic int
2N/Acheck_driver_match(char *path, devperm_info_t *info)
2N/A{
2N/A char *driver, *lasts;
2N/A char *devfs_path = NULL;
2N/A char saveline[MAX_LINELEN];
2N/A int rv;
2N/A char *p;
2N/A
2N/A /*
2N/A * get path and truncate on : to take a snapshot
2N/A */
2N/A if (devfs_resolve_link(path, &devfs_path) != 0)
2N/A return (0);
2N/A p = strrchr(devfs_path, ':');
2N/A *p = '\0';
2N/A
2N/A rv = getdrvname(devfs_path, info);
2N/A free(devfs_path);
2N/A if (rv == 0)
2N/A return (0);
2N/A
2N/A (void) strlcpy(saveline, info->devperm_line, sizeof (saveline));
2N/A p = strstr(saveline, "driver");
2N/A if (p == NULL)
2N/A return (0);
2N/A
2N/A driver = strtok_r(p, "=", &lasts);
2N/A if (driver) {
2N/A if (strcmp(driver, "driver") == 0) {
2N/A driver = strtok_r(NULL, ", \t\n", &lasts);
2N/A while (driver) {
2N/A if (strcmp(driver, info->drvname) == 0)
2N/A return (0);
2N/A driver = strtok_r(NULL, ", \t\n", &lasts);
2N/A }
2N/A }
2N/A }
2N/A
2N/A return (-1);
2N/A}
2N/A
2N/A/*
2N/A * Check whether the user has logged onto "/dev/console" or "/dev/vt/#".
2N/A * There's nothing reasonable we can do with a name service failure
2N/A * so we just drive on as if the lookup had not found a match.
2N/A */
2N/Astatic int
2N/Ais_login_user(uid_t uid)
2N/A{
2N/A int changed = 0;
2N/A struct passwd pwd, *ppwd = NULL;
2N/A char pwd_buf[NSS_BUFLEN_PASSWD];
2N/A struct utmpx *utx;
2N/A
2N/A if (((getpwuid_r(uid, &pwd, pwd_buf, NSS_BUFLEN_PASSWD,
2N/A &ppwd)) != 0) || ppwd == NULL) {
2N/A return (0);
2N/A }
2N/A
2N/A setutxent();
2N/A while ((utx = getutxent()) != NULL) {
2N/A if (utx->ut_type == USER_PROCESS &&
2N/A strncmp(utx->ut_user, ppwd->pw_name,
2N/A strlen(ppwd->pw_name)) == 0 && (strncmp(utx->ut_line,
2N/A "console", strlen("console")) == 0 || strncmp(utx->ut_line,
2N/A "vt", strlen("vt")) == 0)) {
2N/A changed = 1;
2N/A break;
2N/A }
2N/A }
2N/A endutxent();
2N/A
2N/A return (changed);
2N/A}
2N/A
2N/A/*
2N/A * Apply owner/group/perms to all files (except "." and "..")
2N/A * in a directory.
2N/A * This function is recursive. We start with "/" and the rest of the pathname
2N/A * in left_to_do argument, and we walk the entire pathname which may contain
2N/A * regular expressions or '*' for each directory name or basename.
2N/A */
2N/Astatic int
2N/Adir_dev_acc(char *path, char *left_to_do, devperm_info_t *info)
2N/A{
2N/A struct stat stat_buf;
2N/A int err = 0;
2N/A char errstring[MAX_LINELEN];
2N/A char *p;
2N/A regex_t regex;
2N/A int alwaysmatch = 0;
2N/A char *match;
2N/A char *name, *newpath, *remainder_path;
2N/A finddevhdl_t handle;
2N/A
2N/A /*
2N/A * Determine if the search needs to be performed via finddev,
2N/A * which returns only persisted names in the global /dev, or
2N/A * readdir, for paths other than /dev and non-global zones.
2N/A * This use of finddev avoids triggering potential implicit
2N/A * reconfig for names managed by logindevperm but not present
2N/A * on the system.
2N/A */
2N/A if (!device_exists(path)) {
2N/A return (-1);
2N/A }
2N/A if (stat(path, &stat_buf) == -1) {
2N/A /*
2N/A * ENOENT errors are expected errors when there are
2N/A * dangling /dev device links. Ignore them silently
2N/A */
2N/A if (errno == ENOENT) {
2N/A return (0);
2N/A }
2N/A if (info->ferror) {
2N/A (void) snprintf(errstring, MAX_LINELEN,
2N/A "failed to stat %s: %s\n", path,
2N/A strerror(errno));
2N/A (*info->ferror)(errstring);
2N/A }
2N/A return (-1);
2N/A } else {
2N/A if (!S_ISDIR(stat_buf.st_mode)) {
2N/A if (strlen(left_to_do) == 0) {
2N/A /* finally check the driver matches */
2N/A if (check_driver_match(path, info) == 0) {
2N/A /*
2N/A * if the owner of device has been
2N/A * login, the ownership and mode
2N/A * should be set already. in
2N/A * this case, do not set the
2N/A * permissions.
2N/A */
2N/A if (is_login_user(stat_buf.st_uid)) {
2N/A return (0);
2N/A }
2N/A /* we are done, set the permissions */
2N/A if (setdevaccess(path,
2N/A info->uid, info->gid,
2N/A info->mode, info->ferror)) {
2N/A return (-1);
2N/A }
2N/A }
2N/A }
2N/A return (0);
2N/A }
2N/A }
2N/A
2N/A if (finddev_readdir(path, &handle) != 0)
2N/A return (0);
2N/A
2N/A p = strchr(left_to_do, '/');
2N/A alwaysmatch = 0;
2N/A
2N/A newpath = (char *)malloc(MAXPATHLEN);
2N/A if (newpath == NULL) {
2N/A finddev_close(handle);
2N/A return (-1);
2N/A }
2N/A match = (char *)calloc(MAXPATHLEN + 2, 1);
2N/A if (match == NULL) {
2N/A finddev_close(handle);
2N/A free(newpath);
2N/A return (-1);
2N/A }
2N/A
2N/A /* transform pattern into ^pattern$ for exact match */
2N/A if (snprintf(match, MAXPATHLEN + 2, "^%.*s$",
2N/A (int)(p ? ((int)(p - left_to_do)) : strlen(left_to_do)),
2N/A left_to_do) >= MAXPATHLEN + 2) {
2N/A finddev_close(handle);
2N/A free(newpath);
2N/A free(match);
2N/A return (-1);
2N/A }
2N/A
2N/A if (strcmp(match, "^*$") == 0) {
2N/A alwaysmatch = 1;
2N/A } else {
2N/A if (regcomp(&regex, match, REG_EXTENDED) != 0) {
2N/A free(newpath);
2N/A free(match);
2N/A finddev_close(handle);
2N/A return (-1);
2N/A }
2N/A }
2N/A
2N/A while ((name = (char *)finddev_next(handle)) != NULL) {
2N/A if (alwaysmatch ||
2N/A regexec(&regex, name, 0, NULL, 0) == 0) {
2N/A if (strcmp(path, "/") == 0) {
2N/A (void) snprintf(newpath,
2N/A MAXPATHLEN, "%s%s", path, name);
2N/A } else {
2N/A (void) snprintf(newpath,
2N/A MAXPATHLEN, "%s/%s", path, name);
2N/A }
2N/A
2N/A /*
2N/A * recurse but adjust what is still left to do
2N/A */
2N/A remainder_path = (p ?
2N/A left_to_do + (p - left_to_do) + 1 :
2N/A &left_to_do[strlen(left_to_do)]);
2N/A if (dir_dev_acc(newpath, remainder_path, info)) {
2N/A err = -1;
2N/A }
2N/A }
2N/A }
2N/A
2N/A finddev_close(handle);
2N/A free(newpath);
2N/A free(match);
2N/A if (!alwaysmatch) {
2N/A regfree(&regex);
2N/A }
2N/A
2N/A return (err);
2N/A}
2N/A
2N/A/*
2N/A * di_devperm_login - modify access of devices in /etc/logindevperm
2N/A * by changing owner/group/permissions to that of ttyn.
2N/A */
2N/Aint
2N/Adi_devperm_login(const char *ttyn, uid_t uid, gid_t gid,
2N/A void (*ferror)(char *))
2N/A{
2N/A int err;
2N/A struct group grp, *grpp;
2N/A gid_t tty_gid;
2N/A char grbuf[NSS_BUFLEN_GROUP];
2N/A
2N/A if (ferror == NULL)
2N/A ferror = logerror;
2N/A
2N/A if (ttyn == NULL) {
2N/A (*ferror)("di_devperm_login: NULL tty device\n");
2N/A return (-1);
2N/A }
2N/A
2N/A if (getgrnam_r("tty", &grp, grbuf, NSS_BUFLEN_GROUP, &grpp) != 0) {
2N/A tty_gid = grpp->gr_gid;
2N/A } else {
2N/A /*
2N/A * this should never happen, but if it does set
2N/A * group to tty's traditional value.
2N/A */
2N/A tty_gid = 7;
2N/A }
2N/A
2N/A /* set the login console device permission */
2N/A err = setdevaccess((char *)ttyn, uid, tty_gid,
2N/A S_IRUSR|S_IWUSR|S_IWGRP, ferror);
2N/A if (err) {
2N/A return (err);
2N/A }
2N/A
2N/A /* set the device permissions */
2N/A return (logindevperm(ttyn, uid, gid, ferror));
2N/A}
2N/A
2N/A/*
2N/A * di_devperm_logout - clean up access of devices in /etc/logindevperm
2N/A * by resetting owner/group/permissions.
2N/A */
2N/Aint
2N/Adi_devperm_logout(const char *ttyn)
2N/A{
2N/A struct passwd *pwd;
2N/A uid_t root_uid;
2N/A gid_t root_gid;
2N/A
2N/A if (ttyn == NULL)
2N/A return (-1);
2N/A
2N/A pwd = getpwnam("root");
2N/A if (pwd != NULL) {
2N/A root_uid = pwd->pw_uid;
2N/A root_gid = pwd->pw_gid;
2N/A } else {
2N/A /*
2N/A * this should never happen, but if it does set user
2N/A * and group to root's traditional values.
2N/A */
2N/A root_uid = 0;
2N/A root_gid = 0;
2N/A }
2N/A
2N/A return (logindevperm(ttyn, root_uid, root_gid, NULL));
2N/A}
2N/A
2N/Astatic void
2N/Alogerror(char *errstring)
2N/A{
2N/A syslog(LOG_AUTH | LOG_CRIT, "%s", errstring);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * Tokens are separated by ' ', '\t', ':', '=', '&', '|', ';', '\n', or '\0'
2N/A */
2N/Astatic int
2N/Agetnexttoken(char *next, char **nextp, char **tokenpp, char *tchar)
2N/A{
2N/A char *cp;
2N/A char *cp1;
2N/A char *tokenp;
2N/A
2N/A cp = next;
2N/A while (*cp == ' ' || *cp == '\t') {
2N/A cp++; /* skip leading spaces */
2N/A }
2N/A tokenp = cp; /* start of token */
2N/A while (*cp != '\0' && *cp != '\n' && *cp != ' ' && *cp != '\t' &&
2N/A *cp != ':' && *cp != '=' && *cp != '&' &&
2N/A *cp != '|' && *cp != ';') {
2N/A cp++; /* point to next character */
2N/A }
2N/A /*
2N/A * If terminating character is a space or tab, look ahead to see if
2N/A * there's another terminator that's not a space or a tab.
2N/A * (This code handles trailing spaces.)
2N/A */
2N/A if (*cp == ' ' || *cp == '\t') {
2N/A cp1 = cp;
2N/A while (*++cp1 == ' ' || *cp1 == '\t')
2N/A ;
2N/A if (*cp1 == '=' || *cp1 == ':' || *cp1 == '&' || *cp1 == '|' ||
2N/A *cp1 == ';' || *cp1 == '\n' || *cp1 == '\0') {
2N/A *cp = NULL; /* terminate token */
2N/A cp = cp1;
2N/A }
2N/A }
2N/A if (tchar != NULL) {
2N/A *tchar = *cp; /* save terminating character */
2N/A if (*tchar == '\0') {
2N/A *tchar = '\n';
2N/A }
2N/A }
2N/A *cp++ = '\0'; /* terminate token, point to next */
2N/A *nextp = cp; /* set pointer to next character */
2N/A if (cp - tokenp - 1 == 0) {
2N/A return (0);
2N/A }
2N/A *tokenpp = tokenp;
2N/A return (1);
2N/A}
2N/A
2N/A/*
2N/A * get a decimal octal or hex number. Handle '~' for one's complement.
2N/A */
2N/Astatic int
2N/Agetvalue(char *token, int *valuep)
2N/A{
2N/A int radix;
2N/A int retval = 0;
2N/A int onescompl = 0;
2N/A int negate = 0;
2N/A char c;
2N/A
2N/A if (*token == '~') {
2N/A onescompl++; /* perform one's complement on result */
2N/A token++;
2N/A } else if (*token == '-') {
2N/A negate++;
2N/A token++;
2N/A }
2N/A if (*token == '0') {
2N/A token++;
2N/A c = *token;
2N/A
2N/A if (c == '\0') {
2N/A *valuep = 0; /* value is 0 */
2N/A return (0);
2N/A }
2N/A
2N/A if (c == 'x' || c == 'X') {
2N/A radix = 16;
2N/A token++;
2N/A } else {
2N/A radix = 8;
2N/A }
2N/A } else
2N/A radix = 10;
2N/A
2N/A while ((c = *token++)) {
2N/A switch (radix) {
2N/A case 8:
2N/A if (c >= '0' && c <= '7') {
2N/A c -= '0';
2N/A } else {
2N/A /* invalid number */
2N/A return (0);
2N/A }
2N/A retval = (retval << 3) + c;
2N/A break;
2N/A case 10:
2N/A if (c >= '0' && c <= '9') {
2N/A c -= '0';
2N/A } else {
2N/A /* invalid number */
2N/A return (0);
2N/A }
2N/A retval = (retval * 10) + c;
2N/A break;
2N/A case 16:
2N/A if (c >= 'a' && c <= 'f') {
2N/A c = c - 'a' + 10;
2N/A } else if (c >= 'A' && c <= 'F') {
2N/A c = c - 'A' + 10;
2N/A } else if (c >= '0' && c <= '9') {
2N/A c -= '0';
2N/A } else {
2N/A /* invalid number */
2N/A return (0);
2N/A }
2N/A retval = (retval << 4) + c;
2N/A break;
2N/A }
2N/A }
2N/A if (onescompl) {
2N/A retval = ~retval;
2N/A }
2N/A if (negate) {
2N/A retval = -retval;
2N/A }
2N/A *valuep = retval;
2N/A return (1);
2N/A}
2N/A
2N/A/*
2N/A * Read /etc/minor_perm, return mperm list of entries
2N/A */
2N/Astruct mperm *
2N/Ai_devfs_read_minor_perm(char *drvname, void (*errcb)(minorperm_err_t, int))
2N/A{
2N/A FILE *pfd;
2N/A struct mperm *mp;
2N/A char line[MAX_MINOR_PERM_LINE];
2N/A char *cp, *p, t;
2N/A struct mperm *minor_perms = NULL;
2N/A struct mperm *mptail = NULL;
2N/A struct passwd *pw;
2N/A struct group *gp;
2N/A uid_t root_uid;
2N/A gid_t sys_gid;
2N/A int ln = 0;
2N/A
2N/A /*
2N/A * Get root/sys ids, these being the most common
2N/A */
2N/A if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) {
2N/A root_uid = pw->pw_uid;
2N/A } else {
2N/A (*errcb)(MP_CANT_FIND_USER_ERR, 0);
2N/A root_uid = (uid_t)0; /* assume 0 is root */
2N/A }
2N/A if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) {
2N/A sys_gid = gp->gr_gid;
2N/A } else {
2N/A (*errcb)(MP_CANT_FIND_GROUP_ERR, 0);
2N/A sys_gid = (gid_t)3; /* assume 3 is sys */
2N/A }
2N/A
2N/A if ((pfd = fopen(MINOR_PERM_FILE, "r")) == NULL) {
2N/A (*errcb)(MP_FOPEN_ERR, errno);
2N/A return (NULL);
2N/A }
2N/A while (fgets(line, MAX_MINOR_PERM_LINE, pfd) != NULL) {
2N/A ln++;
2N/A /* cut off comments starting with '#' */
2N/A if ((cp = strchr(line, '#')) != NULL)
2N/A *cp = '\0';
2N/A /* ignore comment or blank lines */
2N/A if (is_blank(line))
2N/A continue;
2N/A mp = (struct mperm *)calloc(1, sizeof (struct mperm));
2N/A if (mp == NULL) {
2N/A (*errcb)(MP_ALLOC_ERR, sizeof (struct mperm));
2N/A continue;
2N/A }
2N/A cp = line;
2N/A /* sanity-check */
2N/A if (getnexttoken(cp, &cp, &p, &t) == 0) {
2N/A (*errcb)(MP_IGNORING_LINE_ERR, ln);
2N/A devfs_free_minor_perm(mp);
2N/A continue;
2N/A }
2N/A mp->mp_drvname = strdup(p);
2N/A if (mp->mp_drvname == NULL) {
2N/A (*errcb)(MP_ALLOC_ERR, strlen(p)+1);
2N/A devfs_free_minor_perm(mp);
2N/A continue;
2N/A } else if (t == '\n' || t == '\0') {
2N/A (*errcb)(MP_IGNORING_LINE_ERR, ln);
2N/A devfs_free_minor_perm(mp);
2N/A continue;
2N/A }
2N/A if (t == ':') {
2N/A if (getnexttoken(cp, &cp, &p, &t) == 0) {
2N/A (*errcb)(MP_IGNORING_LINE_ERR, ln);
2N/A devfs_free_minor_perm(mp);
2N/A }
2N/A mp->mp_minorname = strdup(p);
2N/A if (mp->mp_minorname == NULL) {
2N/A (*errcb)(MP_ALLOC_ERR, strlen(p)+1);
2N/A devfs_free_minor_perm(mp);
2N/A continue;
2N/A }
2N/A } else {
2N/A mp->mp_minorname = NULL;
2N/A }
2N/A
2N/A if (t == '\n' || t == '\0') {
2N/A devfs_free_minor_perm(mp);
2N/A (*errcb)(MP_IGNORING_LINE_ERR, ln);
2N/A continue;
2N/A }
2N/A if (getnexttoken(cp, &cp, &p, &t) == 0) {
2N/A goto link;
2N/A }
2N/A if (getvalue(p, (int *)&mp->mp_mode) == 0) {
2N/A goto link;
2N/A }
2N/A if (t == '\n' || t == '\0') { /* no owner or group */
2N/A goto link;
2N/A }
2N/A if (getnexttoken(cp, &cp, &p, &t) == 0) {
2N/A goto link;
2N/A }
2N/A mp->mp_owner = strdup(p);
2N/A if (mp->mp_owner == NULL) {
2N/A (*errcb)(MP_ALLOC_ERR, strlen(p)+1);
2N/A devfs_free_minor_perm(mp);
2N/A continue;
2N/A } else if (t == '\n' || t == '\0') { /* no group */
2N/A goto link;
2N/A }
2N/A if (getnexttoken(cp, &cp, &p, 0) == 0) {
2N/A goto link;
2N/A }
2N/A mp->mp_group = strdup(p);
2N/A if (mp->mp_group == NULL) {
2N/A (*errcb)(MP_ALLOC_ERR, strlen(p)+1);
2N/A devfs_free_minor_perm(mp);
2N/A continue;
2N/A }
2N/Alink:
2N/A if (drvname != NULL) {
2N/A /*
2N/A * We only want the minor perm entry for a
2N/A * the named driver. The driver name is the
2N/A * minor in the clone case.
2N/A */
2N/A if (strcmp(mp->mp_drvname, "clone") == 0) {
2N/A if (mp->mp_minorname == NULL ||
2N/A strcmp(drvname, mp->mp_minorname) != 0) {
2N/A devfs_free_minor_perm(mp);
2N/A continue;
2N/A }
2N/A } else {
2N/A if (strcmp(drvname, mp->mp_drvname) != 0) {
2N/A devfs_free_minor_perm(mp);
2N/A continue;
2N/A }
2N/A }
2N/A }
2N/A if (minor_perms == NULL) {
2N/A minor_perms = mp;
2N/A } else {
2N/A mptail->mp_next = mp;
2N/A }
2N/A mptail = mp;
2N/A
2N/A /*
2N/A * Compute the uid's and gid's here - there are
2N/A * fewer lines in the /etc/minor_perm file than there
2N/A * are devices to be stat(2)ed. And almost every
2N/A * device is 'root sys'. See 1135520.
2N/A */
2N/A if (mp->mp_owner == NULL ||
2N/A strcmp(mp->mp_owner, DEFAULT_DEV_USER) == 0 ||
2N/A (pw = getpwnam(mp->mp_owner)) == NULL) {
2N/A mp->mp_uid = root_uid;
2N/A } else {
2N/A mp->mp_uid = pw->pw_uid;
2N/A }
2N/A
2N/A if (mp->mp_group == NULL ||
2N/A strcmp(mp->mp_group, DEFAULT_DEV_GROUP) == 0 ||
2N/A (gp = getgrnam(mp->mp_group)) == NULL) {
2N/A mp->mp_gid = sys_gid;
2N/A } else {
2N/A mp->mp_gid = gp->gr_gid;
2N/A }
2N/A }
2N/A
2N/A if (fclose(pfd) == EOF) {
2N/A (*errcb)(MP_FCLOSE_ERR, errno);
2N/A }
2N/A
2N/A return (minor_perms);
2N/A}
2N/A
2N/Astruct mperm *
2N/Adevfs_read_minor_perm(void (*errcb)(minorperm_err_t, int))
2N/A{
2N/A return (i_devfs_read_minor_perm(NULL, errcb));
2N/A}
2N/A
2N/Astatic struct mperm *
2N/Ai_devfs_read_minor_perm_by_driver(char *drvname,
2N/A void (*errcb)(minorperm_err_t mp_err, int key))
2N/A{
2N/A return (i_devfs_read_minor_perm(drvname, errcb));
2N/A}
2N/A
2N/A/*
2N/A * Free mperm list of entries
2N/A */
2N/Avoid
2N/Adevfs_free_minor_perm(struct mperm *mplist)
2N/A{
2N/A struct mperm *mp, *next;
2N/A
2N/A for (mp = mplist; mp != NULL; mp = next) {
2N/A next = mp->mp_next;
2N/A
2N/A if (mp->mp_drvname)
2N/A free(mp->mp_drvname);
2N/A if (mp->mp_minorname)
2N/A free(mp->mp_minorname);
2N/A if (mp->mp_owner)
2N/A free(mp->mp_owner);
2N/A if (mp->mp_group)
2N/A free(mp->mp_group);
2N/A free(mp);
2N/A }
2N/A}
2N/A
2N/Astatic int
2N/Ai_devfs_add_perm_entry(nvlist_t *nvl, struct mperm *mp)
2N/A{
2N/A int err;
2N/A
2N/A err = nvlist_add_string(nvl, mp->mp_drvname, mp->mp_minorname);
2N/A if (err != 0)
2N/A return (err);
2N/A
2N/A err = nvlist_add_int32(nvl, "mode", (int32_t)mp->mp_mode);
2N/A if (err != 0)
2N/A return (err);
2N/A
2N/A err = nvlist_add_uint32(nvl, "uid", mp->mp_uid);
2N/A if (err != 0)
2N/A return (err);
2N/A
2N/A err = nvlist_add_uint32(nvl, "gid", mp->mp_gid);
2N/A return (err);
2N/A}
2N/A
2N/Astatic nvlist_t *
2N/Ai_devfs_minor_perm_nvlist(struct mperm *mplist,
2N/A void (*errcb)(minorperm_err_t, int))
2N/A{
2N/A int err;
2N/A struct mperm *mp;
2N/A nvlist_t *nvl = NULL;
2N/A
2N/A if ((err = nvlist_alloc(&nvl, 0, 0)) != 0) {
2N/A (*errcb)(MP_NVLIST_ERR, err);
2N/A return (NULL);
2N/A }
2N/A
2N/A for (mp = mplist; mp != NULL; mp = mp->mp_next) {
2N/A if ((err = i_devfs_add_perm_entry(nvl, mp)) != 0) {
2N/A (*errcb)(MP_NVLIST_ERR, err);
2N/A nvlist_free(nvl);
2N/A return (NULL);
2N/A }
2N/A }
2N/A
2N/A return (nvl);
2N/A}
2N/A
2N/A/*
2N/A * Load all minor perm entries into the kernel
2N/A * Done at boot time via devfsadm
2N/A */
2N/Aint
2N/Adevfs_load_minor_perm(struct mperm *mplist,
2N/A void (*errcb)(minorperm_err_t, int))
2N/A{
2N/A int err;
2N/A char *buf = NULL;
2N/A size_t buflen;
2N/A nvlist_t *nvl;
2N/A
2N/A nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
2N/A if (nvl == NULL)
2N/A return (-1);
2N/A
2N/A if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
2N/A nvlist_free(nvl);
2N/A return (-1);
2N/A }
2N/A
2N/A err = modctl(MODLOADMINORPERM, buf, buflen);
2N/A nvlist_free(nvl);
2N/A free(buf);
2N/A
2N/A return (err);
2N/A}
2N/A
2N/A/*
2N/A * Add/remove minor perm entry for a driver
2N/A */
2N/Astatic int
2N/Ai_devfs_update_minor_perm(char *drv, int ctl,
2N/A void (*errcb)(minorperm_err_t, int))
2N/A{
2N/A int err;
2N/A char *buf;
2N/A size_t buflen;
2N/A nvlist_t *nvl;
2N/A struct mperm *mplist;
2N/A
2N/A mplist = i_devfs_read_minor_perm_by_driver(drv, errcb);
2N/A
2N/A nvl = i_devfs_minor_perm_nvlist(mplist, errcb);
2N/A if (nvl == NULL)
2N/A return (-1);
2N/A
2N/A buf = NULL;
2N/A if (nvlist_pack(nvl, &buf, &buflen, NV_ENCODE_NATIVE, 0) != 0) {
2N/A nvlist_free(nvl);
2N/A return (-1);
2N/A }
2N/A
2N/A err = modctl(ctl, buf, buflen);
2N/A nvlist_free(nvl);
2N/A devfs_free_minor_perm(mplist);
2N/A free(buf);
2N/A
2N/A return (err);
2N/A}
2N/A
2N/Aint
2N/Adevfs_add_minor_perm(char *drv,
2N/A void (*errcb)(minorperm_err_t, int))
2N/A{
2N/A return (i_devfs_update_minor_perm(drv, MODADDMINORPERM, errcb));
2N/A}
2N/A
2N/Aint
2N/Adevfs_rm_minor_perm(char *drv,
2N/A void (*errcb)(minorperm_err_t, int))
2N/A{
2N/A return (i_devfs_update_minor_perm(drv, MODREMMINORPERM, errcb));
2N/A}
2N/A
2N/A/*
2N/A * is_blank() returns 1 (true) if a line specified is composed of
2N/A * whitespace characters only. otherwise, it returns 0 (false).
2N/A *
2N/A * Note. the argument (line) must be null-terminated.
2N/A */
2N/Astatic int
2N/Ais_blank(char *line)
2N/A{
2N/A for (/* nothing */; *line != '\0'; line++)
2N/A if (!isspace(*line))
2N/A return (0);
2N/A return (1);
2N/A}