/* * 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 * J. Bruce Fields * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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: ")); } 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); }