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 > +^([^@]+)@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 = ∅ > + } > + 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 = ∅ > + } > + } > + } > + > + 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 = ∅ > + } > + group_name_prefix_length = strlen(group_name_prefix); > + > + user_prefix = CONFIG_GET_STRING("Regex", "Prepend-Before-User"); > + if (!user_prefix) > + { > + user_prefix = ∅ > + } > + > + user_suffix = CONFIG_GET_STRING("Regex", "Append-After-User"); > + if (!user_suffix) > + { > + user_suffix = ∅ > + } > + > + group_prefix = CONFIG_GET_STRING("Regex", "Prepend-Before-Group"); > + if (!group_prefix) > + { > + group_prefix = ∅ > + } > + > + group_suffix = CONFIG_GET_STRING("Regex", "Append-After-Group"); > + if (!group_suffix) > + { > + group_suffix = ∅ > + } > + > + 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 (®ex_trans); > +} > + >