diff options
Diffstat (limited to 'support/nfsidmap/libnfsidmap.c')
-rw-r--r-- | support/nfsidmap/libnfsidmap.c | 712 |
1 files changed, 712 insertions, 0 deletions
diff --git a/support/nfsidmap/libnfsidmap.c b/support/nfsidmap/libnfsidmap.c new file mode 100644 index 0000000..f8c3648 --- /dev/null +++ b/support/nfsidmap/libnfsidmap.c @@ -0,0 +1,712 @@ +/* + * 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) { + IDMAP_LOG(1, ("libnfsidmap: DNS lookup of hostname failed. Attempting to use domain from hostname as is.")); + if ((c = strchr(hname, '.')) == NULL || *++c == '\0') + return -1; + } + else { + 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(0, ("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); +} |