/* * nss.c * * nsswitch idmapping functions. * * Copyright (c) 2004 The Regents of the University of Michigan. * All rights reserved. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "nfsidmap.h" #include "nfsidmap_plugin.h" #include "nfsidmap_private.h" #include static char *get_default_domain(void) { static char default_domain[NFS4_MAX_DOMAIN_LEN] = ""; if (default_domain[0] == 0) { nfs4_get_default_domain(NULL, default_domain, NFS4_MAX_DOMAIN_LEN); } return default_domain; } /* * NSS Translation Methods * * These are all just wrappers around getpwnam and friends; * we tack on the given domain to the results of getpwnam when looking up a uid, * and ignore the domain entirely when looking up a name. */ static int write_name(char *dest, char *localname, char *domain, size_t len, int doappend) { if (doappend || !strchr(localname,'@')) { if (strlen(localname) + 1 + strlen(domain) + 1 > len) return -ENOMEM; /* XXX: Is there an -ETOOLONG? */ strcpy(dest, localname); strcat(dest, "@"); strcat(dest, domain); } else { if (strlen(localname) + 1 > len) return -ENOMEM; strcpy(dest, localname); } return 0; } static int nss_uid_to_name(uid_t uid, char *domain, char *name, size_t len) { struct passwd *pw = NULL; struct passwd pwbuf; char *buf; size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX); int err = -ENOMEM; buf = malloc(buflen); if (!buf) goto out; if (domain == NULL) domain = get_default_domain(); err = -getpwuid_r(uid, &pwbuf, buf, buflen, &pw); if (pw == NULL) err = -ENOENT; if (err) goto out_buf; if (get_nostrip() & IDTYPE_USER) err = write_name(name, pw->pw_name, domain, len, 0); else err = write_name(name, pw->pw_name, domain, len, 1); out_buf: free(buf); out: return err; } static int nss_gid_to_name(gid_t gid, char *domain, char *name, size_t len) { struct group *gr = NULL; struct group grbuf; char *buf; size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); int err; if (domain == NULL) domain = get_default_domain(); do { err = -ENOMEM; buf = malloc(buflen); if (!buf) goto out; err = -getgrgid_r(gid, &grbuf, buf, buflen, &gr); if (gr == NULL && !err) err = -ENOENT; if (err == -ERANGE) { buflen *= 2; free(buf); } } while (err == -ERANGE); if (err) goto out_buf; if (get_nostrip() & IDTYPE_GROUP) err = write_name(name, gr->gr_name, domain, len, 0); else err = write_name(name, gr->gr_name, domain, len, 1); out_buf: free(buf); out: return err; } /* XXX: actually should return error, so can distinguish between * memory allocation failure and failure to match domain */ static char *strip_domain(const char *name, const char *domain) { const char *c; char *l = NULL; int len; if (name == NULL) goto out; c = strrchr(name, '@'); if (c == NULL && domain != NULL) goto out; if (c == NULL && domain == NULL) { len = strlen(name) + 1; } else { if (domain && strcasecmp(c + 1, domain) != 0) goto out; len = c - name; } l = malloc(len + 1); if (l == NULL) goto out; memcpy(l, name, len); l[len] = '\0'; out: return l; } struct pwbuf { struct passwd pwbuf; char buf[1]; }; static struct passwd *nss_getpwnam(const char *name, const char *domain, int *err_p, int dostrip) { struct passwd *pw; struct pwbuf *buf; size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX); char *localname; int err = ENOMEM; if (buflen > UINT_MAX) goto err; buf = malloc(sizeof(*buf) + buflen); if (buf == NULL) goto err; err = EINVAL; if (dostrip) { localname = strip_domain(name, domain); IDMAP_LOG(4, ("nss_getpwnam: name '%s' domain '%s': " "resulting localname '%s'", name, domain, localname)); if (localname == NULL) { IDMAP_LOG(0, ("nss_getpwnam: name '%s' does not map " "into domain '%s'", name, domain ? domain : "")); goto err_free_buf; } err = getpwnam_r(localname, &buf->pwbuf, buf->buf, buflen, &pw); if (pw == NULL && domain != NULL) IDMAP_LOG(1, ("nss_getpwnam: name '%s' not found in domain '%s'", localname, domain)); free(localname); } else { err = getpwnam_r(name, &buf->pwbuf, buf->buf, buflen, &pw); if (pw == NULL) IDMAP_LOG(1, ("nss_getpwnam: name '%s' not found (domain not stripped)", name)); } if (err == 0 && pw != NULL) { *err_p = 0; return pw; } else if (err == 0 && pw == NULL) { err = ENOENT; } err_free_buf: free(buf); err: *err_p = -err; return NULL; } static int nss_name_to_uid(char *name, uid_t *uid) { struct passwd *pw = NULL; char *domain; int err = -ENOENT; domain = get_default_domain(); if (get_nostrip() & IDTYPE_USER) { pw = nss_getpwnam(name, domain, &err, 0); if (pw != NULL) goto out_uid; } pw = nss_getpwnam(name, domain, &err, 1); if (pw == NULL) goto out; out_uid: *uid = pw->pw_uid; IDMAP_LOG(4, ("nss_name_to_uid: name '%s' uid %u", name, *uid)); free(pw); err = 0; out: return err; } static char *reformat_name(const char *name) { const char *domain; const char *c; const char *d; char *l = NULL; int len; int dlen = 0; int i; c = strchr(name, '@'); if (c == NULL) goto out; len = c - name; domain = ++c; d = strchr(domain, '.'); if (d == NULL) goto out; dlen = d - domain; l = malloc(dlen + 1 + len + 1); if (l == NULL) goto out; for (i = 0; i < dlen; i++) l[i] = toupper(domain[i]); l[dlen] = '\\'; memcpy(l + dlen + 1, name, len); l[dlen + 1 + len] = '\0'; out: return l; } static int _nss_name_to_gid(char *name, gid_t *gid, int dostrip) { struct group *gr = NULL; struct group grbuf; char *buf, *domain; size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); int err = -EINVAL; char *localname = NULL; char *ref_name = NULL; domain = get_default_domain(); if (dostrip) { localname = strip_domain(name, domain); IDMAP_LOG(4, ("nss_name_to_gid: name '%s' domain '%s': " "resulting localname '%s'", name, domain, localname)); if (!localname) { IDMAP_LOG(0, ("nss_name_to_gid: name '%s' does not map " "into domain '%s'", name, domain)); goto out; } } else if (get_reformat_group()) { ref_name = reformat_name(name); if (ref_name == NULL) { IDMAP_LOG(1, ("nss_name_to_gid: failed to reformat name '%s'", name)); err = -ENOENT; goto out; } } err = -ENOMEM; if (buflen > UINT_MAX) goto out_name; do { buf = malloc(buflen); if (!buf) goto out_name; if (dostrip) err = -getgrnam_r(localname, &grbuf, buf, buflen, &gr); else if (get_reformat_group()) err = -getgrnam_r(ref_name, &grbuf, buf, buflen, &gr); else err = -getgrnam_r(name, &grbuf, buf, buflen, &gr); if (gr == NULL && !err) { if (dostrip) IDMAP_LOG(1, ("nss_name_to_gid: name '%s' not found " "in domain '%s'", localname, domain)); else if (get_reformat_group()) IDMAP_LOG(1, ("nss_name_to_gid: name '%s' not found " "(reformatted)", ref_name)); else IDMAP_LOG(1, ("nss_name_to_gid: name '%s' not found " "(domain not stripped)", name)); err = -ENOENT; } if (err == -ERANGE) { buflen *= 2; free(buf); } } while (err == -ERANGE); if (err) goto out_buf; *gid = gr->gr_gid; IDMAP_LOG(4, ("nss_name_to_gid: name '%s' gid %u", name, *gid)); out_buf: free(buf); out_name: free(localname); free(ref_name); out: return err; } static int nss_name_to_gid(char *name, gid_t *gid) { int err = 0; if (get_nostrip() & IDTYPE_GROUP) { err = _nss_name_to_gid(name, gid, 0); if (!err) goto out; } err = _nss_name_to_gid(name, gid, 1); out: return err; } static int nss_gss_princ_to_ids(char *secname, char *princ, uid_t *uid, uid_t *gid, extra_mapping_params **UNUSED(ex)) { struct passwd *pw; int err = 0; char *princ_realm; struct conf_list *realms; struct conf_list_node *r; int found = 0; if (strcmp(secname, "spkm3") == 0) return -ENOENT; if (strcmp(secname, "krb5") != 0) return -EINVAL; /* get princ's realm */ princ_realm = strstr(princ, "@"); if (princ_realm == NULL) return -EINVAL; princ_realm++; /* get list of "local-equivalent" realms and * check against the principal's realm */ realms = get_local_realms(); TAILQ_FOREACH(r, &realms->fields, link) { if (strcmp(r->field, princ_realm) == 0) { found = 1; break; } } if (!found) { IDMAP_LOG(1, ("nss_gss_princ_to_ids: Local-Realm '%s': NOT FOUND", princ_realm)); return -ENOENT; } /* XXX: this should call something like getgssauthnam instead? */ pw = nss_getpwnam(princ, NULL, &err, 1); if (pw == NULL) { err = -ENOENT; goto out; } *uid = pw->pw_uid; *gid = pw->pw_gid; free(pw); out: return err; } static int nss_gss_princ_to_grouplist(char *secname, char *princ, gid_t *groups, int *ngroups, extra_mapping_params **UNUSED(ex)) { struct passwd *pw; int ret = -EINVAL; if (strcmp(secname, "krb5") != 0) goto out; /* XXX: not quite right? Need to know default realm? */ /* XXX: this should call something like getgssauthnam instead? */ pw = nss_getpwnam(princ, NULL, &ret, 1); if (pw == NULL) { ret = -ENOENT; goto out; } if (getgrouplist(pw->pw_name, pw->pw_gid, groups, ngroups) < 0) ret = -ERANGE; free(pw); out: return ret; } static int nss_plugin_init(void) { if (nfsidmap_conf_path) conf_init_file(nfsidmap_conf_path); return 0; } /* * Called by dlclose(). See dlopen(3) man page */ __attribute__((destructor)) static int nss_plugin_term(void) { free_local_realms(); conf_cleanup(); return 0; } struct trans_func nss_trans = { .name = "nsswitch", .init = nss_plugin_init, .princ_to_ids = nss_gss_princ_to_ids, .name_to_uid = nss_name_to_uid, .name_to_gid = nss_name_to_gid, .uid_to_name = nss_uid_to_name, .gid_to_name = nss_gid_to_name, .gss_princ_to_grouplist = nss_gss_princ_to_grouplist, }; struct trans_func *libnfsidmap_plugin_init(void) { return (&nss_trans); }