aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Walter <walteste@inf.ethz.ch>2020-04-10 13:24:03 -0400
committerSteve Dickson <steved@redhat.com>2020-04-13 14:47:24 -0400
commit940caffdfb9953a2ccfecec81664e4a179753461 (patch)
tree0b4010c5ea8dc15cf0b4e411f0686102958f2d59
parent6ae82b44505af92c33678e8ae6a9c9fc1df7e0f3 (diff)
downloadnfs-utils-940caffdfb9953a2ccfecec81664e4a179753461.tar.gz
Add regex plugin for nfsidmap
The patch below adds a new nfsidmap plugin that uses regex to extract ids from NFSv4 names. Names are created from ids by pre- and appending static strings. It works with both idmapd on servers and nfsidmap on clients. This plugin is especially useful in environments with Active Directory where distributed NFS servers use a mix of short (uname) and long (domain\uname) names. Combining it with the nsswitch plugin covers both variants. Currently this plugin has its own git project on github but I think it could be helpful if it would be incorporated in the main nfs-utils plugin set. Signed-off-by: Steve Dickson <steved@redhat.com>
-rw-r--r--support/nfsidmap/Makefile.am6
-rw-r--r--support/nfsidmap/idmapd.conf.566
-rw-r--r--support/nfsidmap/regex.c548
3 files changed, 617 insertions, 3 deletions
diff --git a/support/nfsidmap/Makefile.am b/support/nfsidmap/Makefile.am
index 9c21fa34..35575a95 100644
--- a/support/nfsidmap/Makefile.am
+++ b/support/nfsidmap/Makefile.am
@@ -15,7 +15,7 @@ else
GUMS_MAPPING_LIB =
endif
lib_LTLIBRARIES = libnfsidmap.la
-pkgplugin_LTLIBRARIES = nsswitch.la static.la $(UMICH_LDAP_LIB) $(GUMS_MAPPING_LIB)
+pkgplugin_LTLIBRARIES = nsswitch.la static.la regex.la $(UMICH_LDAP_LIB) $(GUMS_MAPPING_LIB)
# Library versioning notes from:
# http://sources.redhat.com/autobook/autobook/autobook_91.html
@@ -41,6 +41,10 @@ static_la_SOURCES = static.c
static_la_LDFLAGS = -module -avoid-version
static_la_LIBADD = ../../support/nfs/libnfsconf.la
+regex_la_SOURCES = regex.c
+regex_la_LDFLAGS = -module -avoid-version
+regex_la_LIBADD = ../../support/nfs/libnfsconf.la
+
umich_ldap_la_SOURCES = umich_ldap.c
umich_ldap_la_LDFLAGS = -module -avoid-version
umich_ldap_la_LIBADD = -lldap ../../support/nfs/libnfsconf.la
diff --git a/support/nfsidmap/idmapd.conf.5 b/support/nfsidmap/idmapd.conf.5
index 61fbb613..e554a44e 100644
--- a/support/nfsidmap/idmapd.conf.5
+++ b/support/nfsidmap/idmapd.conf.5
@@ -151,6 +151,58 @@ names to local user names. Entries in the list are of the form:
.fi
.\"
.\" -------------------------------------------------------------------
+.\" The [REGEX] section
+.\" -------------------------------------------------------------------
+.\"
+.SS "[REGEX] section variables"
+.nf
+
+.fi
+If the "regex" translation method is specified, the following
+variables within the [REGEX] section are used to map between NFS4 names and local IDs.
+.TP
+.B User-Regex
+Case-insensitive regular expression that extracts the local user name from an NFSv4 name. Multiple expressions may be concatenated with '|'. The first match will be used.
+There is no default. A basic regular expression for domain DOMAIN.ORG and realm MY.DOMAIN.ORG would be:
+.nf
+^DOMAIN\\([^@]+)@MY.DOMAIN.ORG$
+.fi
+.TP
+.B Group-Regex
+Case-insensitive regular expression that extracts the local group name from an NFSv4 name. Multiple expressions may be concatenated with '|'. The first match will be used.
+There is no default. A basic regular expression for domain DOMAIN.ORG and realm MY.DOMAIN.ORG would be:
+.nf
+^([^@]+)@DOMAIN.ORG@MY.DOMAIN.ORG$|^DOMAIN\\([^@]+)@MY.DOMAIN.ORG$
+.fi
+.TP
+.B Prepend-Before-User
+Constant string to put before a local user name when building an NFSv4 name. Usually this is the short domain name followed by '\'.
+(Default: none)
+.TP
+.B Append-After-User
+Constant string to put after a local user name when building an NFSv4 name. Usually this is '@' followed by the default realm.
+(Default: none)
+.TP
+.B Prepend-Before-Group
+Constant string to put before a local group name when building an NFSv4 name. Usually not used.
+(Default: none)
+.TP
+.B Append-After-Group
+Constant string to put before a local group name when building an NFSv4 name. Usually this is '@' followed by the domain name followed by another '@' and the default realm.
+(Default: none)
+.TP
+.B Group-Name-Prefix
+Constant string that is prepended to a local group name when converting it to an NFSv4 name. If an NFSv4 group name has this prefix it is removed when converting it to a local group name.
+With this group names of a central directory can be shortened for an isolated organizational unit if all groups have a common prefix.
+(Default: none)
+.TP
+.B Group-Name-No-Prefix-Regex
+Case-insensitive regular expression to exclude groups from adding and removing the prefix set by
+.B Group-Name-Prefix
+. The regular expression must match both the remote and local group names. Multiple expressions may be concatenated with '|'.
+(Default: none)
+.\"
+.\" -------------------------------------------------------------------
.\" The [UMICH_SCHEMA] section
.\" -------------------------------------------------------------------
.\"
@@ -286,13 +338,23 @@ Nobody-Group = nfsnobody
[Translation]
-Method = umich_ldap,nsswitch
-GSS-Methods = umich_ldap,static
+Method = umich_ldap,regex,nsswitch
+GSS-Methods = umich_ldap,regex,static
[Static]
johndoe@OTHER.DOMAIN.ORG = johnny
+[Regex]
+
+User-Regex = ^DOMAIN\\([^@]+)@DOMAIN.ORG$
+Group-Regex = ^([^@]+)@DOMAIN.ORG@DOMAIN.ORG$|^DOMAIN\\([^@]+)@DOMAIN.ORG$
+Prepend-Before-User = DOMAIN\
+Append-After-User = @DOMAIN.ORG
+Append-After-Group = @domain.org@domain.org
+Group-Name-Prefix = sales-
+Group-Name-No-Prefix-Regex = -personal-group$
+
[UMICH_SCHEMA]
LDAP_server = ldap.domain.org
diff --git a/support/nfsidmap/regex.c b/support/nfsidmap/regex.c
new file mode 100644
index 00000000..fdbb2e2f
--- /dev/null
+++ b/support/nfsidmap/regex.c
@@ -0,0 +1,548 @@
+/*
+ * regex.c
+ *
+ * regex idmapping functions.
+ *
+ * Copyright (c) 2017-2020 Stefan Walter <stefan.walter@inf.ethz.ch>.
+ * Copyright (c) 2008 David H?rdeman <david@hardeman.nu>.
+ * All rights reserved.
+ *
+ * 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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+#include <err.h>
+#include <regex.h>
+
+#include "nfsidmap.h"
+#include "nfsidmap_plugin.h"
+
+#define CONFIG_GET_STRING nfsidmap_config_get
+extern const char *nfsidmap_config_get(const char *, const char *);
+
+#define MAX_MATCHES 100
+
+regex_t group_re;
+regex_t user_re;
+regex_t gpx_re;
+int use_gpx;
+const char * group_prefix;
+const char * group_name_prefix;
+const char * group_suffix;
+const char * user_prefix;
+const char * user_suffix;
+const char * group_map_file;
+const char * group_map_section;
+char empty = '\0';
+size_t group_name_prefix_length;
+
+struct pwbuf {
+ struct passwd pwbuf;
+ char buf[1];
+};
+
+struct grbuf {
+ struct group grbuf;
+ char buf[1];
+};
+
+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;
+}
+
+/*
+ * Regexp Translation Methods
+ *
+ */
+
+static struct passwd *regex_getpwnam(const char *name, const char *UNUSED(domain),
+ int *err_p)
+{
+ struct passwd *pw;
+ struct pwbuf *buf;
+ size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+ char *localname;
+ size_t namelen;
+ int err;
+ int status;
+ int index;
+ regmatch_t matches[MAX_MATCHES];
+
+ buf = malloc(sizeof(*buf) + buflen);
+ if (!buf) {
+ err = ENOMEM;
+ goto err;
+ }
+
+ status = regexec(&user_re, name, MAX_MATCHES, matches, 0);
+ if (status) {
+ IDMAP_LOG(4, ("regexp_getpwnam: user '%s' did not match regex", name));
+ err = ENOENT;
+ goto err_free_buf;
+ }
+
+ for (index = 1; index < MAX_MATCHES ; index++)
+ {
+ if (matches[index].rm_so >= 0)
+ break;
+ }
+
+ if (index == MAX_MATCHES) {
+ IDMAP_LOG(4, ("regexp_getpwnam: user '%s' did not match regex", name));
+ err = ENOENT;
+ goto err_free_buf;
+ }
+
+ namelen = matches[index].rm_eo - matches[index].rm_so;
+ localname= malloc(namelen + 1);
+ if (!localname)
+ {
+ err = ENOMEM;
+ goto err_free_buf;
+ }
+ strncpy(localname, name+matches[index].rm_so, namelen);
+ localname[namelen] = '\0';
+
+again:
+ err = getpwnam_r(localname, &buf->pwbuf, buf->buf, buflen, &pw);
+
+ if (err == EINTR)
+ goto again;
+
+ if (!pw) {
+ if (err == 0)
+ err = ENOENT;
+
+ IDMAP_LOG(4, ("regex_getpwnam: local user '%s' for '%s' not found",
+ localname, name));
+
+ goto err_free_name;
+ }
+
+ IDMAP_LOG(4, ("regexp_getpwnam: name '%s' mapped to '%s'",
+ name, localname));
+
+ *err_p = 0;
+ return pw;
+
+err_free_name:
+ free(localname);
+err_free_buf:
+ free(buf);
+err:
+ *err_p = err;
+ return NULL;
+}
+
+static struct group *regex_getgrnam(const char *name, const char *UNUSED(domain),
+ int *err_p)
+{
+ struct group *gr;
+ struct grbuf *buf;
+ size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
+ char *localgroup;
+ char *groupname;
+ size_t namelen;
+ int err = 0;
+ int index;
+ int status;
+ regmatch_t matches[MAX_MATCHES];
+
+ buf = malloc(sizeof(*buf) + buflen);
+ if (!buf) {
+ err = ENOMEM;
+ goto err;
+ }
+
+ status = regexec(&group_re, name, MAX_MATCHES, matches, 0);
+ if (status) {
+ IDMAP_LOG(4, ("regexp_getgrnam: group '%s' did not match regex", name));
+ err = ENOENT;
+ goto err_free_buf;
+ }
+
+ for (index = 1; index < MAX_MATCHES ; index++)
+ {
+ if (matches[index].rm_so >= 0)
+ break;
+ }
+
+ if (index == MAX_MATCHES) {
+ IDMAP_LOG(4, ("regexp_getgrnam: group '%s' did not match regex", name));
+ err = ENOENT;
+ goto err_free_buf;
+ }
+
+ namelen = matches[index].rm_eo - matches[index].rm_so;
+ localgroup = malloc(namelen + 1);
+ if (!localgroup)
+ {
+ err = ENOMEM;
+ goto err_free_buf;
+ }
+ strncpy(localgroup, name+matches[index].rm_so, namelen);
+ localgroup[namelen] = '\0';
+
+ IDMAP_LOG(4, ("regexp_getgrnam: group '%s' after match of regex", localgroup));
+
+ groupname = localgroup;
+ if (group_name_prefix_length && ! strncmp(group_name_prefix, localgroup, group_name_prefix_length))
+ {
+ err = 1;
+ if (use_gpx)
+ err = regexec(&gpx_re, localgroup, 0, NULL, 0);
+
+ if (err)
+ {
+ IDMAP_LOG(4, ("regexp_getgrnam: removing prefix '%s' (%d long) from group '%s'", group_name_prefix, group_name_prefix_length, localgroup));
+ groupname += group_name_prefix_length;
+ }
+ else
+ {
+ IDMAP_LOG(4, ("regexp_getgrnam: not removing prefix from group '%s'", localgroup));
+ }
+ }
+
+ IDMAP_LOG(4, ("regexp_getgrnam: will use '%s'", groupname));
+
+again:
+ err = getgrnam_r(groupname, &buf->grbuf, buf->buf, buflen, &gr);
+
+ if (err == EINTR)
+ goto again;
+
+ if (!gr) {
+ if (err == 0)
+ err = ENOENT;
+
+ IDMAP_LOG(4, ("regex_getgrnam: local group '%s' for '%s' not found", groupname, name));
+
+ goto err_free_name;
+ }
+
+ IDMAP_LOG(4, ("regex_getgrnam: group '%s' mapped to '%s'", name, groupname));
+
+ free(localgroup);
+
+ *err_p = 0;
+ return gr;
+
+err_free_name:
+ free(localgroup);
+err_free_buf:
+ free(buf);
+err:
+ *err_p = err;
+ return NULL;
+}
+
+static int regex_gss_princ_to_ids(char *secname, char *princ,
+ uid_t *uid, uid_t *gid,
+ extra_mapping_params **UNUSED(ex))
+{
+ struct passwd *pw;
+ int err;
+
+ /* XXX: Is this necessary? */
+ if (strcmp(secname, "krb5") != 0 && strcmp(secname, "spkm3") != 0)
+ return -EINVAL;
+
+ pw = regex_getpwnam(princ, NULL, &err);
+
+ if (pw) {
+ *uid = pw->pw_uid;
+ *gid = pw->pw_gid;
+ free(pw);
+ }
+
+ return -err;
+}
+
+static int regex_gss_princ_to_grouplist(char *secname, char *princ,
+ gid_t *groups, int *ngroups,
+ extra_mapping_params **UNUSED(ex))
+{
+ struct passwd *pw;
+ int err;
+
+ /* XXX: Is this necessary? */
+ if (strcmp(secname, "krb5") != 0 && strcmp(secname, "spkm3") != 0)
+ return -EINVAL;
+
+ pw = regex_getpwnam(princ, NULL, &err);
+
+ if (pw) {
+ if (getgrouplist(pw->pw_name, pw->pw_gid, groups, ngroups) < 0)
+ err = -ERANGE;
+ free(pw);
+ }
+
+ return -err;
+}
+
+static int regex_name_to_uid(char *name, uid_t *uid)
+{
+ struct passwd *pw;
+ int err;
+
+ pw = regex_getpwnam(name, NULL, &err);
+
+ if (pw) {
+ *uid = pw->pw_uid;
+ free(pw);
+ }
+
+ return -err;
+}
+
+static int regex_name_to_gid(char *name, gid_t *gid)
+{
+ struct group *gr;
+ int err;
+
+ gr = regex_getgrnam(name, NULL, &err);
+
+ if (gr) {
+ *gid = gr->gr_gid;
+ free(gr);
+ }
+
+ return -err;
+}
+
+static int write_name(char *dest, char *localname, const char* name_prefix, const char *prefix, const char *suffix, size_t len)
+{
+ if (strlen(localname) + strlen(name_prefix) + strlen(prefix) + strlen(suffix) + 1 > len) {
+ return -ENOMEM; /* XXX: Is there an -ETOOLONG? */
+ }
+ strcpy(dest, prefix);
+ strcat(dest, name_prefix);
+ strcat(dest, localname);
+ strcat(dest, suffix);
+
+ IDMAP_LOG(4, ("write_name: will use '%s'", dest));
+
+ return 0;
+}
+
+static int regex_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;
+ err = write_name(name, pw->pw_name, &empty, user_prefix, user_suffix, len);
+out_buf:
+ free(buf);
+out:
+ return err;
+}
+
+static int regex_gid_to_name(gid_t gid, char *UNUSED(domain), char *name, size_t len)
+{
+ struct group *gr = NULL;
+ struct group grbuf;
+ char *buf;
+ const char *name_prefix;
+ size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
+ int err;
+ char * groupname = NULL;
+
+ 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;
+
+ groupname = gr->gr_name;
+ name_prefix = group_name_prefix;
+ if (group_name_prefix_length)
+ {
+ if(! strncmp(group_name_prefix, groupname, group_name_prefix_length))
+ {
+ name_prefix = &empty;
+ }
+ else if (use_gpx)
+ {
+ err = regexec(&gpx_re, groupname, 0, NULL, 0);
+ if (!err)
+ {
+ IDMAP_LOG(4, ("regex_gid_to_name: not adding prefix to group '%s'", groupname));
+ name_prefix = &empty;
+ }
+ }
+ }
+
+ err = write_name(name, groupname, name_prefix, group_prefix, group_suffix, len);
+
+out_buf:
+ free(buf);
+out:
+ return err;
+}
+
+static int regex_init(void) {
+ const char *string;
+ int status;
+
+
+ string = CONFIG_GET_STRING("Regex", "User-Regex");
+ if (!string)
+ {
+ warnx("regex_init: regex for user mapping missing");
+ goto error1;
+ }
+
+ status = regcomp(&user_re, string, REG_EXTENDED|REG_ICASE);
+ if (status)
+ {
+ warnx("regex_init: compiling regex for user mapping failed with status %u", status);
+ goto error1;
+ }
+
+ string = CONFIG_GET_STRING("Regex", "Group-Regex");
+ if (!string)
+ {
+ warnx("regex_init: regex for group mapping missing");
+ goto error2;
+ }
+
+ status = regcomp(&group_re, string, REG_EXTENDED|REG_ICASE);
+ if (status)
+ {
+ warnx("regex_init: compiling regex for group mapping failed with status %u", status);
+ goto error2;
+ }
+
+ group_name_prefix = CONFIG_GET_STRING("Regex", "Group-Name-Prefix");
+ if (!group_name_prefix)
+ {
+ group_name_prefix = &empty;
+ }
+ group_name_prefix_length = strlen(group_name_prefix);
+
+ user_prefix = CONFIG_GET_STRING("Regex", "Prepend-Before-User");
+ if (!user_prefix)
+ {
+ user_prefix = &empty;
+ }
+
+ user_suffix = CONFIG_GET_STRING("Regex", "Append-After-User");
+ if (!user_suffix)
+ {
+ user_suffix = &empty;
+ }
+
+ group_prefix = CONFIG_GET_STRING("Regex", "Prepend-Before-Group");
+ if (!group_prefix)
+ {
+ group_prefix = &empty;
+ }
+
+ group_suffix = CONFIG_GET_STRING("Regex", "Append-After-Group");
+ if (!group_suffix)
+ {
+ group_suffix = &empty;
+ }
+
+ string = CONFIG_GET_STRING("Regex", "Group-Name-No-Prefix-Regex");
+ use_gpx = 0;
+ if (string)
+ {
+ status = regcomp(&gpx_re, string, REG_EXTENDED|REG_ICASE);
+
+ if (status)
+ {
+ warnx("regex_init: compiling regex for group prefix exclusion failed with status %u", status);
+ goto error3;
+ }
+
+ use_gpx = 1;
+ }
+
+ return 0;
+
+error3:
+ regfree(&group_re);
+error2:
+ regfree(&user_re);
+error1:
+ return 0;
+ /* return -EINVAL; */
+}
+
+
+struct trans_func regex_trans = {
+ .name = "regex",
+ .init = regex_init,
+ .name_to_uid = regex_name_to_uid,
+ .name_to_gid = regex_name_to_gid,
+ .uid_to_name = regex_uid_to_name,
+ .gid_to_name = regex_gid_to_name,
+ .princ_to_ids = regex_gss_princ_to_ids,
+ .gss_princ_to_grouplist = regex_gss_princ_to_grouplist,
+};
+
+struct trans_func *libnfsidmap_plugin_init()
+{
+ return (&regex_trans);
+}
+