summaryrefslogtreecommitdiffstats
path: root/support/nfsidmap/nss.c
diff options
context:
space:
mode:
Diffstat (limited to 'support/nfsidmap/nss.c')
-rw-r--r--support/nfsidmap/nss.c494
1 files changed, 494 insertions, 0 deletions
diff --git a/support/nfsidmap/nss.c b/support/nfsidmap/nss.c
new file mode 100644
index 0000000..0f43076
--- /dev/null
+++ b/support/nfsidmap/nss.c
@@ -0,0 +1,494 @@
+/*
+ * nss.c
+ *
+ * nsswitch idmapping functions.
+ *
+ * Copyright (c) 2004 The Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * 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 <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <netdb.h>
+#include <err.h>
+#include <grp.h>
+#include <limits.h>
+#include <ctype.h>
+#include "nfsidmap.h"
+#include "nfsidmap_plugin.h"
+#include "nfsidmap_private.h"
+#include <syslog.h>
+
+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 : "<not-provided>"));
+ 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);
+}