#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <locale.h>
#include <signal.h>
#include <strings.h>
#include "libshare.h"
#include "libshare_impl.h"
#include <nfs/export.h>
#include <pwd.h>
#include <grp.h>
#include <limits.h>
#include <libscf.h>
#include <syslog.h>
#include <rpcsvc/daemon_utils.h>
#include "nfslog_config.h"
#include "nfslogtab.h"
#include "libshare_nfs.h"
#include <nfs/nfs.h>
#include <nfs/nfssys.h>
#include <netconfig.h>
#include <sys/debug.h>
#include "smfcfg.h"
#define DEF_WIN 30000
#define OPT_CHUNK 1024
int debug = 0;
#define NFS_SERVER_SVC "svc:/network/nfs/server:default"
#define NFS_CLIENT_SVC (char *)"svc:/network/nfs/client:default"
static int nfs_init(void);
static void nfs_fini(void);
static int nfs_enable_share(sa_share_t);
static int nfs_disable_share(sa_share_t, char *);
static int nfs_validate_property(sa_handle_t, sa_property_t, sa_optionset_t);
static int nfs_validate_security_mode(char *);
static int nfs_is_security_opt(char *);
static int nfs_parse_legacy_options(sa_group_t, char *);
static char *nfs_format_options(sa_group_t, int);
static int nfs_set_proto_prop(sa_property_t);
static sa_protocol_properties_t nfs_get_proto_set(void);
static char *nfs_get_status(void);
static char *nfs_space_alias(char *);
static uint64_t nfs_features(void);
static int boolean_check(char *);
static int range_check_validator(uint_t, char *);
static int range_check_validator_client(uint_t, char *);
static int range_check_validator_server(uint_t, char *);
static int onoff_validator(uint_t, char *);
static int domain_validator(uint_t, char *);
static int boolean_validator(uint_t, char *);
static int protocol_validator(uint_t, char *);
struct sa_plugin_ops sa_plugin_ops = {
SA_PLUGIN_VERSION,
"nfs",
nfs_init,
nfs_fini,
nfs_enable_share,
nfs_disable_share,
nfs_validate_property,
nfs_validate_security_mode,
nfs_is_security_opt,
nfs_parse_legacy_options,
nfs_format_options,
nfs_set_proto_prop,
nfs_get_proto_set,
nfs_get_status,
nfs_space_alias,
NULL,
NULL,
NULL,
NULL,
NULL,
nfs_features,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
static char *service_list_default[] =
{ STATD, LOCKD, MOUNTD, NFSD, NFSMAPID, RQUOTAD, REPARSED, NULL };
static char *service_list_logging[] =
{ STATD, LOCKD, MOUNTD, NFSD, NFSMAPID, RQUOTAD, NFSLOGD, REPARSED,
NULL };
struct option_defs optdefs[] = {
#define OPT_RO 0
{SHOPT_RO, OPT_RO, OPT_TYPE_ACCLIST},
#define OPT_RW 1
{SHOPT_RW, OPT_RW, OPT_TYPE_ACCLIST},
#define OPT_ROOT 2
{SHOPT_ROOT, OPT_ROOT, OPT_TYPE_ACCLIST},
#define OPT_SECURE 3
{SHOPT_SECURE, OPT_SECURE, OPT_TYPE_DEPRECATED},
#define OPT_ANON 4
{SHOPT_ANON, OPT_ANON, OPT_TYPE_USER},
#define OPT_WINDOW 5
{SHOPT_WINDOW, OPT_WINDOW, OPT_TYPE_NUMBER},
#define OPT_NOSUID 6
{SHOPT_NOSUID, OPT_NOSUID, OPT_TYPE_BOOLEAN},
#define OPT_ACLOK 7
{SHOPT_ACLOK, OPT_ACLOK, OPT_TYPE_BOOLEAN},
#define OPT_NOSUB 8
{SHOPT_NOSUB, OPT_NOSUB, OPT_TYPE_BOOLEAN},
#define OPT_SEC 9
{SHOPT_SEC, OPT_SEC, OPT_TYPE_SECURITY},
#define OPT_PUBLIC 10
{SHOPT_PUBLIC, OPT_PUBLIC, OPT_TYPE_BOOLEAN, OPT_SHARE_ONLY},
#define OPT_INDEX 11
{SHOPT_INDEX, OPT_INDEX, OPT_TYPE_FILE},
#define OPT_LOG 12
{SHOPT_LOG, OPT_LOG, OPT_TYPE_LOGTAG},
#define OPT_CKSUM 13
{SHOPT_CKSUM, OPT_CKSUM, OPT_TYPE_STRINGSET},
#define OPT_NONE 14
{SHOPT_NONE, OPT_NONE, OPT_TYPE_ACCLIST},
#define OPT_ROOT_MAPPING 15
{SHOPT_ROOT_MAPPING, OPT_ROOT_MAPPING, OPT_TYPE_USER},
#define OPT_CHARSET_MAP 16
{"", OPT_CHARSET_MAP, OPT_TYPE_ACCLIST},
#define OPT_NOACLFAB 17
{SHOPT_NOACLFAB, OPT_NOACLFAB, OPT_TYPE_BOOLEAN},
#define OPT_UIDMAP 18
{SHOPT_UIDMAP, OPT_UIDMAP, OPT_TYPE_MAPPING},
#define OPT_GIDMAP 19
{SHOPT_GIDMAP, OPT_GIDMAP, OPT_TYPE_MAPPING},
#define OPT_NOHIDE 20
{SHOPT_NOHIDE, OPT_NOHIDE, OPT_TYPE_BOOLEAN},
#ifdef VOLATILE_FH_TEST
#define OPT_VOLFH 21
{SHOPT_VOLFH, OPT_VOLFH},
#endif
};
static char *legal_conv[] = {
"euc-cn",
"euc-jp",
"euc-jpms",
"euc-kr",
"euc-tw",
"iso8859-1",
"iso8859-2",
"iso8859-5",
"iso8859-6",
"iso8859-7",
"iso8859-8",
"iso8859-9",
"iso8859-13",
"iso8859-15",
"koi8-r",
NULL
};
static char *seclist[] = {
SHOPT_RO,
SHOPT_RW,
SHOPT_ROOT,
SHOPT_WINDOW,
SHOPT_NONE,
SHOPT_ROOT_MAPPING,
SHOPT_UIDMAP,
SHOPT_GIDMAP,
NULL
};
struct securities {
sa_security_t security;
struct securities *next;
};
static boolean_t
findcharset(char *charset)
{
int i;
for (i = 0; legal_conv[i] != NULL; i++)
if (strcmp(charset, legal_conv[i]) == 0)
return (B_TRUE);
return (B_FALSE);
}
static int
findopt(char *name)
{
int i;
if (name != NULL) {
for (i = 0; i < ARRAY_SIZE(optdefs); i++) {
if (strcmp(optdefs[i].tag, name) == 0)
return (optdefs[i].index);
}
if (findcharset(name))
return (OPT_CHARSET_MAP);
}
return (-1);
}
static int
gettype(char *name)
{
int optdef;
optdef = findopt(name);
if (optdef != -1)
return (optdefs[optdef].type);
return (OPT_TYPE_ANY);
}
static int
nfs_validate_security_mode(char *mode)
{
seconfig_t secinfo;
int err;
(void) memset(&secinfo, '\0', sizeof (secinfo));
err = nfs_getseconfig_byname(mode, &secinfo);
if (err == SC_NOERROR)
return (1);
return (0);
}
static int
nfs_is_security_opt(char *tok)
{
int i;
for (i = 0; seclist[i] != NULL; i++) {
if (strcmp(tok, seclist[i]) == 0)
return (1);
}
return (0);
}
static int
find_security(struct securities *seclist, sa_security_t sec)
{
while (seclist != NULL) {
if (seclist->security == sec)
return (1);
seclist = seclist->next;
}
return (0);
}
static struct securities *
make_security_list(sa_group_t group, char *securitymodes, char *proto)
{
char *tok, *next = NULL;
struct securities *curp, *headp = NULL, *prev;
sa_security_t check;
int freetok = 0;
for (tok = securitymodes; tok != NULL; tok = next) {
next = strchr(tok, ':');
if (next != NULL)
*next++ = '\0';
if (strcmp(tok, "default") == 0) {
tok = nfs_space_alias(tok);
freetok = 1;
}
check = sa_get_security(group, tok, proto);
if (check == NULL || !find_security(headp, check)) {
curp = (struct securities *)calloc(1,
sizeof (struct securities));
if (curp != NULL) {
if (check == NULL) {
curp->security = sa_create_security(
group, tok, proto);
} else {
curp->security = check;
}
if (headp == NULL) {
headp = curp;
prev = curp;
} else {
prev->next = curp;
prev = curp;
}
}
}
if (freetok) {
freetok = 0;
sa_free_attr_string(tok);
}
}
return (headp);
}
static void
free_security_list(struct securities *sec)
{
struct securities *next;
if (sec != NULL) {
for (next = sec->next; sec != NULL; sec = next) {
next = sec->next;
free(sec);
}
}
}
static char *
nfs_alistcat(char *str1, char *str2, char sep)
{
char *newstr;
size_t len;
len = strlen(str1) + strlen(str2) + 2;
newstr = (char *)malloc(len);
if (newstr != NULL)
(void) snprintf(newstr, len, "%s%c%s", str1, sep, str2);
return (newstr);
}
static int
add_security_prop(struct securities *sec, char *name, char *value,
int persist, int iszfs)
{
sa_property_t prop;
int ret = SA_OK;
for (; sec != NULL; sec = sec->next) {
if (value == NULL) {
if (strcmp(name, SHOPT_RW) == 0 ||
strcmp(name, SHOPT_RO) == 0)
value = "*";
else
value = "true";
}
prop = sa_get_property(sec->security, name);
if (prop != NULL) {
char *oldvalue;
char *newvalue;
oldvalue = sa_get_property_attr(prop, "value");
if (oldvalue != NULL) {
char sep = ':';
if (strcmp(name, SHOPT_UIDMAP) == 0 ||
strcmp(name, SHOPT_GIDMAP) == 0)
sep = '~';
if (strcmp(oldvalue, "*") == 0) {
newvalue = strdup(value);
} else if (strcmp(value, "*") == 0 ||
strcmp(oldvalue, value) == 0) {
newvalue = NULL;
} else {
newvalue = nfs_alistcat(oldvalue,
value, sep);
}
if (newvalue != NULL) {
(void) sa_remove_property(prop);
prop = sa_create_property(name,
newvalue);
ret = sa_add_property(sec->security,
prop);
free(newvalue);
}
sa_free_attr_string(oldvalue);
}
} else {
prop = sa_create_property(name, value);
ret = sa_add_property(sec->security, prop);
}
if (ret == SA_OK && !iszfs) {
ret = sa_commit_properties(sec->security, !persist);
}
}
return (ret);
}
static int
is_persistent(sa_group_t group)
{
char *type;
int persist = 1;
type = sa_get_group_attr(group, "type");
if (type != NULL && strcmp(type, "persist") != 0)
persist = 0;
if (type != NULL)
sa_free_attr_string(type);
return (persist);
}
static int
invalid_security(char *options)
{
char *copy, *base, *token, *value;
int ret = 0;
copy = strdup(options);
token = base = copy;
while (token != NULL && ret == 0) {
token = strtok(base, ",");
base = NULL;
if (token != NULL) {
value = strchr(token, '=');
if (value != NULL)
*value++ = '\0';
if (strcmp(token, SHOPT_SEC) == 0) {
char *tok, *next;
for (next = NULL, tok = value; tok != NULL;
tok = next) {
next = strchr(tok, ':');
if (next != NULL)
*next++ = '\0';
ret = !nfs_validate_security_mode(tok);
if (ret)
break;
}
}
}
}
if (copy != NULL)
free(copy);
return (ret);
}
static int
nfs_parse_legacy_options(sa_group_t group, char *options)
{
char *dup;
char *base;
char *token;
sa_optionset_t optionset;
struct securities *security_list = NULL;
sa_property_t prop;
int ret = SA_OK;
int iszfs = 0;
sa_group_t parent;
int persist = 0;
char *lasts;
optionset = sa_get_optionset(group, "nfs");
if (optionset == NULL) {
optionset = sa_create_optionset(group, "nfs");
} else {
if (sa_get_property(optionset, NULL) != NULL)
return (ret);
}
if (strcmp(options, SHOPT_RW) == 0) {
return (ret);
}
if (invalid_security(options)) {
return (SA_INVALID_SECURITY);
}
if (sa_is_share(group)) {
char *zfs;
parent = sa_get_parent_group(group);
if (parent != NULL) {
zfs = sa_get_group_attr(parent, "zfs");
if (zfs != NULL) {
sa_free_attr_string(zfs);
iszfs++;
}
}
} else {
iszfs = sa_group_is_zfs(group);
}
dup = strdup(options);
if (dup == NULL)
return (SA_NO_MEMORY);
persist = is_persistent(group);
base = dup;
token = dup;
lasts = NULL;
while (token != NULL && ret == SA_OK) {
token = strtok_r(base, ",", &lasts);
base = NULL;
if (token != NULL) {
char *value;
value = strchr(token, '=');
if (value != NULL) {
*value++ = '\0';
}
if (strcmp(token, SHOPT_SEC) == 0 ||
strcmp(token, SHOPT_SECURE) == 0) {
if (security_list != NULL) {
free_security_list(security_list);
}
if (strcmp(token, SHOPT_SECURE) == 0) {
value = "dh";
} else {
if (value == NULL) {
ret = SA_SYNTAX_ERR;
break;
}
}
security_list = make_security_list(group,
value, "nfs");
} else {
if (nfs_is_security_opt(token)) {
if (security_list == NULL) {
security_list =
make_security_list(group,
"default",
"nfs");
}
if (security_list != NULL) {
ret = add_security_prop(
security_list, token,
value, persist, iszfs);
} else {
ret = SA_NO_MEMORY;
}
} else {
if (value == NULL) {
if (strcmp(token, SHOPT_RW) ==
0 || strcmp(token,
SHOPT_RO) == 0) {
value = "*";
} else {
value = "global";
if (strcmp(token,
SHOPT_LOG) != 0) {
value = "true";
}
}
}
prop = sa_create_property(token, value);
ret = sa_add_property(optionset, prop);
if (ret != SA_OK)
break;
if (!iszfs) {
ret = sa_commit_properties(
optionset, !persist);
}
}
}
}
}
if (security_list != NULL)
free_security_list(security_list);
free(dup);
return (ret);
}
static int
is_a_number(char *number)
{
int ret = 1;
int hex = 0;
if (strncmp(number, "0x", 2) == 0) {
number += 2;
hex = 1;
} else if (*number == '-') {
number++;
}
while (ret == 1 && *number != '\0') {
if (hex) {
ret = isxdigit(*number++);
} else {
ret = isdigit(*number++);
}
}
return (ret);
}
static void
configlog(struct exportdata *exp, char *tag)
{
nfsl_config_t *configlist = NULL, *configp;
int error = 0;
char globaltag[] = DEFAULTTAG;
nfsl_errs_to_syslog = B_FALSE;
error = nfsl_getconfig_list(&configlist);
if (error) {
(void) fprintf(stderr,
dgettext(TEXT_DOMAIN, "Cannot get log configuration: %s\n"),
strerror(error));
}
if (tag == NULL)
tag = globaltag;
if ((configp = nfsl_findconfig(configlist, tag, &error)) == NULL) {
nfsl_freeconfig_list(&configlist);
(void) fprintf(stderr,
dgettext(TEXT_DOMAIN, "No tags matching \"%s\"\n"), tag);
error = ENOENT;
goto err;
}
if ((exp->ex_tag = strdup(tag)) == NULL) {
error = ENOMEM;
goto out;
}
if ((exp->ex_log_buffer = strdup(configp->nc_bufferpath)) == NULL) {
error = ENOMEM;
goto out;
}
exp->ex_flags |= EX_LOG;
if (configp->nc_rpclogpath != NULL)
exp->ex_flags |= EX_LOG_ALLOPS;
out:
if (configlist != NULL)
nfsl_freeconfig_list(&configlist);
err:
if (error != 0) {
free(exp->ex_tag);
free(exp->ex_log_buffer);
(void) fprintf(stderr,
dgettext(TEXT_DOMAIN, "Cannot set log configuration: %s\n"),
strerror(error));
}
}
static int
fill_export_from_optionset(struct exportdata *export, sa_optionset_t optionset)
{
sa_property_t option;
int ret = SA_OK;
for (option = sa_get_property(optionset, NULL);
option != NULL; option = sa_get_next_property(option)) {
char *name;
char *value;
uint32_t val;
name = sa_get_property_attr(option, "type");
value = sa_get_property_attr(option, "value");
switch (findopt(name)) {
case OPT_ANON:
if (value != NULL && is_a_number(value)) {
val = strtoul(value, NULL, 0);
} else {
struct passwd *pw;
pw = getpwnam(value != NULL ? value : "nobody");
if (pw != NULL) {
val = pw->pw_uid;
} else {
val = UID_NOBODY;
}
endpwent();
}
export->ex_anon = val;
break;
case OPT_NOSUID:
if (value != NULL && (strcasecmp(value, "true") == 0 ||
strcmp(value, "1") == 0))
export->ex_flags |= EX_NOSUID;
else
export->ex_flags &= ~EX_NOSUID;
break;
case OPT_ACLOK:
if (value != NULL && (strcasecmp(value, "true") == 0 ||
strcmp(value, "1") == 0))
export->ex_flags |= EX_ACLOK;
else
export->ex_flags &= ~EX_ACLOK;
break;
case OPT_NOSUB:
if (value != NULL && (strcasecmp(value, "true") == 0 ||
strcmp(value, "1") == 0))
export->ex_flags |= EX_NOSUB;
else
export->ex_flags &= ~EX_NOSUB;
break;
case OPT_PUBLIC:
if (value != NULL && (strcasecmp(value, "true") == 0 ||
strcmp(value, "1") == 0))
export->ex_flags |= EX_PUBLIC;
else
export->ex_flags &= ~EX_PUBLIC;
break;
case OPT_INDEX:
if (value != NULL && (strcmp(value, "..") == 0 ||
strchr(value, '/') != NULL)) {
(void) printf(dgettext(TEXT_DOMAIN,
"NFS: index=\"%s\" not valid;"
"must be a filename.\n"),
value);
break;
}
if (value != NULL && *value != '\0' &&
strcmp(value, ".") != 0) {
if (export->ex_index != NULL) {
free(export->ex_index);
}
export->ex_index = strdup(value);
if (export->ex_index == NULL) {
(void) printf(dgettext(TEXT_DOMAIN,
"NFS: out of memory setting "
"index property\n"));
break;
}
export->ex_flags |= EX_INDEX;
}
break;
case OPT_LOG:
if (value == NULL)
value = strdup("global");
if (value != NULL)
configlog(export,
strlen(value) ? value : "global");
break;
case OPT_CHARSET_MAP:
export->ex_flags |= EX_CHARMAP;
break;
case OPT_NOACLFAB:
if (value != NULL && (strcasecmp(value, "true") == 0 ||
strcmp(value, "1") == 0))
export->ex_flags |= EX_NOACLFAB;
else
export->ex_flags &= ~EX_NOACLFAB;
break;
case OPT_NOHIDE:
if (value != NULL && (strcasecmp(value, "true") == 0 ||
strcmp(value, "1") == 0))
export->ex_flags |= EX_NOHIDE;
else
export->ex_flags &= ~EX_NOHIDE;
break;
default:
(void) printf(dgettext(TEXT_DOMAIN,
"NFS: unrecognized option %s=%s\n"),
name != NULL ? name : "",
value != NULL ? value : "");
break;
}
if (name != NULL)
sa_free_attr_string(name);
if (value != NULL)
sa_free_attr_string(value);
}
return (ret);
}
static void
cleanup_export(struct exportdata *export)
{
int i;
free(export->ex_index);
for (i = 0; i < export->ex_seccnt; i++) {
struct secinfo *s = &export->ex_secinfo[i];
while (s->s_rootcnt > 0)
free(s->s_rootnames[--s->s_rootcnt]);
free(s->s_rootnames);
}
free(export->ex_secinfo);
}
static caddr_t *
get_rootnames(seconfig_t *sec, char *list, int *count)
{
caddr_t *a;
int c, i;
char *host, *p;
c = 1;
for (p = list; *p; p++)
if (*p == ':')
c++;
*count = c;
a = (caddr_t *)malloc(c * sizeof (char *));
if (a == NULL) {
(void) printf(dgettext(TEXT_DOMAIN,
"get_rootnames: no memory\n"));
*count = 0;
} else {
for (i = 0; i < c; i++) {
host = strtok(list, ":");
if (!nfs_get_root_principal(sec, host, &a[i])) {
while (i > 0)
free(a[--i]);
free(a);
a = NULL;
*count = 0;
break;
}
list = NULL;
}
}
return (a);
}
static int
fill_security_from_secopts(struct secinfo *sp, sa_security_t secopts)
{
sa_property_t prop;
char *type;
int longform;
int err = SC_NOERROR;
uint32_t val;
type = sa_get_security_attr(secopts, "sectype");
if (type != NULL) {
err = nfs_getseconfig_byname(type, &sp->s_secinfo);
sa_free_attr_string(type);
if (err != SC_NOERROR)
return (err);
} else {
err = nfs_getseconfig_default(&sp->s_secinfo);
if (err != SC_NOERROR)
return (err);
}
err = SA_OK;
for (prop = sa_get_property(secopts, NULL);
prop != NULL && err == SA_OK;
prop = sa_get_next_property(prop)) {
char *name;
char *value;
name = sa_get_property_attr(prop, "type");
value = sa_get_property_attr(prop, "value");
longform = value != NULL && strcmp(value, "*") != 0;
switch (findopt(name)) {
case OPT_RO:
sp->s_flags |= longform ? M_ROL : M_RO;
break;
case OPT_RW:
sp->s_flags |= longform ? M_RWL : M_RW;
break;
case OPT_ROOT:
sp->s_flags |= M_ROOT;
if (sp->s_secinfo.sc_rpcnum == AUTH_UNIX)
break;
if (value != NULL) {
sp->s_rootnames = get_rootnames(&sp->s_secinfo,
value, &sp->s_rootcnt);
if (sp->s_rootnames == NULL) {
err = SA_BAD_VALUE;
(void) fprintf(stderr,
dgettext(TEXT_DOMAIN,
"Bad root list\n"));
}
}
break;
case OPT_NONE:
sp->s_flags |= M_NONE;
break;
case OPT_WINDOW:
if (value != NULL) {
sp->s_window = atoi(value);
if (sp->s_window < 0)
sp->s_window = DEF_WIN;
}
break;
case OPT_ROOT_MAPPING:
if (value != NULL && is_a_number(value)) {
val = strtoul(value, NULL, 0);
} else {
struct passwd *pw;
pw = getpwnam(value != NULL ? value : "nobody");
if (pw != NULL) {
val = pw->pw_uid;
} else {
val = UID_NOBODY;
}
endpwent();
}
sp->s_rootid = val;
break;
case OPT_UIDMAP:
case OPT_GIDMAP:
sp->s_flags |= M_MAP;
break;
default:
break;
}
if (name != NULL)
sa_free_attr_string(name);
if (value != NULL)
sa_free_attr_string(value);
}
if ((sp->s_flags & NFS_RWMODES) == 0)
sp->s_flags |= M_RW;
return (err);
}
static void
printarg(char *path, struct exportdata *ep)
{
int i, j;
struct secinfo *sp;
if (debug == 0)
return;
(void) printf("%s:\n", path);
(void) printf("\tex_version = %d\n", ep->ex_version);
(void) printf("\tex_path = %s\n", ep->ex_path);
(void) printf("\tex_pathlen = %ld\n", (ulong_t)ep->ex_pathlen);
(void) printf("\tex_flags: (0x%02x) ", ep->ex_flags);
if (ep->ex_flags & EX_NOSUID)
(void) printf("NOSUID ");
if (ep->ex_flags & EX_ACLOK)
(void) printf("ACLOK ");
if (ep->ex_flags & EX_PUBLIC)
(void) printf("PUBLIC ");
if (ep->ex_flags & EX_NOSUB)
(void) printf("NOSUB ");
if (ep->ex_flags & EX_LOG)
(void) printf("LOG ");
if (ep->ex_flags & EX_CHARMAP)
(void) printf("CHARMAP ");
if (ep->ex_flags & EX_LOG_ALLOPS)
(void) printf("LOG_ALLOPS ");
if (ep->ex_flags == 0)
(void) printf("(none)");
(void) printf("\n");
if (ep->ex_flags & EX_LOG) {
(void) printf("\tex_log_buffer = %s\n",
(ep->ex_log_buffer ? ep->ex_log_buffer : "(NULL)"));
(void) printf("\tex_tag = %s\n",
(ep->ex_tag ? ep->ex_tag : "(NULL)"));
}
(void) printf("\tex_anon = %d\n", ep->ex_anon);
(void) printf("\tex_seccnt = %d\n", ep->ex_seccnt);
(void) printf("\n");
for (i = 0; i < ep->ex_seccnt; i++) {
sp = &ep->ex_secinfo[i];
(void) printf("\t\ts_secinfo = %s\n", sp->s_secinfo.sc_name);
(void) printf("\t\ts_flags: (0x%02x) ", sp->s_flags);
if (sp->s_flags & M_ROOT) (void) printf("M_ROOT ");
if (sp->s_flags & M_RO) (void) printf("M_RO ");
if (sp->s_flags & M_ROL) (void) printf("M_ROL ");
if (sp->s_flags & M_RW) (void) printf("M_RW ");
if (sp->s_flags & M_RWL) (void) printf("M_RWL ");
if (sp->s_flags & M_NONE) (void) printf("M_NONE ");
if (sp->s_flags & M_MAP) (void) printf("M_MAP ");
if (sp->s_flags == 0) (void) printf("(none)");
(void) printf("\n");
(void) printf("\t\ts_window = %d\n", sp->s_window);
(void) printf("\t\ts_rootid = %d\n", sp->s_rootid);
(void) printf("\t\ts_rootcnt = %d ", sp->s_rootcnt);
(void) fflush(stdout);
for (j = 0; j < sp->s_rootcnt; j++)
(void) printf("%s ", sp->s_rootnames[j] ?
sp->s_rootnames[j] : "<null>");
(void) printf("\n\n");
}
}
static int
count_security(sa_optionset_t opts)
{
int count = 0;
sa_property_t prop;
if (opts != NULL) {
for (prop = sa_get_property(opts, NULL); prop != NULL;
prop = sa_get_next_property(prop)) {
count++;
}
}
return (count);
}
static int
nfs_sprint_option(char **rbuff, size_t *rbuffsize, size_t incr,
sa_property_t prop, int sep)
{
char *name;
char *value;
int curlen;
char *buff = *rbuff;
size_t buffsize = *rbuffsize;
int printed = B_FALSE;
name = sa_get_property_attr(prop, "type");
value = sa_get_property_attr(prop, "value");
if (buff != NULL)
curlen = strlen(buff);
else
curlen = 0;
if (name != NULL) {
int len;
len = strlen(name) + sep;
switch (gettype(name)) {
case OPT_TYPE_BOOLEAN:
if (value != NULL && strcasecmp(value, "false") == 0)
goto skip;
if (value != NULL) {
sa_free_attr_string(value);
value = NULL;
}
break;
case OPT_TYPE_ACCLIST:
if (value != NULL && strcmp(value, "*") == 0) {
sa_free_attr_string(value);
value = NULL;
} else {
if (value != NULL)
len += 1 + strlen(value);
}
break;
case OPT_TYPE_LOGTAG:
if (value != NULL && strlen(value) == 0) {
sa_free_attr_string(value);
value = NULL;
} else {
if (value != NULL)
len += 1 + strlen(value);
}
break;
default:
if (value != NULL)
len += 1 + strlen(value);
break;
}
while (buffsize <= (curlen + len)) {
buffsize += incr;
buff = realloc(buff, buffsize);
if (buff == NULL) {
if (*rbuff != NULL)
free(*rbuff);
}
*rbuff = buff;
*rbuffsize = buffsize;
if (buff == NULL)
goto skip;
}
if (buff == NULL)
goto skip;
if (value == NULL) {
(void) snprintf(buff + curlen, buffsize - curlen,
"%s%s", sep ? "," : "", name);
} else {
(void) snprintf(buff + curlen, buffsize - curlen,
"%s%s=%s", sep ? "," : "",
name, value != NULL ? value : "");
}
printed = B_TRUE;
}
skip:
if (name != NULL)
sa_free_attr_string(name);
if (value != NULL)
sa_free_attr_string(value);
return (printed);
}
static char *
nfs_format_options(sa_group_t group, int hier)
{
sa_optionset_t options = NULL;
sa_optionset_t secoptions = NULL;
sa_property_t prop, secprop;
sa_security_t security = NULL;
char *buff;
size_t buffsize;
char *sectype = NULL;
int sep = 0;
buff = malloc(OPT_CHUNK);
if (buff == NULL) {
return (NULL);
}
buff[0] = '\0';
buffsize = OPT_CHUNK;
options = sa_get_derived_optionset(group, "nfs", hier);
if (options != NULL) {
for (prop = sa_get_property(options, NULL);
prop != NULL; prop = sa_get_next_property(prop)) {
if (nfs_sprint_option(&buff, &buffsize, OPT_CHUNK,
prop, sep))
sep = 1;
if (buff == NULL) {
if (options != NULL)
sa_free_derived_optionset(
options);
return (buff);
}
}
}
secoptions = (sa_optionset_t)sa_get_all_security_types(group,
"nfs", hier);
if (secoptions != NULL) {
for (secprop = sa_get_property(secoptions, NULL);
secprop != NULL;
secprop = sa_get_next_property(secprop)) {
sectype = sa_get_property_attr(secprop, "type");
security =
(sa_security_t)sa_get_derived_security(
group, sectype, "nfs", hier);
if (security != NULL) {
if (sectype != NULL) {
prop = sa_create_property(
"sec", sectype);
if (prop == NULL)
goto err;
if (nfs_sprint_option(&buff,
&buffsize, OPT_CHUNK, prop, sep))
sep = 1;
(void) sa_remove_property(prop);
if (buff == NULL)
goto err;
}
for (prop = sa_get_property(security,
NULL); prop != NULL;
prop = sa_get_next_property(prop)) {
if (nfs_sprint_option(&buff,
&buffsize, OPT_CHUNK, prop, sep))
sep = 1;
if (buff == NULL)
goto err;
}
sa_free_derived_optionset(security);
}
if (sectype != NULL)
sa_free_attr_string(sectype);
}
sa_free_derived_optionset(secoptions);
}
if (options != NULL)
sa_free_derived_optionset(options);
return (buff);
err:
if (secoptions != NULL)
sa_free_derived_optionset(secoptions);
if (security != NULL)
sa_free_derived_optionset(security);
if (sectype != NULL)
sa_free_attr_string(sectype);
if (options != NULL)
sa_free_derived_optionset(options);
return (buff);
}
static int
nfslogtab_add(char *dir, char *buffer, char *tag)
{
FILE *f;
struct logtab_ent lep;
int error = 0;
f = fopen(NFSLOGTAB, "a+");
if (f == NULL) {
error = errno;
goto out;
}
rewind(f);
if (lockf(fileno(f), F_LOCK, 0L) < 0) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"share complete, however failed to lock %s "
"for update: %s\n"), NFSLOGTAB, strerror(errno));
error = -1;
goto out;
}
if (logtab_deactivate_after_boot(f) == -1) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"share complete, however could not deactivate "
"entries in %s\n"), NFSLOGTAB);
error = -1;
goto out;
}
if (logtab_rement(f, buffer, dir, NULL, -1)) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"share complete, however could not remove matching "
"entries in %s\n"), NFSLOGTAB);
error = -1;
goto out;
}
if (logtab_deactivate(f, NULL, dir, NULL)) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"share complete, however could not deactivate matching "
"entries in %s\n"), NFSLOGTAB);
error = -1;
goto out;
}
lep.le_buffer = buffer;
lep.le_path = dir;
lep.le_tag = tag;
lep.le_state = LES_ACTIVE;
if (logtab_putent(f, &lep) < 0) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"share complete, however could not add %s to %s\n"),
dir, NFSLOGTAB);
error = -1;
}
out:
if (f != NULL)
(void) fclose(f);
return (error);
}
static int
nfslogtab_deactivate(char *path)
{
FILE *f;
int error = 0;
f = fopen(NFSLOGTAB, "r+");
if (f == NULL) {
error = errno;
goto out;
}
if (lockf(fileno(f), F_LOCK, 0L) < 0) {
error = errno;
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"share complete, however could not lock %s for "
"update: %s\n"), NFSLOGTAB, strerror(error));
goto out;
}
if (logtab_deactivate(f, NULL, path, NULL) == -1) {
error = -1;
(void) fprintf(stderr,
dgettext(TEXT_DOMAIN,
"share complete, however could not "
"deactivate %s in %s\n"), path, NFSLOGTAB);
goto out;
}
out: if (f != NULL)
(void) fclose(f);
return (error);
}
static int
check_public(sa_group_t group, sa_share_t skipshare)
{
int exists = B_FALSE;
sa_share_t share;
sa_optionset_t opt;
sa_property_t prop;
char *shared;
for (share = sa_get_share(group, NULL); share != NULL;
share = sa_get_next_share(share)) {
if (share == skipshare)
continue;
opt = sa_get_optionset(share, "nfs");
if (opt == NULL)
continue;
prop = sa_get_property(opt, "public");
if (prop == NULL)
continue;
shared = sa_get_share_attr(share, "shared");
if (shared != NULL) {
exists = strcmp(shared, "true") == 0;
sa_free_attr_string(shared);
if (exists == B_TRUE)
break;
}
}
return (exists);
}
static int
public_exists(sa_handle_t handle, sa_share_t skipshare)
{
sa_group_t group = NULL;
if (handle == NULL)
return (SA_OK);
if (skipshare != NULL) {
group = sa_get_parent_group(skipshare);
if (group == NULL)
return (SA_NO_SUCH_GROUP);
}
for (group = sa_get_group(handle, NULL); group != NULL;
group = sa_get_next_group(group)) {
if (sa_group_is_zfs(group)) {
sa_group_t subgroup;
for (subgroup = sa_get_sub_group(group);
subgroup != NULL;
subgroup = sa_get_next_group(subgroup)) {
if (check_public(subgroup, skipshare))
return (B_TRUE);
}
} else {
if (check_public(group, skipshare))
return (B_TRUE);
}
}
return (B_FALSE);
}
static int
nfs_enable_share(sa_share_t share)
{
struct exportdata export;
sa_optionset_t secoptlist;
struct secinfo *sp;
int num_secinfo;
sa_optionset_t opt;
sa_security_t sec;
sa_property_t prop;
char *path;
int err = SA_OK;
int i;
int iszfs;
sa_handle_t handle;
(void) signal(SIGSYS, SIG_IGN);
path = sa_get_share_attr(share, "path");
if (path == NULL)
return (SA_NO_SUCH_PATH);
iszfs = sa_path_is_zfs(path);
opt = sa_get_derived_optionset(share, "nfs", 1);
secoptlist = (sa_optionset_t)sa_get_all_security_types(share, "nfs", 1);
if (secoptlist != NULL)
num_secinfo = MAX(1, count_security(secoptlist));
else
num_secinfo = 1;
(void) memset(&export, '\0', sizeof (export));
export.ex_version = EX_CURRENT_VERSION;
export.ex_anon = UID_NOBODY;
export.ex_index = NULL;
export.ex_path = path;
export.ex_pathlen = strlen(path) + 1;
if (opt != NULL)
err = fill_export_from_optionset(&export, opt);
handle = sa_find_group_handle((sa_group_t)share);
if (export.ex_flags & EX_PUBLIC && public_exists(handle, share)) {
(void) printf(dgettext(TEXT_DOMAIN,
"NFS: Cannot share more than one file "
"system with 'public' property\n"));
err = SA_NOT_ALLOWED;
goto out;
}
sp = calloc(num_secinfo, sizeof (struct secinfo));
if (sp == NULL) {
err = SA_NO_MEMORY;
(void) printf(dgettext(TEXT_DOMAIN,
"NFS: NFS: no memory for security\n"));
goto out;
}
export.ex_secinfo = sp;
export.ex_seccnt = num_secinfo;
sp[0].s_window = DEF_WIN;
sp[0].s_rootnames = NULL;
if (nfs_getseconfig_default(&sp[0].s_secinfo)) {
(void) printf(dgettext(TEXT_DOMAIN,
"NFS: nfs_getseconfig_default: failed to "
"get default security mode\n"));
err = SA_CONFIG_ERR;
}
if (secoptlist != NULL) {
for (i = 0, prop = sa_get_property(secoptlist, NULL);
prop != NULL && i < num_secinfo;
prop = sa_get_next_property(prop), i++) {
char *sectype;
sectype = sa_get_property_attr(prop, "type");
if (sectype == NULL) {
err = SA_NO_MEMORY;
(void) printf(dgettext(TEXT_DOMAIN,
"NFS: Cannot share %s: "
"no memory\n"), path);
goto out;
}
sec = (sa_security_t)sa_get_derived_security(
share, sectype, "nfs", 1);
sp[i].s_window = DEF_WIN;
sp[i].s_rootcnt = 0;
sp[i].s_rootnames = NULL;
(void) fill_security_from_secopts(&sp[i], sec);
if (sec != NULL)
sa_free_derived_security(sec);
if (sectype != NULL)
sa_free_attr_string(sectype);
}
}
printarg(path, &export);
if (iszfs) {
struct exportfs_args ea;
share_t sh;
ea.dname = path;
ea.uex = &export;
(void) sa_sharetab_fill_zfs(share, &sh, "nfs");
err = sa_share_zfs(share, NULL, path, &sh, &ea, ZFS_SHARE_NFS);
if (err != SA_OK) {
errno = err;
err = -1;
}
sa_emptyshare(&sh);
} else {
err = exportfs(path, &export);
}
if (err < 0) {
err = SA_SYSTEM_ERR;
switch (errno) {
case EPERM:
err = SA_NO_PERMISSION;
break;
case EEXIST:
err = SA_SHARE_EXISTS;
break;
default:
break;
}
} else {
if (!iszfs) {
(void) sa_update_sharetab(share, "nfs");
}
}
if (err == SA_OK) {
if (export.ex_flags & EX_LOG) {
if (nfslogtab_add(path, export.ex_log_buffer,
export.ex_tag) != 0) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"Could not enable logging for %s\n"),
path);
}
_check_services(service_list_logging);
} else {
(void) nfslogtab_deactivate(path);
_check_services(service_list_default);
}
}
out:
if (path != NULL)
free(path);
cleanup_export(&export);
if (opt != NULL)
sa_free_derived_optionset(opt);
if (secoptlist != NULL)
(void) sa_destroy_optionset(secoptlist);
return (err);
}
static int
nfs_disable_share(sa_share_t share, char *path)
{
int err;
int ret = SA_OK;
int iszfs;
sa_group_t parent;
sa_handle_t handle;
if (path == NULL)
return (ret);
parent = sa_get_parent_group(share);
iszfs = sa_group_is_zfs(parent);
if (iszfs) {
struct exportfs_args ea;
share_t sh = { 0 };
ea.dname = path;
ea.uex = NULL;
sh.sh_path = path;
sh.sh_fstype = "nfs";
err = sa_share_zfs(share, NULL, path, &sh,
&ea, ZFS_UNSHARE_NFS);
if (err != SA_OK) {
errno = err;
err = -1;
}
} else {
err = exportfs(path, NULL);
}
if (err < 0) {
switch (errno) {
case EPERM:
case EACCES:
ret = SA_NO_PERMISSION;
break;
case EINVAL:
case ENOENT:
ret = SA_NO_SUCH_PATH;
break;
default:
ret = SA_SYSTEM_ERR;
break;
}
}
if (ret == SA_OK || ret == SA_NO_SUCH_PATH) {
handle = sa_find_group_handle((sa_group_t)share);
if (!iszfs)
(void) sa_delete_sharetab(handle, path, "nfs");
(void) nfslogtab_deactivate(path);
}
return (ret);
}
static int
check_user(char *value)
{
int ret = SA_OK;
if (!is_a_number(value)) {
struct passwd *pw;
pw = getpwnam(value);
if (pw == NULL)
ret = SA_BAD_VALUE;
endpwent();
} else {
uint64_t intval;
intval = strtoull(value, NULL, 0);
if (intval > UID_MAX && intval != -1)
ret = SA_BAD_VALUE;
}
return (ret);
}
static int
check_group(char *value)
{
int ret = SA_OK;
if (!is_a_number(value)) {
struct group *gr;
gr = getgrnam(value);
if (gr == NULL)
ret = SA_BAD_VALUE;
endgrent();
} else {
uint64_t intval;
intval = strtoull(value, NULL, 0);
if (intval > UID_MAX && intval != -1)
ret = SA_BAD_VALUE;
}
return (ret);
}
static int
check_rorwnone(char *v1, char *v2, char *v3)
{
int ret = SA_OK;
if (v2 != NULL && strcmp(v1, v2) == 0)
ret = SA_VALUE_CONFLICT;
else if (v3 != NULL && strcmp(v1, v3) == 0)
ret = SA_VALUE_CONFLICT;
return (ret);
}
static int
nfs_validate_property(sa_handle_t handle, sa_property_t property,
sa_optionset_t parent)
{
int ret = SA_OK;
char *propname;
char *other1;
char *other2;
int optindex;
nfsl_config_t *configlist;
sa_group_t parent_group;
char *value;
propname = sa_get_property_attr(property, "type");
if ((optindex = findopt(propname)) < 0)
ret = SA_NO_SUCH_PROP;
if (ret == SA_OK) {
parent_group = sa_get_parent_group((sa_share_t)parent);
if (optdefs[optindex].share && parent_group != NULL &&
!sa_is_share(parent_group))
ret = SA_PROP_SHARE_ONLY;
}
if (ret == SA_OK) {
if (optdefs[optindex].index == OPT_PUBLIC) {
if (public_exists(handle, parent_group)) {
sa_free_attr_string(propname);
return (SA_VALUE_CONFLICT);
}
}
value = sa_get_property_attr(property, "value");
if (value != NULL) {
switch (optdefs[optindex].type) {
case OPT_TYPE_NUMBER:
if (!is_a_number(value))
ret = SA_BAD_VALUE;
break;
case OPT_TYPE_BOOLEAN:
ret = boolean_check(value);
break;
case OPT_TYPE_USER:
ret = check_user(value);
break;
case OPT_TYPE_FILE:
if (strcmp(value, "..") == 0 ||
strchr(value, '/') != NULL) {
ret = SA_BAD_VALUE;
}
break;
case OPT_TYPE_ACCLIST: {
sa_property_t oprop1;
sa_property_t oprop2;
char *ovalue1 = NULL;
char *ovalue2 = NULL;
if (parent == NULL)
break;
if (strcmp(propname, SHOPT_RO) == 0) {
other1 = SHOPT_RW;
other2 = SHOPT_NONE;
} else if (strcmp(propname, SHOPT_RW) == 0) {
other1 = SHOPT_RO;
other2 = SHOPT_NONE;
} else if (strcmp(propname, SHOPT_NONE) == 0) {
other1 = SHOPT_RO;
other2 = SHOPT_RW;
} else {
other1 = NULL;
other2 = NULL;
}
if (other1 == NULL && other2 == NULL)
break;
oprop1 = sa_get_property(parent, other1);
oprop2 = sa_get_property(parent, other2);
if (oprop1 == NULL && oprop2 == NULL)
break;
ovalue1 = sa_get_property_attr(oprop1, "value");
ovalue2 = sa_get_property_attr(oprop2, "value");
if (ovalue1 != NULL || ovalue2 != NULL)
ret = check_rorwnone(value, ovalue1,
ovalue2);
if (ovalue1 != NULL)
sa_free_attr_string(ovalue1);
if (ovalue2 != NULL)
sa_free_attr_string(ovalue2);
break;
}
case OPT_TYPE_LOGTAG:
if (nfsl_getconfig_list(&configlist) == 0) {
int error;
if (value == NULL ||
strlen(value) == 0) {
if (value != NULL)
sa_free_attr_string(
value);
value = strdup("global");
}
if (value != NULL &&
nfsl_findconfig(configlist, value,
&error) == NULL) {
ret = SA_BAD_VALUE;
}
nfsl_freeconfig_list(&configlist);
} else {
ret = SA_CONFIG_ERR;
}
break;
case OPT_TYPE_STRING:
break;
case OPT_TYPE_SECURITY:
ret = SA_NO_SUCH_PROP;
break;
case OPT_TYPE_MAPPING: {
char *p;
char *n;
char *c;
int (*f)(char *);
sa_security_t security;
ret = SA_CONFIG_ERR;
if (parent_group == NULL)
break;
for (security = sa_get_security(parent_group,
NULL, NULL); security != NULL;
security = sa_get_next_security(security)) {
char *type;
char *sectype;
type = sa_get_security_attr(security,
"type");
if (type == NULL)
continue;
if (strcmp(type, "nfs") != 0) {
sa_free_attr_string(type);
continue;
}
sa_free_attr_string(type);
sectype = sa_get_security_attr(security,
"sectype");
if (sectype == NULL)
continue;
if (strcmp(sectype, "sys") != 0) {
sa_free_attr_string(sectype);
ret = SA_CONFIG_ERR;
break;
}
sa_free_attr_string(sectype);
ret = SA_OK;
}
if (ret != SA_OK)
break;
assert(optindex == OPT_UIDMAP ||
optindex == OPT_GIDMAP);
f = optindex == OPT_UIDMAP ? check_user :
check_group;
p = strdup(value);
if (p == NULL)
ret = SA_BAD_VALUE;
for (c = p; ret == SA_OK && c != NULL; c = n) {
char *s;
char *t;
n = strchr(c, '~');
if (n != NULL)
*n++ = '\0';
s = strchr(c, ':');
if (s != NULL) {
*s++ = '\0';
t = strchr(s, ':');
if (t != NULL)
*t = '\0';
}
if (s == NULL || t == NULL)
ret = SA_BAD_VALUE;
if (ret == SA_OK && *c != '\0' &&
strcmp(c, "*") != 0)
ret = f(c);
if (ret == SA_OK && *s != '\0' &&
strcmp(s, "-1") != 0)
ret = f(s);
}
free(p);
break;
}
default:
break;
}
if (value != NULL)
sa_free_attr_string(value);
if (ret == SA_OK && optdefs[optindex].check != NULL) {
ret = optdefs[optindex].check(handle, property);
}
}
}
if (propname != NULL)
sa_free_attr_string(propname);
return (ret);
}
struct proto_option_defs {
char *tag;
char *name;
uint_t index;
int type;
union {
int intval;
char *string;
} defvalue;
uint32_t svcs;
int32_t minval;
int32_t maxval;
int (*validator)(uint_t, char *);
} proto_options[] = {
#define PROTO_OPT_NFSD_SERVERS 0
{
.tag = "nfsd_servers",
.name = "servers",
.index = PROTO_OPT_NFSD_SERVERS,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 1024,
.svcs = SVC_NFSD,
.minval = 1,
.maxval = INT32_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_LOCKD_LISTEN_BACKLOG 1
{
.tag = "lockd_listen_backlog",
.name = "lockd_listen_backlog",
.index = PROTO_OPT_LOCKD_LISTEN_BACKLOG,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 32,
.svcs = SVC_LOCKD,
.minval = 32,
.maxval = INT32_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_LOCKD_SERVERS 2
{
.tag = "lockd_servers",
.name = "lockd_servers",
.index = PROTO_OPT_LOCKD_SERVERS,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 256,
.svcs = SVC_LOCKD,
.minval = 1,
.maxval = INT32_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_LOCKD_RETRANSMIT_TIMEOUT 3
{
.tag = "lockd_retransmit_timeout",
.name = "lockd_retransmit_timeout",
.index = PROTO_OPT_LOCKD_RETRANSMIT_TIMEOUT,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 5,
.svcs = SVC_LOCKD,
.minval = 0,
.maxval = INT32_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_GRACE_PERIOD 4
{
.tag = "grace_period",
.name = "grace_period",
.index = PROTO_OPT_GRACE_PERIOD,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 90,
.svcs = SVC_LOCKD,
.minval = 0,
.maxval = INT32_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_NFS_SERVER_VERSMIN 5
{
.tag = "nfs_server_versmin",
.name = "server_versmin",
.index = PROTO_OPT_NFS_SERVER_VERSMIN,
.type = OPT_TYPE_STRING,
.defvalue.string = "2",
.svcs = SVC_NFSD | SVC_MOUNTD,
.validator = range_check_validator_server
},
#define PROTO_OPT_NFS_SERVER_VERSMAX 6
{
.tag = "nfs_server_versmax",
.name = "server_versmax",
.index = PROTO_OPT_NFS_SERVER_VERSMAX,
.type = OPT_TYPE_STRING,
.defvalue.string = "4",
.svcs = SVC_NFSD | SVC_MOUNTD,
.validator = range_check_validator_server
},
#define PROTO_OPT_NFS_CLIENT_VERSMIN 7
{
.tag = "nfs_client_versmin",
.name = "client_versmin",
.index = PROTO_OPT_NFS_CLIENT_VERSMIN,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = NFS_VERSMIN_DEFAULT,
.svcs = SVC_CLIENT,
.minval = NFS_VERSMIN,
.maxval = NFS_VERSMAX,
.validator = range_check_validator_client
},
#define PROTO_OPT_NFS_CLIENT_VERSMAX 8
{
.tag = "nfs_client_versmax",
.name = "client_versmax",
.index = PROTO_OPT_NFS_CLIENT_VERSMAX,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = NFS_VERSMAX_DEFAULT,
.svcs = SVC_CLIENT,
.minval = NFS_VERSMIN,
.maxval = NFS_VERSMAX,
.validator = range_check_validator_client
},
#define PROTO_OPT_NFS_SERVER_DELEGATION 9
{
.tag = "nfs_server_delegation",
.name = "server_delegation",
.index = PROTO_OPT_NFS_SERVER_DELEGATION,
.type = OPT_TYPE_ONOFF,
.defvalue.intval = NFS_SERVER_DELEGATION_DEFAULT,
.svcs = SVC_NFSD,
.validator = onoff_validator
},
#define PROTO_OPT_NFSMAPID_DOMAIN 10
{
.tag = "nfsmapid_domain",
.name = "nfsmapid_domain",
.index = PROTO_OPT_NFSMAPID_DOMAIN,
.type = OPT_TYPE_DOMAIN,
.defvalue.string = NULL,
.svcs = SVC_NFSMAPID,
.validator = domain_validator
},
#define PROTO_OPT_NFSD_MAX_CONNECTIONS 11
{
.tag = "nfsd_max_connections",
.name = "max_connections",
.index = PROTO_OPT_NFSD_MAX_CONNECTIONS,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = -1,
.svcs = SVC_NFSD,
.minval = -1,
.maxval = INT32_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_NFSD_PROTOCOL 12
{
.tag = "nfsd_protocol",
.name = "protocol",
.index = PROTO_OPT_NFSD_PROTOCOL,
.type = OPT_TYPE_PROTOCOL,
.defvalue.intval = 0,
.svcs = SVC_NFSD,
.validator = protocol_validator
},
#define PROTO_OPT_NFSD_LISTEN_BACKLOG 13
{
.tag = "nfsd_listen_backlog",
.name = "listen_backlog",
.index = PROTO_OPT_NFSD_LISTEN_BACKLOG,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 0,
.svcs = SVC_NFSD,
.minval = 0,
.maxval = INT32_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_NFSD_DEVICE 14
{
.tag = "nfsd_device",
.name = "device",
.index = PROTO_OPT_NFSD_DEVICE,
.type = OPT_TYPE_STRING,
.defvalue.string = NULL,
.svcs = SVC_NFSD
},
#define PROTO_OPT_MOUNTD_LISTEN_BACKLOG 15
{
.tag = "mountd_listen_backlog",
.name = "mountd_listen_backlog",
.index = PROTO_OPT_MOUNTD_LISTEN_BACKLOG,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 64,
.svcs = SVC_NFSD | SVC_MOUNTD,
.minval = 1,
.maxval = INT32_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_MOUNTD_MAX_THREADS 16
{
.tag = "mountd_max_threads",
.name = "mountd_max_threads",
.index = PROTO_OPT_MOUNTD_MAX_THREADS,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 16,
.svcs = SVC_NFSD | SVC_MOUNTD,
.minval = 1,
.maxval = INT32_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_MOUNTD_PORT 17
{
.tag = "mountd_port",
.name = "mountd_port",
.index = PROTO_OPT_MOUNTD_PORT,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 0,
.svcs = SVC_NFSD | SVC_MOUNTD,
.minval = 1,
.maxval = UINT16_MAX,
.validator = range_check_validator
},
#define PROTO_OPT_MOUNTD_REMOTE_DUMP 18
{
.tag = "mountd_remote_dump",
.name = "mountd_remote_dump",
.index = PROTO_OPT_MOUNTD_REMOTE_DUMP,
.type = OPT_TYPE_BOOLEAN,
.defvalue.intval = B_FALSE,
.svcs = SVC_NFSD | SVC_MOUNTD,
.minval = B_FALSE,
.maxval = B_TRUE,
.validator = boolean_validator
},
#define PROTO_OPT_STATD_PORT 19
{
.tag = "statd_port",
.name = "statd_port",
.index = PROTO_OPT_STATD_PORT,
.type = OPT_TYPE_NUMBER,
.defvalue.intval = 0,
.svcs = SVC_STATD,
.minval = 1,
.maxval = UINT16_MAX,
.validator = range_check_validator
}
};
static int
range_check_validator_value(uint_t index, char *value, int *intval)
{
int val;
const char *errstr;
if (index >= ARRAY_SIZE(proto_options))
return (SA_NO_SUCH_PROP);
VERIFY3S(proto_options[index].index, ==, index);
if (!is_a_number(value))
return (SA_BAD_VALUE);
val = strtonumx(value, proto_options[index].minval,
proto_options[index].maxval, &errstr, 0);
if (errstr != NULL) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"bad option value: %s: %s\n"), proto_options[index].name,
errstr);
return (SA_BAD_VALUE);
}
if (intval != NULL)
*intval = val;
return (SA_OK);
}
static int
range_check_validator(uint_t index, char *value)
{
return (range_check_validator_value(index, value, NULL));
}
static uint32_t
nfs_get_vers_minmax(uint_t index)
{
char *pvalue;
sa_property_t prop;
sa_optionset_t opts;
struct proto_option_defs *po;
uint32_t rval = 0;
const char *errstr;
po = &proto_options[index];
opts = nfs_get_proto_set();
prop = sa_get_property(opts, po->name);
if (prop != NULL) {
pvalue = sa_get_property_attr(prop, "value");
if (po->type == OPT_TYPE_STRING)
rval = nfs_convert_version_str(pvalue);
else
rval = strtonumx(pvalue, po->minval, po->maxval,
&errstr, 0);
sa_free_attr_string(pvalue);
}
if (rval == 0) {
if (po->type == OPT_TYPE_STRING)
rval = nfs_convert_version_str(
proto_options[index].defvalue.string);
else
rval = proto_options[index].defvalue.intval;
}
return (rval);
}
static int
range_check_validator_client(uint_t index, char *value)
{
int ret, val;
if (value == NULL)
return (SA_BAD_VALUE);
ret = range_check_validator_value(index, value, &val);
if (ret != SA_OK)
return (ret);
switch (index) {
case PROTO_OPT_NFS_CLIENT_VERSMIN:
if (val > nfs_get_vers_minmax(PROTO_OPT_NFS_CLIENT_VERSMAX))
ret = SA_VALUE_CONFLICT;
break;
case PROTO_OPT_NFS_CLIENT_VERSMAX:
if (val < nfs_get_vers_minmax(PROTO_OPT_NFS_CLIENT_VERSMIN))
ret = SA_VALUE_CONFLICT;
break;
default:
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"Unexpected nfs protocol validator index: %u\n"), index);
ret = SA_BAD_VALUE;
break;
}
return (ret);
}
static int
range_check_validator_server(uint_t index, char *value)
{
int ret = SA_BAD_VALUE;
uint32_t val;
if (value == NULL)
return (ret);
if (index >= ARRAY_SIZE(proto_options))
return (SA_NO_SUCH_PROP);
VERIFY3S(proto_options[index].index, ==, index);
val = nfs_convert_version_str(value);
if (val == 0)
return (ret);
ret = SA_OK;
switch (index) {
case PROTO_OPT_NFS_SERVER_VERSMIN:
if (val > nfs_get_vers_minmax(PROTO_OPT_NFS_SERVER_VERSMAX))
ret = SA_VALUE_CONFLICT;
break;
case PROTO_OPT_NFS_SERVER_VERSMAX:
if (val < nfs_get_vers_minmax(PROTO_OPT_NFS_SERVER_VERSMIN))
ret = SA_VALUE_CONFLICT;
break;
default:
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"Unexpected nfs protocol validator index: %u\n"), index);
ret = SA_BAD_VALUE;
break;
}
return (ret);
}
static int
onoff_validator(uint_t index, char *value)
{
if (index >= ARRAY_SIZE(proto_options))
return (SA_NO_SUCH_PROP);
VERIFY3S(proto_options[index].index, ==, index);
if (strcasecmp(value, "on") != 0 &&
strcasecmp(value, "off") != 0) {
return (SA_BAD_VALUE);
}
return (SA_OK);
}
static int
domain_validator(uint_t index, char *value)
{
char *cp;
if (index >= ARRAY_SIZE(proto_options))
return (SA_NO_SUCH_PROP);
VERIFY3S(proto_options[index].index, ==, index);
if (strlen(value) == 0)
return (SA_OK);
cp = strchr(value, '.');
if (cp == NULL || cp == value || strchr(value, '@') != NULL)
return (SA_BAD_VALUE);
return (SA_OK);
}
static int
boolean_check(char *value)
{
if (strlen(value) == 0 ||
strcasecmp(value, "true") == 0 ||
strcasecmp(value, "1") == 0 ||
strcasecmp(value, "false") == 0 ||
strcasecmp(value, "0") == 0)
return (SA_OK);
return (SA_BAD_VALUE);
}
static int
boolean_validator(uint_t index, char *value)
{
if (index >= ARRAY_SIZE(proto_options))
return (SA_NO_SUCH_PROP);
VERIFY3S(proto_options[index].index, ==, index);
return (boolean_check(value));
}
static int
protocol_validator(uint_t index, char *value)
{
struct netconfig *nconf;
void *nc;
boolean_t pfound = B_FALSE;
if (index >= ARRAY_SIZE(proto_options))
return (SA_NO_SUCH_PROP);
VERIFY3S(proto_options[index].index, ==, index);
if (strcasecmp(value, "all") == 0)
return (SA_OK);
nc = setnetconfig();
if (nc == NULL) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"setnetconfig failed: %s\n"), strerror(errno));
} else {
while ((nconf = getnetconfig(nc)) != NULL) {
if (strcmp(nconf->nc_proto, value) == 0) {
pfound = B_TRUE;
break;
}
}
(void) endnetconfig(nc);
}
if (!pfound)
return (SA_BAD_VALUE);
return (SA_OK);
}
static sa_protocol_properties_t protoset;
static int
findprotoopt(char *name)
{
int i;
for (i = 0; i < ARRAY_SIZE(proto_options); i++) {
if (strcasecmp(proto_options[i].name, name) == 0)
return (i);
}
return (-1);
}
static void
fixcaselower(char *str)
{
while (*str) {
*str = tolower(*str);
str++;
}
}
static char *
skipwhitespace(char *str)
{
while (*str && isspace(*str))
str++;
return (str);
}
static int
extractprop(char *name, char *value)
{
sa_property_t prop;
int index;
int ret = SA_OK;
name = skipwhitespace(name);
index = findprotoopt(name);
if (index >= 0) {
fixcaselower(name);
prop = sa_create_property(proto_options[index].name, value);
if (prop != NULL)
ret = sa_add_protocol_property(protoset, prop);
else
ret = SA_NO_MEMORY;
}
return (ret);
}
scf_type_t
getscftype(int type)
{
scf_type_t ret;
switch (type) {
case OPT_TYPE_NUMBER:
ret = SCF_TYPE_INTEGER;
break;
case OPT_TYPE_BOOLEAN:
ret = SCF_TYPE_BOOLEAN;
break;
default:
ret = SCF_TYPE_ASTRING;
}
return (ret);
}
char *
getsvcname(uint32_t svcs)
{
char *service;
switch (svcs) {
case SVC_LOCKD:
service = LOCKD;
break;
case SVC_STATD:
service = STATD;
break;
case SVC_NFSD:
service = NFSD;
break;
case SVC_CLIENT:
service = NFS_CLIENT_SVC;
break;
case SVC_NFS4CBD:
service = NFS4CBD;
break;
case SVC_NFSMAPID:
service = NFSMAPID;
break;
case SVC_RQUOTAD:
service = RQUOTAD;
break;
case SVC_NFSLOGD:
service = NFSLOGD;
break;
case SVC_REPARSED:
service = REPARSED;
break;
default:
service = NFSD;
}
return (service);
}
static int
initprotofromsmf(void)
{
char name[PATH_MAX];
char value[PATH_MAX];
int ret = SA_OK, bufsz = 0, i;
protoset = sa_create_protocol_properties("nfs");
if (protoset != NULL) {
for (i = 0; i < ARRAY_SIZE(proto_options); i++) {
scf_type_t ptype;
char *svc_name;
bzero(value, PATH_MAX);
(void) strncpy(name, proto_options[i].name, PATH_MAX);
ptype = getscftype(proto_options[i].type);
svc_name = getsvcname(proto_options[i].svcs);
bufsz = PATH_MAX;
ret = nfs_smf_get_prop(name, value,
(char *)DEFAULT_INSTANCE, ptype,
svc_name, &bufsz);
if (ret == SA_OK) {
ret = extractprop(name, value);
}
}
} else {
ret = SA_NO_MEMORY;
}
return (ret);
}
static void
add_defaults(void)
{
int i;
char number[MAXDIGITS];
for (i = 0; i < ARRAY_SIZE(proto_options); i++) {
sa_property_t prop;
prop = sa_get_protocol_property(protoset,
proto_options[i].name);
if (prop == NULL) {
switch (proto_options[i].type) {
case OPT_TYPE_NUMBER:
(void) snprintf(number, sizeof (number), "%d",
proto_options[i].defvalue.intval);
prop = sa_create_property(proto_options[i].name,
number);
break;
case OPT_TYPE_BOOLEAN:
prop = sa_create_property(proto_options[i].name,
proto_options[i].defvalue.intval ?
"true" : "false");
break;
case OPT_TYPE_ONOFF:
prop = sa_create_property(proto_options[i].name,
proto_options[i].defvalue.intval ?
"on" : "off");
break;
case OPT_TYPE_STRING:
if (proto_options[i].defvalue.string != NULL) {
prop = sa_create_property(
proto_options[i].name,
proto_options[i].defvalue.string);
break;
}
default:
prop = sa_create_property(proto_options[i].name,
"");
break;
}
if (prop != NULL)
(void) sa_add_protocol_property(protoset, prop);
}
}
}
static void
free_protoprops(void)
{
if (protoset != NULL) {
xmlFreeNode(protoset);
protoset = NULL;
}
}
static int
nfs_init(void)
{
int ret = SA_OK;
if (sa_plugin_ops.sa_init != nfs_init) {
(void) printf(dgettext(TEXT_DOMAIN,
"NFS plugin not properly initialized\n"));
return (SA_CONFIG_ERR);
}
ret = initprotofromsmf();
if (ret != SA_OK) {
(void) printf(dgettext(TEXT_DOMAIN,
"NFS plugin problem with SMF repository: %s\n"),
sa_errorstr(ret));
ret = SA_OK;
}
add_defaults();
return (ret);
}
static void
nfs_fini(void)
{
free_protoprops();
}
static sa_protocol_properties_t
nfs_get_proto_set(void)
{
return (protoset);
}
static int
service_in_state(char *service, const char *chkstate)
{
char *state;
int ret = B_FALSE;
state = smf_get_state(service);
if (state != NULL) {
ret = strcmp(state, chkstate) == 0 ? B_TRUE : B_FALSE;
free(state);
}
return (ret);
}
static void
restart_service(uint32_t svcs)
{
uint32_t mask;
int ret;
char *service;
for (mask = 1; svcs != 0; mask <<= 1) {
switch (svcs & mask) {
case SVC_LOCKD:
service = LOCKD;
break;
case SVC_STATD:
service = STATD;
break;
case SVC_NFSD:
service = NFSD;
break;
case SVC_MOUNTD:
service = MOUNTD;
break;
case SVC_NFS4CBD:
service = NFS4CBD;
break;
case SVC_NFSMAPID:
service = NFSMAPID;
break;
case SVC_RQUOTAD:
service = RQUOTAD;
break;
case SVC_NFSLOGD:
service = NFSLOGD;
break;
case SVC_REPARSED:
service = REPARSED;
break;
case SVC_CLIENT:
service = NFS_CLIENT_SVC;
break;
default:
continue;
}
if (service_in_state(service, SCF_STATE_STRING_ONLINE)) {
ret = smf_restart_instance(service);
if (ret != 0) {
(void) fprintf(stderr,
dgettext(TEXT_DOMAIN,
"%s failed to restart: %s\n"),
service, scf_strerror(scf_error()));
} else {
if (service_in_state(service,
SCF_STATE_STRING_MAINT)) {
(void) fprintf(stderr,
dgettext(TEXT_DOMAIN,
"%s failed to restart\n"),
service);
}
}
}
svcs &= ~mask;
}
}
static int
nfs_set_proto_prop(sa_property_t prop)
{
int ret = SA_OK;
char *name;
char *value;
struct proto_option_defs *opt;
name = sa_get_property_attr(prop, "type");
value = sa_get_property_attr(prop, "value");
if (name == NULL || value == NULL) {
ret = SA_NO_SUCH_PROP;
goto out;
}
int index = findprotoopt(name);
if (index < 0) {
ret = SA_NO_SUCH_PROP;
goto out;
}
opt = &proto_options[index];
if (opt->validator != NULL)
ret = opt->validator(index, value);
if (ret == SA_OK) {
scf_type_t sctype;
char *svc_name;
char *instance = NULL;
sctype = getscftype(opt->type);
svc_name = getsvcname(opt->svcs);
if (sctype == SCF_TYPE_BOOLEAN) {
if (value != NULL)
sa_free_attr_string(value);
if (string_to_boolean(value) == 0)
value = strdup("0");
else
value = strdup("1");
}
ret = nfs_smf_set_prop(name, value, instance, sctype, svc_name);
if (ret == SA_OK) {
restart_service(opt->svcs);
} else {
(void) printf(dgettext(TEXT_DOMAIN,
"Cannot restart NFS services : %s\n"),
sa_errorstr(ret));
}
}
out:
if (name != NULL)
sa_free_attr_string(name);
if (value != NULL)
sa_free_attr_string(value);
return (ret);
}
static char *
nfs_get_status(void)
{
return (smf_get_state(NFSD));
}
static char *
nfs_space_alias(char *space)
{
char *name = space;
seconfig_t secconf;
if (strcmp(space, "default") == 0 &&
nfs_getseconfig_default(&secconf) == 0) {
if (nfs_getseconfig_bynumber(secconf.sc_nfsnum, &secconf) == 0)
name = secconf.sc_name;
}
return (strdup(name));
}
static uint64_t
nfs_features(void)
{
return ((uint64_t)SA_FEATURE_DFSTAB | SA_FEATURE_SERVER);
}