Files
nfs-util/nfs-utils-2.5.2/support/nfsidmap/libnfsidmap.c
2023-10-14 08:57:12 +00:00

708 lines
17 KiB
C

/*
* libnfsidmap.c
*
* nfs idmapping library, primarily for nfs4 client/server kernel idmapping
* and for userland nfs4 idmapping by acl libraries.
*
* Copyright (c) 2004 The Regents of the University of Michigan.
* All rights reserved.
*
* Marius Aamodt Eriksen <marius@umich.edu>
* J. Bruce Fields <bfields@umich.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <netdb.h>
#include <err.h>
#include <syslog.h>
#include <stdarg.h>
#include <dlfcn.h>
#include <ctype.h>
#include <resolv.h>
#include <arpa/nameser.h>
#include <arpa/nameser_compat.h>
#include "nfsidmap.h"
#include "nfsidmap_private.h"
#include "nfsidmap_plugin.h"
#include "conffile.h"
#pragma GCC visibility push(hidden)
void nfs4_cleanup_name_mapping(void);
static char *default_domain;
static struct mapping_plugin **nfs4_plugins = NULL;
static struct mapping_plugin **gss_plugins = NULL;
uid_t nobody_uid = (uid_t)-1;
gid_t nobody_gid = (gid_t)-1;
#ifndef PATH_PLUGINS
#define PATH_PLUGINS "/usr/lib/libnfsidmap"
#endif
#define PLUGIN_INIT_FUNC "libnfsidmap_plugin_init"
#ifndef PATH_IDMAPDCONF
#define PATH_IDMAPDCONF "/etc/idmapd.conf"
#endif
#ifndef IDMAPD_DEFAULT_DOMAIN
#define IDMAPD_DEFAULT_DOMAIN "localdomain"
#endif
#ifndef NFS4DNSTXTREC
#define NFS4DNSTXTREC "_nfsv4idmapdomain"
#endif
#ifndef NS_MAXMSG
#define NS_MAXMSG 65535
#endif
/* Default logging fuction */
static void default_logger(const char *fmt, ...)
{
va_list vp;
va_start(vp, fmt);
vsyslog(LOG_WARNING, fmt, vp);
va_end(vp);
}
#pragma GCC visibility pop
nfs4_idmap_log_function_t idmap_log_func = default_logger;
int idmap_verbosity = 0;
#pragma GCC visibility push(hidden)
static int id_as_chars(char *name, uid_t *id)
{
long int value;
if (name == NULL)
return 0;
value = strtol(name, NULL, 10);
if (value == 0) {
/* zero value ids are valid */
if (strcmp(name, "0") != 0)
return 0;
}
*id = (int)value;
return 1;
}
static int dns_txt_query(char *domain, char **nfs4domain)
{
char *txtname = NFS4DNSTXTREC;
unsigned char *msg, *eom, *mptr;
char *answ;
int len, status = -1;
HEADER *hdr;
msg = calloc(1, NS_MAXMSG);
if (msg == NULL)
return -1;
answ = calloc(1, NS_MAXMSG);
if (answ == NULL) {
free(msg);
return -1;
}
if (res_init() < 0) {
IDMAP_LOG(2, ("libnfsidmap: res_init() failed for %s.%s: %s\n",
txtname, domain, hstrerror(h_errno)));
goto freemem;
}
len = res_querydomain(txtname, domain, C_IN, T_TXT, msg, NS_MAXMSG);
if (len < 0) {
IDMAP_LOG(2, ("libnfsidmap: res_querydomain() failed for %s.%s: %s\n",
txtname, domain, hstrerror(h_errno)));
goto freemem;
}
hdr = (HEADER *)msg;
/* See if there is an answer */
if (ntohs(hdr->ancount) < 1) {
IDMAP_LOG(2, ("libnfsidmap: No TXT record for %s.%s\n",
txtname, domain));
goto freemem;
}
/* find the EndOfMessage */
eom = msg + len;
/* skip header */
mptr = &msg[HFIXEDSZ];
/* skip name field in question section */
mptr += dn_skipname(mptr, eom) + QFIXEDSZ;
/* read in the question */
len = dn_expand(msg, eom, mptr, answ, NS_MAXDNAME);
if (len < 0) { /* does this really matter?? */
IDMAP_LOG(2, ("libnfsidmap: No question section for %s.%s: %s\n",
txtname, domain, hstrerror(h_errno)));
goto freemem;
}
/*
* Now, dissect the answer section, Note: if there
* are more than one answer only the first
* one will be used.
*/
/* skip passed the name field */
mptr += dn_skipname(mptr, eom);
/* skip pass the type class and ttl fields */
mptr += 2 + 2 + 4;
/* make sure there is some data */
GETSHORT(len, mptr);
if (len < 0) {
IDMAP_LOG(2, ("libnfsidmap: No data in answer for %s.%s\n",
txtname, domain));
goto freemem;
}
/* get the lenght field */
len = (int)*mptr++;
/* copy the data */
memcpy(answ, mptr, len);
answ[len] = '\0';
*nfs4domain = strdup(answ);
status = 0;
freemem:
free(msg);
free(answ);
return (status);
}
static int domain_from_dns(char **domain)
{
struct hostent *he;
char hname[64], *c;
if (gethostname(hname, sizeof(hname)) == -1)
return -1;
if ((he = gethostbyname(hname)) == NULL)
return -1;
if ((c = strchr(he->h_name, '.')) == NULL || *++c == '\0')
return -1;
/*
* Query DNS to see if the _nfsv4idmapdomain TXT record exists
* If so use it...
*/
if (dns_txt_query(c, domain) < 0)
*domain = strdup(c);
return 0;
}
static int load_translation_plugin(char *method, struct mapping_plugin *plgn)
{
void *dl = NULL;
struct trans_func *trans = NULL;
libnfsidmap_plugin_init_t init_func = NULL;
char plgname[128];
int ret = 0;
/* Look for library using search path first to allow overriding */
snprintf(plgname, sizeof(plgname), "%s.so", method);
dl = dlopen(plgname, RTLD_NOW | RTLD_LOCAL);
if (dl != NULL) {
/* Is it really one of our libraries */
init_func = (libnfsidmap_plugin_init_t) dlsym(dl, PLUGIN_INIT_FUNC);
if (init_func == NULL) {
dlclose(dl);
dl = NULL;
}
}
if (dl == NULL) {
/* Fallback to hard-coded path */
snprintf(plgname, sizeof(plgname), "%s/%s.so", PATH_PLUGINS, method);
dl = dlopen(plgname, RTLD_NOW | RTLD_LOCAL);
if (dl == NULL) {
IDMAP_LOG(1, ("libnfsidmap: Unable to load plugin: %s: %s",
plgname, dlerror()));
return -1;
}
init_func = (libnfsidmap_plugin_init_t) dlsym(dl, PLUGIN_INIT_FUNC);
if (init_func == NULL) {
IDMAP_LOG(1, ("libnfsidmap: Unable to get init function: %s: %s",
plgname, dlerror()));
dlclose(dl);
return -1;
}
}
trans = init_func();
if (trans == NULL) {
IDMAP_LOG(1, ("libnfsidmap: Failed to initialize plugin %s",
PLUGIN_INIT_FUNC, plgname));
dlclose(dl);
return -1;
}
if (trans->init) {
ret = trans->init();
if (ret) {
IDMAP_LOG(1, ("libnfsidmap: Failed in %s's init(), "
"returned %d", plgname, ret));
dlclose(dl);
return -1;
}
}
plgn->dl_handle = dl;
plgn->trans = trans;
IDMAP_LOG(1, ("libnfsidmap: loaded plugin %s for method %s",
plgname, method));
return 0;
}
static void unload_plugins(struct mapping_plugin **plgns)
{
int i;
for (i = 0; plgns[i] != NULL; i++) {
if (plgns[i]->dl_handle && dlclose(plgns[i]->dl_handle))
IDMAP_LOG(1, ("libnfsidmap: failed to "
"unload plugin for method = %s",
plgns[i]->trans->name));
free(plgns[i]);
}
free(plgns);
}
static int load_plugins(struct conf_list *methods,
struct mapping_plugin ***plugins)
{
int ret = -1, i = 0;
struct mapping_plugin **plgns;
struct conf_list_node *m;
plgns = calloc(methods->cnt + 1, sizeof(struct mapping_plugin *));
if (plgns == NULL)
return -1;
plgns[methods->cnt] = NULL;
for (m = TAILQ_FIRST(&methods->fields), i = 0; m;
m = TAILQ_NEXT(m, link), i++) {
plgns[i] = calloc(1, sizeof(struct mapping_plugin));
if (plgns[i] == NULL)
goto out;
if (load_translation_plugin(m->field, plgns[i]) == -1) {
IDMAP_LOG(0, ("libnfsidmap: requested translation "
"method, '%s', is not available",
m->field));
goto out;
}
}
ret = 0;
*plugins = plgns;
out:
if (ret)
unload_plugins(plgns);
return ret;
}
static char *get_default_domain(void)
{
int ret;
if (default_domain)
return default_domain;
ret = domain_from_dns(&default_domain);
if (ret) {
IDMAP_LOG(0, ("Unable to determine a default nfsv4 domain; "
" consider specifying one in idmapd.conf"));
default_domain = "";
}
return default_domain;
}
void nfs4_cleanup_name_mapping(void)
{
if (nfs4_plugins)
unload_plugins(nfs4_plugins);
if (gss_plugins)
unload_plugins(gss_plugins);
nfs4_plugins = gss_plugins = NULL;
}
#pragma GCC visibility pop
const char * nfsidmap_conf_path = PATH_IDMAPDCONF;
int nfs4_init_name_mapping(char *conffile)
{
int ret = -ENOENT;
int dflt = 0;
struct conf_list *nfs4_methods, *gss_methods;
char *nobody_user, *nobody_group;
/* XXX: need to be able to reload configurations... */
if (nfs4_plugins) /* already succesfully initialized */
return 0;
if (conffile)
nfsidmap_conf_path = conffile;
conf_init_file(nfsidmap_conf_path);
default_domain = conf_get_str("General", "Domain");
if (default_domain == NULL) {
dflt = 1;
ret = domain_from_dns(&default_domain);
if (ret) {
IDMAP_LOG(1, ("libnfsidmap: Unable to determine "
"the NFSv4 domain; Using '%s' as the NFSv4 domain "
"which means UIDs will be mapped to the 'Nobody-User' "
"user defined in %s",
IDMAPD_DEFAULT_DOMAIN, PATH_IDMAPDCONF));
default_domain = IDMAPD_DEFAULT_DOMAIN;
}
}
IDMAP_LOG(1, ("libnfsidmap: using%s domain: %s",
(dflt ? " (default)" : ""), default_domain));
struct conf_list *local_realms = get_local_realms();
if (local_realms == NULL) return -ENOMEM;
if (idmap_verbosity >= 1) {
struct conf_list_node *r;
char *buf = NULL;
int siz=0;
if (local_realms) {
TAILQ_FOREACH(r, &local_realms->fields, link) {
siz += (strlen(r->field)+4);
}
buf = malloc(siz);
if (buf) {
*buf = 0;
TAILQ_FOREACH(r, &local_realms->fields, link) {
sprintf(buf+strlen(buf), "'%s' ", r->field);
}
IDMAP_LOG(1, ("libnfsidmap: Realms list: %s", buf));
free(buf);
}
} else
IDMAP_LOG(1, ("libnfsidmap: Realms list: <NULL> "));
}
nfs4_methods = conf_get_list("Translation", "Method");
if (nfs4_methods) {
IDMAP_LOG(1, ("libnfsidmap: processing 'Method' list"));
if (load_plugins(nfs4_methods, &nfs4_plugins) == -1) {
conf_free_list(nfs4_methods);
return -ENOENT;
}
} else {
struct conf_list list;
struct conf_list_node node;
TAILQ_INIT(&list.fields);
list.cnt = 1;
node.field = "nsswitch";
TAILQ_INSERT_TAIL (&list.fields, &node, link);
if (load_plugins(&list, &nfs4_plugins) == -1)
return -ENOENT;
}
gss_methods = conf_get_list("Translation", "GSS-Methods");
if (gss_methods) {
IDMAP_LOG(1, ("libnfsidmap: processing 'GSS-Methods' list"));
if (load_plugins(gss_methods, &gss_plugins) == -1)
goto out;
}
nobody_user = conf_get_str("Mapping", "Nobody-User");
if (nobody_user) {
size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
struct passwd *buf;
struct passwd *pw = NULL;
int err;
buf = malloc(sizeof(*buf) + buflen);
if (buf) {
err = getpwnam_r(nobody_user, buf, ((char *)buf) + sizeof(*buf), buflen, &pw);
if (err == 0 && pw != NULL)
nobody_uid = pw->pw_uid;
else
IDMAP_LOG(1, ("libnfsidmap: Nobody-User (%s) not found: %s",
nobody_user, strerror(errno)));
free(buf);
} else
IDMAP_LOG(0,("libnfsidmap: Nobody-User: no memory : %s",
nobody_user, strerror(errno)));
}
nobody_group = conf_get_str("Mapping", "Nobody-Group");
if (nobody_group) {
size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
struct group *buf;
struct group *gr = NULL;
int err;
buf = malloc(sizeof(*buf) + buflen);
if (buf) {
err = getgrnam_r(nobody_group, buf, ((char *)buf) + sizeof(*buf), buflen, &gr);
if (err == 0 && gr != NULL)
nobody_gid = gr->gr_gid;
else
IDMAP_LOG(1, ("libnfsidmap: Nobody-Group (%s) not found: %s",
nobody_group, strerror(errno)));
free(buf);
} else
IDMAP_LOG(0,("libnfsidmap: Nobody-Group: no memory : %s",
nobody_group, strerror(errno)));
}
ret = 0;
out:
if (ret) {
if (nfs4_plugins)
unload_plugins(nfs4_plugins);
if (gss_plugins) {
unload_plugins(gss_plugins);
}
nfs4_plugins = gss_plugins = NULL;
}
if (gss_methods)
conf_free_list(gss_methods);
if (nfs4_methods)
conf_free_list(nfs4_methods);
return ret ? -ENOENT: 0;
}
void nfs4_term_name_mapping(void)
{
if (nfs4_plugins)
unload_plugins(nfs4_plugins);
if (gss_plugins)
unload_plugins(gss_plugins);
nfs4_plugins = gss_plugins = NULL;
free_local_realms();
conf_cleanup();
}
int
nfs4_get_default_domain(char *UNUSED(server), char *domain, size_t len)
{
char *d = get_default_domain();
if (strlen(d) + 1 > len)
return -ERANGE;
strcpy(domain, d);
return 0;
}
/*
* Run through each configured translation method for
* function "funcname".
* If "prefer_gss" is true, then use the gss_plugins list,
* if present. Otherwise, use the default nfs4_plugins list.
*
* If the plugin function returns -ENOENT, then continue
* to the next plugin.
*/
#define RUN_TRANSLATIONS(funcname, prefer_gss, args...) \
do { \
int ret, i; \
struct mapping_plugin **plgns; \
\
ret = nfs4_init_name_mapping(NULL); \
if (ret) \
return ret; \
\
if ((prefer_gss) && gss_plugins) \
plgns = gss_plugins; \
else \
plgns = nfs4_plugins; \
\
for (i = 0; plgns[i] != NULL; i++) { \
if (plgns[i]->trans->funcname == NULL) \
continue; \
\
IDMAP_LOG(4, ("%s: calling %s->%s", __func__, \
plgns[i]->trans->name, #funcname)); \
\
ret = plgns[i]->trans->funcname(args); \
\
IDMAP_LOG(4, ("%s: %s->%s returned %d", \
__func__, plgns[i]->trans->name, \
#funcname, ret)); \
\
if (ret == -ENOENT) \
continue; \
\
break; \
} \
IDMAP_LOG(4, ("%s: final return value is %d", \
__func__, ret)); \
return ret; \
} while (0)
int nfs4_uid_to_name(uid_t uid, char *domain, char *name, size_t len)
{
RUN_TRANSLATIONS(uid_to_name, 0, uid, domain, name, len);
}
int nfs4_gid_to_name(gid_t gid, char *domain, char *name, size_t len)
{
RUN_TRANSLATIONS(gid_to_name, 0, gid, domain, name, len);
}
int nfs4_uid_to_owner(uid_t uid, char *domain, char *name, size_t len)
{
if (nfs4_uid_to_name(uid, domain, name, len))
sprintf(name, "%u", uid);
return 0;
}
int nfs4_gid_to_group_owner(gid_t gid, char *domain, char *name, size_t len)
{
if (nfs4_gid_to_name(gid, domain, name, len))
sprintf(name, "%u", gid);
return 0;
}
int nfs4_name_to_uid(char *name, uid_t *uid)
{
RUN_TRANSLATIONS(name_to_uid, 0, name, uid);
}
int nfs4_name_to_gid(char *name, gid_t *gid)
{
RUN_TRANSLATIONS(name_to_gid, 0, name, gid);
}
static int set_id_to_nobody(uid_t *id, uid_t is_uid)
{
int rc = 0;
const char name[] = "nobody@";
char nobody[strlen(name) + strlen(get_default_domain()) + 1];
/* First try to see whether a Nobody-User/Nobody-Group was
* configured, before we try to do a full lookup for the
* NFS nobody user. */
if (is_uid && nobody_uid != (uid_t)-1) {
*id = (uid_t)nobody_uid;
return 0;
} else if (!is_uid && nobody_gid != (gid_t)-1) {
*id = (uid_t)nobody_gid;
return 0;
}
strcpy(nobody, name);
strcat(nobody, get_default_domain());
if (is_uid)
rc = nfs4_name_to_uid(nobody, id);
else
rc = nfs4_name_to_gid(nobody, id);
if (rc) {
*id = -2;
rc = 0;
}
return rc;
}
int nfs4_owner_to_uid(char *name, uid_t *uid)
{
int rc = nfs4_name_to_uid(name, uid);
if (rc && id_as_chars(name, uid))
rc = 0;
else if (rc)
rc = set_id_to_nobody(uid, 1);
return rc;
}
int nfs4_group_owner_to_gid(char *name, gid_t *gid)
{
int rc = nfs4_name_to_gid(name, gid);
if (rc && id_as_chars(name, gid))
rc = 0;
else if (rc)
rc = set_id_to_nobody((uid_t *)gid, 0);
return rc;
}
int nfs4_gss_princ_to_ids(char *secname, char *princ, uid_t *uid, gid_t *gid)
{
RUN_TRANSLATIONS(princ_to_ids, 1, secname, princ, uid, gid, NULL);
}
int nfs4_gss_princ_to_grouplist(char *secname, char *princ,
gid_t *groups, int *ngroups)
{
RUN_TRANSLATIONS(gss_princ_to_grouplist, 1, secname, princ,
groups, ngroups, NULL);
}
int nfs4_gss_princ_to_ids_ex(char *secname, char *princ, uid_t *uid,
gid_t *gid, extra_mapping_params **ex)
{
RUN_TRANSLATIONS(princ_to_ids, 1, secname, princ, uid, gid, ex);
}
int nfs4_gss_princ_to_grouplist_ex(char *secname, char *princ, gid_t *groups,
int *ngroups, extra_mapping_params **ex)
{
RUN_TRANSLATIONS(gss_princ_to_grouplist, 1, secname, princ,
groups, ngroups, ex);
}
void nfs4_set_debug(int dbg_level, void (*logger)(const char *, ...))
{
if (logger)
idmap_log_func = logger;
idmap_verbosity = dbg_level;
IDMAP_LOG(0, ("Setting log level to %d\n", idmap_verbosity));
}
const char *nfsidmap_config_get(const char *section, const char *tag)
{
return conf_get_section(section, NULL, tag);
}