2020-03-31 09:23:22

by Stefan Walter

[permalink] [raw]
Subject: [PATCH] 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.

---
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
+^([^@]+)@[email protected]$|^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]

[email protected] = johnny

+[Regex]
+
+User-Regex = ^DOMAIN\\([^@]+)@DOMAIN.ORG$
+Group-Regex = ^([^@]+)@[email protected]$|^DOMAIN\\([^@]+)@DOMAIN.ORG$
+Prepend-Before-User = DOMAIN\
+Append-After-User = @DOMAIN.ORG
+Append-After-Group = @[email protected]
+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 <[email protected]>.
+ * Copyright (c) 2008 David H?rdeman <[email protected]>.
+ * 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


2020-03-31 16:22:15

by Trond Myklebust

[permalink] [raw]
Subject: Re: [PATCH] Add regex plugin for nfsidmap

On Tue, 2020-03-31 at 11:02 +0200, Stefan Walter wrote:
> 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.

Hmm... Why wouldn't you rather want to use something like the
sss_rpcidmapd plugin in the AD environment? Manual editing of the
username sounds error prone, particularly if your domain is part of an
AD forest.

I'm not saying that this plugin couldn't be useful in other
circumstances (please elaborate), just that the AD use case sounds a
little iffy...

--
Trond Myklebust
Linux NFS client maintainer, Hammerspace
[email protected]


2020-03-31 18:42:31

by Walter Stefan

[permalink] [raw]
Subject: Re: [PATCH] Add regex plugin for nfsidmap



> On 31 Mar 2020, at 18:20, Trond Myklebust <[email protected]> wrote:
>
> On Tue, 2020-03-31 at 11:02 +0200, Stefan Walter wrote:
>> 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.
>
> Hmm... Why wouldn't you rather want to use something like the
> sss_rpcidmapd plugin in the AD environment? Manual editing of the
> username sounds error prone, particularly if your domain is part of an
> AD forest.
>
> I'm not saying that this plugin couldn't be useful in other
> circumstances (please elaborate), just that the AD use case sounds a
> little iffy…

The reason why I wrote the plugin initially was because we had a
new SpectrumScale file server with NFS4+Krb5. This system uses
user names of the form DOMAIN\uname. According to IBM that is
by design so that it can support multiple domains as in an AD forest.

Now, our linux clients and servers too only know users by their
uname. We could not get clients to work in this mixed setup with
neither nsswitch.so nor sss.so. I just tried again and even fiddling
with No-Strip and Reformat-Group, all I get is nobody:nobody
(on an up-to-date RHEL7).

Another goal was to have raw control over the parsing and
generation of the names, even if only for debugging purposes.
If you think about why the Reformat-Group option was added
according to the man page, this is exactly something a
sysadmin could fix quickly with this plugin while waiting
until the devs figure out how to permanently fix it.

True, in an AD forest the name->id mapping would work, but the
id->name mapping fails because there is no uname->realm
mapping available. The sss plugin can do this correctly I guess,
but then again nsswitch.so cannot either if I read the source right.

2020-04-14 16:12:50

by Steve Dickson

[permalink] [raw]
Subject: Re: [PATCH] Add regex plugin for nfsidmap



On 3/31/20 5:02 AM, Stefan Walter wrote:
> 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.
Committed... (tag: nfs-utils-2-4-4-rc3)

steved.

>
> ---
> 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
> +^([^@]+)@[email protected]$|^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]
>
> [email protected] = johnny
>
> +[Regex]
> +
> +User-Regex = ^DOMAIN\\([^@]+)@DOMAIN.ORG$
> +Group-Regex = ^([^@]+)@[email protected]$|^DOMAIN\\([^@]+)@DOMAIN.ORG$
> +Prepend-Before-User = DOMAIN\
> +Append-After-User = @DOMAIN.ORG
> +Append-After-Group = @[email protected]
> +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 <[email protected]>.
> + * Copyright (c) 2008 David Härdeman <[email protected]>.
> + * 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);
> +}
> +
>