[PATCH] Add regex plugin for nfsidmap

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.

---
 support/nfsidmap/Makefile.am   |   6 +-
 support/nfsidmap/idmapd.conf.5 |  66 +++-
 support/nfsidmap/regex.c       | 548 +++++++++++++++++++++++++++++++++
 3 files changed, 617 insertions(+), 3 deletions(-)
 create mode 100644 support/nfsidmap/regex.c

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@xxxxxxxxxxxxx$|^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@xxxxxxxxxxxxxxxx = johnny
 
+[Regex]
+
+User-Regex = ^DOMAIN\\([^@]+)@DOMAIN.ORG$
+Group-Regex = ^([^@]+)@DOMAIN.ORG@xxxxxxxxxx$|^DOMAIN\\([^@]+)@DOMAIN.ORG$
+Prepend-Before-User = DOMAIN\ 
+Append-After-User = @DOMAIN.ORG
+Append-After-Group = @domain.org@xxxxxxxxxx
+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..3a793152
--- /dev/null
+++ b/support/nfsidmap/regex.c
@@ -0,0 +1,548 @@
+/*
+ *  regex.c
+ *
+ *  regex idmapping functions.
+ *
+ *  Copyright (c) 2017-2020 Stefan Walter <stefan.walter@xxxxxxxxxxx>.
+ *  Copyright (c) 2008 David Härdeman <david@xxxxxxxxxxx>.
+ *  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);
+}
+
-- 
2.25.1




[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux