/*
* 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.
*
* Device policy specific subroutines. We cannot merge them with
* drvsubr.c because of static linking requirements.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <priv.h>
#include <string.h>
#include <libgen.h>
#include <libintl.h>
#include <errno.h>
#include <alloca.h>
#include <sys/modctl.h>
#include <sys/devpolicy.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include "addrem.h"
#include "errmsg.h"
#include "plcysubr.h"
size_t devplcysys_sz;
const priv_impl_info_t *privimplinfo;
/*
* New token types should be parsed in parse_plcy_entry.
*/
#define PSET 0
typedef struct token {
const char *token;
int type;
ptrdiff_t off;
} token_t;
static token_t toktab[] = {
{ DEVPLCY_TKN_RDP, PSET /* offsetof(devplcysys_t, dps_rdp) */ },
{ DEVPLCY_TKN_WRP, PSET /* offsetof(devplcysys_t, dps_wrp) */ },
};
#define RDPOL 0
#define WRPOL 1
#define NTOK (sizeof (toktab)/sizeof (token_t))
/*
* Compute the size of the datastructures needed.
*/
void
devplcy_init(void)
{
if ((privimplinfo = getprivimplinfo()) == NULL) {
(void) fprintf(stderr, gettext(ERR_PRIVIMPL));
exit(1);
}
devplcysys_sz = DEVPLCYSYS_SZ(privimplinfo);
toktab[RDPOL].off =
(char *)DEVPLCYSYS_RDP((devplcysys_t *)0, privimplinfo) -
(char *)0;
toktab[WRPOL].off =
(char *)DEVPLCYSYS_WRP((devplcysys_t *)0, privimplinfo) -
(char *)0;
}
/*
* Read a configuration file line and return a static buffer pointing to it.
* It returns a static struct fileentry which has several fields:
* - rawbuf, which includes the lines including empty lines and comments
* leading up to the file and the entry as found in the file
* - orgentry, pointer in rawbuf to the start of the entry proper.
* - entry, a pre-parsed entry, escaped newlines removed.
* - startline, the line number of the first line in the file
*/
fileentry_t *
fgetline(FILE *fp)
{
static size_t sz = BUFSIZ;
static struct fileentry fe;
static int linecnt = 1;
char *buf = fe.rawbuf;
ptrdiff_t off;
char *p;
int c, lastc, i;
if (buf == NULL) {
fe.rawbuf = buf = malloc(sz);
if (buf == NULL)
return (NULL);
}
if (fe.entry != NULL) {
free(fe.entry);
fe.orgentry = fe.entry = NULL;
}
i = 0;
off = -1;
c = '\n';
while (lastc = c, (c = getc(fp)) != EOF) {
buf[i++] = c;
if (i == sz) {
sz *= 2;
fe.rawbuf = buf = realloc(buf, sz);
if (buf == NULL)
return (NULL);
}
if (c == '\n') {
linecnt++;
/* Newline, escaped or not yet processing an entry */
if (off == -1 || lastc == '\\')
continue;
} else if (lastc == '\n' && off == -1) {
/* Start of more comments */
if (c == '#')
continue;
/* Found start of entry */
off = i - 1;
fe.startline = linecnt;
continue;
} else
continue;
buf[i] = '\0';
fe.orgentry = buf + off;
p = fe.entry = strdup(fe.orgentry);
if (p == NULL)
return (NULL);
/* Remove <backslash><newline> */
if ((p = strchr(p, '\\')) != NULL) {
for (off = 0; (p[-off] = p[0]) != '\0'; p++)
if (p[0] == '\\' && p[1] == '\n') {
off += 2;
p++;
}
}
return (&fe);
}
if (lastc != '\n' || off != -1)
return (NULL);
buf[i] = '\0';
linecnt = 1;
return (&fe);
}
/*
* Parse minor number ranges:
* (minor) or (lowminor-highminor)
* Return 0 for success, -1 for failure.
*/
int
parse_minor_range(const char *range, minor_t *lo, minor_t *hi, char *type)
{
unsigned long tmp;
char *p;
if (*range++ != '(')
return (-1);
errno = 0;
tmp = strtoul(range, &p, 0);
if (tmp > L_MAXMIN32 || (tmp == 0 && errno != 0) ||
(*p != '-' && *p != ')'))
return (-1);
*lo = tmp;
if (*p == '-') {
errno = 0;
tmp = strtoul(p + 1, &p, 0);
if (tmp > L_MAXMIN32 || (tmp == 0 && errno != 0) || *p != ')')
return (-1);
}
*hi = tmp;
if (*lo > *hi)
return (-1);
switch (p[1]) {
case '\0':
*type = '\0';
break;
case 'c':
case 'C':
*type = 'c';
break;
case 'b':
case 'B':
*type = 'b';
break;
default:
return (-1);
}
return (0);
}
static void
put_minor_range(FILE *fp, fileentry_t *old, const char *devn, const char *tail,
minor_t lo, minor_t hi, char type)
{
/* Preserve preceeding comments */
if (old != NULL && old->rawbuf != old->orgentry)
(void) fwrite(old->rawbuf, 1, old->orgentry - old->rawbuf, fp);
if (type == '\0') {
put_minor_range(fp, NULL, devn, tail, lo, hi, 'b');
put_minor_range(fp, NULL, devn, tail, lo, hi, 'c');
} else if (lo == hi) {
(void) fprintf(fp, "%s:(%d)%c%s", devn, (int)lo, type, tail);
} else {
(void) fprintf(fp, "%s:(%d-%d)%c%s", devn, (int)lo, (int)hi,
type, tail);
}
}
static int
delete_one_entry(const char *filename, const char *entry)
{
char tfile[MAXPATHLEN];
char ofile[MAXPATHLEN];
char *nfile;
FILE *old, *new;
fileentry_t *fep;
struct stat buf;
int newfd;
char *mpart;
boolean_t delall;
boolean_t delrange;
minor_t rlo, rhi;
char rtype;
mpart = strchr(entry, ':');
if (mpart == NULL) {
delall = B_TRUE;
delrange = B_FALSE;
} else {
delall = B_FALSE;
mpart++;
if (*mpart == '(') {
if (parse_minor_range(mpart, &rlo, &rhi, &rtype) != 0)
return (-1);
delrange = B_TRUE;
} else {
delrange = B_FALSE;
}
}
if (strlen(filename) + sizeof (XEND) > sizeof (tfile))
return (-1);
old = fopen(filename, "r");
if (old == NULL)
return (-1);
(void) snprintf(tfile, sizeof (tfile), "%s%s", filename, XEND);
(void) snprintf(ofile, sizeof (ofile), "%s%s", filename, ".old");
nfile = mktemp(tfile);
new = fopen(nfile, "w");
if (new == NULL) {
(void) fclose(old);
return (ERROR);
}
newfd = fileno(new);
/* Copy permissions, ownership */
if (fstat(fileno(old), &buf) == 0) {
(void) fchown(newfd, buf.st_uid, buf.st_gid);
(void) fchmod(newfd, buf.st_mode);
} else {
(void) fchown(newfd, 0, 3); /* root:sys */
(void) fchmod(newfd, 0644);
}
while ((fep = fgetline(old))) {
char *tok;
char *min;
char *tail;
char tc;
int len;
/* Trailing comments */
if (fep->entry == NULL) {
(void) fputs(fep->rawbuf, new);
break;
}
tok = fep->entry;
while (*tok && isspace(*tok))
tok++;
if (*tok == '\0') {
(void) fputs(fep->rawbuf, new);
break;
}
/* Make sure we can recover the remainder incl. whitespace */
tail = strpbrk(tok, "\t\n ");
if (tail == NULL)
tail = tok + strlen(tok);
tc = *tail;
*tail = '\0';
if (delall || delrange) {
min = strchr(tok, ':');
if (min)
*min++ = '\0';
}
len = strlen(tok);
if (delrange) {
minor_t lo, hi;
char type;
/*
* Delete or shrink overlapping ranges.
*/
if (strncmp(entry, tok, len) == 0 &&
entry[len] == ':' &&
min != NULL &&
parse_minor_range(min, &lo, &hi, &type) == 0 &&
(type == rtype || rtype == '\0') &&
lo <= rhi && hi >= rlo) {
minor_t newlo, newhi;
/* Complete overlap, then drop it. */
if (lo >= rlo && hi <= rhi)
continue;
/* Partial overlap, shrink range */
if (lo < rlo)
newhi = rlo - 1;
else
newhi = hi;
if (hi > rhi)
newlo = rhi + 1;
else
newlo = lo;
/* restore NULed character */
*tail = tc;
/* Split range? */
if (newlo > newhi) {
/*
* We have two ranges:
* lo ... newhi (== rlo - 1)
* newlo (== rhi + 1) .. hi
*/
put_minor_range(new, fep, tok, tail,
lo, newhi, type);
put_minor_range(new, NULL, tok, tail,
newlo, hi, type);
} else {
put_minor_range(new, fep, tok, tail,
newlo, newhi, type);
}
continue;
}
} else if (strcmp(entry, tok) == 0 ||
(strncmp(entry, tok, len) == 0 &&
entry[len] == ':' &&
entry[len+1] == '*' &&
entry[len+2] == '\0')) {
/*
* Delete exact match.
*/
continue;
}
/* Copy unaffected entry. */
(void) fputs(fep->rawbuf, new);
}
(void) fclose(old);
(void) fflush(new);
(void) fsync(newfd);
if (ferror(new) == 0 && fclose(new) == 0 && fep != NULL) {
if (rename(filename, ofile) != 0) {
perror(NULL);
(void) fprintf(stderr, gettext(ERR_UPDATE), ofile);
(void) unlink(ofile);
(void) unlink(nfile);
return (ERROR);
} else if (rename(nfile, filename) != 0) {
perror(NULL);
(void) fprintf(stderr, gettext(ERR_UPDATE), ofile);
(void) rename(ofile, filename);
(void) unlink(nfile);
return (ERROR);
}
(void) unlink(ofile);
} else
(void) unlink(nfile);
return (0);
}
int
delete_plcy_entry(const char *filename, const char *entry)
{
char *p, *single;
char *copy;
int ret = 0;
copy = strdup(entry);
if (copy == NULL)
return (ERROR);
for (single = strtok_r(copy, " \t\n", &p);
single != NULL;
single = strtok_r(NULL, " \t\n", &p)) {
if ((ret = delete_one_entry(filename, single)) != 0) {
free(copy);
return (ret);
}
}
free(copy);
return (0);
}
/*
* Analyze the device policy token; new tokens should be added to
* toktab; new token types should be coded here.
*/
int
parse_plcy_token(char *token, devplcysys_t *dp)
{
char *val = strchr(token, '=');
const char *perr;
int i;
priv_set_t *pset;
if (val == NULL) {
(void) fprintf(stderr, gettext(ERR_NO_EQUALS), token);
return (1);
}
*val++ = '\0';
for (i = 0; i < NTOK; i++) {
if (strcmp(token, toktab[i].token) == 0) {
/* standard pointer computation for tokens */
void *item = (char *)dp + toktab[i].off;
switch (toktab[i].type) {
case PSET:
pset = priv_str_to_set(val, ",", &perr);
if (pset == NULL) {
if (perr == NULL)
(void) fprintf(stderr,
gettext(ERR_NO_MEM));
else
(void) fprintf(stderr,
gettext(ERR_BAD_PRIVS),
perr - val, val, perr);
return (1);
}
priv_copyset(pset, item);
priv_freeset(pset);
break;
default:
(void) fprintf(stderr,
"Internal Error: bad token type: %d\n",
toktab[i].type);
return (1);
}
/* Standard cleanup & return for good tokens */
val[-1] = '=';
return (0);
}
}
(void) fprintf(stderr, gettext(ERR_BAD_TOKEN), token);
return (1);
}
static int
add2str(char **dstp, const char *str, size_t *sz)
{
char *p = *dstp;
size_t len = strlen(p) + strlen(str) + 1;
if (len > *sz) {
*sz *= 2;
if (*sz < len)
*sz = len;
*dstp = p = realloc(p, *sz);
if (p == NULL) {
(void) fprintf(stderr, gettext(ERR_NO_MEM));
return (-1);
}
}
(void) strcat(p, str);
return (0);
}
/*
* Verify that the policy entry is valid and return the canonical entry.
*/
char *
check_plcy_entry(char *entry, const char *driver, boolean_t todel)
{
char *res;
devplcysys_t *ds;
char *tok;
size_t sz = strlen(entry) * 2 + strlen(driver) + 3;
boolean_t tokseen = B_FALSE;
devplcy_init();
res = malloc(sz);
ds = alloca(devplcysys_sz);
if (res == NULL || ds == NULL) {
(void) fprintf(stderr, gettext(ERR_NO_MEM));
return (NULL);
}
*res = '\0';
while ((tok = strtok(entry, " \t\n")) != NULL) {
entry = NULL;
/* It's not a token */
if (strchr(tok, '=') == NULL) {
if (strchr(tok, ':') != NULL) {
(void) fprintf(stderr, gettext(ERR_BAD_MINOR));
free(res);
return (NULL);
}
if (*res != '\0' && add2str(&res, "\n", &sz) != 0)
return (NULL);
if (*tok == '(') {
char type;
if (parse_minor_range(tok, &ds->dps_lomin,
&ds->dps_himin, &type) != 0 ||
(!todel && type == '\0')) {
(void) fprintf(stderr,
gettext(ERR_BAD_MINOR));
free(res);
return (NULL);
}
} else {
char *tmp = strchr(tok, '*');
if (tmp != NULL &&
strchr(tmp + 1, '*') != NULL) {
(void) fprintf(stderr,
gettext(ERR_BAD_MINOR));
free(res);
}
}
if (add2str(&res, driver, &sz) != 0)
return (NULL);
if (add2str(&res, ":", &sz) != 0)
return (NULL);
if (add2str(&res, tok, &sz) != 0)
return (NULL);
tokseen = B_FALSE;
} else {
if (*res == '\0') {
if (add2str(&res, driver, &sz) != 0)
return (NULL);
if (add2str(&res, ":*", &sz) != 0)
return (NULL);
}
if (parse_plcy_token(tok, ds) != 0) {
free(res);
return (NULL);
}
if (add2str(&res, "\t", &sz) != 0)
return (NULL);
if (add2str(&res, tok, &sz) != 0)
return (NULL);
tokseen = B_TRUE;
}
}
if (todel && tokseen || *res == '\0' || !todel && !tokseen) {
(void) fprintf(stderr, gettext(ERR_INVALID_PLCY));
free(res);
return (NULL);
}
if (!todel)
if (add2str(&res, "\n", &sz) != 0)
return (NULL);
return (res);
}
int
update_device_policy(const char *filename, const char *entry, boolean_t repl)
{
FILE *fp;
if (repl) {
char *dup, *tok, *s1;
dup = strdup(entry);
if (dup == NULL) {
(void) fprintf(stderr, gettext(ERR_NO_MEM));
return (ERROR);
}
/*
* Split the entry in lines; then get the first token
* of each line.
*/
for (tok = strtok_r(dup, "\n", &s1); tok != NULL;
tok = strtok_r(NULL, "\n", &s1)) {
tok = strtok(tok, " \n\t");
if (delete_one_entry(filename, tok) != 0) {
free(dup);
return (ERROR);
}
}
free(dup);
}
fp = fopen(filename, "a");
if (fp == NULL)
return (ERROR);
(void) fputs(entry, fp);
if (fflush(fp) != 0 || fsync(fileno(fp)) != 0 || fclose(fp) != 0)
return (ERROR);
return (NOERR);
}
/*
* We need to allocate the privileges now or the privilege set
* parsing code will not allow them.
*/
int
check_priv_entry(const char *privlist, boolean_t add)
{
char *l = strdup(privlist);
char *pr;
if (l == NULL) {
(void) fprintf(stderr, gettext(ERR_NO_MEM));
return (ERROR);
}
while ((pr = strtok_r(l, ",", &l)) != NULL) {
/* Privilege already exists */
if (priv_getbyname(pr) != -1)
continue;
if (add && modctl(MODALLOCPRIV, pr) != 0) {
(void) fprintf(stderr, gettext(ERR_BAD_PRIV), pr,
strerror(errno));
return (ERROR);
}
}
return (NOERR);
}