/*
* 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 (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
*/
#define _POSIX_PTHREAD_SEMANTICS 1
#include <alloca.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <nss_dbdefs.h>
#include <priv.h>
#include <pwd.h>
#include <rpc/auth_sys.h>
#include <rpc/rpc.h>
#include <secdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/procset.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <xpol.h>
extern bool_t xdr_xpol_rule(XDR *, xpol_rule_t *);
static void
xpol_rules_free(xpol_rule_t *r)
{
struct xpol_rule *n;
for (; r != NULL; r = n) {
n = r->xr_next;
switch (r->xr_args_type) {
case XPOL_PATH:
free(r->xr_path);
break;
}
priv_freeset(r->xr_privs);
free(r);
}
}
xpol_rule_t *
xpol_decode(void *buf, size_t size)
{
XDR xdrs;
xpol_rule_t *r = calloc(sizeof (xpol_rule_t), 1);
xdrmem_create(&xdrs, buf, size, XDR_DECODE);
if (!xdr_xpol_rule(&xdrs, r)) {
xpol_rules_free(r);
return (NULL);
}
return (r);
}
void *
xpol_encode(xpol_ctxt_t *ctxt, size_t *rs)
{
XDR xdrs;
size_t size = xdr_sizeof(xdr_xpol_rule, ctxt->xc_rules);
char *buf = malloc(size);
if (buf == NULL)
return (NULL);
xdrmem_create(&xdrs, buf, size, XDR_ENCODE);
if (!xdr_xpol_rule(&xdrs, ctxt->xc_rules)) {
free(buf);
return (NULL);
}
*rs = size;
return (buf);
}
size_t
xpol_encode_size(xpol_ctxt_t *ctxt)
{
return (xdr_sizeof(xdr_xpol_rule, ctxt->xc_rules));
}
void
xpol_decode_free(xpol_rule_t *r)
{
xdr_free(xdr_xpol_rule, (char *)r);
free(r);
}
static xpol_ctxt_t *
xpol_context_create(void)
{
return (calloc(1, sizeof (xpol_ctxt_t)));
}
int
xpol_context_install(xpol_ctxt_t *ctxt)
{
int bytes;
int res = 0;
char *mem;
XDR xdrs;
if (ctxt == NULL || ctxt->xc_rules == NULL) {
errno = EINVAL;
return (-1);
}
bytes = xdr_sizeof(xdr_xpol_rule, ctxt->xc_rules);
mem = malloc(bytes);
xdrmem_create(&xdrs, mem, bytes, XDR_ENCODE);
if (!xdr_xpol_rule(&xdrs, ctxt->xc_rules))
res = EBADF;
else
res = syscall(SYS_privsys, PRIVSYS_XPOLICY_ADD, 0,
0, mem, bytes);
free(mem);
return (res);
}
void
xpol_context_free(xpol_ctxt_t *ctxt)
{
if (ctxt == NULL)
return;
xpol_rules_free(ctxt->xc_rules);
free(ctxt);
}
static int
xpol_add_rule(xpol_ctxt_t *ctxt, xpol_rule_t *r)
{
r->xr_next = NULL;
if (ctxt->xc_rules == NULL)
ctxt->xc_rules = r;
else
ctxt->xc_last->xr_next = r;
ctxt->xc_last = r;
return (0);
}
static int
xpol_context_add_rule_path(xpol_ctxt_t *ctxt, priv_set_t *set, const char *path)
{
struct xpol_rule *r;
if (ctxt == NULL || set == NULL || path == NULL)
return (-1);
r = malloc(sizeof (struct xpol_rule));
if (r == NULL)
return (-1);
r->xr_privs = priv_allocset();
if (r->xr_privs != NULL)
priv_copyset(set, r->xr_privs);
r->xr_args_type = XPOL_PATH;
r->xr_path = _unescape(path, ",");
if (r->xr_privs == NULL || r->xr_path == NULL) {
priv_freeset(r->xr_privs);
free(r->xr_path);
free(r);
return (-1);
}
return (xpol_add_rule(ctxt, r));
}
static int
xpol_context_add_rule_uids(xpol_ctxt_t *ctxt, priv_set_t *set,
uid_t min, uid_t max)
{
struct xpol_rule *r;
if (ctxt == NULL || set == NULL)
return (-1);
r = malloc(sizeof (struct xpol_rule));
if (r == NULL)
return (-1);
r->xr_privs = priv_allocset();
if (r->xr_privs == NULL) {
free(r);
return (-1);
}
priv_copyset(set, r->xr_privs);
r->xr_args_type = XPOL_UIDS;
r->xr_uids.umin = min;
r->xr_uids.umax = max;
return (xpol_add_rule(ctxt, r));
}
static int
xpol_context_add_rule_ports(xpol_ctxt_t *ctxt, priv_set_t *set, int type,
in_port_t min, in_port_t max)
{
struct xpol_rule *r;
if (ctxt == NULL || set == NULL)
return (-1);
r = malloc(sizeof (struct xpol_rule));
if (r == NULL)
return (-1);
r->xr_privs = priv_allocset();
if (r->xr_privs == NULL) {
free(r);
return (-1);
}
priv_copyset(set, r->xr_privs);
r->xr_args_type = XPOL_PORTS;
r->xr_ports.proto = type;
r->xr_ports.pmin = min;
r->xr_ports.pmax = max;
return (xpol_add_rule(ctxt, r));
}
/* port/proto or port1-port2/proto */
static char *
findproto(char *ports, int *type)
{
char *proto = strchr(ports, '/');
if (proto == NULL)
return (NULL);
*proto++ = '\0';
if (strcmp(proto, "tcp") == 0)
*type = IPPROTO_TCP;
else if (strcmp(proto, "udp") == 0)
*type = IPPROTO_UDP;
else if (strcmp(proto, "sctp") == 0)
*type = IPPROTO_SCTP;
else if (strcmp(proto, "all") == 0 || strcmp(proto, "*") == 0)
*type = -1;
else
return (NULL);
return (proto);
}
static uint_t
atoi_range(const char *n, uint_t min, uint_t max, int *err)
{
uint_t res;
char *q = NULL;
res = strtoul(n, &q, 10);
if (q == n || *q != '\0') {
res = (uint_t)-1;
*err = EINVAL;
} else if (res < min || res > max) {
res = (uint_t)-1;
*err = ERANGE;
}
return (res);
}
static boolean_t
alldigits_or_range(const char *p)
{
boolean_t seendash = B_FALSE;
for (; *p; p++) {
if (*p == '-' && !seendash) {
seendash = B_FALSE;
continue;
}
if (!isdigit(*p))
return (B_FALSE);
}
return (B_TRUE);
}
static int
makerange(char *input, uint_t *min, uint_t *max,
uint_t minmin, uint_t maxmax)
{
char *p = strchr(input, '-');
int err = 0;
if (p != NULL)
*p++ = '\0';
*max = *min = atoi_range(input, minmin, maxmax, &err);
if (p != NULL) {
*max = atoi_range(p, minmin, maxmax, &err);
if (*min > *max) {
errno = ERANGE;
return (-1);
}
}
if (err != 0) {
errno = err;
return (-1);
}
return (0);
}
extern struct servent *_switch_getservbyname_r(const char *, const char *,
struct servent *, char *, int);
#define getservbyname_r _switch_getservbyname_r
/*
* We should add a way to return error message; also,
* it should make sure that there are no ambiguities
* in rules.
*/
static int
add_rule(xpol_ctxt_t *ctxt, char *rule, priv_set_t *pset)
{
char *mstr;
char *p;
uint_t min, max;
int type;
int res;
char b[NSS_BUFSIZ];
mstr = rule;
if (mstr[0] == '\0') {
/* No object description. */
return (-1);
} else if (mstr[0] == '/' || (mstr[0] == '*' && mstr[1] == '\0')) {
/* Path, many privileges apply here */
res = xpol_context_add_rule_path(ctxt, pset, mstr);
} else if (strchr(mstr, '/') != NULL) {
/* Ports, two privileges are relevant. */
if (!priv_ismember(pset, PRIV_NET_PRIVADDR) &&
!priv_ismember(pset, PRIV_NET_BINDMLP))
return (-1);
p = findproto(mstr, &type);
if (p == NULL)
return (-1);
if (!alldigits_or_range(mstr)) {
struct servent _s, *s;
s = getservbyname_r(mstr, type == -1 ? NULL : p,
&_s, b, sizeof (b));
if (s == NULL)
return (-1);
min = max = ntohs(s->s_port);
} else if (makerange(mstr, &min, &max, 0, USHRT_MAX) != 0 ||
(min == 0 && max != 0)) {
return (-1);
}
res = xpol_context_add_rule_ports(ctxt, pset, type, min, max);
} else {
/* Uids, one privilege is relevant. */
if (!priv_ismember(pset, PRIV_PROC_SETID))
return (-1);
if (!alldigits_or_range(mstr)) {
struct passwd _p, *p;
if (getpwnam_r(mstr, &_p, b, sizeof (b), &p) != 0 ||
p == NULL)
return (-1);
min = max = p->pw_uid;
} else if (makerange(mstr, &min, &max, 0, UINT_MAX) != 0)
return (-1);
res = xpol_context_add_rule_uids(ctxt, pset, min, max);
}
priv_freeset(pset);
return (res);
}
/*
* xpol_parse_ruleset(const char *rule, xpol_ctxt **cres, priv_set_t **iset);
* Returns Inheritable set and the xpol context but one of them can be NULL.
* Multiple calls are allowed and the first call must set *ctxtp and *isetp
* to NULL or initialize them to proper values.
*/
int
xpol_parse_ruleset(const char *orules, xpol_ctxt_t **ctxtp, priv_set_t **isetp)
{
char *q;
char *p;
priv_set_t *tset;
int l = strlen(orules);
char *rule = alloca(l + 1);
int olderr = errno;
errno = EINVAL;
(void) memcpy(rule, orules, l + 1);
for (q = rule; q && q[0] != '\0'; ) {
rule = q;
q = strchr(rule, '{');
if (q == rule) {
q++;
p = strchr(q, '}');
if (p == NULL || p[1] != ':')
return (-1);
*p = '\0';
if ((tset = priv_str_to_set(q, ",", NULL)) == NULL)
return (-1);
q = _strpbrk_escape(p + 2, ",");
if (q != NULL)
*q++ = '\0';
if (*ctxtp == NULL) {
*ctxtp = xpol_context_create();
if (*ctxtp == NULL)
return (-1);
}
if (add_rule(*ctxtp, (char *)p + 2, tset) != 0)
return (-1);
} else {
if (q != NULL)
q[-1] = '\0';
tset = priv_str_to_set(rule, ",", NULL);
if (tset == NULL)
return (-1);
if (*isetp != NULL) {
priv_union(tset, *isetp);
priv_freeset(tset);
} else {
*isetp = tset;
}
}
}
errno = olderr;
return (0);
}
/*
* Return privileges which should be restricted if the extended policy is
* enabled.
*/
static priv_set_t *
xpol_privs(xpol_ctxt_t *ctxt, priv_set_t *zonep)
{
xpol_rule_t *r;
priv_set_t *rset;
if ((rset = priv_allocset()) == NULL)
return (NULL);
priv_emptyset(rset);
if (ctxt == NULL)
return (rset);
for (r = ctxt->xc_rules; r != NULL; r = r->xr_next)
if (zonep == NULL || !priv_issubset(zonep, r->xr_privs))
priv_union(r->xr_privs, rset);
if (zonep != NULL)
priv_freeset(zonep);
return (rset);
}
/*
* Return privileges which should be restricted if the extended policy
* is enabled.
*/
priv_set_t *
xpol_restricted_privs(xpol_ctxt_t *ctxt)
{
priv_set_t *zonep = priv_str_to_set("zone", ",", NULL);
if (zonep == NULL)
return (NULL);
return (xpol_privs(ctxt, zonep));
}
/*
* Return privileges which have extended policies defined for them.
*/
priv_set_t *
xpol_extended_privs(xpol_ctxt_t *ctxt)
{
return (xpol_privs(ctxt, NULL));
}