diff options
author | Justin Mitchell <jumitche@redhat.com> | 2017-09-28 14:22:17 -0400 |
---|---|---|
committer | Steve Dickson <steved@redhat.com> | 2017-10-26 08:50:08 -0400 |
commit | 1ea6d9231f839b968adb44eaf98b363f436cb1d5 (patch) | |
tree | b6bda545f3066b4b3f86ab10deef3fc9821f4c80 /support/nfsidmap | |
parent | 745287cca2fad6dfe63290bb8053ffe86637c18b (diff) | |
download | nfs-utils-1ea6d9231f839b968adb44eaf98b363f436cb1d5.tar.gz |
nfs-utils: Import libnfsidmap codebase
Merge the libnfsidmap code tree into nfs-utils.
These are the original unmodified files, cherry picked only the
necessary files that dont duplicate code, so will not build
correctly at this stage.
Signed-off-by: Justin Mitchell <jumitche@redhat.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
Diffstat (limited to 'support/nfsidmap')
-rw-r--r-- | support/nfsidmap/AUTHORS | 1 | ||||
-rw-r--r-- | support/nfsidmap/COPYING | 30 | ||||
-rw-r--r-- | support/nfsidmap/Makefile.am | 62 | ||||
-rw-r--r-- | support/nfsidmap/README | 126 | ||||
-rw-r--r-- | support/nfsidmap/gums.c | 788 | ||||
-rw-r--r-- | support/nfsidmap/idmapd.conf | 137 | ||||
-rw-r--r-- | support/nfsidmap/idmapd.conf.5 | 308 | ||||
-rw-r--r-- | support/nfsidmap/libnfsidmap.c | 709 | ||||
-rw-r--r-- | support/nfsidmap/libnfsidmap.pc.in | 11 | ||||
-rw-r--r-- | support/nfsidmap/libtest.c | 160 | ||||
-rw-r--r-- | support/nfsidmap/nfs4_uid_to_name.3 | 174 | ||||
-rw-r--r-- | support/nfsidmap/nfsidmap.h | 67 | ||||
-rw-r--r-- | support/nfsidmap/nfsidmap_internal.h | 72 | ||||
-rw-r--r-- | support/nfsidmap/nss.c | 468 | ||||
-rw-r--r-- | support/nfsidmap/static.c | 412 | ||||
-rw-r--r-- | support/nfsidmap/umich_ldap.c | 1302 |
16 files changed, 4827 insertions, 0 deletions
diff --git a/support/nfsidmap/AUTHORS b/support/nfsidmap/AUTHORS new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/support/nfsidmap/AUTHORS @@ -0,0 +1 @@ +J. Bruce Fields <bfields@citi.umich.edu> diff --git a/support/nfsidmap/COPYING b/support/nfsidmap/COPYING new file mode 100644 index 0000000..7571bb7 --- /dev/null +++ b/support/nfsidmap/COPYING @@ -0,0 +1,30 @@ +Copyright (c) 2004 The Regents of the University of Michigan. +All rights reserved. + +Marius Aamodt Eriksen <marius@umich.edu> +J. Bruce Fields <bfields@umich.edu> + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the University nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/support/nfsidmap/Makefile.am b/support/nfsidmap/Makefile.am new file mode 100644 index 0000000..85f19c8 --- /dev/null +++ b/support/nfsidmap/Makefile.am @@ -0,0 +1,62 @@ +ACLOCAL_AMFLAGS = -I m4 + +if ENABLE_LDAP +UMICH_LDAP_LIB = umich_ldap.la +else +UMICH_LDAP_LIB = +endif +if ENABLE_GUMS +GUMS_MAPPING_LIB = gums.la +else +GUMS_MAPPING_LIB = +endif +lib_LTLIBRARIES = libnfsidmap.la +pkglib_LTLIBRARIES = nsswitch.la static.la $(UMICH_LDAP_LIB) $(GUMS_MAPPING_LIB) + +# Library versioning notes from: +# http://sources.redhat.com/autobook/autobook/autobook_91.html +# +# -version-info <current>:<revision>:<age> +# <current> The number of the current interface exported by library. +# <revision> The implementation number of the most recent interface +# exported by the library. (i.e. revision should be updated +# with each new release of the library, and reset to zero +# when <current> is updated.) +# <age> The number of previous additional interfaces supported +# by this library. + +libnfsidmap_la_SOURCES = libnfsidmap.c cfg.c strlcpy.c cfg.h nfsidmap_internal.h queue.h +libnfsidmap_la_LDFLAGS = -version-info 3:0:3 +libnfsidmap_la_LIBADD = -ldl + +nsswitch_la_SOURCES = nss.c +nsswitch_la_LDFLAGS = -module -avoid-version + +static_la_SOURCES = static.c +static_la_LDFLAGS = -module -avoid-version + +umich_ldap_la_SOURCES = umich_ldap.c +umich_ldap_la_LDFLAGS = -module -avoid-version +umich_ldap_la_LIBADD = -lldap + +gums_la_SOURCES = gums.c +gums_la_LDFLAGS = -module -avoid-version + +man3_MANS = nfs4_uid_to_name.3 +man5_MANS = idmapd.conf.5 +include_HEADERS = nfsidmap.h + +EXTRA_DIST = $(man3_MANS) \ + $(man5_MANS) \ + libtest.c \ + idmapd.conf + +# XXX: also exclude debian/files and debian/files.new ? do a clean?? +dist-hook: + mkdir $(distdir)/debian/ + find $(srcdir)/debian -maxdepth 1 -not -type d |xargs -i cp {} $(distdir)/debian/ + +pkgconfigdir=$(libdir)/pkgconfig +pkgconfig_DATA = libnfsidmap.pc + +$(pkgconfig_DATA): $(top_builddir)/config.status diff --git a/support/nfsidmap/README b/support/nfsidmap/README new file mode 100644 index 0000000..5a448ef --- /dev/null +++ b/support/nfsidmap/README @@ -0,0 +1,126 @@ +Library to help mapping id's, mainly for NFSv4. + +When NFSv4 is using AUTH_GSS (which currently only supports Kerberos v5), the +NFSv4 server mapping functions MUST use secure communications. + +We provide several mapping functions, configured using /etc/idmapd.conf + +As of the 0.21 version of this library, mapping methods are separate +dynamically-loaded libaries. This allows the separation of any +LDAP requirements from the main libnfsidmap library. The main library +now basically loads and calls the functions in the method-specific +libaries. The method libraries are expected to be named +"libnfsidmap_<method>.so", for example, "libnfsidmap_nsswitch.so". + +Several methods may be specified in the /etc/idmapd.conf configuration +file. Each method is called until a mapping is found. + +The following translation methods are delivered in the default distribution: + +nsswitch +-------- + +The default method is called nsswitch. This method uses the get password +file entry functions getpwname(), getpwid(), and the get group file entry +functions getgrnam(), getgrgid(). The nsswitch method can therefore be +configured by the /etc/nss_switch.conf passwd data base stanza. If secure +communications are required (AUTH_GSS), the passwd data base stanza can contain +the 'file' entry because the rpc.idmapd and rpc.svcgssd run as root, and/or the +'ldap' entry if the ldap service is configured to use SASL in /etc/ldap.conf. +The 'nis' entry is NOT recommended, it does not have a secure communications +mode. + + +static +------ + +This method works only for translating GSS authenticated names to local +names. It uses a static mapping setup defined in the [Static] section +of the idmapd.conf file. The form of the entries are: + <GSS-Authenticated name> = <localuser> + +For example: + nfs/host.domain.org@DOMAIN.ORG = root + +It is recommended that this module be used in combination with another +module (e.g. the nsswitch module). + +umich_ldap +---------- +An experimental method, umich_ldap uses an LDAP schema and ldap functions +to perform translations. This method is designed to service remote users, +allowing remote users to set and get ACLs as well as map GSS principals +to id's. The functions are LDAP based, and the ldap search filters look +for attribute names set by idmapd.conf [UMICH_SCHEMA] +NFSv4_name_attr, NFSv4_group_attr, and GSS_principal_attr. + +It is assumed that the LDAP server will index these attributes, and that these +attributes will be associated with the nss.schema posixAccount uidNumber and +gidNumber. We expect that the uidNumber and gidNumber attribute will be +configurable via the idmapd.conf file soon. + +NFSv4_name_attr holds an NFSv4 name of the form user@domain, where the domain +portion of the name is a valid NFSv4 domain name. There is a one-to-one +mapping between the NFSv4_name_attr name and a UID. + +NFSv4_group_attr holds an NFSv4 name of the form group@domain, where the domain +portion of the name is a valid NFSv4 domain name. There is a one-to-one +mapping between the NFSv4_group_attr name and a GID. + +GSS_principal_attr holds a GSS security mechanism specific context principal +name. For Kerberos v5, it is a Kerberos principal <service/>principal@REALM. +For SPKM3, it is a PKI DN such as (line is split):` +"/C=US/ST=Michigan/O=University of Michigan/OU=UMICH Kerberos + Certification Authority/CN=andros/USERID=andros/Email=andros@UMICH.EDU". +There is a many-to-one relationship between the GSS_principal_attr +name and a UID plus GID. + +We have defined LDAP object classes for our experimental NFSv4 id mapping. +We made the attribute names configurable so that other sites could still use +the TR_UMICH_LDAP translation functions with different LDAP attribute names. + +We use the same attribute name, NFSv4Name for the NFSv4_name_attr and the +NFSv4_group_attr. For local users and remote users that we wish to give +a local machine account, we add the NFSv4Name attribute and the GSSAuthName +attribute to the existing inetorgPerson and posixAccount schema. +For remote users that we do not wish to give a local machine account, +we use the NFSv4RemotePerson object to contain the NFSv4Name, uidNumber, +gidNumber, and GSSAuthName. + +nfsv4.schema +------------ +attributetype ( 1.3.6.1.4.1.250.1.61 + NAME ( 'NFSv4Name') + DESC 'NFS version 4 Name' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 + SINGLE-VALUE) + +attributetype ( 1.3.6.1.4.1.250.1.62 + NAME ( 'GSSAuthName') + DESC 'RPCSEC GSS authenticated user name' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) + +# +# minimal information for NFSv4 access. used when local filesystem +# access is not permitted (nsswitch ldap calls fail), or when +# inetorgPerson is too much info. +# +objectclass ( 1.3.6.1.4.1.250.1.60 NAME 'NFSv4RemotePerson' + DESC 'NFS version4 person from remote NFSv4 Domain' + SUP top STRUCTURAL + MUST ( uidNumber $ gidNumber $ NFSv4Name ) + MAY ( cn $ GSSAuthName $ description) ) + +# +# minimal information for NFSv4 access. used when local filesystem +# access is not permitted (nsswitch ldap calls fail), or when +# inetorgPerson is too much info. +# +objectclass ( 1.3.6.1.4.1.250.1.63 NAME 'NFSv4RemoteGroup' + DESC 'NFS version4 group from remote NFSv4 Domain' + SUP top STRUCTURAL + MUST ( gidNumber $ NFSv4Name ) + MAY ( cn $ memberUid $ description) ) + diff --git a/support/nfsidmap/gums.c b/support/nfsidmap/gums.c new file mode 100644 index 0000000..2b12d95 --- /dev/null +++ b/support/nfsidmap/gums.c @@ -0,0 +1,788 @@ +/* + * gums.c + * + * Copyright (c) 2004 The Regents of the University of Michigan. + * All rights reserved. + * + * Olga Kornievskaia <aglo@umich.edu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> +#include <err.h> +#include <syslog.h> +#include "nfsidmap.h" +#include "nfsidmap_internal.h" +#include "cfg.h" + +#include <voms_apic.h> + +#include <prima_logger.h> +#include <prima_soap_client.h> +#include <prima_saml_support.h> + +#define DEFAULT_PRIMA_CONF_LOCATION "/etc/grid-security/prima-authz.conf" +#define DEFAULT_VOMSDIR "/etc/grid-security/vomsdir" +#define DEFAULT_CADIR "/etc/grid-security/certificates" +#define X509_DN_SIZE 1024 + +//#define DEBUG_PRINT_VOMS + +#define USING_TEST_PROGRAM +#ifdef USING_TEST_PROGRAM +nfs4_idmap_log_function_t idmap_log_func = printf; +int idmap_verbosity = 10; +#endif + +/* + * GUMS Translation Methods + * + */ + +/* global variables. voms/gums configuration attributes*/ +static char prima_conf[] = DEFAULT_PRIMA_CONF_LOCATION; +typedef struct _plugin_config_params { + char *saml_schema_dir; + int saml_log_level; + char *server_cert; + char *server_key; + char *ca_dir; + char *gums_server_location; + char *voms_dir; +} plugin_config_params; +plugin_config_params conf; + +#ifdef VOMS_BUG +static void my_VOMS_Delete(struct voms *v) +{ + int i; + + if (!v) return; + if (v->user) + free(v->user); + if (v->server) + free(v->server); + if (v->fqan) { + for (i = 0; v->fqan[i] != NULL; i++) + free(v->fqan[i]); + free(v->fqan); + } + free(v); +} + +static struct voms *my_VOMS_Copy(struct voms *v, int *err) +{ + struct voms *cv; + int i; + + cv = calloc(1, sizeof(struct voms)); + if (cv == NULL) + goto out; + cv->user = strdup(v->user); + if (cv->user == NULL) + goto out; + cv->server = strdup(v->server); + if (cv->server == NULL) + goto out; + for (i = 0; v->fqan[i] != NULL; i++) { + if (v->fqan[i] == NULL) + break; + } + cv->fqan = calloc(i+1, sizeof(char *)); + if (cv->fqan == NULL) + goto out; + cv->fqan[i] = NULL; + for (i = 0; v->fqan[i] != NULL; i++) { + cv->fqan[i] = strdup(v->fqan[i]); + if (cv->fqan[i] == NULL) + goto out; + } + return cv; +out: + if (cv) + my_VOMS_Delete(cv); + + return NULL; +} +#endif + + +#ifdef DEBUG_PRINT_VOMS +void printvoms(struct voms *v) +{ + int j; + + printf("SIGLEN: %d\nUSER: %s\n", v->siglen, v->user); + printf("UCA: %s\nSERVER: %s\n", v->userca, v->server); + printf("SCA: %s\nVO: %s\n", v->serverca, v->voname); + printf("URI: %s\nDATE1: %s\n", v->uri, v->date1); + printf("DATE2: %s\n", v->date2); + + switch (v->type) { + case TYPE_NODATA: + printf("NO DATA\n"); + break; + case TYPE_CUSTOM: + printf("%*s\n", v->datalen - 10, v->custom); + break; + case TYPE_STD: + j = 0; + while (v->std[j]) { + printf("GROUP: %s\nROLE: %s\nCAP: %s\n",v->std[j]->group, + v->std[j]->role,v->std[j]->cap); + j++; + } + } +} + +void print(struct vomsdata *d) +{ + struct voms **vo = d->data; + struct voms *v; + int k = 0; + + while(vo[k]) { + v = vo[k++]; + printf("%d *******************************************\n",k); + printvoms(v); + } + + if (d->workvo) + printf("WORKVO: %s\n", d->workvo); + + if (d->extra_data) + printf("EXTRA: %s\n", d->extra_data); +} +#endif + +static void free_plugin_config_params() +{ + if (conf.saml_schema_dir) + free(conf.saml_schema_dir); + conf.saml_schema_dir = NULL; + if (conf.server_cert) + free(conf.server_cert); + conf.server_cert = NULL; + if (conf.server_key) + free(conf.server_key); + conf.server_key = NULL; + if (conf.ca_dir) + free(conf.ca_dir); + conf.ca_dir = NULL; + if (conf.voms_dir) + free(conf.voms_dir); + conf.voms_dir = NULL; +} + +static int validate_plugin_config_params() +{ + if (conf.saml_schema_dir == NULL || + conf.server_cert == NULL || + conf.server_key == NULL || + conf.gums_server_location == NULL) + return -1; + + if (conf.ca_dir == NULL) { + conf.ca_dir = strdup(DEFAULT_CADIR); + if (conf.ca_dir == NULL) + return -1; + } + if (conf.voms_dir == NULL) { + conf.voms_dir = strdup(DEFAULT_VOMSDIR); + if (conf.voms_dir == NULL) + return -1; + } + return 0; +} + +static int gums_init(void) +{ + FILE *f = NULL; + int ret = -1, i = 0; + char buf[512], type[128], value[256]; + char *alt_conf = NULL; + + alt_conf = conf_get_str("GUMS", "Conf_File"); + if (alt_conf == NULL) + f = fopen(prima_conf, "r"); + else + f = fopen(alt_conf, "r"); + if (f == NULL) + goto out; + + while (fgets(buf, 512, f)) { + i = 0; + while(buf[i] == ' ' || buf[i] == '\t') + i++; + if (buf[i] == '#' || buf[i] == '\0' || buf[i] == '\n') + continue; + if (sscanf(&buf[i], "%127s%255s",type,value) < 2) { + IDMAP_LOG(0, ("ERROR: malformed line: %s\n", &buf[i])); + goto out; + } + IDMAP_LOG(1, ("PRIMA conf: type=%s value=%s\n", type, value)); + if (strncmp(type, "imsContact", 10) == 0) { + conf.gums_server_location = strdup(value); + } else if (strncmp(type, "serviceCert", 11) == 0) { + conf.server_cert = strdup(value); + } else if (strncmp(type, "serviceKey", 10) == 0) { + conf.server_key = strdup(value); + } else if (strncmp(type, "caCertDir", 9) == 0) { + conf.ca_dir = strdup(value); + } else if (strncmp(type, "samlSchemaDir", 13) == 0) { + conf.saml_schema_dir = strdup(value); + } else if (strncmp(type, "logLevel", 8) == 0) { + if (strncmp(value, "debug", 5) == 0) + conf.saml_log_level = PRIMA_LOG_DEBUG; + else if (strncmp(value, "error", 5) == 0) + conf.saml_log_level = PRIMA_LOG_ERROR; + else if (strncmp(value, "none", 4) == 0) + conf.saml_log_level = PRIMA_LOG_NONE; + else + conf.saml_log_level = PRIMA_LOG_INFO; + } + } + + if (validate_plugin_config_params() != 0) + goto out; + + ret = 0; +out: + if (f) + fclose(f); + if (ret) + free_plugin_config_params(); + + return ret; +} + +static int retrieve_attributes(X509 *cert, STACK_OF(X509) *cas, + struct voms **attrs) +{ + int ret = -1, err = 0; + struct vomsdata *vd = NULL; + + vd = VOMS_Init(conf.voms_dir, conf.ca_dir); + if (vd == NULL) { + IDMAP_LOG(0, ("VOMS_Init failed\n")); + return -1; + } + ret = VOMS_Retrieve(cert, cas, RECURSE_CHAIN, vd, &err); + if (err) { + char *err_msg; + err_msg = VOMS_ErrorMessage(vd, err, NULL, 0); + if (err == VERR_NOEXT) + ret = 0; + else + IDMAP_LOG(0, ("VOMS error %s\n", err_msg)); + goto out; + } else if (ret) { + struct voms *v, *v2; +#ifdef DEBUG_PRINT_VOMS + print(vd); +#endif + v = VOMS_DefaultData(vd, &err); + if (err == VERR_NONE) { +#ifdef DEBUG_PRINT_VOMS + printvoms(v); + while (v->fqan[i] != NULL) + IDMAP_LOG(1, ("user's fqan: %s\n", v->fqan[i++])); +#endif +#ifdef VOMS_BUG + v2 = my_VOMS_Copy(v, &err); +#else + v2 = VOMS_Copy(v, &err); +#endif + if (v2 == NULL) { + IDMAP_LOG(0, ("VOMS_Copy failed err=%d\n", err)); + goto out; + } + *attrs = v2; + } + } + ret = 0; +out: + if (vd) + VOMS_Destroy(vd); + return ret; +} + +static int get_server_dn(unsigned char **server_dn) +{ + BIO *tmp = NULL; + X509 *cert = NULL; + int ret = -1; + char dn[X509_DN_SIZE]; + + tmp = BIO_new(BIO_s_file()); + if (tmp == NULL) + goto out; + + ret = BIO_read_filename(tmp, conf.server_cert); + if (ret == 0) { + ret = errno; + goto out; + } + + cert = (X509 *) PEM_read_bio_X509(tmp, NULL, NULL, NULL); + if (cert == NULL) + goto out; + + X509_NAME_oneline(X509_get_subject_name(cert), dn, sizeof(dn)); + + *server_dn = strdup(dn); + if (*server_dn == NULL) + goto out; + + ret = 0; +out: + if (tmp) + BIO_free(tmp); + if (cert) + X509_free(cert); + + return ret; +} + +static int create_saml_request(char *dn, struct voms *attrs, char **saml_req) +{ + int ret = -1, i; + char *req = NULL; + unsigned char *server_dn = NULL; + prima_saml_fqans fqans; + + IDMAP_LOG(2, ("create_saml_request start\n")); + ret = initPrimaSAMLFQANs(&fqans); + if (ret) { + IDMAP_LOG(0, ("initPrimaSAMLFQANs failed with %d\n", ret)); + goto out; + } + + if (attrs) { + for (i = 0; attrs->fqan[i] != NULL; i++) { + ret = addPrimaSAMLFQAN(&fqans, attrs->server, attrs->fqan[i]); + IDMAP_LOG(1, ("addPrimaSAMLFQAN returned %d\n", ret)); + } + dn = attrs->user; + } else + IDMAP_LOG(1, ("No VOMS attributes present in the cert\n")); + + if (get_server_dn(&server_dn) != 0) + goto out; + req = createSAMLQueryAndRequest(server_dn, dn, &fqans); + if (req == NULL) { + IDMAP_LOG(0, ("createSAMLQueryAndRequest failed to create " + "SAML request\n")); + goto out; + } + IDMAP_LOG(1, ("SAML Request %s\n", req)); + + ret = 0; + *saml_req = req; +out: + cleanupPrimaSAMLFQANs(&fqans); + + if (server_dn) + free(server_dn); + + IDMAP_LOG(2, ("create_saml_request returning %d\n", ret)); + return ret; +} + +static int process_parameters(extra_mapping_params **ex, X509 **user_cert, + STACK_OF(X509) **user_chain) +{ + + int ret = -1, i; + X509 *cert = NULL, *x; + STACK_OF(X509) *chain = NULL; + unsigned char *p; + + if (ex[0]->content_type != X509_CERT) + return -1; + + /* get user's x509 certificate */ + p = ex[0]->content; + cert = d2i_X509(NULL, &p, ex[0]->content_len); + if (cert == NULL) + goto out; + + /* get user's other certificates */ + chain = sk_X509_new_null(); + if (chain == NULL) + goto out; + for (i = 1; ex[i] != NULL; i++) { + if (ex[i]->content_type != X509_CERT) + continue; + p = ex[i]->content; + x = d2i_X509(NULL, &p, ex[i]->content_len); + if (x == NULL) + goto out; + sk_X509_push(chain, x); + } + ret = 0; + + *user_cert = cert; + *user_chain = chain; +out: + if (ret) { + int num; + if (cert) + X509_free(cert); + if (chain) + sk_X509_pop_free(chain, X509_free); + } + + return ret; +} + +struct pwbuf { + struct passwd pwbuf; + char buf[1]; +}; + +static int translate_to_uid(char *local_uid, uid_t *uid, uid_t *gid) +{ + int ret = -1; + struct passwd *pw = NULL; + struct pwbuf *buf = NULL; + size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX); + + buf = malloc(sizeof(*buf) + buflen); + if (buf == NULL) + goto out; + + ret = getpwnam_r(local_uid, &buf->pwbuf, buf->buf, buflen, &pw); + if (pw == NULL) { + IDMAP_LOG(0, ("getpwnam: name %s not found\n", local_uid)); + goto out; + } + *uid = pw->pw_uid; + *gid = pw->pw_gid; + + ret = 0; +out: + if (buf) + free(buf); + return ret; +} + +static int translate_to_gid(char *local_gid, uid_t *gid) +{ + struct group *gr = NULL; + struct group grbuf; + char *buf = NULL; + size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); + int ret = -1; + + do { + buf = malloc(buflen); + if (buf == NULL) + goto out; + + ret = -getgrnam_r(local_gid, &grbuf, buf, buflen, &gr); + if (gr == NULL && !ret) + ret = -ENOENT; + if (ret == -ERANGE) { + buflen *= 2; + free(buf); + } + } while (ret == -ERANGE); + + if (ret) + goto out; + + *gid = gr->gr_gid; + + ret = 0; +out: + if (buf) + free(buf); + return ret; +} + +static int gums_gss_princ_to_ids(char *secname, char *princ, + uid_t *uid, uid_t *gid, + extra_mapping_params **ex) +{ + int ret = -1, size, i; + X509 *cert = NULL; + STACK_OF(X509) *cas = NULL; + char dn[X509_DN_SIZE]; + struct voms *attrs = NULL; + char *saml_req = NULL, *saml_resp = NULL; + int saml_result; + char *local_uid = NULL, *local_gid = NULL, *p; + + /* accept only spkm3 translations */ + if (strcmp(secname, "spkm3")) + return -EINVAL; + + /* must supply either a DN and/or at least 1 binary blob */ + if (princ == NULL && (ex == NULL || (ex && ex[0] == NULL))) + return -EINVAL; + + /* process extra parameters */ + if (process_parameters(ex, &cert, &cas) != 0) + goto out; + + IDMAP_LOG(1, ("Processing name translation of client\n")); + X509_NAME_oneline(X509_get_subject_name(cert), dn, sizeof(dn)); + IDMAP_LOG(1, ("DN=%s\n", dn)); + size = sk_X509_num(cas); + IDMAP_LOG(1, ("Including following CAs (%d)\n", size)); + for (i=0; i < size; i++) { + X509_NAME_oneline(X509_get_subject_name(sk_X509_value(cas, i)), + dn, sizeof(dn)); + IDMAP_LOG(1, ("DN=%s\n", dn)); + } + + /* retrieve VOMS attributes */ + if (retrieve_attributes(cert, cas, &attrs) != 0) + goto out; + if (attrs == NULL) + X509_NAME_oneline(X509_get_subject_name(cert), dn, sizeof(dn)); + + /* initialize SAML library */ + if (initPrimaSAMLSupport(conf.saml_schema_dir, + conf.saml_log_level) != 0) { + IDMAP_LOG(0, ("initPrimaSAMLSupport failed\n")); + goto out; + } + + /* create SAML request */ + if (create_saml_request(dn, attrs, &saml_req) != 0) + goto out; + + /* contact GUMS server */ + saml_resp = queryIdentityMappingService(conf.gums_server_location, + saml_req, conf.server_cert, conf.server_key, + conf.ca_dir); + if (saml_resp != NULL) { + saml_result = processResponse(saml_resp, saml_req, &local_uid, + &local_gid); + IDMAP_LOG(1, ("processResponse returned %d\n", saml_result)); + if (saml_result || local_uid == NULL) { + IDMAP_LOG(0, ("processResponse failed to return " + "local id\n")); + ret = -ENOENT; + goto out; + } + IDMAP_LOG(1, ("GUMS returned uid=%s gid=%s\n", local_uid, + local_gid)); + } + + /* translate account name to uid */ + if (translate_to_uid(local_uid, uid, gid)) + goto out; + if (local_gid) + if (translate_to_gid(local_gid, gid)) + goto out; + + ret = 0; +out: + if (cert) + X509_free(cert); + + if (cas) + sk_X509_pop_free(cas, X509_free); + + if (attrs) +#ifdef VOMS_BUG + my_VOMS_Delete(attrs); +#else + VOMS_Delete(attrs); +#endif + + if (saml_req) + free(saml_req); + + if (saml_resp) + free(saml_resp); + + cleanupPrimaSAMLSupport(); + + return ret; +} + +struct trans_func gums_trans = { + .name = "gums", + .init = gums_init, + .princ_to_ids = gums_gss_princ_to_ids, + .name_to_uid = NULL, + .name_to_gid = NULL, + .uid_to_name = NULL, + .gid_to_name = NULL, + .gss_princ_to_grouplist = NULL, +}; + +struct trans_func *libnfsidmap_plugin_init() +{ + return (&gums_trans); +} + +#ifdef USING_TEST_PROGRAM +static STACK_OF(X509) *load_chain(char *certfile) +{ + STACK_OF(X509_INFO) *sk=NULL; + STACK_OF(X509) *stack=NULL, *ret=NULL; + BIO *in=NULL; + X509_INFO *xi; + int first = 1; + + if (!(stack = sk_X509_new_null())) { + printf("memory allocation failure\n"); + goto end; + } + + if (!(in=BIO_new_file(certfile, "r"))) { + printf("error opening the file, %s\n",certfile); + goto end; + } + + /* This loads from a file, a stack of x509/crl/pkey sets */ + if (!(sk=(STACK_OF(X509_INFO) *)PEM_X509_INFO_read_bio(in,NULL,NULL,NULL))) { + /* if (!(sk=PEM_X509_read_bio(in,NULL,NULL,NULL))) { */ + printf("error reading the file, %s\n",certfile); + goto end; + } + + /* scan over it and pull out the certs */ + while (sk_X509_INFO_num(sk)) { + /* skip first cert */ + if (first) { + xi=sk_X509_INFO_shift(sk); + X509_INFO_free(xi); + first = 0; + continue; + } + xi=sk_X509_INFO_shift(sk); + if (xi->x509 != NULL) { + sk_X509_push(stack,xi->x509); + xi->x509=NULL; + } + X509_INFO_free(xi); + } + if (!sk_X509_num(stack)) { + printf("no certificates in file, %s\n",certfile); + sk_X509_free(stack); + goto end; + } + ret=stack; +end: + BIO_free(in); + sk_X509_INFO_free(sk); + return(ret); +} + +void create_params(X509 *cert, STACK_OF(X509) *cas, + extra_mapping_params ***ret_params) +{ + int len = 0, i, size = 0; + unsigned char *p, *buf = NULL; + extra_mapping_params **params = NULL; + X509 *x; + + if (cas) + size = sk_X509_num(cas); + params = malloc((size+2)*sizeof(extra_mapping_params *)); + params[size+1] = NULL; + + /* 1st element is user's certificate */ + len = i2d_X509(cert, NULL); + p = buf = malloc(len); + i2d_X509(cert, &p); + params[0] = malloc(sizeof(extra_mapping_params)); + params[0]->content_type = X509_CERT; + params[0]->content = buf; + params[0]->content_len = len; + + /* add other certificates to the array */ + for (i = 0; i < size; i++) { + x = sk_X509_value(cas, i); + params[i+1] = malloc(sizeof(extra_mapping_params)); + len = i2d_X509(x, NULL); + p = buf = malloc(len); + i2d_X509(x, &p); + params[i+1]->content_type = X509_CERT; + params[i+1]->content = buf; + params[i+1]->content_len = len; + } + *ret_params = params; +} + +int main(void) +{ + int uid, gid, ret, i; + extra_mapping_params **params = NULL; + BIO *tmp = NULL; + X509 *cert = NULL, *x; + STACK_OF(X509) *cas = NULL; + unsigned char *proxy_file; + + if (gums_init()) + return -1; + proxy_file = getenv("X509_USER_PROXY"); + if (proxy_file == NULL) { + fprintf(stderr, "X509_USER_PROXY is not set\n"); + return -1; + } + tmp = BIO_new(BIO_s_file()); + BIO_read_filename(tmp, proxy_file); + cert = (X509 *) PEM_read_bio_X509(tmp, NULL, NULL, NULL); + cas = load_chain(proxy_file); + create_params(cert, cas, ¶ms); + ret = gums_gss_princ_to_ids("spkm3", NULL, &uid, &gid, params); + fprintf(stderr, "gums_gss_princ_to_ids returns %d uid=%d gid=%d\n", + ret, uid, gid); + + if (tmp) + BIO_free(tmp); + if (cert) + X509_free(cert); + if (cas) + sk_X509_pop_free(cas, X509_free); + + free_plugin_config_params(); + + if (params) { + for (i=0; params[i] != NULL; i++) { + free(params[i]->content); + free(params[i]); + } + free(params); + } + + return 0; +} +#endif diff --git a/support/nfsidmap/idmapd.conf b/support/nfsidmap/idmapd.conf new file mode 100644 index 0000000..f07286c --- /dev/null +++ b/support/nfsidmap/idmapd.conf @@ -0,0 +1,137 @@ +[General] +#Verbosity = 0 +# The following should be set to the local NFSv4 domain name +# The default is the host's DNS domain name. +#Domain = local.domain.edu + +# In multi-domain environments, some NFS servers will append the identity +# management domain to the owner and owner_group in lieu of a true NFSv4 +# domain. This option can facilitate lookups in such environments. If +# set to a value other than "none", the nsswitch plugin will first pass +# the name to the password/group lookup function without stripping the +# domain off. If that mapping fails then the plugin will try again using +# the old method (comparing the domain in the string to the Domain value, +# stripping it if it matches, and passing the resulting short name to the +# lookup function). Valid values are "user", "group", "both", and +# "none". The default is "none". +#No-Strip = none + +# Winbind has a quirk whereby doing a group lookup in UPN format +# (e.g. staff@americas.example.com) will cause the group to be +# displayed prefixed with the full domain in uppercase +# (e.g. AMERICAS.EXAMPLE.COM\staff) instead of in the familiar netbios +# name format (e.g. AMERICAS\staff). Setting this option to true +# causes the name to be reformatted before passing it to the group +# lookup function in order to work around this. This setting is +# ignored unless No-Strip is set to either "both" or "group". +# The default is "false". +#Reformat-Group = false + +# The following is a comma-separated list of Kerberos realm +# names that should be considered to be equivalent to the +# local realm, such that <user>@REALM.A can be assumed to +# be the same user as <user>@REALM.B +# If not specified, the default local realm is the domain name, +# which defaults to the host's DNS domain name, +# translated to upper-case. +# Note that if this value is specified, the local realm name +# must be included in the list! +#Local-Realms = + +[Mapping] + +#Nobody-User = nobody +#Nobody-Group = nobody + +[Translation] + +# Translation Method is an comma-separated, ordered list of +# translation methods that can be used. Distributed methods +# include "nsswitch", "umich_ldap", and "static". Each method +# is a dynamically loadable plugin library. +# New methods may be defined and inserted in the list. +# The default is "nsswitch". +#Method = nsswitch + +# Optional. This is a comma-separated, ordered list of +# translation methods to be used for translating GSS +# authenticated names to ids. +# If this option is omitted, the same methods as those +# specified in "Method" are used. +#GSS-Methods = <alternate method list for translating GSS names> + +#-------------------------------------------------------------------# +# The following are used only for the "static" Translation Method. +#-------------------------------------------------------------------# +[Static] + +# A "static" list of GSS-Authenticated names to +# local user name mappings + +#someuser@REALM = localuser + + +#-------------------------------------------------------------------# +# The following are used only for the "umich_ldap" Translation Method. +#-------------------------------------------------------------------# + +[UMICH_SCHEMA] + +# server information (REQUIRED) +LDAP_server = ldap-server.local.domain.edu + +# the default search base (REQUIRED) +LDAP_base = dc=local,dc=domain,dc=edu + +#-----------------------------------------------------------# +# The remaining options have defaults (as shown) +# and are therefore not required. +#-----------------------------------------------------------# + +# whether or not to perform canonicalization on the +# name given as LDAP_server +#LDAP_canonicalize_name = true + +# absolute search base for (people) accounts +#LDAP_people_base = <LDAP_base> + +# absolute search base for groups +#LDAP_group_base = <LDAP_base> + +# Set to true to enable SSL - anything else is not enabled +#LDAP_use_ssl = false + +# You must specify a CA certificate location if you enable SSL +#LDAP_ca_cert = /etc/ldapca.cert + +# Objectclass mapping information + +# Mapping for the person (account) object class +#NFSv4_person_objectclass = NFSv4RemotePerson + +# Mapping for the nfsv4name attribute the person object +#NFSv4_name_attr = NFSv4Name + +# Mapping for the UID number +#NFSv4_uid_attr = UIDNumber + +# Mapping for the GSSAPI Principal name +#GSS_principal_attr = GSSAuthName + +# Mapping for the account name attribute (usually uid) +# The value for this attribute must match the value of +# the group member attribute - NFSv4_member_attr +#NFSv4_acctname_attr = uid + +# Mapping for the group object class +#NFSv4_group_objectclass = NFSv4RemoteGroup + +# Mapping for the GID attribute +#NFSv4_gid_attr = GIDNumber + +# Mapping for the Group NFSv4 name +#NFSv4_group_attr = NFSv4Name + +# Mapping for the Group member attribute (usually memberUID) +# The value of this attribute must match the value of NFSv4_acctname_attr +#NFSv4_member_attr = memberUID diff --git a/support/nfsidmap/idmapd.conf.5 b/support/nfsidmap/idmapd.conf.5 new file mode 100644 index 0000000..9a6457e --- /dev/null +++ b/support/nfsidmap/idmapd.conf.5 @@ -0,0 +1,308 @@ +.\" +.\" idmapd.conf(5) +.\" +.\" COPYRIGHT (c) 2008 +.\" The Regents of the University of Michigan +.\" ALL RIGHTS RESERVED +.\" +.\" Permission is granted to use, copy, create derivative works +.\" and redistribute this software and such derivative works +.\" for any purpose, so long as the name of The University of +.\" Michigan is not used in any advertising or publicity +.\" pertaining to the use of distribution of this software +.\" without specific, written prior authorization. If the +.\" above copyright notice or any other identification of the +.\" University of Michigan is included in any copy of any +.\" portion of this software, then the disclaimer below must +.\" also be included. +.\" +.\" THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION +.\" FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY +.\" PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF +.\" MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING +.\" WITHOUT LIMITATION THE IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +.\" REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE +.\" FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR +.\" CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING +.\" OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN +.\" IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGES. +.\" +.TH idmapd.conf 5 "19 Nov 2008" +.SH NAME +idmapd.conf \- configuration file for libnfsidmap +.SH SYNOPSIS +Configuration file for libnfsidmap. Used by idmapd and svcgssd to map NFSv4 name to and from ids. +.SH DESCRIPTION +The +.B idmapd.conf +configuration file consists of several sections, initiated by strings of the +form [General] and [Mapping]. Each section may contain lines of the form +.nf + variable = value +.fi +The recognized sections and their recognized variables are as follows: +.\" +.\" ------------------------------------------------------------------- +.\" The [General] section +.\" ------------------------------------------------------------------- +.\" +.SS "[General] section variables" +.nf + + +.fi +.TP +.B Verbosity +Verbosity level of debugging +(Default: 0) +.TP +.B Domain +The local NFSv4 domain name. An NFSv4 domain is a namespace with +a unique username<->UID and groupname<->GID mapping. +(Default: Host's fully-qualified DNS domain name) +.TP +.B No-Strip +In multi-domain environments, some NFS servers will append the identity +management domain to the owner and owner_group in lieu of a true NFSv4 +domain. This option can facilitate lookups in such environments. If +set to a value other than "none", the nsswitch plugin will first pass +the name to the password/group lookup function without stripping the +domain off. If that mapping fails then the plugin will try again using +the old method (comparing the domain in the string to the Domain value, +stripping it if it matches, and passing the resulting short name to the +lookup function). Valid values are "user", "group", "both", and +"none". +(Default: "none") +.TP +.B Reformat-Group +Winbind has a quirk whereby doing a group lookup in UPN format +(e.g. staff@americas.example.com) will cause the group to be +displayed prefixed with the full domain in uppercase +(e.g. AMERICAS.EXAMPLE.COM\\staff) instead of in the familiar netbios +name format (e.g. AMERICAS\\staff). Setting this option to true +causes the name to be reformatted before passing it to the group +lookup function in order to work around this. This setting is +ignored unless No-Strip is set to either "both" or "group". +(Default: "false") +.TP +.B Local-Realms +A comma-separated list of Kerberos realm names that may be considered equivalent to the +local realm name. For example, users juser@ORDER.EDU and juser@MAIL.ORDER.EDU +may be considered to be the same user in the specified +.B Domain. +(Default: the host's default realm name) +.br +.B Note: +If a value is specified here, the default local realm must be included as well. +.\" +.\" ------------------------------------------------------------------- +.\" The [Mapping] section +.\" ------------------------------------------------------------------- +.\" +.SS "[Mapping] section variables" +.nf + +.fi +.TP +.B Nobody-User +Local user name to be used when a mapping cannot be completed. +.TP +.B Nobody-Group +Local group name to be used when a mapping cannot be completed. +.\" +.\" ------------------------------------------------------------------- +.\" The [Translation] section +.\" ------------------------------------------------------------------- +.\" +.SS "[Translation] section variables" +.nf + +.fi +.TP +.B Method +A comma-separated, ordered list of mapping methods (plug-ins) +to use when mapping between NFSv4 names and local IDs. Each +specified method is tried in order until a mapping is found, +or there are no more methods to try. The methods included in +the default distribution include "nsswitch", "umich_ldap", and +"static". +(Default: nsswitch) +.TP +.B GSS-Methods +An optional comma-separated, ordered list of mapping methods (plug-ins) +to use when mapping between GSS Authenticated names and local IDs. +(Default: the same list as specified for +.B Method) +.\" +.\" ------------------------------------------------------------------- +.\" The [Static] section +.\" ------------------------------------------------------------------- +.\" +.SS "[Static] section variables" +.nf + +.fi +The "static" translation method uses a static list of GSS-Authenticated +names to local user names. Entries in the list are of the form: +.nf + principal@REALM = localusername +.fi +.\" +.\" ------------------------------------------------------------------- +.\" The [UMICH_SCHEMA] section +.\" ------------------------------------------------------------------- +.\" +.SS "[UMICH_SCHEMA] section variables" +.nf + +.fi +If the "umich_ldap" translation method is specified, the following +variables within the [UMICH_SCHEMA] section are used. +.TP +.B LDAP_server +LDAP server name or address +(Required if using UMICH_LDAP) +.TP +.B LDAP_base +Absolute LDAP search base. +(Required if using UMICH_LDAP) +.TP +.B LDAP_people_base +Absolute LDAP search base for people accounts. +(Default: The +.B LDAP_base +value) +.TP +.B LDAP_group_base +Absolute LDAP search base for group accounts. +(Default: The +.B LDAP_base +value) +.TP +.B LDAP_canonicalize_name +Whether or not to perform name canonicalization on the +name given as +.B LDAP_server +(Default: "true") +.TP +.B LDAP_use_ssl +Set to "true" to enable SSL communication with the LDAP server. +(Default: "false") +.TP +.B LDAP_ca_cert +Location of a trusted CA certificate used when SSL is enabled +(Required if +.B LDAP_use_ssl +is true) +.TP +.B NFSv4_person_objectclass +The object class name for people accounts in your local LDAP schema +(Default: NFSv4RemotePerson) +.TP +.B NFSv4_name_attr +Your local schema's attribute name to be used for NFSv4 user names +(Default: NFSv4Name) +.TP +.B NFSv4_uid_attr +Your local schema's attribute name to be used for uidNumber +(Default: uidNumber) +.TP +.B GSS_principal_attr +Your local schema's attribute name for GSSAPI Principal names +(Default: GSSAuthName) +.TP +.B NFSv4_acctname_attr +Your local schema's attribute name to be used for account names +(Default: uid) +.TP +.B NFSv4_group_objectclass +The object class name for group accounts in your local LDAP schema +(Default: NFSv4RemoteGroup) +.TP +.B NFSv4_gid_attr +Your local schema's attribute name to be used for gidNumber +(Default: gidNumber) +.TP +.B NFSv4_group_attr +Your local schema's attribute name to be used for NFSv4 group names +(Default: NFSv4Name) +.TP +.B LDAP_use_memberof_for_groups +Some LDAP servers do a better job with indexing where searching +through all the groups searching for the user in the memberuid +list. Others like SunOne directory that search can takes minutes +if there are thousands of groups. So setting +.B LDAP_use_memberof_for_groups +to true in the configuration file will use the memberof lists of +the account and search through only those groups to obtain gids. +(Default: false) +.TP +.B NFSv4_member_attr +If +.B LDAP_use_memberof_for_groups +is true, this is the attribute to be searched for. +(Default: memberUid) +.TP +.B NFSv4_grouplist_filter +An optional search filter for determining group membership. +(No Default) +.TP +.B LDAP_timeout_seconds +Number of seconds before timing out an LDAP request +(Default: 4) +.\" +.\" ------------------------------------------------------------------- +.\" An Example +.\" ------------------------------------------------------------------- +.\" +.SH EXAMPLES +An example +.I /etc/idmapd.conf +file: +.nf + + +[General] + +Verbosity = 0 +Domain = domain.org +Local-Realms = DOMAIN.ORG,MY.DOMAIN.ORG,YOUR.DOMAIN.ORG + +[Mapping] + +Nobody-User = nfsnobody +Nobody-Group = nfsnobody + +[Translation] + +Method = umich_ldap,nsswitch +GSS-Methods = umich_ldap,static + +[Static] + +johndoe@OTHER.DOMAIN.ORG = johnny + +[UMICH_SCHEMA] + +LDAP_server = ldap.domain.org +LDAP_base = dc=org,dc=domain + +.fi +.\" +.\" ------------------------------------------------------------------- +.\" Additional sections +.\" ------------------------------------------------------------------- +.\" +.SH SEE ALSO +.BR idmapd (8) +.BR svcgssd (8) +.\".SH COMPATIBILITY +.\".SH STANDARDS +.\".SH ACKNOWLEDGEMENTS +.\".SH AUTHORS +.\".SH HISTORY +.SH BUGS +Report bugs to <nfsv4@linux-nfs.org> +.\".SH CAVEATS diff --git a/support/nfsidmap/libnfsidmap.c b/support/nfsidmap/libnfsidmap.c new file mode 100644 index 0000000..d484101 --- /dev/null +++ b/support/nfsidmap/libnfsidmap.c @@ -0,0 +1,709 @@ +/* + * libnfsidmap.c + * + * nfs idmapping library, primarily for nfs4 client/server kernel idmapping + * and for userland nfs4 idmapping by acl libraries. + * + * Copyright (c) 2004 The Regents of the University of Michigan. + * All rights reserved. + * + * Marius Aamodt Eriksen <marius@umich.edu> + * J. Bruce Fields <bfields@umich.edu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include <sys/types.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <pwd.h> +#include <grp.h> +#include <netdb.h> +#include <err.h> +#include <syslog.h> +#include <stdarg.h> +#include <dlfcn.h> +#include <ctype.h> +#include <resolv.h> +#include <arpa/nameser.h> +#include <arpa/nameser_compat.h> + +#include "nfsidmap.h" +#include "nfsidmap_internal.h" +#include "cfg.h" + +static char *default_domain; +static struct conf_list *local_realms; +int idmap_verbosity = 0; +int no_strip = 0; +int reformat_group = 0; +static struct mapping_plugin **nfs4_plugins = NULL; +static struct mapping_plugin **gss_plugins = NULL; +uid_t nobody_uid = (uid_t)-1; +gid_t nobody_gid = (gid_t)-1; + +#ifndef PATH_PLUGINS +#define PATH_PLUGINS "/usr/lib/libnfsidmap" +#endif +#define PLUGIN_INIT_FUNC "libnfsidmap_plugin_init" + + +#ifndef PATH_IDMAPDCONF +#define PATH_IDMAPDCONF "/etc/idmapd.conf" +#endif + +#ifndef IDMAPD_DEFAULT_DOMAIN +#define IDMAPD_DEFAULT_DOMAIN "localdomain" +#endif + +#ifndef NFS4DNSTXTREC +#define NFS4DNSTXTREC "_nfsv4idmapdomain" +#endif + + +/* Default logging fuction */ +static void default_logger(const char *fmt, ...) +{ + va_list vp; + + va_start(vp, fmt); + vsyslog(LOG_WARNING, fmt, vp); + va_end(vp); +} +nfs4_idmap_log_function_t idmap_log_func = default_logger; + +static char * toupper_str(char *s) +{ + int i; + for (i=0; i < strlen(s); i++) + s[i] = toupper(s[i]); + return s; +} + +static int id_as_chars(char *name, uid_t *id) +{ + long int value; + + if (name == NULL) + return 0; + value = strtol(name, NULL, 10); + if (value == 0) { + /* zero value ids are valid */ + if (strcmp(name, "0") != 0) + return 0; + } + *id = (int)value; + return 1; +} + +static int dns_txt_query(char *domain, char **nfs4domain) +{ + char *txtname = NFS4DNSTXTREC; + char *msg, *answ, *eom, *mptr; + int len, status = -1; + HEADER *hdr; + + msg = calloc(1, NS_MAXMSG); + if (msg == NULL) + return -1; + + answ = calloc(1, NS_MAXMSG); + if (answ == NULL) { + free(msg); + return -1; + } + + if (res_init() < 0) { + IDMAP_LOG(2, ("libnfsidmap: res_init() failed for %s.%s: %s\n", + txtname, domain, hstrerror(h_errno))); + goto freemem; + } + len = res_querydomain(txtname, domain, C_IN, T_TXT, msg, NS_MAXMSG); + if (len < 0) { + IDMAP_LOG(2, ("libnfsidmap: res_querydomain() failed for %s.%s: %s\n", + txtname, domain, hstrerror(h_errno))); + goto freemem; + } + hdr = (HEADER *)msg; + + /* See if there is an answer */ + if (ntohs(hdr->ancount) < 1) { + IDMAP_LOG(2, ("libnfsidmap: No TXT record for %s.%s\n", + txtname, domain)); + goto freemem; + } + /* find the EndOfMessage */ + eom = msg + len; + + /* skip header */ + mptr = &msg[HFIXEDSZ]; + + /* skip name field in question section */ + mptr += dn_skipname(mptr, eom) + QFIXEDSZ; + + /* read in the question */ + len = dn_expand(msg, eom, mptr, answ, NS_MAXDNAME); + if (len < 0) { /* does this really matter?? */ + IDMAP_LOG(2, ("libnfsidmap: No question section for %s.%s: %s\n", + txtname, domain, hstrerror(h_errno))); + goto freemem; + } + + /* + * Now, dissect the answer section, Note: if there + * are more than one answer only the first + * one will be used. + */ + + /* skip passed the name field */ + mptr += dn_skipname(mptr, eom); + /* skip pass the type class and ttl fields */ + mptr += 2 + 2 + 4; + + /* make sure there is some data */ + GETSHORT(len, mptr); + if (len < 0) { + IDMAP_LOG(2, ("libnfsidmap: No data in answer for %s.%s\n", + txtname, domain)); + goto freemem; + } + /* get the lenght field */ + len = (int)*mptr++; + /* copy the data */ + memcpy(answ, mptr, len); + answ[len] = '\0'; + + *nfs4domain = strdup(answ); + status = 0; + +freemem: + free(msg); + free(answ); + + return (status); +} + +static int domain_from_dns(char **domain) +{ + struct hostent *he; + char hname[64], *c; + + if (gethostname(hname, sizeof(hname)) == -1) + return -1; + if ((he = gethostbyname(hname)) == NULL) + return -1; + if ((c = strchr(he->h_name, '.')) == NULL || *++c == '\0') + return -1; + /* + * Query DNS to see if the _nfsv4idmapdomain TXT record exists + * If so use it... + */ + if (dns_txt_query(c, domain) < 0) + *domain = strdup(c); + + return 0; +} + +static int load_translation_plugin(char *method, struct mapping_plugin *plgn) +{ + void *dl = NULL; + struct trans_func *trans = NULL; + libnfsidmap_plugin_init_t init_func; + char plgname[128]; + int ret = 0; + + snprintf(plgname, sizeof(plgname), "%s/%s.so", PATH_PLUGINS, method); + + dl = dlopen(plgname, RTLD_NOW | RTLD_LOCAL); + if (dl == NULL) { + IDMAP_LOG(1, ("libnfsidmap: Unable to load plugin: %s", + dlerror())); + return -1; + } + init_func = (libnfsidmap_plugin_init_t) dlsym(dl, PLUGIN_INIT_FUNC); + if (init_func == NULL) { + IDMAP_LOG(1, ("libnfsidmap: Unable to get init function: %s", + dlerror())); + dlclose(dl); + return -1; + } + trans = init_func(); + if (trans == NULL) { + IDMAP_LOG(1, ("libnfsidmap: Failed to initialize plugin %s", + PLUGIN_INIT_FUNC, plgname)); + dlclose(dl); + return -1; + } + if (trans->init) { + ret = trans->init(); + if (ret) { + IDMAP_LOG(1, ("libnfsidmap: Failed in %s's init(), " + "returned %d", plgname, ret)); + dlclose(dl); + return -1; + } + } + plgn->dl_handle = dl; + plgn->trans = trans; + IDMAP_LOG(1, ("libnfsidmap: loaded plugin %s for method %s", + plgname, method)); + + return 0; +} + +static void unload_plugins(struct mapping_plugin **plgns) +{ + int i; + for (i = 0; plgns[i] != NULL; i++) { + if (plgns[i]->dl_handle && dlclose(plgns[i]->dl_handle)) + IDMAP_LOG(1, ("libnfsidmap: failed to " + "unload plugin for method = %s", + plgns[i]->trans->name)); + free(plgns[i]); + } + free(plgns); +} + +static int load_plugins(struct conf_list *methods, + struct mapping_plugin ***plugins) +{ + int ret = -1, i = 0; + struct mapping_plugin **plgns; + struct conf_list_node *m; + + plgns = calloc(methods->cnt + 1, sizeof(struct mapping_plugin *)); + if (plgns == NULL) + return -1; + plgns[methods->cnt] = NULL; + for (m = TAILQ_FIRST(&methods->fields), i = 0; m; + m = TAILQ_NEXT(m, link), i++) { + plgns[i] = calloc(1, sizeof(struct mapping_plugin)); + if (plgns[i] == NULL) + goto out; + if (load_translation_plugin(m->field, plgns[i]) == -1) { + IDMAP_LOG(0, ("libnfsidmap: requested translation " + "method, '%s', is not available", + m->field)); + goto out; + } + } + ret = 0; + *plugins = plgns; +out: + if (ret) + unload_plugins(plgns); + return ret; +} +void nfs4_cleanup_name_mapping() +{ + if (nfs4_plugins) + unload_plugins(nfs4_plugins); + if (gss_plugins) + unload_plugins(gss_plugins); + nfs4_plugins = gss_plugins = NULL; +} + +int nfs4_init_name_mapping(char *conffile) +{ + int ret = -ENOENT; + int dflt = 0; + struct conf_list *nfs4_methods, *gss_methods; + char *nobody_user, *nobody_group; + char *nostrip; + char *reformatgroup; + + /* XXX: need to be able to reload configurations... */ + if (nfs4_plugins) /* already succesfully initialized */ + return 0; + if (conffile) + conf_path = conffile; + else + conf_path = PATH_IDMAPDCONF; + conf_init(); + default_domain = conf_get_str("General", "Domain"); + if (default_domain == NULL) { + dflt = 1; + ret = domain_from_dns(&default_domain); + if (ret) { + IDMAP_LOG(1, ("libnfsidmap: Unable to determine " + "the NFSv4 domain; Using '%s' as the NFSv4 domain " + "which means UIDs will be mapped to the 'Nobody-User' " + "user defined in %s", + IDMAPD_DEFAULT_DOMAIN, PATH_IDMAPDCONF)); + default_domain = IDMAPD_DEFAULT_DOMAIN; + } + } + IDMAP_LOG(1, ("libnfsidmap: using%s domain: %s", + (dflt ? " (default)" : ""), default_domain)); + + /* Get list of "local equivalent" realms. Meaning the list of realms + * where john@REALM.A is considered the same user as john@REALM.B + * If not specified, default to upper-case of local domain name */ + local_realms = conf_get_list("General", "Local-Realms"); + if (local_realms == NULL) { + struct conf_list_node *node; + + local_realms = malloc(sizeof *local_realms); + if (local_realms == NULL) + return -ENOMEM; + local_realms->cnt = 0; + TAILQ_INIT(&local_realms->fields); + + node = calloc(1, sizeof *node); + if (node == NULL) + return -ENOMEM; + node->field = strdup(get_default_domain()); + if (node->field == NULL) + return -ENOMEM; + toupper_str(node->field); + + TAILQ_INSERT_TAIL(&local_realms->fields, node, link); + local_realms->cnt++; + } + + if (idmap_verbosity >= 1) { + struct conf_list_node *r; + char *buf = NULL; + int siz=0; + + if (local_realms) { + TAILQ_FOREACH(r, &local_realms->fields, link) { + siz += (strlen(r->field)+4); + } + buf = malloc(siz); + if (buf) { + *buf = 0; + TAILQ_FOREACH(r, &local_realms->fields, link) { + sprintf(buf+strlen(buf), "'%s' ", r->field); + } + IDMAP_LOG(1, ("libnfsidmap: Realms list: %s", buf)); + free(buf); + } + } else + IDMAP_LOG(1, ("libnfsidmap: Realms list: <NULL> ")); + } + + nostrip = conf_get_str_with_def("General", "No-Strip", "none"); + if (strcasecmp(nostrip, "both") == 0) + no_strip = IDTYPE_USER|IDTYPE_GROUP; + else if (strcasecmp(nostrip, "group") == 0) + no_strip = IDTYPE_GROUP; + else if (strcasecmp(nostrip, "user") == 0) + no_strip = IDTYPE_USER; + else + no_strip = 0; + + if (no_strip & IDTYPE_GROUP) { + reformatgroup = conf_get_str_with_def("General", "Reformat-Group", "false"); + if ((strcasecmp(reformatgroup, "true") == 0) || + (strcasecmp(reformatgroup, "on") == 0) || + (strcasecmp(reformatgroup, "yes") == 0)) + reformat_group = 1; + else + reformat_group = 0; + } + + nfs4_methods = conf_get_list("Translation", "Method"); + if (nfs4_methods) { + IDMAP_LOG(1, ("libnfsidmap: processing 'Method' list")); + if (load_plugins(nfs4_methods, &nfs4_plugins) == -1) + return -ENOENT; + } else { + struct conf_list list; + struct conf_list_node node; + + TAILQ_INIT(&list.fields); + list.cnt = 1; + node.field = "nsswitch"; + TAILQ_INSERT_TAIL (&list.fields, &node, link); + + if (load_plugins(&list, &nfs4_plugins) == -1) + return -ENOENT; + } + + gss_methods = conf_get_list("Translation", "GSS-Methods"); + if (gss_methods) { + IDMAP_LOG(1, ("libnfsidmap: processing 'GSS-Methods' list")); + if (load_plugins(gss_methods, &gss_plugins) == -1) + goto out; + } + + nobody_user = conf_get_str("Mapping", "Nobody-User"); + if (nobody_user) { + size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX); + struct passwd *buf; + struct passwd *pw = NULL; + int err; + + buf = malloc(sizeof(*buf) + buflen); + if (buf) { + err = getpwnam_r(nobody_user, buf, ((char *)buf) + sizeof(*buf), buflen, &pw); + if (err == 0 && pw != NULL) + nobody_uid = pw->pw_uid; + else + IDMAP_LOG(1, ("libnfsidmap: Nobody-User (%s) not found: %s", + nobody_user, strerror(errno))); + free(buf); + } else + IDMAP_LOG(0,("libnfsidmap: Nobody-User: no memory : %s", + nobody_user, strerror(errno))); + } + + nobody_group = conf_get_str("Mapping", "Nobody-Group"); + if (nobody_group) { + size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); + struct group *buf; + struct group *gr = NULL; + int err; + + buf = malloc(sizeof(*buf) + buflen); + if (buf) { + err = getgrnam_r(nobody_group, buf, ((char *)buf) + sizeof(*buf), buflen, &gr); + if (err == 0 && gr != NULL) + nobody_gid = gr->gr_gid; + else + IDMAP_LOG(1, ("libnfsidmap: Nobody-Group (%s) not found: %s", + nobody_group, strerror(errno))); + free(buf); + } else + IDMAP_LOG(0,("libnfsidmap: Nobody-Group: no memory : %s", + nobody_group, strerror(errno))); + } + + ret = 0; +out: + if (ret) { + if (nfs4_plugins) + unload_plugins(nfs4_plugins); + if (gss_plugins) + unload_plugins(gss_plugins); + nfs4_plugins = gss_plugins = NULL; + } + + return ret ? -ENOENT: 0; +} + +char * get_default_domain(void) +{ + int ret; + + if (default_domain) + return default_domain; + ret = domain_from_dns(&default_domain); + if (ret) { + IDMAP_LOG(0, ("Unable to determine a default nfsv4 domain; " + " consider specifying one in idmapd.conf")); + default_domain = ""; + } + return default_domain; +} + +struct conf_list *get_local_realms(void) +{ + return local_realms; +} + +int +nfs4_get_default_domain(char *server, char *domain, size_t len) +{ + char *d = get_default_domain(); + + if (strlen(d) + 1 > len) + return -ERANGE; + strcpy(domain, d); + return 0; +} + +/* + * Run through each configured translation method for + * function "funcname". + * If "prefer_gss" is true, then use the gss_plugins list, + * if present. Otherwise, use the default nfs4_plugins list. + * + * If the plugin function returns -ENOENT, then continue + * to the next plugin. + */ +#define RUN_TRANSLATIONS(funcname, prefer_gss, args...) \ + do { \ + int ret, i; \ + struct mapping_plugin **plgns; \ + \ + ret = nfs4_init_name_mapping(NULL); \ + if (ret) \ + return ret; \ + \ + if ((prefer_gss) && gss_plugins) \ + plgns = gss_plugins; \ + else \ + plgns = nfs4_plugins; \ + \ + for (i = 0; plgns[i] != NULL; i++) { \ + if (plgns[i]->trans->funcname == NULL) \ + continue; \ + \ + IDMAP_LOG(4, ("%s: calling %s->%s", __func__, \ + plgns[i]->trans->name, #funcname)); \ + \ + ret = plgns[i]->trans->funcname(args); \ + \ + IDMAP_LOG(4, ("%s: %s->%s returned %d", \ + __func__, plgns[i]->trans->name, \ + #funcname, ret)); \ + \ + if (ret == -ENOENT) \ + continue; \ + \ + break; \ + } \ + IDMAP_LOG(4, ("%s: final return value is %d", \ + __func__, ret)); \ + return ret; \ + } while (0) + +int nfs4_uid_to_name(uid_t uid, char *domain, char *name, size_t len) +{ + RUN_TRANSLATIONS(uid_to_name, 0, uid, domain, name, len); +} + +int nfs4_gid_to_name(gid_t gid, char *domain, char *name, size_t len) +{ + RUN_TRANSLATIONS(gid_to_name, 0, gid, domain, name, len); +} + +int nfs4_uid_to_owner(uid_t uid, char *domain, char *name, size_t len) +{ + if (nfs4_uid_to_name(uid, domain, name, len)) + sprintf(name, "%u", uid); + return 0; +} + +int nfs4_gid_to_group_owner(gid_t gid, char *domain, char *name, size_t len) +{ + if (nfs4_gid_to_name(gid, domain, name, len)) + sprintf(name, "%u", gid); + return 0; +} + +int nfs4_name_to_uid(char *name, uid_t *uid) +{ + RUN_TRANSLATIONS(name_to_uid, 0, name, uid); +} + +int nfs4_name_to_gid(char *name, gid_t *gid) +{ + RUN_TRANSLATIONS(name_to_gid, 0, name, gid); +} + +static int set_id_to_nobody(uid_t *id, uid_t is_uid) +{ + int rc = 0; + const char name[] = "nobody@"; + char nobody[strlen(name) + strlen(get_default_domain()) + 1]; + + /* First try to see whether a Nobody-User/Nobody-Group was + * configured, before we try to do a full lookup for the + * NFS nobody user. */ + if (is_uid && nobody_uid != (uid_t)-1) { + *id = (uid_t)nobody_uid; + return 0; + } else if (!is_uid && nobody_gid != (gid_t)-1) { + *id = (uid_t)nobody_gid; + return 0; + } + + strcpy(nobody, name); + strcat(nobody, get_default_domain()); + + if (is_uid) + rc = nfs4_name_to_uid(nobody, id); + else + rc = nfs4_name_to_gid(nobody, id); + + if (rc) { + *id = -2; + rc = 0; + } + return rc; +} + +int nfs4_owner_to_uid(char *name, uid_t *uid) +{ + int rc = nfs4_name_to_uid(name, uid); + if (rc && id_as_chars(name, uid)) + rc = 0; + else if (rc) + rc = set_id_to_nobody(uid, 1); + return rc; +} + +int nfs4_group_owner_to_gid(char *name, gid_t *gid) +{ + int rc = nfs4_name_to_gid(name, gid); + if (rc && id_as_chars(name, gid)) + rc = 0; + else if (rc) + rc = set_id_to_nobody((uid_t *)gid, 0); + return rc; +} + +int nfs4_gss_princ_to_ids(char *secname, char *princ, uid_t *uid, gid_t *gid) +{ + RUN_TRANSLATIONS(princ_to_ids, 1, secname, princ, uid, gid, NULL); +} + +int nfs4_gss_princ_to_grouplist(char *secname, char *princ, + gid_t *groups, int *ngroups) +{ + RUN_TRANSLATIONS(gss_princ_to_grouplist, 1, secname, princ, + groups, ngroups, NULL); +} + +int nfs4_gss_princ_to_ids_ex(char *secname, char *princ, uid_t *uid, + gid_t *gid, extra_mapping_params **ex) +{ + RUN_TRANSLATIONS(princ_to_ids, 1, secname, princ, uid, gid, ex); +} + +int nfs4_gss_princ_to_grouplist_ex(char *secname, char *princ, gid_t *groups, + int *ngroups, extra_mapping_params **ex) +{ + RUN_TRANSLATIONS(gss_princ_to_grouplist, 1, secname, princ, + groups, ngroups, ex); +} + +void nfs4_set_debug(int dbg_level, void (*logger)(const char *, ...)) +{ + if (logger) + idmap_log_func = logger; + idmap_verbosity = dbg_level; +} + diff --git a/support/nfsidmap/libnfsidmap.pc.in b/support/nfsidmap/libnfsidmap.pc.in new file mode 100644 index 0000000..a11dbec --- /dev/null +++ b/support/nfsidmap/libnfsidmap.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libnfsidmap +Description: Library that handles mapping between names and ids for NFSv4. +Requires: +Version: @PACKAGE_VERSION@ +Libs: -L@libdir@ -lnfsidmap +Cflags: -I@includedir@ diff --git a/support/nfsidmap/libtest.c b/support/nfsidmap/libtest.c new file mode 100644 index 0000000..1c717b8 --- /dev/null +++ b/support/nfsidmap/libtest.c @@ -0,0 +1,160 @@ +/* + * libtest.c + * + * nfs idmapping library, primarily for nfs4 client/server kernel idmapping + * and for userland nfs4 idmapping by acl libraries. + * + * Copyright (c) 2004 The Regents of the University of Michigan. + * All rights reserved. + * + * Andy Adamson <andros@umich.edu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * + * libtest: Test the translation table functions + * Reads /etc/idmapd.conf + * + * To compile: + * gcc -g libtest.c -lnfsidmap -o libtest + * + */ + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <nfsidmap.h> + +#define QUIT_ON_ERROR 1 +#define PATH_IDMAPDCONF "/etc/idmapd.conf" +char *conf_path = PATH_IDMAPDCONF; + +main(int ac, char **av) +{ + char *name, *princ; + int err, uid = 0, gid = 0; + char name_buf[32]; + int gids[1000]; + int i, ngids; + + if (ac != 3) { + printf("Usage: %s <user@nfsv4domain> <k5princ@REALM>\n",av[0]); + return 1; + } + + nfs4_set_debug(3, NULL); + + name = av[1]; + princ = av[2]; + err = nfs4_init_name_mapping(NULL); + if (err) { + printf("nfs4_init_name_mapping: error %d\n", err); + return 1; + } + + err = nfs4_gss_princ_to_ids("krb5", princ, &uid, &gid); + if (err) + printf("nfs4_gss_princ_to_ids: error %d\n", err); + else + printf("nfs4_gss_princ_to_ids: princ %s has uid %d gid %d\n", + princ, uid, gid); +#if QUIT_ON_ERROR + if (err) { + printf("calling it quits!\n"); + return err; + } +#endif + + err = nfs4_name_to_uid(name, &uid); + if (err) + printf("nfs4_name_to_uid: error %d\n", err); + else + printf("nfs4_name_to_uid: name %s has uid %d\n", + name, uid); + +#if QUIT_ON_ERROR + if (err) { + printf("calling it quits!\n"); + return err; + } +#endif + err = nfs4_name_to_gid(name, &gid); + if (err) + printf("nfs4_name_to_gid: error %d\n", err); + else + printf("nfs4_name_to_gid: name %s has gid %d\n", + name, gid); + + ngids = 1000; + err = nfs4_gss_princ_to_grouplist("krb5", princ, gids, &ngids); + if (err){ + printf(" nfs4_gss_princ_to_grouplist: error %d\n", err); + } else { + printf(" nfs4_gss_princ_to_grouplist: princ %s has gids ", + princ); + for (i = 0; i < ngids; i++) printf("%d ", gids[i]); + printf("\n"); + } + +#if QUIT_ON_ERROR + if (err) { + printf("calling it quits!\n"); + return err; + } +#endif + /* uid is set by nfs4_name_to_uid() */ + memset(name_buf, 0, 32); + err = nfs4_uid_to_name(uid, NULL, name_buf, 32); + if (err) + printf("nfs4_uid_to_name: error %d\n", err); + else + printf("nfs4_uid_to_name: uid %d has name %s\n", + uid, name_buf); + +#if QUIT_ON_ERROR + if (err) { + printf("calling it quits!\n"); + return err; + } +#endif + /* gid is set by nfs4_name_to_gid() */ + memset(name_buf, 0, 32); + err = nfs4_gid_to_name(gid, NULL, name_buf, 32); + if (err) + printf("nfs4_gid_to_name: error %d\n", err); + else + printf("nfs4_gid_to_name: gid %d has name %s\n", + gid, name_buf); + +#if QUIT_ON_ERROR + if (err) { + printf("calling it quits!\n"); + return err; + } +#endif + return 0; +} diff --git a/support/nfsidmap/nfs4_uid_to_name.3 b/support/nfsidmap/nfs4_uid_to_name.3 new file mode 100644 index 0000000..8a62d8a --- /dev/null +++ b/support/nfsidmap/nfs4_uid_to_name.3 @@ -0,0 +1,174 @@ +.TH nfs4_uid_to_name 3 2004-08-05 +.SH NAME +nfs4_uid_to_name, nfs4_gid_to_name, nfs4_name_to_uid, nfs4_name_to_gid, +nfs4_init_name_mapping, nfs4_get_default_domain, +nfs4_gss_princ_to_ids, nfs4_gss_princ_to_grouplist, +nfs4_gss_princ_to_ids_ex, +nfs4_gss_princ_to_grouplist_ex, +nfs4_set_debug \- ID mapping routines used for NFSv4 +.SH SYNOPSIS +.B #include <nfs4_idmap.h> +.sp +.BI "int nfs4_init_name_mapping(char *conffile);" +.sp +.BI "int nfs4_get_default_domain(char *server, char *domain, size_t len);" +.sp +.BI "int nfs4_uid_to_name(uid_t uid, char *domain, char *name, size_t len);" +.sp +.BI "int nfs4_uid_to_owner(uid_t uid, char *domain, char *name, size_t len);" +.sp +.BI "int nfs4_gid_to_name(gid_t gid, char *domain, char *name, size_t len);" +.sp +.BI "int nfs4_gid_to_owner(gid_t gid, char *domain, char *name, size_t len);" +.sp +.BI "int nfs4_name_to_uid(char *name, uid_t *uid);" +.sp +.BI "int nfs4_name_to_gid(char *name, gid_t *gid);" +.sp +.BI "int nfs4_owner_to_uid(char *name, uid_t *uid);" +.sp +.BI "int nfs4_owner_to_gid(char *name, gid_t *gid);" +.sp +.BI "int nfs4_gss_princ_to_ids(char *secname, char *princ, uid_t *uid, gid_t *gid);" +.sp +.BI "int nfs4_gss_princ_to_grouplist(char *secname, char *princ, gid_t *groups, int *ngroups);" +.sp +.BI "int nfs4_gss_princ_to_ids_ex(char *secname, char *princ, uid_t *uid, gid_t *gid, extra_mapping_params **ex);" +.sp +.BI "int nfs4_gss_princ_to_grouplist_ex(char *secname, char *princ, gid_t *groups, int *ngroups, extra_mapping_params **ex);" +.sp +.BI "void nfs4_set_debug(int dbg_level, void (*logger)(const char *, ...));" +.sp +.fi +.SH DESCRIPTION +NFSv4 uses names of the form +.IR user@domain . +To write code that helps the kernel map uid's (as +rpc.idmapd +does) or that processes NFSv4 ACLs, you need to be able to convert between +NFSv4 names and local uids and gids. +.PP +The +.B nfs4_uid_to_name() +and +.B nfs4_gid_to_name() +functions, given +.I uid +or +.I gid +and +.I domain +(as a null-terminated string), +write the corresponding nfsv4 name into the buffer provided in +.IR name , +which must be of length at least +.IR len . +.PP +The +.B nfs4_uid_to_owner() +and +.B nfs4_gid_to_group_owner() +functions, given +.I uid +or +.I gid +and +.I domain +(as a null-terminated string), +write the corresponding nfsv4 name into the buffer provided in +.IR name , +which must be of length at least +.IR len . +If there is no valid mapping from +.I uid +or +.I gid +to +.IR name , +then the numerical string representing uid or gid is returned instead. +.PP +The +.B nfs4_name_to_uid() +and +.B nfs4_name_to_gid() +functions, given +.I name +(as a null-terminated string), return the corresponding uid or gid in +the second parameter. +.PP +The +.B nfs4_owner_to_uid() +and +.B nfs4_group_owner_to_gid() +functions, given +.I name +(as a null-terminated string), return the corresponding uid or gid in +the second parameter. +If there is no valid mapping from +.I name +to +.I uid +or +.I gid +the value for the user or group "nobody" will be returned instead. +. PP +The +.B nfs4_init_name_mapping() +function must be called before using any of these functions. It reads +defaults from the configuration file at the provided path, usually +"etc/idmapd.conf". +.PP +The +.I domain +argument to the id-to-name functions is there to provide a hint to the name +mapper in the case where an id might be mapped to names in multiple domains. +In most cases, this argument should just be the name returned in the +.I domain +argument to +.B nfs4_get_default_domain() +which should be called with +.I server +set to NULL. The +.I domain +should be a buffer of length +.IR len . +The constant NFS4_MAX_DOMAIN_LEN may be used to determine a reasonable +value for that length. +.PP +The function +.BR nfs4_get_grouplist() , +given a +.IR name , +fills the provided array +.I groups +with up to +.I *ngroups +group IDs corresponding to which the user +.I name +belongs to, setting +.I *ngroups +to the actual number of such groups. If the user belongs to more than +.I *ngroups +groups, then an error is returned and the actual number of groups is stored in +*ngroups. +.PP +Functions +.BR nfs4_gss_princ_to_ids() , +.BR nfs4_gss_princ_to_grouplist() , +.BR nfs4_gss_princ_to_ids_ex() , +and +.B nfs4_gss_princ_to_grouplist_ex() +are used to convert from a gss principal name (as returned by +.BR gss_display_name() ) +to a uid and gid, or list of gids. +.PP +Finally, +.B nfs4_set_debug() +allows the application to set a debugging level to produce extra +debugging information from within the library. The optional +.I logger +function specifies an alternative logging function to call for +the debug messages rather than the default internal function +within the library. +.SH RETURN VALUE +All functions return 0 or, in the case of error, -ERRNO. diff --git a/support/nfsidmap/nfsidmap.h b/support/nfsidmap/nfsidmap.h new file mode 100644 index 0000000..1063065 --- /dev/null +++ b/support/nfsidmap/nfsidmap.h @@ -0,0 +1,67 @@ +/* + * nfsidmap.h + * + * nfs idmapping library, primarily for nfs4 client/server kernel idmapping + * and for userland nfs4 idmapping by acl libraries. + * + * Copyright (c) 2004 The Regents of the University of Michigan. + * All rights reserved. + * + * J. Bruce Fields <bfields@umich.edu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* XXX arbitrary */ +#define NFS4_MAX_DOMAIN_LEN 512 +typedef enum { + X509_CERT = 1 +} extra_mapping_types; + +typedef struct _extra_mapping_params { + void *content; + int content_type; + int content_len; +} extra_mapping_params; + +typedef void (*nfs4_idmap_log_function_t)(const char *, ...); + +int nfs4_init_name_mapping(char *conffile); +int nfs4_get_default_domain(char *server, char *domain, size_t len); +int nfs4_uid_to_name(uid_t uid, char *domain, char *name, size_t len); +int nfs4_gid_to_name(gid_t gid, char *domain, char *name, size_t len); +int nfs4_uid_to_owner(uid_t uid, char *domain, char *name, size_t len); +int nfs4_gid_to_group_owner(gid_t gid, char *domain, char *name, size_t len); +int nfs4_name_to_uid(char *name, uid_t *uid); +int nfs4_name_to_gid(char *name, gid_t *gid); +int nfs4_owner_to_uid(char *name, uid_t *uid); +int nfs4_owner_to_gid(char *name, gid_t *gid); +int nfs4_group_owner_to_gid(char *name, gid_t *gid); +int nfs4_gss_princ_to_ids(char *secname, char *princ, uid_t *uid, gid_t *gid); +int nfs4_gss_princ_to_grouplist(char *secname, char *princ, gid_t *groups, int *ngroups); +int nfs4_gss_princ_to_ids_ex(char *secname, char *princ, uid_t *uid, gid_t *gid, extra_mapping_params **ex); +int nfs4_gss_princ_to_grouplist_ex(char *secname, char *princ, gid_t *groups, int *ngroups, extra_mapping_params **ex); +void nfs4_set_debug(int dbg_level, nfs4_idmap_log_function_t dbg_logfunc); diff --git a/support/nfsidmap/nfsidmap_internal.h b/support/nfsidmap/nfsidmap_internal.h new file mode 100644 index 0000000..6696f50 --- /dev/null +++ b/support/nfsidmap/nfsidmap_internal.h @@ -0,0 +1,72 @@ +/* + * nfsidmap_internal.h + * + * nfs idmapping library, primarily for nfs4 client/server kernel idmapping + * and for userland nfs4 idmapping by acl libraries. + * + * Copyright (c) 2004 The Regents of the University of Michigan. + * All rights reserved. + * + * Andy Adamson <andros@umich.edu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +char *get_default_domain(void); +struct conf_list *get_local_realms(void); + +typedef struct trans_func * (*libnfsidmap_plugin_init_t)(void); + +struct trans_func { + char *name; + int (*init)(void); + int (*princ_to_ids)(char *secname, char *princ, uid_t *uid, gid_t *gid, + extra_mapping_params **ex); + int (*name_to_uid)(char *name, uid_t *uid); + int (*name_to_gid)(char *name, gid_t *gid); + int (*uid_to_name)(uid_t uid, char *domain, char *name, size_t len); + int (*gid_to_name)(gid_t gid, char *domain, char *name, size_t len); + int (*gss_princ_to_grouplist)(char *secname, char *princ, gid_t *groups, + int *ngroups, extra_mapping_params **ex); +}; + +struct mapping_plugin { + void *dl_handle; + struct trans_func *trans; +}; + +typedef enum { + IDTYPE_USER = 1, + IDTYPE_GROUP = 2 +} idtypes; + +extern int no_strip; +extern int reformat_group; +extern int idmap_verbosity; +extern nfs4_idmap_log_function_t idmap_log_func; +/* Level zero always prints, others print depending on verbosity level */ +#define IDMAP_LOG(LVL, MSG) \ + do { if (LVL <= idmap_verbosity) (*idmap_log_func)MSG; } while (0) diff --git a/support/nfsidmap/nss.c b/support/nfsidmap/nss.c new file mode 100644 index 0000000..82799ce --- /dev/null +++ b/support/nfsidmap/nss.c @@ -0,0 +1,468 @@ +/* + * nss.c + * + * nsswitch idmapping functions. + * + * Copyright (c) 2004 The Regents of the University of Michigan. + * All rights reserved. + * + * J. Bruce Fields <bfields@umich.edu> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> +#include <netdb.h> +#include <err.h> +#include <grp.h> +#include <limits.h> +#include <ctype.h> +#include "nfsidmap.h" +#include "nfsidmap_internal.h" +#include "cfg.h" +#include <syslog.h> + +/* + * NSS Translation Methods + * + * These are all just wrappers around getpwnam and friends; + * we tack on the given domain to the results of getpwnam when looking up a uid, + * and ignore the domain entirely when looking up a name. + */ + +static int write_name(char *dest, char *localname, char *domain, size_t len, + int doappend) +{ + if (doappend || !strchr(localname,'@')) { + if (strlen(localname) + 1 + strlen(domain) + 1 > len) + return -ENOMEM; /* XXX: Is there an -ETOOLONG? */ + strcpy(dest, localname); + strcat(dest, "@"); + strcat(dest, domain); + } else { + if (strlen(localname) + 1 > len) + return -ENOMEM; + strcpy(dest, localname); + } + return 0; +} + +static int nss_uid_to_name(uid_t uid, char *domain, char *name, size_t len) +{ + struct passwd *pw = NULL; + struct passwd pwbuf; + char *buf; + size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX); + int err = -ENOMEM; + + buf = malloc(buflen); + if (!buf) + goto out; + if (domain == NULL) + domain = get_default_domain(); + err = -getpwuid_r(uid, &pwbuf, buf, buflen, &pw); + if (pw == NULL) + err = -ENOENT; + if (err) + goto out_buf; + if (no_strip & IDTYPE_USER) + err = write_name(name, pw->pw_name, domain, len, 0); + else + err = write_name(name, pw->pw_name, domain, len, 1); +out_buf: + free(buf); +out: + return err; +} + +static int nss_gid_to_name(gid_t gid, char *domain, char *name, size_t len) +{ + struct group *gr = NULL; + struct group grbuf; + char *buf; + size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); + int err; + + if (domain == NULL) + domain = get_default_domain(); + + do { + err = -ENOMEM; + buf = malloc(buflen); + if (!buf) + goto out; + err = -getgrgid_r(gid, &grbuf, buf, buflen, &gr); + if (gr == NULL && !err) + err = -ENOENT; + if (err == -ERANGE) { + buflen *= 2; + free(buf); + } + } while (err == -ERANGE); + + if (err) + goto out_buf; + if (no_strip & IDTYPE_GROUP) + err = write_name(name, gr->gr_name, domain, len, 0); + else + err = write_name(name, gr->gr_name, domain, len, 1); +out_buf: + free(buf); +out: + return err; +} + +/* XXX: actually should return error, so can distinguish between + * memory allocation failure and failure to match domain */ +static char *strip_domain(const char *name, const char *domain) +{ + const char *c; + char *l = NULL; + int len; + + if (name == NULL) + goto out; + + c = strrchr(name, '@'); + if (c == NULL && domain != NULL) + goto out; + if (c == NULL && domain == NULL) { + len = strlen(name) + 1; + } else { + if (domain && strcasecmp(c + 1, domain) != 0) + goto out; + len = c - name; + } + + l = malloc(len + 1); + if (l == NULL) + goto out; + memcpy(l, name, len); + l[len] = '\0'; +out: + return l; +} + +struct pwbuf { + struct passwd pwbuf; + char buf[1]; +}; + +static struct passwd *nss_getpwnam(const char *name, const char *domain, + int *err_p, int dostrip) +{ + struct passwd *pw; + struct pwbuf *buf; + size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX); + char *localname; + int err = ENOMEM; + + if (buflen > UINT_MAX) + goto err; + + buf = malloc(sizeof(*buf) + buflen); + if (buf == NULL) + goto err; + + err = EINVAL; + if (dostrip) { + localname = strip_domain(name, domain); + IDMAP_LOG(4, ("nss_getpwnam: name '%s' domain '%s': " + "resulting localname '%s'", name, domain, localname)); + if (localname == NULL) { + IDMAP_LOG(0, ("nss_getpwnam: name '%s' does not map " + "into domain '%s'", name, + domain ? domain : "<not-provided>")); + goto err_free_buf; + } + + err = getpwnam_r(localname, &buf->pwbuf, buf->buf, buflen, &pw); + if (pw == NULL && domain != NULL) + IDMAP_LOG(1, + ("nss_getpwnam: name '%s' not found in domain '%s'", + localname, domain)); + free(localname); + } else { + err = getpwnam_r(name, &buf->pwbuf, buf->buf, buflen, &pw); + if (pw == NULL) + IDMAP_LOG(1, + ("nss_getpwnam: name '%s' not found (domain not stripped)", name)); + } + if (err == 0 && pw != NULL) { + *err_p = 0; + return pw; + } else if (err == 0 && pw == NULL) { + err = ENOENT; + } + +err_free_buf: + free(buf); +err: + *err_p = -err; + return NULL; +} + +static int nss_name_to_uid(char *name, uid_t *uid) +{ + struct passwd *pw = NULL; + char *domain; + int err = -ENOENT; + + domain = get_default_domain(); + if (no_strip & IDTYPE_USER) { + pw = nss_getpwnam(name, domain, &err, 0); + if (pw != NULL) + goto out_uid; + } + pw = nss_getpwnam(name, domain, &err, 1); + if (pw == NULL) + goto out; +out_uid: + *uid = pw->pw_uid; + IDMAP_LOG(4, ("nss_name_to_uid: name '%s' uid %u", name, *uid)); + free(pw); + err = 0; +out: + return err; +} + +static char *reformat_name(const char *name) +{ + const char *domain; + const char *c; + const char *d; + char *l = NULL; + int len; + int dlen = 0; + int i; + + c = strchr(name, '@'); + if (c == NULL) + goto out; + len = c - name; + domain = ++c; + d = strchr(domain, '.'); + if (d == NULL) + goto out; + dlen = d - domain; + l = malloc(dlen + 1 + len + 1); + if (l == NULL) + goto out; + for (i = 0; i < dlen; i++) + l[i] = toupper(domain[i]); + l[dlen] = '\\'; + memcpy(l + dlen + 1, name, len); + l[dlen + 1 + len] = '\0'; +out: + return l; +} + +static int _nss_name_to_gid(char *name, gid_t *gid, int dostrip) +{ + struct group *gr = NULL; + struct group grbuf; + char *buf, *domain; + size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); + int err = -EINVAL; + char *localname = NULL; + char *ref_name = NULL; + + domain = get_default_domain(); + if (dostrip) { + localname = strip_domain(name, domain); + IDMAP_LOG(4, ("nss_name_to_gid: name '%s' domain '%s': " + "resulting localname '%s'", name, domain, localname)); + if (!localname) { + IDMAP_LOG(0, ("nss_name_to_gid: name '%s' does not map " + "into domain '%s'", name, domain)); + goto out; + } + } else if (reformat_group) { + ref_name = reformat_name(name); + if (ref_name == NULL) { + IDMAP_LOG(1, ("nss_name_to_gid: failed to reformat name '%s'", + name)); + err = -ENOENT; + goto out; + } + } + + err = -ENOMEM; + if (buflen > UINT_MAX) + goto out_name; + + do { + buf = malloc(buflen); + if (!buf) + goto out_name; + if (dostrip) + err = -getgrnam_r(localname, &grbuf, buf, buflen, &gr); + else if (reformat_group) + err = -getgrnam_r(ref_name, &grbuf, buf, buflen, &gr); + else + err = -getgrnam_r(name, &grbuf, buf, buflen, &gr); + if (gr == NULL && !err) { + if (dostrip) + IDMAP_LOG(1, ("nss_name_to_gid: name '%s' not found " + "in domain '%s'", localname, domain)); + else if (reformat_group) + IDMAP_LOG(1, ("nss_name_to_gid: name '%s' not found " + "(reformatted)", ref_name)); + else + IDMAP_LOG(1, ("nss_name_to_gid: name '%s' not found " + "(domain not stripped)", name)); + err = -ENOENT; + } + if (err == -ERANGE) { + buflen *= 2; + free(buf); + } + } while (err == -ERANGE); + + if (err) + goto out_buf; + *gid = gr->gr_gid; + IDMAP_LOG(4, ("nss_name_to_gid: name '%s' gid %u", name, *gid)); +out_buf: + free(buf); +out_name: + if (dostrip) + free(localname); + if (reformat_group) + free(ref_name); +out: + return err; +} + +static int nss_name_to_gid(char *name, gid_t *gid) +{ + int err = 0; + + if (no_strip & IDTYPE_GROUP) { + err = _nss_name_to_gid(name, gid, 0); + if (!err) + goto out; + } + err = _nss_name_to_gid(name, gid, 1); +out: + return err; +} + +static int nss_gss_princ_to_ids(char *secname, char *princ, + uid_t *uid, uid_t *gid, + extra_mapping_params **ex) +{ + struct passwd *pw; + int err = 0; + char *princ_realm; + struct conf_list *realms; + struct conf_list_node *r; + int found = 0; + + if (strcmp(secname, "spkm3") == 0) + return -ENOENT; + + if (strcmp(secname, "krb5") != 0) + return -EINVAL; + + /* get princ's realm */ + princ_realm = strstr(princ, "@"); + if (princ_realm == NULL) + return -EINVAL; + princ_realm++; + + /* get list of "local-equivalent" realms and + * check against the principal's realm */ + realms = get_local_realms(); + TAILQ_FOREACH(r, &realms->fields, link) { + if (strcmp(r->field, princ_realm) == 0) { + found = 1; + break; + } + } + if (!found) { + IDMAP_LOG(1, ("nss_gss_princ_to_ids: Local-Realm '%s': NOT FOUND", + princ_realm)); + return -ENOENT; + } + /* XXX: this should call something like getgssauthnam instead? */ + pw = nss_getpwnam(princ, NULL, &err, 1); + if (pw == NULL) { + err = -ENOENT; + goto out; + } + *uid = pw->pw_uid; + *gid = pw->pw_gid; + free(pw); +out: + return err; +} + +int nss_gss_princ_to_grouplist(char *secname, char *princ, + gid_t *groups, int *ngroups, + extra_mapping_params **ex) +{ + struct passwd *pw; + int ret = -EINVAL; + + if (strcmp(secname, "krb5") != 0) + goto out; + /* XXX: not quite right? Need to know default realm? */ + /* XXX: this should call something like getgssauthnam instead? */ + pw = nss_getpwnam(princ, NULL, &ret, 1); + if (pw == NULL) { + ret = -ENOENT; + goto out; + } + if (getgrouplist(pw->pw_name, pw->pw_gid, groups, ngroups) < 0) + ret = -ERANGE; + free(pw); +out: + return ret; +} + + +struct trans_func nss_trans = { + .name = "nsswitch", + .init = NULL, + .princ_to_ids = nss_gss_princ_to_ids, + .name_to_uid = nss_name_to_uid, + .name_to_gid = nss_name_to_gid, + .uid_to_name = nss_uid_to_name, + .gid_to_name = nss_gid_to_name, + .gss_princ_to_grouplist = nss_gss_princ_to_grouplist, +}; + +struct trans_func *libnfsidmap_plugin_init() +{ + return (&nss_trans); +} diff --git a/support/nfsidmap/static.c b/support/nfsidmap/static.c new file mode 100644 index 0000000..9f587af --- /dev/null +++ b/support/nfsidmap/static.c @@ -0,0 +1,412 @@ +/* + * static.c + * + * static idmapping functions for gss principals. + * + * 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 "queue.h" +#include "cfg.h" +#include "nfsidmap.h" +#include "nfsidmap_internal.h" + +/* + * Static Translation Methods + * + * These functions use getpwnam to find uid/gid(s) for gss principals + * which are first mapped to local user names using static mappings + * in idmapd.conf. + */ + +struct pwbuf { + struct passwd pwbuf; + char buf[1]; +}; + +struct grbuf { + struct group grbuf; + char buf[1]; +}; + +struct uid_mapping { + LIST_ENTRY (uid_mapping) link; + uid_t uid; + char * principal; + char * localname; +}; + +struct gid_mapping { + LIST_ENTRY (gid_mapping) link; + gid_t gid; + char * principal; + char * localgroup; +}; + +static __inline__ u_int8_t uid_hash (uid_t uid) +{ + return uid % 256; +} + +static __inline__ u_int8_t gid_hash (gid_t gid) +{ + return gid % 256; +} + +//Hash tables of uid and guids to principals mappings. +//We reuse some queue/hash functions from cfg.c. +LIST_HEAD (uid_mappings, uid_mapping) uid_mappings[256]; +LIST_HEAD (gid_mappings, gid_mapping) gid_mappings[256]; + +static struct passwd *static_getpwnam(const char *name, const char *domain, + int *err_p) +{ + struct passwd *pw; + struct pwbuf *buf; + size_t buflen = sysconf(_SC_GETPW_R_SIZE_MAX); + char *localname; + int err; + + buf = malloc(sizeof(*buf) + buflen); + if (!buf) { + err = ENOMEM; + goto err; + } + + localname = conf_get_str("Static", (char *)name); + if (!localname) { + err = ENOENT; + goto err_free_buf; + } + +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(0, ("static_getpwnam: localname '%s' for '%s' not found", + localname, name)); + + goto err_free_buf; + } + + IDMAP_LOG(4, ("static_getpwnam: name '%s' mapped to '%s'", + name, localname)); + + *err_p = 0; + return pw; + +err_free_buf: + free(buf); +err: + *err_p = err; + return NULL; +} + +static struct group *static_getgrnam(const char *name, const char *domain, + int *err_p) +{ + struct group *gr; + struct grbuf *buf; + size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); + char *localgroup; + int err; + + buf = malloc(sizeof(*buf) + buflen); + if (!buf) { + err = ENOMEM; + goto err; + } + + localgroup = conf_get_str("Static", (char *)name); + if (!localgroup) { + err = ENOENT; + goto err_free_buf; + } + +again: + err = getgrnam_r(localgroup, &buf->grbuf, buf->buf, buflen, &gr); + + if (err == EINTR) + goto again; + + if (!gr) { + if (err == 0) + err = ENOENT; + + IDMAP_LOG(0, ("static_getgrnam: local group '%s' for '%s' not found", + localgroup, name)); + + goto err_free_buf; + } + + IDMAP_LOG(4, ("static_getgrnam: group '%s' mapped to '%s'", + name, localgroup)); + + *err_p = 0; + return gr; + +err_free_buf: + free(buf); +err: + *err_p = err; + return NULL; +} + +static int static_gss_princ_to_ids(char *secname, char *princ, + uid_t *uid, uid_t *gid, + extra_mapping_params **ex) +{ + struct passwd *pw; + int err; + + /* XXX: Is this necessary? */ + if (strcmp(secname, "krb5") != 0 && strcmp(secname, "spkm3") != 0) + return -EINVAL; + + pw = static_getpwnam(princ, NULL, &err); + + if (pw) { + *uid = pw->pw_uid; + *gid = pw->pw_gid; + free(pw); + } + + return -err; +} + +static int static_gss_princ_to_grouplist(char *secname, char *princ, + gid_t *groups, int *ngroups, + extra_mapping_params **ex) +{ + struct passwd *pw; + int err; + + /* XXX: Is this necessary? */ + if (strcmp(secname, "krb5") != 0 && strcmp(secname, "spkm3") != 0) + return -EINVAL; + + pw = static_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 static_name_to_uid(char *name, uid_t *uid) +{ + struct passwd *pw; + int err; + + pw = static_getpwnam(name, NULL, &err); + + if (pw) { + *uid = pw->pw_uid; + free(pw); + } + + return -err; +} + +static int static_name_to_gid(char *name, gid_t *gid) +{ + struct group *gr; + int err; + + gr = static_getgrnam(name, NULL, &err); + + if (gr) { + *gid = gr->gr_gid; + free(gr); + } + + return -err; +} + +static int static_uid_to_name(uid_t uid, char *domain, char *name, size_t len) +{ + struct uid_mapping * um; + + for (um = LIST_FIRST (&uid_mappings[uid_hash (uid)]); um; + um = LIST_NEXT (um, link)) { + if (um->uid == uid) { + strcpy(name, um->principal); + return 0; + } + } + + return -ENOENT; +} + +static int static_gid_to_name(gid_t gid, char *domain, char *name, size_t len) +{ + struct gid_mapping * gm; + + for (gm = LIST_FIRST (&gid_mappings[gid_hash (gid)]); gm; + gm = LIST_NEXT (gm, link)) { + if (gm->gid == gid) { + strcpy(name, gm->principal); + return 0; + } + } + + return -ENOENT; +} + +/* + * We buffer all UID's for which static mappings is defined in advance, so the + * uid_to_name functions will be fast enough. + */ + +static int static_init() { + int err; + struct conf_list * princ_list = NULL; + struct conf_list_node * cln, *next; + struct uid_mapping * unode; + struct gid_mapping * gnode; + struct passwd * pw = NULL; + struct group * gr = NULL; + unsigned int i; + + //init hash_table first + for (i = 0; i < sizeof uid_mappings / sizeof uid_mappings[0]; i++) + LIST_INIT (&uid_mappings[i]); + + //get all principals for which we have mappings + princ_list = conf_get_tag_list("Static"); + + if (!princ_list) { + return -ENOENT; + } + + /* As we can not distinguish between mappings for users and groups, we try to + * resolve all mappings for both cases. + */ + + //resolve uid of localname account for all such principals and cache it + for (cln = TAILQ_FIRST (&princ_list->fields); cln; cln = next) + { + next = TAILQ_NEXT (cln, link); + + pw = static_getpwnam(cln->field, NULL, &err); + if (!pw) { + continue; + } + + unode = calloc (1, sizeof *unode); + if (!unode) + { + warnx("static_init: calloc (1, %lu) failed", + (unsigned long)sizeof *unode); + free(pw); + return -ENOMEM; + } + unode->uid = pw->pw_uid; + unode->principal = strdup(cln->field); + + unode->localname = conf_get_str("Static", cln->field); + if (!unode->localname) { + free(pw); + return -ENOENT; + } + + free(pw); + + LIST_INSERT_HEAD (&uid_mappings[uid_hash(unode->uid)], unode, link); + } + + //resolve gid of localgroup accounts and cache it + for (cln = TAILQ_FIRST (&princ_list->fields); cln; cln = next) + { + next = TAILQ_NEXT (cln, link); + + gr = static_getgrnam(cln->field, NULL, &err); + if (!gr) { + continue; + } + + gnode = calloc (1, sizeof *gnode); + if (!gnode) + { + warnx("static_init: calloc (1, %lu) failed", + (unsigned long)sizeof *gnode); + free(gr); + return -ENOMEM; + } + gnode->gid = gr->gr_gid; + gnode->principal = strdup(cln->field); + + gnode->localgroup = conf_get_str("Static", cln->field); + if (!gnode->localgroup) { + free(gr); + return -ENOENT; + } + + free(gr); + + LIST_INSERT_HEAD (&gid_mappings[gid_hash(gnode->gid)], gnode, link); + } + return 0; +} + + +struct trans_func static_trans = { + .name = "static", + .init = static_init, + .name_to_uid = static_name_to_uid, + .name_to_gid = static_name_to_gid, + .uid_to_name = static_uid_to_name, + .gid_to_name = static_gid_to_name, + .princ_to_ids = static_gss_princ_to_ids, + .gss_princ_to_grouplist = static_gss_princ_to_grouplist, +}; + +struct trans_func *libnfsidmap_plugin_init() +{ + return (&static_trans); +} + diff --git a/support/nfsidmap/umich_ldap.c b/support/nfsidmap/umich_ldap.c new file mode 100644 index 0000000..886fa0c --- /dev/null +++ b/support/nfsidmap/umich_ldap.c @@ -0,0 +1,1302 @@ +/* + * umich_ldap.c + * + * Copyright (c) 2000 The Regents of the University of Michigan. + * All rights reserved. + * + * Copyright (c) 2004 Andy Adamson <andros@UMICH.EDU> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> +#include <pwd.h> +#include <err.h> +/* We are using deprecated functions, get the prototypes... */ +#define LDAP_DEPRECATED 1 +#include <ldap.h> +#include "nfsidmap.h" +#include "nfsidmap_internal.h" +#include "cfg.h" + +/* attribute/objectclass default mappings */ +#define DEFAULT_UMICH_OBJCLASS_REMOTE_PERSON "NFSv4RemotePerson" +#define DEFAULT_UMICH_OBJCLASS_REMOTE_GROUP "NFSv4RemoteGroup" +#define DEFAULT_UMICH_ATTR_NFSNAME "NFSv4Name" +#define DEFAULT_UMICH_ATTR_ACCTNAME "uid" +#define DEFAULT_UMICH_ATTR_UIDNUMBER "uidNumber" +#define DEFAULT_UMICH_ATTR_GROUP_NFSNAME "NFSv4Name" +#define DEFAULT_UMICH_ATTR_GIDNUMBER "gidNumber" +#define DEFAULT_UMICH_ATTR_MEMBERUID "memberUid" +#define DEFAULT_UMICH_ATTR_GSSAUTHNAME "GSSAuthName" +#define DEFAULT_UMICH_ATTR_MEMBEROF "memberof" + +#define DEFAULT_UMICH_SEARCH_TIMEOUT 4 + +/* config section */ +#define LDAP_SECTION "UMICH_SCHEMA" + +#ifndef LDAP_FILT_MAXSIZ +#define LDAP_FILT_MAXSIZ 1024 +#endif + + +/* Local structure definitions */ + +struct ldap_map_names{ + char *NFSv4_person_objcls; + char *NFSv4_nfsname_attr; + char *NFSv4_acctname_attr; + char *NFSv4_uid_attr; + char *NFSv4_group_objcls; + char *NFSv4_group_nfsname_attr; + char *NFSv4_gid_attr; + char *NFSv4_member_attr; + char *NFSv4_member_of_attr; + char *GSS_principal_attr; + char *NFSv4_grouplist_filter; /* Filter for grouplist lookups */ +}; + +struct umich_ldap_info { + char *server; /* server name/address */ + int port; /* server port */ + char *base; /* base DN */ + char *people_tree; /* base DN to start searches for people */ + char *group_tree; /* base DN to start searches for groups */ + char *user_dn; /* optional DN for user account when binding */ + char *passwd; /* Password to use when binding to directory */ + int use_ssl; /* SSL flag */ + char *ca_cert; /* File location of the ca_cert */ + int memberof_for_groups;/* Use 'memberof' attribute when + looking up user groups */ + int ldap_timeout; /* Timeout in seconds for searches + by ldap_search_st */ +}; + +/* GLOBAL data */ + +static struct umich_ldap_info ldap_info = { + .server = NULL, + .port = 0, + .base = NULL, + .people_tree = NULL, + .group_tree = NULL, + .user_dn = NULL, + .passwd = NULL, + .use_ssl = 0, + .ca_cert = NULL, + .memberof_for_groups = 0, + .ldap_timeout = DEFAULT_UMICH_SEARCH_TIMEOUT, +}; + +static struct ldap_map_names ldap_map = { + .NFSv4_person_objcls = NULL, + .NFSv4_nfsname_attr = NULL, + .NFSv4_uid_attr = NULL, + .NFSv4_acctname_attr = NULL, + .NFSv4_group_objcls = NULL, + .NFSv4_group_nfsname_attr = NULL, + .NFSv4_gid_attr = NULL, + .NFSv4_member_attr = NULL, + .NFSv4_member_of_attr = NULL, + .GSS_principal_attr = NULL, + .NFSv4_grouplist_filter = NULL, +}; + +/* Local routines */ + +static int +ldap_init_and_bind(LDAP **pld, + int *sizelimit, + struct umich_ldap_info *linfo) +{ + LDAP *ld; + int lerr; + int err = -1; + int current_version, new_version; + char server_url[1024]; + int debug_level = 65535; + int i; + LDAPAPIInfo apiinfo = {.ldapai_info_version = LDAP_API_INFO_VERSION}; + + snprintf(server_url, sizeof(server_url), "%s://%s:%d", + (linfo->use_ssl && linfo->ca_cert) ? "ldaps" : "ldap", + linfo->server, linfo->port); + + /* + * XXX We really, REALLY only want to initialize once, not for + * each request. Figure out how to do that! + */ + if ((lerr = ldap_initialize(&ld, server_url)) != LDAP_SUCCESS) { + IDMAP_LOG(0, ("ldap_init_and_bind: ldap_initialize() failed " + "to [%s]: %s (%d)", server_url, + ldap_err2string(lerr), lerr)); + goto out; + } + + if ((ldap_set_option(ld, LDAP_OPT_DEBUG_LEVEL, &debug_level) + != LDAP_SUCCESS)) { + IDMAP_LOG(0, ("ldap_init_and_bind: error setting ldap " + "library debugging level")); + goto out; + } + + /* + * Get LDAP API information and compare the protocol version there + * to the protocol version returned directly from get_option. + */ + ldap_get_option(ld, LDAP_OPT_API_INFO, &apiinfo); + if (apiinfo.ldapai_info_version != LDAP_API_INFO_VERSION) { + IDMAP_LOG(0, ("ldap_init_and_bind: APIInfo version mismatch: " + "library %d, header %d", + apiinfo.ldapai_info_version, LDAP_API_INFO_VERSION)); + goto out; + } + ldap_get_option(ld, LDAP_OPT_PROTOCOL_VERSION, ¤t_version); + if (apiinfo.ldapai_protocol_version == LDAP_VERSION3 && + current_version != LDAP_VERSION3) { + new_version = LDAP_VERSION3; + IDMAP_LOG(4, ("ldap_init_and_bind: version mismatch between " + "API information and protocol version. Setting " + "protocol version to %d", new_version)); + ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &new_version); + } + + for (i = 0; apiinfo.ldapai_extensions[i]; i++) { + char *extension = apiinfo.ldapai_extensions[i]; + ldap_memfree (extension); + } + ldap_memfree (apiinfo.ldapai_extensions); + ldap_memfree(apiinfo.ldapai_vendor_name); + + /* Set sizelimit option if requested */ + if (sizelimit) { + ldap_set_option(ld, LDAP_OPT_SIZELIMIT, (void *)sizelimit); + } + + /* Set option to to use SSL/TLS if requested */ + if (linfo->use_ssl && linfo->ca_cert) { + int tls_type = LDAP_OPT_X_TLS_HARD; + + lerr = ldap_set_option(ld, LDAP_OPT_X_TLS, &tls_type); + if (lerr != LDAP_SUCCESS) { + IDMAP_LOG(2, ("ldap_init_and_bind: setting SSL " + "failed : %s (%d)", + ldap_err2string(lerr), lerr)); + goto out; + } + lerr = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, + linfo->ca_cert); + if (lerr != LDAP_SUCCESS) { + IDMAP_LOG(2, ("ldap_init_and_bind: setting CA " + "certificate file failed : %s (%d)", + ldap_err2string(lerr), lerr)); + goto out; + } + } + + /* If we have a DN (and password) attempt an authenticated bind */ + if (linfo->user_dn) { +retry_bind: + lerr = ldap_simple_bind_s(ld, linfo->user_dn, linfo->passwd); + if (lerr) { + char *errmsg; + if (lerr == LDAP_PROTOCOL_ERROR) { + ldap_get_option(ld, LDAP_OPT_PROTOCOL_VERSION, + ¤t_version); + new_version = current_version == LDAP_VERSION2 ? + LDAP_VERSION3 : LDAP_VERSION2; + ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, + &new_version); + IDMAP_LOG(2, ("ldap_init_and_bind: " + "got protocol error while attempting " + "bind with protocol version %d, " + "trying protocol version %d", + current_version, new_version)); + if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg) == LDAP_SUCCESS) + && (errmsg != NULL) && (*errmsg != '\0')) { + IDMAP_LOG(2, ("ldap_init_and_bind: " + "Additional info: %s", errmsg)); + ldap_memfree(errmsg); + } + goto retry_bind; + } + IDMAP_LOG(2, ("ldap_init_and_bind: ldap_simple_bind_s " + "to [%s] as user '%s': %s (%d)", + server_url, linfo->user_dn, + ldap_err2string(lerr), lerr)); + if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg) == LDAP_SUCCESS) + && (errmsg != NULL)&& (*errmsg != '\0')) { + IDMAP_LOG(2, ("ldap_init_and_bind: " + "Additional info: %s", errmsg)); + ldap_memfree(errmsg); + } + goto out; + } + } +#ifdef LDAP_ANONYMOUS_BIND_REQUIRED + else { + lerr = ldap_simple_bind_s(ld, NULL, NULL); + if (lerr) { + char *errmsg; + + IDMAP_LOG(2, ("ldap_init_and_bind: ldap_simple_bind_s " + "to [%s] as anonymous: %s (%d)", server_url, + ldap_err2string(lerr), lerr)); + if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg) == LDAP_SUCCESS) + && (errmsg != NULL) && (*errmsg != '\0')) { + IDMAP_LOG(2, ("ldap_init_and_bind: " + "Additional info: %s", errmsg)); + ldap_memfree(errmsg); + } + goto out; + } + } +#endif + + *pld = ld; + err = 0; +out: + return err; +} + +static int +umich_name_to_ids(char *name, int idtype, uid_t *uid, gid_t *gid, + char *attrtype, struct umich_ldap_info *linfo) +{ + LDAP *ld = NULL; + struct timeval timeout = { + .tv_sec = linfo->ldap_timeout, + }; + LDAPMessage *result = NULL, *entry; + BerElement *ber = NULL; + char **idstr, filter[LDAP_FILT_MAXSIZ], *base; + char *attrs[3]; + char *attr_res; + int count = 0, err, lerr, f_len; + int sizelimit = 1; + + err = -EINVAL; + if (uid == NULL || gid == NULL || name == NULL || + attrtype == NULL || linfo == NULL || linfo->server == NULL || + linfo->people_tree == NULL || linfo->group_tree == NULL) + goto out; + + *uid = -1; + *gid = -1; + + if (idtype == IDTYPE_USER) { + if ((f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s=%s))", + ldap_map.NFSv4_person_objcls, + attrtype, name)) + == LDAP_FILT_MAXSIZ) { + IDMAP_LOG(0, ("ERROR: umich_name_to_ids: filter " + "too long!")); + goto out; + } + base = linfo->people_tree; + } + else if (idtype == IDTYPE_GROUP) { + if ((f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s=%s))", + ldap_map.NFSv4_group_objcls, + attrtype, name)) + == LDAP_FILT_MAXSIZ) { + IDMAP_LOG(0, ("ERROR: umich_name_to_ids: filter " + "too long!")); + goto out; + } + base = linfo->group_tree; + } + else { + IDMAP_LOG(0, ("ERROR: umich_name_to_ids: invalid idtype (%d)", + idtype)); + goto out; + } + + if (ldap_init_and_bind(&ld, &sizelimit, linfo)) + goto out; + + attrs[0] = ldap_map.NFSv4_uid_attr; + attrs[1] = ldap_map.NFSv4_gid_attr; + attrs[2] = NULL; + + err = ldap_search_st(ld, base, LDAP_SCOPE_SUBTREE, + filter, (char **)attrs, + 0, &timeout, &result); + if (err) { + char *errmsg; + + IDMAP_LOG(2, ("umich_name_to_ids: ldap_search_st for " + "base '%s', filter '%s': %s (%d)", + base, filter, ldap_err2string(err), err)); + if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg) == LDAP_SUCCESS) + && (errmsg != NULL) && (*errmsg != '\0')) { + IDMAP_LOG(2, ("umich_name_to_ids: " + "Additional info: %s", errmsg)); + ldap_memfree(errmsg); + } + err = -ENOENT; + goto out_unbind; + } + + err = -ENOENT; + count = ldap_count_entries(ld, result); + if (count != 1) { + goto out_unbind; + } + + if (!(entry = ldap_first_entry(ld, result))) { + lerr = ldap_result2error(ld, result, 0); + IDMAP_LOG(2, ("umich_name_to_ids: ldap_first_entry: " + "%s (%d)", ldap_err2string(lerr), lerr)); + goto out_unbind; + } + + /* + * Attributes come back in no particular order, so we need + * to check each one to see what it is before assigning values. + * XXX There must be a better way than comparing the + * name of each attribute? + */ + for (attr_res = ldap_first_attribute(ld, result, &ber); + attr_res != NULL; + attr_res = ldap_next_attribute(ld, result, ber)) { + + unsigned long tmp_u, tmp_g; + uid_t tmp_uid; + gid_t tmp_gid; + + if ((idstr = ldap_get_values(ld, result, attr_res)) == NULL) { + lerr = ldap_result2error(ld, result, 0); + IDMAP_LOG(2, ("umich_name_to_ids: ldap_get_values: " + "%s (%d)", ldap_err2string(lerr), lerr)); + goto out_memfree; + } + if (strcasecmp(attr_res, ldap_map.NFSv4_uid_attr) == 0) { + tmp_u = strtoul(*idstr, (char **)NULL, 10); + tmp_uid = tmp_u; + if (tmp_uid != tmp_u || + (errno == ERANGE && tmp_u == ULONG_MAX)) { + IDMAP_LOG(0, ("ERROR: umich_name_to_ids: " + "uidNumber too long converting '%s'", + *idstr)); + ldap_memfree(attr_res); + ldap_value_free(idstr); + goto out_memfree; + } + *uid = tmp_uid; + } else if (strcasecmp(attr_res, ldap_map.NFSv4_gid_attr) == 0) { + tmp_g = strtoul(*idstr, (char **)NULL, 10); + tmp_gid = tmp_g; + if (tmp_gid != tmp_g || + (errno == ERANGE && tmp_g == ULONG_MAX)) { + IDMAP_LOG(0, ("ERROR: umich_name_to_ids: " + "gidNumber too long converting '%s'", + *idstr)); + ldap_memfree(attr_res); + ldap_value_free(idstr); + goto out_memfree; + } + *gid = tmp_gid; + } else { + IDMAP_LOG(0, ("umich_name_to_ids: received attr " + "'%s' ???", attr_res)); + ldap_memfree(attr_res); + ldap_value_free(idstr); + goto out_memfree; + } + ldap_memfree(attr_res); + ldap_value_free(idstr); + } + + err = 0; +out_memfree: + ber_free(ber, 0); +out_unbind: + if (result) + ldap_msgfree(result); + ldap_unbind(ld); +out: + return err; +} + +static int +umich_id_to_name(uid_t id, int idtype, char **name, size_t len, + struct umich_ldap_info *linfo) +{ + LDAP *ld = NULL; + struct timeval timeout = { + .tv_sec = linfo->ldap_timeout, + }; + LDAPMessage *result = NULL, *entry; + BerElement *ber; + char **names = NULL, filter[LDAP_FILT_MAXSIZ], *base; + char idstr[16]; + char *attrs[2]; + char *attr_res; + int count = 0, err, lerr, f_len; + int sizelimit = 1; + + err = -EINVAL; + if (name == NULL || linfo == NULL || linfo->server == NULL || + linfo->people_tree == NULL || linfo->group_tree == NULL) + goto out; + + snprintf(idstr, sizeof(idstr), "%d", id); + + + if (idtype == IDTYPE_USER) { + if ((f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s=%s))", + ldap_map.NFSv4_person_objcls, + ldap_map.NFSv4_uid_attr, idstr)) + == LDAP_FILT_MAXSIZ) { + IDMAP_LOG(0, ("ERROR: umich_id_to_name: " + "uid filter too long!")); + goto out; + } + base = linfo->people_tree; + } else if (idtype == IDTYPE_GROUP) { + if ((f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s=%s))", + ldap_map.NFSv4_group_objcls, + ldap_map.NFSv4_gid_attr,idstr)) + == LDAP_FILT_MAXSIZ) { + IDMAP_LOG(0, ("ERROR: umich_id_to_name: " + "gid filter too long!")); + goto out; + } + base = linfo->group_tree; + } else { + IDMAP_LOG(0, ("ERROR: umich_id_to_name: invalid idtype (%d)", + idtype)); + err = -EINVAL; + goto out; + } + + if (ldap_init_and_bind(&ld, &sizelimit, linfo)) + goto out; + + if (idtype == IDTYPE_USER) + attrs[0] = ldap_map.NFSv4_nfsname_attr; + else + attrs[0] = ldap_map.NFSv4_group_nfsname_attr; + attrs[1] = NULL; + + err = ldap_search_st(ld, base, LDAP_SCOPE_SUBTREE, + filter, (char **)attrs, + 0, &timeout, &result); + if (err) { + char * errmsg; + + IDMAP_LOG(2, ("umich_id_to_name: ldap_search_st for " + "base '%s, filter '%s': %s (%d)", base, filter, + ldap_err2string(err), err)); + if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg) == LDAP_SUCCESS) + && (errmsg != NULL) && (*errmsg != '\0')) { + IDMAP_LOG(2, ("umich_id_to_name: " + "Additional info: %s", errmsg)); + ldap_memfree(errmsg); + } + + err = -ENOENT; + goto out_unbind; + } + + err = -ENOENT; + count = ldap_count_entries(ld, result); + if (count != 1) + goto out_unbind; + + if (!(entry = ldap_first_entry(ld, result))) { + lerr = ldap_result2error(ld, result, 0); + IDMAP_LOG(2, ("umich_id_to_name: ldap_first_entry: " + "%s (%d)", ldap_err2string(lerr), lerr)); + goto out_unbind; + } + + if (!(attr_res = ldap_first_attribute(ld, result, &ber))) { + lerr = ldap_result2error(ld, result, 0); + IDMAP_LOG(2, ("umich_id_to_name: ldap_first_attribute: " + "%s (%d)", ldap_err2string(lerr), lerr)); + goto out_unbind; + } + + if ((names = ldap_get_values(ld, result, attr_res)) == NULL) { + lerr = ldap_result2error(ld, result, 0); + IDMAP_LOG(2, ("umich_id_to_name: ldap_get_values: " + "%s (%d)", ldap_err2string(lerr), lerr)); + goto out_memfree; + } + + /* + * Verify there is enough room in the output buffer before + * copying returned string. (strlen doesn't count the null, + * we make sure there is room for the null also, therefore + * we use ">=" not just ">") + */ + if (strlen(names[0]) >= len) { + /* not enough space to return the name */ + IDMAP_LOG(1, ("umich_id_to_name: output buffer size (%d) " + "too small to return string, '%s', of length %d", + len, names[0], strlen(names[0]))); + goto out_memfree; + } + strcpy(*name, names[0]); + + err = 0; +out_memfree: + if (names) + ldap_value_free(names); + ldap_memfree(attr_res); + ber_free(ber, 0); +out_unbind: + if (result) + ldap_msgfree(result); + ldap_unbind(ld); +out: + return err; +} + +static int +umich_gss_princ_to_grouplist(char *principal, gid_t *groups, int *ngroups, + struct umich_ldap_info *linfo) +{ + LDAP *ld = NULL; + struct timeval timeout = { + .tv_sec = linfo->ldap_timeout, + }; + LDAPMessage *result, *entry; + char **names, filter[LDAP_FILT_MAXSIZ]; + char *attrs[2]; + int count = 0, err = -ENOMEM, lerr, f_len; + int i, num_gids; + gid_t *curr_group = groups; + + err = -EINVAL; + if (linfo == NULL || linfo->server == NULL || + linfo->people_tree == NULL || linfo->group_tree == NULL) + goto out; + + + if (ldap_init_and_bind(&ld, NULL, linfo)) + goto out; + + /* + * First we need to map the gss principal name to a uid (name) string + */ + err = -EINVAL; + if ((f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s=%s))", + ldap_map.NFSv4_person_objcls, + ldap_map.GSS_principal_attr, principal)) + == LDAP_FILT_MAXSIZ) { + IDMAP_LOG(0, ("ERROR: umich_gss_princ_to_grouplist: " + "filter too long!")); + goto out; + } + + attrs[0] = ldap_map.NFSv4_acctname_attr; + attrs[1] = NULL; + + err = ldap_search_st(ld, linfo->people_tree, LDAP_SCOPE_SUBTREE, + filter, attrs, 0, &timeout, &result); + if (err) { + char *errmsg; + + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: ldap_search_st " + "for tree '%s, filter '%s': %s (%d)", + linfo->people_tree, filter, + ldap_err2string(err), err)); + if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg) == LDAP_SUCCESS) + && (errmsg != NULL) && (*errmsg != '\0')) { + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: " + "Additional info: %s", errmsg)); + ldap_memfree(errmsg); + } + err = -ENOENT; + goto out_unbind; + } + + err = -ENOENT; + count = ldap_count_entries(ld, result); + if (count != 1) { + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: " + "ldap account lookup of gssauthname %s returned %d accounts", + principal,count)); + goto out_unbind; + } + + if (!(entry = ldap_first_entry(ld, result))) { + lerr = ldap_result2error(ld, result, 0); + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: ldap_first_entry: " + "%s (%d)", ldap_err2string(lerr), lerr)); + goto out_unbind; + } + + if ((names = ldap_get_values(ld, result, attrs[0])) == NULL) { + lerr = ldap_result2error(ld, result, 0); + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: ldap_get_values: " + "%s (%d)", ldap_err2string(lerr), lerr)); + goto out_unbind; + } + + if (ldap_info.memberof_for_groups) { + + /* + * Collect the groups the user belongs to + */ + if ((f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s=%s))", + ldap_map.NFSv4_person_objcls, + ldap_map.NFSv4_acctname_attr, + names[0])) == LDAP_FILT_MAXSIZ ) { + IDMAP_LOG(2, ("ERROR: umich_gss_princ_to_grouplist: " + "filter too long!")); + ldap_value_free(names); + goto out_unbind; + } + + ldap_value_free(names); + + attrs[0] = ldap_map.NFSv4_member_of_attr; + attrs[1] = NULL; + + err = ldap_search_st(ld, linfo->people_tree, LDAP_SCOPE_SUBTREE, + filter, attrs, 0, &timeout, &result); + + if (err) { + char *errmsg; + + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: ldap_search_st " + "for tree '%s, filter '%s': %s (%d)", + linfo->people_tree, filter, + ldap_err2string(err), err)); + if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg) == LDAP_SUCCESS) + && (errmsg != NULL) && (*errmsg != '\0')) { + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: " + "Additional info: %s", errmsg)); + ldap_memfree(errmsg); + } + err = -ENOENT; + goto out_unbind; + } + err = -ENOENT; + + /* pull the list of groups and place into names */ + count = ldap_count_entries(ld, result); + if (count != 1) { + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: " + "ldap group member lookup of gssauthname %s returned %d multiple entries", + principal,count)); + goto out_unbind; + } + + if (!(entry = ldap_first_entry(ld, result))) { + lerr = ldap_result2error(ld, result, 0); + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: ldap_first_entry: " + "%s (%d)", ldap_err2string(lerr), lerr)); + goto out_unbind; + } + + if ((names = ldap_get_values(ld, result, attrs[0])) == NULL) { + lerr = ldap_result2error(ld, result, 0); + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: ldap_get_values: " + "%s (%d)", ldap_err2string(lerr), lerr)); + goto out_unbind; + } + + /* Count the groups first before doing a lookup of the group. + If it exceeds the desired number of groups set the needed value + and abort. */ + for (i = 0; names[i] != NULL; i++); + if ( i > *ngroups ) { + ldap_value_free(names); + err = -EINVAL; + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: User %s, " + "number of groups %d, exceeds requested number %d", + principal, i, *ngroups)); + *ngroups = i; + goto out_unbind; + } + + /* Loop through the groupnames (names) and get the group gid */ + num_gids = 0; + for (i = 0; names[i] != NULL; i++){ + char **vals; + int valcount; + unsigned long tmp_g; + gid_t tmp_gid; + char *cnptr = NULL; + + cnptr = strchr(names[i],','); + if (cnptr) *cnptr = '\0'; + + err = -ENOENT; + if (ldap_map.NFSv4_grouplist_filter) + f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s)%s)", + ldap_map.NFSv4_group_objcls, + names[i], + ldap_map.NFSv4_grouplist_filter); + else + f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s))", + ldap_map.NFSv4_group_objcls, + names[i]); + + if ( f_len == LDAP_FILT_MAXSIZ ) { + IDMAP_LOG(2, ("ERROR: umich_gss_princ_to_grouplist: " + "filter too long!")); + ldap_value_free(names); + goto out_unbind; + } + attrs[0] = ldap_map.NFSv4_gid_attr; + attrs[1] = NULL; + + err = ldap_search_st(ld, linfo->group_tree, LDAP_SCOPE_SUBTREE, + filter, attrs, 0, &timeout, &result); + if (err) { + char *errmsg; + + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: ldap_search_st " + "for tree '%s, filter '%s': %s (%d)", + linfo->group_tree, filter, + ldap_err2string(err), err)); + if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg)==LDAP_SUCCESS) + && + (errmsg != NULL) && (*errmsg != '\0')) { + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: " + "Additional info: %s", errmsg)); + ldap_memfree(errmsg); + } + continue; + } + + count = ldap_count_entries(ld, result); + if (count == 0) + continue; + if (count != 1 ){ + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist:" + "Group %s has %d gids defined - aborting", names[i], count)); + ldap_value_free(names); + err = -ENOENT; + goto out_unbind; + } + + vals = ldap_get_values(ld, result, ldap_map.NFSv4_gid_attr); + + /* There should be only one gidNumber attribute per group */ + if ((valcount = ldap_count_values(vals)) != 1) { + IDMAP_LOG(2, ("DB problem getting gidNumber of " + "posixGroup! (count was %d)", valcount)); + ldap_value_free(vals); + continue; + } + + tmp_g = strtoul(vals[0], (char **)NULL, 10); + tmp_gid = tmp_g; + if (tmp_gid != tmp_g || + (errno == ERANGE && tmp_g == ULONG_MAX)) { + IDMAP_LOG(2, ("ERROR: umich_gss_princ_to_grouplist: " + "gidNumber too long converting '%s'", + vals[0])); + ldap_value_free(vals); + continue; + } + *curr_group++ = tmp_gid; + num_gids++; + ldap_value_free(vals); + } + ldap_value_free(names); + *ngroups = num_gids; + err = 0; + } else { + + /* + * Then determine the groups that uid (name) string is a member of + */ + err = -EINVAL; + if (ldap_map.NFSv4_grouplist_filter) + f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s=%s)%s)", + ldap_map.NFSv4_group_objcls, + ldap_map.NFSv4_member_attr, + names[0], + ldap_map.NFSv4_grouplist_filter); + + else + f_len = snprintf(filter, LDAP_FILT_MAXSIZ, + "(&(objectClass=%s)(%s=%s))", + ldap_map.NFSv4_group_objcls, + ldap_map.NFSv4_member_attr, + names[0]); + + if ( f_len == LDAP_FILT_MAXSIZ ) { + IDMAP_LOG(0, ("ERROR: umich_gss_princ_to_grouplist: " + "filter too long!")); + ldap_value_free(names); + goto out_unbind; + } + + ldap_value_free(names); + + attrs[0] = ldap_map.NFSv4_gid_attr; + attrs[1] = NULL; + + err = ldap_search_st(ld, linfo->group_tree, LDAP_SCOPE_SUBTREE, + filter, attrs, 0, &timeout, &result); + + if (err) { + char *errmsg; + + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: ldap_search_st " + "for tree '%s, filter '%s': %s (%d)", + linfo->group_tree, filter, + ldap_err2string(err), err)); + if ((ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &errmsg) == LDAP_SUCCESS) && + (errmsg != NULL) && (*errmsg != '\0')) { + IDMAP_LOG(2, ("umich_gss_princ_to_grouplist: " + "Additional info: %s", errmsg)); + ldap_memfree(errmsg); + } + err = -ENOENT; + goto out_unbind; + } + + /* + * If we can't determine count, return that error + * If we have nothing to return, return success + * If we have more than they asked for, tell them the + * number required and return an error + */ + count = ldap_count_entries(ld, result); + + if (count < 0) { + err = count; + goto out_unbind; + } + if (count == 0) { + *ngroups = 0; + err = 0; + goto out_unbind; + } + if (count > *ngroups) { + *ngroups = count; + err = -EINVAL; + goto out_unbind; + } + *ngroups = count; + + curr_group = groups; + + err = -ENOENT; + for (entry = ldap_first_entry(ld, result); + entry != NULL; + entry = ldap_next_entry(ld, entry)) { + + char **vals; + int valcount; + unsigned long tmp_g; + gid_t tmp_gid; + + vals = ldap_get_values(ld, entry, ldap_map.NFSv4_gid_attr); + + /* There should be only one gidNumber attribute per group */ + if ((valcount = ldap_count_values(vals)) != 1) { + IDMAP_LOG(0, ("DB problem getting gidNumber of " + "posixGroup! (count was %d)", valcount)); + goto out_unbind; + } + tmp_g = strtoul(vals[0], (char **)NULL, 10); + tmp_gid = tmp_g; + if (tmp_gid != tmp_g || + (errno == ERANGE && tmp_g == ULONG_MAX)) { + IDMAP_LOG(0, ("ERROR: umich_gss_princ_to_grouplist: " + "gidNumber too long converting '%s'", + vals[0])); + ldap_value_free(vals); + goto out_unbind; + } + *curr_group++ = tmp_gid; + ldap_value_free(vals); + } + err = 0; + } + +out_unbind: + ldap_unbind(ld); +out: + return err; +} + + +/* + * principal: krb5 - princ@realm, use KrbName ldap attribute + * spkm3 - X.509 dn, use X509Name ldap attribute + */ +static int +umichldap_gss_princ_to_ids(char *secname, char *principal, + uid_t *uid, gid_t *gid, extra_mapping_params **ex) +{ + uid_t rtnd_uid = -1; + gid_t rtnd_gid = -1; + int err = -EINVAL; + + if ((strcmp(secname, "krb5") != 0) && (strcmp(secname, "spkm3") != 0)) { + IDMAP_LOG(0, ("ERROR: umichldap_gss_princ_to_ids: " + "invalid secname '%s'", secname)); + return err; + } + + err = umich_name_to_ids(principal, IDTYPE_USER, &rtnd_uid, &rtnd_gid, + ldap_map.GSS_principal_attr, &ldap_info); + if (err < 0) + goto out; + + *uid = rtnd_uid; + *gid = rtnd_gid; +out: + return err; +} + +static int +umichldap_name_to_uid(char *name, uid_t *uid) +{ + gid_t gid; + + return umich_name_to_ids(name, IDTYPE_USER, uid, + &gid, ldap_map.NFSv4_nfsname_attr, &ldap_info); +} + +static int +umichldap_name_to_gid(char *name, gid_t *gid) +{ + uid_t uid; + + return umich_name_to_ids(name, IDTYPE_GROUP, &uid, gid, + ldap_map.NFSv4_group_nfsname_attr, &ldap_info); +} + +static int +umichldap_uid_to_name(uid_t uid, char *domain, char *name, size_t len) +{ + return umich_id_to_name(uid, IDTYPE_USER, &name, len, &ldap_info); +} + +static int +umichldap_gid_to_name(gid_t gid, char *domain, char *name, size_t len) +{ + return umich_id_to_name(gid, IDTYPE_GROUP, &name, len, &ldap_info); +} + +static int +umichldap_gss_princ_to_grouplist(char *secname, char *principal, + gid_t *groups, int *ngroups, extra_mapping_params **ex) +{ + int err = -EINVAL; + + if ((strcmp(secname, "krb5") != 0) && (strcmp(secname, "spkm3") != 0)) { + IDMAP_LOG(0, ("ERROR: umichldap_gss_princ_to_grouplist: " + "invalid secname '%s'", secname)); + return err; + } + + return umich_gss_princ_to_grouplist(principal, groups, ngroups, + &ldap_info); +} + +/* + * TLS connections require that the hostname we specify matches + * the hostname in the certificate that the server uses. + * Get a canonical name for the host specified in the config file. + */ +static char * +get_canonical_hostname(const char *inname) +{ + int aierr, error; + struct addrinfo *ap, aihints; + char *return_name = NULL; + char tmphost[NI_MAXHOST]; + + memset(&aihints, 0, sizeof(aihints)); + aihints.ai_socktype = SOCK_STREAM; + aihints.ai_flags = AI_CANONNAME; + aihints.ai_family = PF_INET; + aierr = getaddrinfo(inname, NULL, &aihints, &ap); + if (aierr) { + const char *msg; + /* We want to customize some messages. */ + switch (aierr) { + case EAI_NONAME: + msg = "host unknown"; + break; + default: + msg = gai_strerror(aierr); + break; + } + IDMAP_LOG(1, ("%s: '%s': %s", __FUNCTION__, inname, msg)); + goto out_err; + } + if (ap == 0) { + IDMAP_LOG(1, ("%s: no addresses for host '%s'?", + __FUNCTION__, inname)); + goto out_err; + } + + error = getnameinfo (ap->ai_addr, ap->ai_addrlen, tmphost, + sizeof(tmphost), NULL, 0, 0); + if (error) { + IDMAP_LOG(1, ("%s: getnameinfo for host '%s' failed (%d)", + __FUNCTION__, inname)); + goto out_free; + } + return_name = strdup (tmphost); + +out_free: + freeaddrinfo(ap); +out_err: + return return_name; +} + +static int +umichldap_init(void) +{ + char *tssl, *canonicalize, *memberof; + char missing_msg[128] = ""; + char *server_in, *canon_name; + + server_in = conf_get_str(LDAP_SECTION, "LDAP_server"); + ldap_info.base = conf_get_str(LDAP_SECTION, "LDAP_base"); + ldap_info.people_tree = conf_get_str(LDAP_SECTION, "LDAP_people_base"); + ldap_info.group_tree = conf_get_str(LDAP_SECTION, "LDAP_group_base"); + ldap_info.user_dn = conf_get_str(LDAP_SECTION, "LDAP_user_dn"); + ldap_info.passwd = conf_get_str(LDAP_SECTION, "LDAP_passwd"); + tssl = conf_get_str_with_def(LDAP_SECTION, "LDAP_use_ssl", "false"); + if ((strcasecmp(tssl, "true") == 0) || + (strcasecmp(tssl, "on") == 0) || + (strcasecmp(tssl, "yes") == 0)) + ldap_info.use_ssl = 1; + else + ldap_info.use_ssl = 0; + ldap_info.ca_cert = conf_get_str(LDAP_SECTION, "LDAP_CA_CERT"); + /* vary the default port depending on whether they use SSL or not */ + ldap_info.port = conf_get_num(LDAP_SECTION, "LDAP_port", + (ldap_info.use_ssl) ? + LDAPS_PORT : LDAP_PORT); + + /* Verify required information is supplied */ + if (server_in == NULL || strlen(server_in) == 0) + strncat(missing_msg, "LDAP_server ", sizeof(missing_msg)); + if (ldap_info.base == NULL || strlen(ldap_info.base) == 0) + strncat(missing_msg, "LDAP_base ", sizeof(missing_msg)); + if (strlen(missing_msg) != 0) { + IDMAP_LOG(0, ("umichldap_init: Missing required information: " + "%s", missing_msg)); + goto fail; + } + + ldap_info.server = server_in; + canonicalize = conf_get_str_with_def(LDAP_SECTION, "LDAP_canonicalize_name", "yes"); + if ((strcasecmp(canonicalize, "true") == 0) || + (strcasecmp(canonicalize, "on") == 0) || + (strcasecmp(canonicalize, "yes") == 0)) { + canon_name = get_canonical_hostname(server_in); + if (canon_name == NULL) + IDMAP_LOG(0, ("umichldap_init: Warning! Unable to " + "canonicalize server name '%s' as requested.", + server_in)); + else + ldap_info.server = canon_name; + } + + /* get the ldap mapping attributes/objectclasses (all have defaults) */ + ldap_map.NFSv4_person_objcls = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_person_objectclass", + DEFAULT_UMICH_OBJCLASS_REMOTE_PERSON); + + ldap_map.NFSv4_group_objcls = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_group_objectclass", + DEFAULT_UMICH_OBJCLASS_REMOTE_GROUP); + + ldap_map.NFSv4_nfsname_attr = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_name_attr", + DEFAULT_UMICH_ATTR_NFSNAME); + + ldap_map.NFSv4_uid_attr = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_uid_attr", + DEFAULT_UMICH_ATTR_UIDNUMBER); + + ldap_map.NFSv4_acctname_attr = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_acctname_attr", + DEFAULT_UMICH_ATTR_ACCTNAME); + + ldap_map.NFSv4_group_nfsname_attr = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_group_attr", + DEFAULT_UMICH_ATTR_GROUP_NFSNAME); + + ldap_map.NFSv4_gid_attr = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_gid_attr", + DEFAULT_UMICH_ATTR_GIDNUMBER); + + ldap_map.NFSv4_member_attr = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_member_attr", + DEFAULT_UMICH_ATTR_MEMBERUID); + + ldap_map.GSS_principal_attr = + conf_get_str_with_def(LDAP_SECTION, "GSS_principal_attr", + DEFAULT_UMICH_ATTR_GSSAUTHNAME); + + ldap_map.NFSv4_grouplist_filter = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_grouplist_filter", + NULL); + + ldap_map.NFSv4_member_of_attr = + conf_get_str_with_def(LDAP_SECTION, "NFSv4_member_of_attr", + DEFAULT_UMICH_ATTR_MEMBEROF); + + ldap_info.ldap_timeout = + conf_get_num(LDAP_SECTION, "LDAP_timeout_seconds", + DEFAULT_UMICH_SEARCH_TIMEOUT); + + + /* + * Some LDAP servers do a better job with indexing where searching + * through all the groups searching for the user in the memberuid + * list. Others like SunOne directory that search can takes minutes + * if there are thousands of groups. So setting + * LDAP_use_memberof_for_groups to true in the configuration file + * will use the memberof lists of the account and search through + * only those groups to obtain gids. + */ + memberof = conf_get_str_with_def(LDAP_SECTION, + "LDAP_use_memberof_for_groups", "false"); + if ((strcasecmp(memberof, "true") == 0) || + (strcasecmp(memberof, "on") == 0) || + (strcasecmp(memberof, "yes") == 0)) + ldap_info.memberof_for_groups = 1; + else + ldap_info.memberof_for_groups = 0; + + /* + * If they specified a search base for the + * people tree or group tree we use that. + * Otherwise we use the default search base. + * Note: We no longer append the default base to the tree -- + * that should already be specified. + * this functions much like the NSS_LDAP modules + */ + if (ldap_info.people_tree == NULL || strlen(ldap_info.people_tree) == 0) + ldap_info.people_tree = ldap_info.base; + if (ldap_info.group_tree == NULL || strlen(ldap_info.group_tree) == 0) + ldap_info.group_tree = ldap_info.base; + + if (ldap_info.use_ssl && ldap_info.ca_cert == NULL) { + IDMAP_LOG(0, ("umichldap_init: You must specify LDAP_ca_cert " + "with LDAP_use_ssl=yes")); + goto fail; + } + + + /* print out some good debugging info */ + IDMAP_LOG(1, ("umichldap_init: canonicalize_name: %s", + canonicalize)); + IDMAP_LOG(1, ("umichldap_init: server : %s (from config value '%s')", + ldap_info.server, server_in)); + IDMAP_LOG(1, ("umichldap_init: port : %d", ldap_info.port)); + IDMAP_LOG(1, ("umichldap_init: people : %s", ldap_info.people_tree)); + IDMAP_LOG(1, ("umichldap_init: groups : %s", ldap_info.group_tree)); + + IDMAP_LOG(1, ("umichldap_init: user_dn : %s", + (ldap_info.user_dn && strlen(ldap_info.user_dn) != 0) + ? ldap_info.user_dn : "<not-supplied>")); + /* Don't print actual password into the log. */ + IDMAP_LOG(1, ("umichldap_init: passwd : %s", + (ldap_info.passwd && strlen(ldap_info.passwd) != 0) ? + "<supplied>" : "<not-supplied>")); + IDMAP_LOG(1, ("umichldap_init: use_ssl : %s", + ldap_info.use_ssl ? "yes" : "no")); + IDMAP_LOG(1, ("umichldap_init: ca_cert : %s", + ldap_info.ca_cert ? ldap_info.ca_cert : "<not-supplied>")); + IDMAP_LOG(1, ("umichldap_init: use_memberof_for_groups : %s", + ldap_info.memberof_for_groups ? "yes" : "no")); + + IDMAP_LOG(1, ("umichldap_init: NFSv4_person_objectclass : %s", + ldap_map.NFSv4_person_objcls)); + IDMAP_LOG(1, ("umichldap_init: NFSv4_nfsname_attr : %s", + ldap_map.NFSv4_nfsname_attr)); + IDMAP_LOG(1, ("umichldap_init: NFSv4_acctname_attr : %s", + ldap_map.NFSv4_acctname_attr)); + IDMAP_LOG(1, ("umichldap_init: NFSv4_uid_attr : %s", + ldap_map.NFSv4_uid_attr)); + IDMAP_LOG(1, ("umichldap_init: NFSv4_group_objectclass : %s", + ldap_map.NFSv4_group_objcls)); + IDMAP_LOG(1, ("umichldap_init: NFSv4_gid_attr : %s", + ldap_map.NFSv4_gid_attr)); + IDMAP_LOG(1, ("umichldap_init: NFSv4_group_nfsname_attr : %s", + ldap_map.NFSv4_group_nfsname_attr)); + IDMAP_LOG(1, ("umichldap_init: NFSv4_member_attr : %s", + ldap_map.NFSv4_member_attr)); + IDMAP_LOG(1, ("umichldap_init: NFSv4_member_of_attr : %s", + ldap_map.NFSv4_member_of_attr)); + IDMAP_LOG(1, ("umichldap_init: NFSv4_grouplist_filter : %s", + ldap_map.NFSv4_grouplist_filter ? + ldap_map.NFSv4_grouplist_filter : "<not-specified>")); + IDMAP_LOG(1, ("umichldap_init: GSS_principal_attr : %s", + ldap_map.GSS_principal_attr)); + return 0; +fail: + return -1; +} + + +/* The external interface */ + +struct trans_func umichldap_trans = { + .name = "umich_ldap", + .init = umichldap_init, + .princ_to_ids = umichldap_gss_princ_to_ids, + .name_to_uid = umichldap_name_to_uid, + .name_to_gid = umichldap_name_to_gid, + .uid_to_name = umichldap_uid_to_name, + .gid_to_name = umichldap_gid_to_name, + .gss_princ_to_grouplist = umichldap_gss_princ_to_grouplist, +}; + +struct trans_func *libnfsidmap_plugin_init() +{ + return (&umichldap_trans); +} |