/* * 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. */ /* * Copyright 2020 OmniOS Community Edition (OmniOSce) Association. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 */ 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)); free(res); 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); }