diff --git a/build.vc10/daemon.vcxproj b/build.vc10/daemon.vcxproj
index 7035e57..7c730a0 100644
--- a/build.vc10/daemon.vcxproj
+++ b/build.vc10/daemon.vcxproj
@@ -105,7 +105,7 @@
CompileAsC
- ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)
+ ws2_32.lib;iphlpapi.lib;wldap32.lib;%(AdditionalDependencies)
$(OutDir)$(TargetName)$(TargetExt)
true
MachineX86
@@ -131,7 +131,7 @@
CompileAsC
- ws2_32.lib;iphlpapi.lib;kernel32.lib;advapi32.lib;shell32.lib;%(AdditionalDependencies)
+ ws2_32.lib;iphlpapi.lib;wldap32.lib;kernel32.lib;advapi32.lib;shell32.lib;%(AdditionalDependencies)
$(OutDir)$(TargetName)$(TargetExt)
true
MachineX64
@@ -154,7 +154,7 @@
CompileAsC
- ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)
+ ws2_32.lib;iphlpapi.lib;wldap32.lib;%(AdditionalDependencies)
$(OutDir)$(TargetName)$(TargetExt)
true
true
@@ -182,7 +182,7 @@
CompileAsC
- ws2_32.lib;iphlpapi.lib;%(AdditionalDependencies)
+ ws2_32.lib;iphlpapi.lib;wldap32.lib;%(AdditionalDependencies)
$(OutDir)$(TargetName)$(TargetExt)
true
true
@@ -195,6 +195,7 @@
+
@@ -227,6 +228,7 @@
+
diff --git a/build.vc10/daemon.vcxproj.filters b/build.vc10/daemon.vcxproj.filters
index 8390631..4b26309 100644
--- a/build.vc10/daemon.vcxproj.filters
+++ b/build.vc10/daemon.vcxproj.filters
@@ -110,6 +110,9 @@
Source Files
+
+ Source Files
+
@@ -160,6 +163,9 @@
Header Files
+
+ Header Files
+
diff --git a/daemon/idmap.c b/daemon/idmap.c
new file mode 100644
index 0000000..91cb560
--- /dev/null
+++ b/daemon/idmap.c
@@ -0,0 +1,1058 @@
+/* Copyright (c) 2010
+ * The Regents of the University of Michigan
+ * All Rights Reserved
+ *
+ * Permission is granted to use, copy and redistribute this software
+ * for noncommercial education and research purposes, so long as no
+ * fee is charged, and so long as the name of the University of Michigan
+ * is not used in any advertising or publicity pertaining to the use
+ * or distribution of this software without specific, written prior
+ * authorization. Permission to modify or otherwise create derivative
+ * works of this software is not granted.
+ *
+ * This software is provided as is, without representation or warranty
+ * of any kind either express or implied, including without limitation
+ * the implied warranties of merchantability, fitness for a particular
+ * purpose, or noninfringement. 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.
+ */
+
+#include
+#include
+#include
+#include /* for strtoul() */
+#include
+#include
+
+#include "idmap.h"
+#include "nfs41_const.h"
+#include "list.h"
+#include "daemon_debug.h"
+
+
+#define IDLVL 2 /* dprintf level for idmap logging */
+
+#define FILTER_LEN 1024
+#define NAME_LEN 32
+#define VAL_LEN 257
+
+
+enum ldap_class {
+ CLASS_USER,
+ CLASS_GROUP,
+
+ NUM_CLASSES
+};
+
+enum ldap_attr {
+ ATTR_USER_NAME,
+ ATTR_GROUP_NAME,
+ ATTR_PRINCIPAL,
+ ATTR_UID,
+ ATTR_GID,
+
+ NUM_ATTRIBUTES
+};
+
+#define ATTR_FLAG(attr) (1 << (attr))
+#define ATTR_ISSET(mask, attr) (((mask) & ATTR_FLAG(attr)) != 0)
+
+
+/* ldap/cache lookups */
+struct idmap_lookup {
+ enum ldap_attr attr;
+ enum ldap_class klass;
+ enum config_type type;
+ list_compare_fn compare;
+ const void *value;
+};
+
+
+/* configuration */
+static const char CONFIG_FILENAME[] = "C:\\etc\\ms-nfs41-idmap.conf";
+
+struct idmap_config {
+ /* ldap server information */
+ char hostname[NFS41_HOSTNAME_LEN+1];
+ UINT port;
+ UINT version;
+ UINT timeout;
+
+ /* ldap schema information */
+ char classes[NUM_CLASSES][NAME_LEN];
+ char attributes[NUM_ATTRIBUTES][NAME_LEN];
+ char base[VAL_LEN];
+
+ /* caching configuration */
+ UINT cache_ttl;
+};
+
+
+enum config_type {
+ TYPE_STR,
+ TYPE_INT
+};
+
+struct config_option {
+ const char *key;
+ const char *def;
+ enum config_type type;
+ size_t offset;
+ size_t max_len;
+};
+
+/* helper macros for declaring config_options */
+#define OPT_INT(key,def,field) \
+ { key, def, TYPE_INT, FIELD_OFFSET(struct idmap_config, field), 0 }
+#define OPT_STR(key,def,field,len) \
+ { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, field), len }
+#define OPT_CLASS(key,def,index) \
+ { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, classes[index]), NAME_LEN }
+#define OPT_ATTR(key,def,index) \
+ { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, attributes[index]), NAME_LEN }
+
+/* table of recognized config options, including type and default value */
+static const struct config_option g_options[] = {
+ /* server information */
+ OPT_STR("ldap_hostname", "localhost", hostname, NFS41_HOSTNAME_LEN+1),
+ OPT_INT("ldap_port", "389", port),
+ OPT_INT("ldap_version", "3", version),
+ OPT_INT("ldap_timeout", "0", timeout),
+
+ /* schema information */
+ OPT_STR("ldap_base", "cn=localhost", base, VAL_LEN),
+ OPT_CLASS("ldap_class_users", "user", CLASS_USER),
+ OPT_CLASS("ldap_class_groups", "group", CLASS_GROUP),
+ OPT_ATTR("ldap_attr_username", "cn", ATTR_USER_NAME),
+ OPT_ATTR("ldap_attr_groupname", "cn", ATTR_GROUP_NAME),
+ OPT_ATTR("ldap_attr_gssAuthName", "gssAuthName", ATTR_PRINCIPAL),
+ OPT_ATTR("ldap_attr_uidNumber", "uidNumber", ATTR_UID),
+ OPT_ATTR("ldap_attr_gidNumber", "gidNumber", ATTR_GID),
+
+ /* caching configuration */
+ OPT_INT("cache_ttl", "60", cache_ttl),
+};
+
+
+/* parse each line into key-value pairs
+ * accepts 'key = value' or 'key = "value"',
+ * ignores whitespace anywhere outside the ""s */
+struct config_pair {
+ const char *key, *value;
+ size_t key_len, value_len;
+};
+
+static int config_parse_pair(
+ char *line,
+ struct config_pair *pair)
+{
+ char *pos = line;
+ int status = NO_ERROR;
+
+ /* terminate at comment */
+ pos = strchr(line, '#');
+ if (pos) *pos = 0;
+
+ /* skip whitespace before key */
+ pos = line;
+ while (isspace(*pos)) pos++;
+ pair->key = pos;
+
+ pos = strchr(pos, '=');
+ if (pos == NULL) {
+ eprintf("missing '='\n");
+ status = ERROR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* skip whitespace after key */
+ pair->key_len = pos - pair->key;
+ while (pair->key_len && isspace(pair->key[pair->key_len-1]))
+ pair->key_len--;
+
+ if (pair->key_len <= 0) {
+ eprintf("empty key\n");
+ status = ERROR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* skip whitespace after = */
+ pos++;
+ while (isspace(*pos)) pos++;
+
+ if (*pos == 0) {
+ eprintf("end of line looking for value\n");
+ status = ERROR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (*pos == '\"') {
+ /* value is between the "s */
+ pair->value = pos + 1;
+ pos = strchr(pair->value, '\"');
+ if (pos == NULL) {
+ eprintf("no matching '\"'\n");
+ status = ERROR_INVALID_PARAMETER;
+ goto out;
+ }
+ pair->value_len = pos - pair->value;
+ } else {
+ pair->value = pos;
+ pair->value_len = strlen(pair->value);
+
+ /* skip whitespace after value */
+ while (pair->value_len && isspace(pair->value[pair->value_len-1]))
+ pair->value_len--;
+ }
+
+ /* on success, null terminate the key and value */
+ ((char*)pair->key)[pair->key_len] = 0;
+ ((char*)pair->value)[pair->value_len] = 0;
+out:
+ return status;
+}
+
+static BOOL parse_uint(
+ const char *str,
+ UINT *id_out)
+{
+ PCHAR endp;
+ const UINT id = strtoul(str, &endp, 10);
+
+ /* must convert the whole string */
+ if ((endp - str) < (ptrdiff_t)strlen(str))
+ return FALSE;
+
+ /* result must fit in 32 bits */
+ if (id == ULONG_MAX && errno == ERANGE)
+ return FALSE;
+
+ *id_out = id;
+ return TRUE;
+}
+
+/* parse default values from g_options[] into idmap_config */
+static int config_defaults(
+ struct idmap_config *config)
+{
+ const struct config_option *option;
+ const int count = ARRAYSIZE(g_options);
+ char *dst;
+ int i, status = NO_ERROR;
+
+ for (i = 0; i < count; i++) {
+ option = &g_options[i];
+ dst = (char*)config + option->offset;
+
+ if (option->type == TYPE_INT) {
+ if (!parse_uint(option->def, (UINT*)dst)) {
+ status = ERROR_INVALID_PARAMETER;
+ eprintf("failed to parse default value of %s=\"%s\": "
+ "expected a number\n", option->key, option->def);
+ break;
+ }
+ } else {
+ if (FAILED(StringCchCopyA(dst, option->max_len, option->def))) {
+ status = ERROR_BUFFER_OVERFLOW;
+ eprintf("failed to parse default value of %s=\"%s\": "
+ "buffer overflow > %u\n", option->key, option->def,
+ option->max_len);
+ break;
+ }
+ }
+ }
+ return status;
+}
+
+static int config_find_option(
+ const struct config_pair *pair,
+ const struct config_option **option)
+{
+ int i, count = ARRAYSIZE(g_options);
+ int status = ERROR_NOT_FOUND;
+
+ /* find the config_option by key */
+ for (i = 0; i < count; i++) {
+ if (stricmp(pair->key, g_options[i].key) == 0) {
+ *option = &g_options[i];
+ status = NO_ERROR;
+ break;
+ }
+ }
+ return status;
+}
+
+static int config_load(
+ struct idmap_config *config,
+ const char *filename)
+{
+ char buffer[1024], *pos;
+ FILE *file;
+ struct config_pair pair;
+ const struct config_option *option;
+ int line = 0;
+ int status = NO_ERROR;
+
+ /* open the file */
+ file = fopen(filename, "r");
+ if (file == NULL) {
+ eprintf("config_load() failed to open file '%s'\n", filename);
+ goto out;
+ }
+
+ /* read each line */
+ while (fgets(buffer, sizeof(buffer), file)) {
+ line++;
+
+ /* skip whitespace */
+ pos = buffer;
+ while (isspace(*pos)) pos++;
+
+ /* skip comments and empty lines */
+ if (*pos == '#' || *pos == 0)
+ continue;
+
+ /* parse line into a key=value pair */
+ status = config_parse_pair(buffer, &pair);
+ if (status) {
+ eprintf("error on line %d: %s\n", line, buffer);
+ break;
+ }
+
+ /* find the config_option by key */
+ status = config_find_option(&pair, &option);
+ if (status) {
+ eprintf("unrecognized option '%s' on line %d: %s\n",
+ pair.key, line, buffer);
+ status = ERROR_INVALID_PARAMETER;
+ break;
+ }
+
+ if (option->type == TYPE_INT) {
+ if (!parse_uint(pair.value, (UINT*)((char*)config + option->offset))) {
+ status = ERROR_INVALID_PARAMETER;
+ eprintf("expected a number on line %d: %s=\"%s\"\n",
+ line, pair.key, pair.value);
+ break;
+ }
+ } else {
+ if (FAILED(StringCchCopyNA((char*)config + option->offset,
+ option->max_len, pair.value, pair.value_len))) {
+ status = ERROR_BUFFER_OVERFLOW;
+ eprintf("overflow on line %d: %s=\"%s\"\n",
+ line, pair.key, pair.value);
+ break;
+ }
+ }
+ }
+
+ fclose(file);
+out:
+ return status;
+}
+
+static int config_init(
+ struct idmap_config *config)
+{
+ int status;
+
+ /* load default values */
+ status = config_defaults(config);
+ if (status) {
+ eprintf("config_defaults() failed with %d\n", status);
+ goto out;
+ }
+
+ /* load configuration from file */
+ status = config_load(config, CONFIG_FILENAME);
+ if (status) {
+ eprintf("config_load('%s') failed with %d\n", CONFIG_FILENAME, status);
+ goto out;
+ }
+out:
+ return status;
+}
+
+
+/* generic cache */
+typedef struct list_entry* (*entry_alloc_fn)();
+typedef void (*entry_free_fn)(struct list_entry*);
+typedef void (*entry_copy_fn)(struct list_entry*, const struct list_entry*);
+
+struct cache_ops {
+ entry_alloc_fn entry_alloc;
+ entry_free_fn entry_free;
+ entry_copy_fn entry_copy;
+};
+
+struct idmap_cache {
+ struct list_entry head;
+ const struct cache_ops *ops;
+ SRWLOCK lock;
+};
+
+
+static void cache_init(
+ struct idmap_cache *cache,
+ const struct cache_ops *ops)
+{
+ list_init(&cache->head);
+ cache->ops = ops;
+ InitializeSRWLock(&cache->lock);
+}
+
+static void cache_cleanup(
+ struct idmap_cache *cache)
+{
+ struct list_entry *entry, *tmp;
+ list_for_each_tmp(entry, tmp, &cache->head)
+ cache->ops->entry_free(entry);
+ list_init(&cache->head);
+}
+
+static int cache_insert(
+ struct idmap_cache *cache,
+ const struct idmap_lookup *lookup,
+ const struct list_entry *src)
+{
+ struct list_entry *entry;
+ int status = NO_ERROR;
+
+ AcquireSRWLockExclusive(&cache->lock);
+
+ /* search for an existing match */
+ entry = list_search(&cache->head, lookup->value, lookup->compare);
+ if (entry) {
+ /* overwrite the existing entry with the new results */
+ cache->ops->entry_copy(entry, src);
+ goto out;
+ }
+
+ /* initialize a new entry and add it to the list */
+ entry = cache->ops->entry_alloc();
+ if (entry == NULL) {
+ status = GetLastError();
+ goto out;
+ }
+ cache->ops->entry_copy(entry, src);
+ list_add_head(&cache->head, entry);
+out:
+ ReleaseSRWLockExclusive(&cache->lock);
+ return status;
+}
+
+static int cache_lookup(
+ struct idmap_cache *cache,
+ const struct idmap_lookup *lookup,
+ struct list_entry *entry_out)
+{
+ struct list_entry *entry;
+ int status = ERROR_NOT_FOUND;
+
+ AcquireSRWLockShared(&cache->lock);
+
+ entry = list_search(&cache->head, lookup->value, lookup->compare);
+ if (entry) {
+ /* make a copy for use outside of the lock */
+ cache->ops->entry_copy(entry_out, entry);
+ status = NO_ERROR;
+ }
+
+ ReleaseSRWLockShared(&cache->lock);
+ return status;
+}
+
+
+/* user cache */
+struct idmap_user {
+ struct list_entry entry;
+ char username[VAL_LEN];
+ char principal[VAL_LEN];
+ uid_t uid;
+ gid_t gid;
+ time_t last_updated;
+};
+
+static struct list_entry* user_cache_alloc()
+{
+ struct idmap_user *user = calloc(1, sizeof(struct idmap_user));
+ return user == NULL ? NULL : &user->entry;
+}
+static void user_cache_free(struct list_entry *entry)
+{
+ free(list_container(entry, struct idmap_user, entry));
+}
+static void user_cache_copy(
+ struct list_entry *lhs,
+ const struct list_entry *rhs)
+{
+ struct idmap_user *dst = list_container(lhs, struct idmap_user, entry);
+ const struct idmap_user *src = list_container(rhs, const struct idmap_user, entry);
+ StringCchCopyA(dst->username, VAL_LEN, src->username);
+ StringCchCopyA(dst->principal, VAL_LEN, src->principal);
+ dst->uid = src->uid;
+ dst->gid = src->gid;
+ dst->last_updated = src->last_updated;
+}
+static const struct cache_ops user_cache_ops = {
+ user_cache_alloc,
+ user_cache_free,
+ user_cache_copy
+};
+
+
+/* group cache */
+struct idmap_group {
+ struct list_entry entry;
+ char name[VAL_LEN];
+ gid_t gid;
+ time_t last_updated;
+};
+
+static struct list_entry* group_cache_alloc()
+{
+ struct idmap_group *group = calloc(1, sizeof(struct idmap_group));
+ return group == NULL ? NULL : &group->entry;
+}
+static void group_cache_free(struct list_entry *entry)
+{
+ free(list_container(entry, struct idmap_group, entry));
+}
+static void group_cache_copy(
+ struct list_entry *lhs,
+ const struct list_entry *rhs)
+{
+ struct idmap_group *dst = list_container(lhs, struct idmap_group, entry);
+ const struct idmap_group *src = list_container(rhs, const struct idmap_group, entry);
+ StringCchCopyA(dst->name, VAL_LEN, src->name);
+ dst->gid = src->gid;
+ dst->last_updated = src->last_updated;
+}
+static const struct cache_ops group_cache_ops = {
+ group_cache_alloc,
+ group_cache_free,
+ group_cache_copy
+};
+
+
+/* ldap context */
+struct idmap_context {
+ struct idmap_config config;
+ struct idmap_cache users;
+ struct idmap_cache groups;
+ LDAP *ldap;
+};
+
+
+static int idmap_filter(
+ struct idmap_config *config,
+ const struct idmap_lookup *lookup,
+ char *filter,
+ size_t filter_len)
+{
+ UINT_PTR i;
+ int status = NO_ERROR;
+
+ switch (lookup->type) {
+ case TYPE_INT:
+ i = (UINT_PTR)lookup->value;
+ if (FAILED(StringCchPrintfA(filter, filter_len,
+ "(&(objectClass=%s)(%s=%u))",
+ config->classes[lookup->klass],
+ config->attributes[lookup->attr], (UINT)i))) {
+ status = ERROR_BUFFER_OVERFLOW;
+ eprintf("ldap filter buffer overflow: '%s=%u'\n",
+ config->attributes[lookup->attr], (UINT)i);
+ }
+ break;
+
+ case TYPE_STR:
+ if (FAILED(StringCchPrintfA(filter, filter_len,
+ "(&(objectClass=%s)(%s=%s))",
+ config->classes[lookup->klass],
+ config->attributes[lookup->attr], lookup->value))) {
+ status = ERROR_BUFFER_OVERFLOW;
+ eprintf("ldap filter buffer overflow: '%s=%s'\n",
+ config->attributes[lookup->attr], lookup->value);
+ }
+ break;
+
+ default:
+ status = ERROR_INVALID_PARAMETER;
+ break;
+ }
+ return status;
+}
+
+static int idmap_query_attrs(
+ struct idmap_context *context,
+ const struct idmap_lookup *lookup,
+ const unsigned attributes,
+ const unsigned optional,
+ PCHAR *values[],
+ const size_t len)
+{
+ char filter[FILTER_LEN];
+ struct idmap_config *config = &context->config;
+ LDAPMessage *res = NULL, *entry;
+ int i, status;
+
+ /* format the ldap filter */
+ status = idmap_filter(config, lookup, filter, FILTER_LEN);
+ if (status)
+ goto out;
+
+ /* send the ldap query */
+ status = ldap_search_st(context->ldap, config->base,
+ LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, &res);
+ if (status) {
+ eprintf("ldap search for '%s' failed with %d: %s\n",
+ filter, status, ldap_err2stringA(status));
+ status = LdapMapErrorToWin32(status);
+ goto out;
+ }
+
+ entry = ldap_first_entry(context->ldap, res);
+ if (entry == NULL) {
+ status = LDAP_NO_RESULTS_RETURNED;
+ eprintf("ldap search for '%s' failed with %d: %s\n",
+ filter, status, ldap_err2stringA(status));
+ status = LdapMapErrorToWin32(status);
+ goto out;
+ }
+
+ /* fetch the attributes */
+ for (i = 0; i < len; i++) {
+ if (ATTR_ISSET(attributes, i)) {
+ values[i] = ldap_get_values(context->ldap,
+ entry, config->attributes[i]);
+
+ /* fail if required attributes are missing */
+ if (values[i] == NULL && !ATTR_ISSET(optional, i)) {
+ status = LDAP_NO_SUCH_ATTRIBUTE;
+ eprintf("ldap entry for '%s' missing required "
+ "attribute '%s', returning %d: %s\n",
+ filter, config->attributes[i],
+ status, ldap_err2stringA(status));
+ status = LdapMapErrorToWin32(status);
+ goto out;
+ }
+ }
+ }
+out:
+ if (res) ldap_msgfree(res);
+ return status;
+}
+
+static int idmap_lookup_user(
+ struct idmap_context *context,
+ const struct idmap_lookup *lookup,
+ struct idmap_user *user)
+{
+ PCHAR* values[NUM_ATTRIBUTES] = { NULL };
+ const unsigned attributes = ATTR_FLAG(ATTR_USER_NAME)
+ | ATTR_FLAG(ATTR_PRINCIPAL)
+ | ATTR_FLAG(ATTR_UID)
+ | ATTR_FLAG(ATTR_GID);
+ /* principal is optional; we'll cache it if we have it */
+ const unsigned optional = ATTR_FLAG(ATTR_PRINCIPAL);
+ int i, status;
+
+ /* check the user cache for an existing entry */
+ status = cache_lookup(&context->users, lookup, &user->entry);
+ if (status == NO_ERROR) {
+ /* don't return expired entries; query new attributes
+ * and overwrite the entry with cache_insert() */
+ if (time(NULL) - user->last_updated < context->config.cache_ttl)
+ goto out;
+ }
+
+ /* send the query to the ldap server */
+ status = idmap_query_attrs(context, lookup,
+ attributes, optional, values, NUM_ATTRIBUTES);
+ if (status)
+ goto out_free_values;
+
+ /* parse attributes */
+ if (FAILED(StringCchCopyA(user->username, VAL_LEN,
+ *values[ATTR_USER_NAME]))) {
+ eprintf("ldap attribute %s='%s' longer than %u characters\n",
+ context->config.attributes[ATTR_USER_NAME],
+ *values[ATTR_USER_NAME], VAL_LEN);
+ status = ERROR_BUFFER_OVERFLOW;
+ goto out_free_values;
+ }
+ if (FAILED(StringCchCopyA(user->principal, VAL_LEN,
+ values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : ""))) {
+ eprintf("ldap attribute %s='%s' longer than %u characters\n",
+ context->config.attributes[ATTR_PRINCIPAL],
+ values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : "", VAL_LEN);
+ status = ERROR_BUFFER_OVERFLOW;
+ goto out_free_values;
+ }
+ if (!parse_uint(*values[ATTR_UID], &user->uid)) {
+ eprintf("failed to parse ldap attribute %s='%s'\n",
+ context->config.attributes[ATTR_UID], *values[ATTR_UID]);
+ status = ERROR_INVALID_PARAMETER;
+ goto out_free_values;
+ }
+ if (!parse_uint(*values[ATTR_GID], &user->gid)) {
+ eprintf("failed to parse ldap attribute %s='%s'\n",
+ context->config.attributes[ATTR_GID], *values[ATTR_GID]);
+ status = ERROR_INVALID_PARAMETER;
+ goto out_free_values;
+ }
+ user->last_updated = time(NULL);
+
+ if (context->config.cache_ttl) {
+ /* insert the entry into the cache */
+ cache_insert(&context->users, lookup, &user->entry);
+ }
+out_free_values:
+ for (i = 0; i < NUM_ATTRIBUTES; i++)
+ ldap_value_free(values[i]);
+out:
+ return status;
+}
+
+static int idmap_lookup_group(
+ struct idmap_context *context,
+ const struct idmap_lookup *lookup,
+ struct idmap_group *group)
+{
+ PCHAR* values[NUM_ATTRIBUTES] = { NULL };
+ const unsigned attributes = ATTR_FLAG(ATTR_GROUP_NAME)
+ | ATTR_FLAG(ATTR_GID);
+ int i, status;
+
+ /* check the group cache for an existing entry */
+ status = cache_lookup(&context->groups, lookup, &group->entry);
+ if (status == NO_ERROR) {
+ /* don't return expired entries; query new attributes
+ * and overwrite the entry with cache_insert() */
+ if (time(NULL) - group->last_updated < context->config.cache_ttl)
+ goto out;
+ }
+
+ /* send the query to the ldap server */
+ status = idmap_query_attrs(context, lookup,
+ attributes, 0, values, NUM_ATTRIBUTES);
+ if (status)
+ goto out_free_values;
+
+ /* parse attributes */
+ if (FAILED(StringCchCopyA(group->name, VAL_LEN,
+ *values[ATTR_GROUP_NAME]))) {
+ eprintf("ldap attribute %s='%s' longer than %u characters\n",
+ context->config.attributes[ATTR_GROUP_NAME],
+ *values[ATTR_GROUP_NAME], VAL_LEN);
+ status = ERROR_BUFFER_OVERFLOW;
+ goto out_free_values;
+ }
+ if (!parse_uint(*values[ATTR_GID], &group->gid)) {
+ eprintf("failed to parse ldap attribute %s='%s'\n",
+ context->config.attributes[ATTR_GID], *values[ATTR_GID]);
+ status = ERROR_INVALID_PARAMETER;
+ goto out_free_values;
+ }
+ group->last_updated = time(NULL);
+
+ if (context->config.cache_ttl) {
+ /* insert the entry into the cache */
+ cache_insert(&context->groups, lookup, &group->entry);
+ }
+out_free_values:
+ for (i = 0; i < NUM_ATTRIBUTES; i++)
+ ldap_value_free(values[i]);
+out:
+ return status;
+}
+
+
+/* public idmap interface */
+int nfs41_idmap_create(
+ struct idmap_context **context_out)
+{
+ struct idmap_context *context;
+ int status = NO_ERROR;
+
+ context = calloc(1, sizeof(struct idmap_context));
+ if (context == NULL) {
+ status = GetLastError();
+ goto out;
+ }
+
+ /* initialize the caches */
+ cache_init(&context->users, &user_cache_ops);
+ cache_init(&context->groups, &group_cache_ops);
+
+ /* load ldap configuration from file */
+ status = config_init(&context->config);
+ if (status) {
+ eprintf("config_init() failed with %d\n", status);
+ goto out_err_free;
+ }
+
+ /* initialize ldap and configure options */
+ context->ldap = ldap_init(context->config.hostname, context->config.port);
+ if (context->ldap == NULL) {
+ status = LdapGetLastError();
+ eprintf("ldap_init(%s) failed with %d: %s\n",
+ context->config.hostname, status, ldap_err2stringA(status));
+ status = LdapMapErrorToWin32(status);
+ goto out_err_free;
+ }
+
+ status = ldap_set_option(context->ldap, LDAP_OPT_PROTOCOL_VERSION,
+ (void *)&context->config.version);
+ if (status != LDAP_SUCCESS) {
+ eprintf("ldap_set_option(version=%d) failed with %d\n",
+ context->config.version, status);
+ status = LdapMapErrorToWin32(status);
+ goto out_err_free;
+ }
+
+ if (context->config.timeout) {
+ status = ldap_set_option(context->ldap, LDAP_OPT_TIMELIMIT,
+ (void *)&context->config.timeout);
+ if (status != LDAP_SUCCESS) {
+ eprintf("ldap_set_option(timeout=%d) failed with %d\n",
+ context->config.timeout, status);
+ status = LdapMapErrorToWin32(status);
+ goto out_err_free;
+ }
+ }
+
+ *context_out = context;
+out:
+ return status;
+
+out_err_free:
+ nfs41_idmap_free(context);
+ goto out;
+}
+
+void nfs41_idmap_free(
+ struct idmap_context *context)
+{
+ /* clean up the connection */
+ if (context->ldap)
+ ldap_unbind(context->ldap);
+
+ cache_cleanup(&context->users);
+ cache_cleanup(&context->groups);
+ free(context);
+}
+
+
+/* username -> uid, gid */
+static int username_cmp(const struct list_entry *list, const void *value)
+{
+ const struct idmap_user *entry = list_container(list,
+ const struct idmap_user, entry);
+ const char *username = (const char*)value;
+ return strcmp(entry->username, username);
+}
+
+int nfs41_idmap_name_to_ids(
+ struct idmap_context *context,
+ const char *username,
+ uid_t *uid_out,
+ gid_t *gid_out)
+{
+ struct idmap_lookup lookup = { ATTR_USER_NAME,
+ CLASS_USER, TYPE_STR, username_cmp };
+ struct idmap_user user;
+ int status;
+
+ dprintf(IDLVL, "--> nfs41_idmap_name_to_ids('%s')\n", username);
+
+ lookup.value = username;
+
+ /* look up the user entry */
+ status = idmap_lookup_user(context, &lookup, &user);
+ if (status) {
+ dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') "
+ "failed with %d\n", username, status);
+ goto out;
+ }
+
+ *uid_out = user.uid;
+ *gid_out = user.gid;
+ dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') "
+ "returning uid=%u, gid=%u\n", username, user.uid, user.gid);
+out:
+ return status;
+}
+
+/* uid -> username */
+static int uid_cmp(const struct list_entry *list, const void *value)
+{
+ const struct idmap_user *entry = list_container(list,
+ const struct idmap_user, entry);
+ const UINT_PTR uid = (const UINT_PTR)value;
+ return (UINT)uid - entry->uid;
+}
+
+int nfs41_idmap_uid_to_name(
+ struct idmap_context *context,
+ uid_t uid,
+ char *name,
+ size_t len)
+{
+ UINT_PTR uidp = uid; /* convert to pointer size to pass as void* */
+ struct idmap_lookup lookup = { ATTR_UID, CLASS_USER, TYPE_INT, uid_cmp };
+ struct idmap_user user;
+ int status;
+
+ dprintf(IDLVL, "--> nfs41_idmap_uid_to_name(%u)\n", uid);
+
+ lookup.value = (const void*)uidp;
+
+ /* look up the user entry */
+ status = idmap_lookup_user(context, &lookup, &user);
+ if (status) {
+ dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) "
+ "failed with %d\n", uid, status);
+ goto out;
+ }
+
+ if (FAILED(StringCchCopyA(name, len, user.username))) {
+ status = ERROR_BUFFER_OVERFLOW;
+ eprintf("username buffer overflow: '%s' > %u\n",
+ user.username, len);
+ goto out;
+ }
+
+ dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) "
+ "returning '%s'\n", uid, name);
+out:
+ return status;
+}
+
+/* principal -> uid, gid */
+static int principal_cmp(const struct list_entry *list, const void *value)
+{
+ const struct idmap_user *entry = list_container(list,
+ const struct idmap_user, entry);
+ const char *principal = (const char*)value;
+ return strcmp(entry->principal, principal);
+}
+
+int nfs41_idmap_principal_to_ids(
+ struct idmap_context *context,
+ const char *principal,
+ uid_t *uid_out,
+ gid_t *gid_out)
+{
+ struct idmap_lookup lookup = { ATTR_PRINCIPAL,
+ CLASS_USER, TYPE_STR, principal_cmp };
+ struct idmap_user user;
+ int status;
+
+ dprintf(IDLVL, "--> nfs41_idmap_principal_to_ids('%s')\n", principal);
+
+ lookup.value = principal;
+
+ /* look up the user entry */
+ status = idmap_lookup_user(context, &lookup, &user);
+ if (status) {
+ dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') "
+ "failed with %d\n", principal, status);
+ goto out;
+ }
+
+ *uid_out = user.uid;
+ *gid_out = user.gid;
+ dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') "
+ "returning uid=%u, gid=%u\n", principal, user.uid, user.gid);
+out:
+ return status;
+}
+
+/* group -> gid */
+static int group_cmp(const struct list_entry *list, const void *value)
+{
+ const struct idmap_group *entry = list_container(list,
+ const struct idmap_group, entry);
+ const char *group = (const char*)value;
+ return strcmp(entry->name, group);
+}
+
+int nfs41_idmap_group_to_gid(
+ struct idmap_context *context,
+ const char *name,
+ gid_t *gid_out)
+{
+ struct idmap_lookup lookup = { ATTR_GROUP_NAME,
+ CLASS_GROUP, TYPE_STR, group_cmp };
+ struct idmap_group group;
+ int status;
+
+ dprintf(IDLVL, "--> nfs41_idmap_group_to_gid('%s')\n", name);
+
+ lookup.value = name;
+
+ /* look up the group entry */
+ status = idmap_lookup_group(context, &lookup, &group);
+ if (status) {
+ dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') "
+ "failed with %d\n", name, status);
+ goto out;
+ }
+
+ *gid_out = group.gid;
+ dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') "
+ "returning %u\n", name, group.gid);
+out:
+ return status;
+}
+
+/* gid -> group */
+static int gid_cmp(const struct list_entry *list, const void *value)
+{
+ const struct idmap_group *entry = list_container(list,
+ const struct idmap_group, entry);
+ const UINT_PTR gid = (const UINT_PTR)value;
+ return (UINT)gid - entry->gid;
+}
+
+int nfs41_idmap_gid_to_group(
+ struct idmap_context *context,
+ gid_t gid,
+ char *name,
+ size_t len)
+{
+ UINT_PTR gidp = gid; /* convert to pointer size to pass as void* */
+ struct idmap_lookup lookup = { ATTR_GID, CLASS_GROUP, TYPE_INT, gid_cmp };
+ struct idmap_group group;
+ int status;
+
+ dprintf(IDLVL, "--> nfs41_idmap_gid_to_group(%u)\n", gid);
+
+ lookup.value = (const void*)gidp;
+
+ /* look up the group entry */
+ status = idmap_lookup_group(context, &lookup, &group);
+ if (status) {
+ dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) "
+ "failed with %d\n", gid, status);
+ goto out;
+ }
+
+ if (FAILED(StringCchCopyA(name, len, group.name))) {
+ status = ERROR_BUFFER_OVERFLOW;
+ eprintf("group name buffer overflow: '%s' > %u\n",
+ group.name, len);
+ goto out;
+ }
+
+ dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) "
+ "returning '%s'\n", gid, name);
+out:
+ return status;
+}
diff --git a/daemon/idmap.h b/daemon/idmap.h
new file mode 100644
index 0000000..07ae482
--- /dev/null
+++ b/daemon/idmap.h
@@ -0,0 +1,69 @@
+/* Copyright (c) 2010
+ * The Regents of the University of Michigan
+ * All Rights Reserved
+ *
+ * Permission is granted to use, copy and redistribute this software
+ * for noncommercial education and research purposes, so long as no
+ * fee is charged, and so long as the name of the University of Michigan
+ * is not used in any advertising or publicity pertaining to the use
+ * or distribution of this software without specific, written prior
+ * authorization. Permission to modify or otherwise create derivative
+ * works of this software is not granted.
+ *
+ * This software is provided as is, without representation or warranty
+ * of any kind either express or implied, including without limitation
+ * the implied warranties of merchantability, fitness for a particular
+ * purpose, or noninfringement. 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.
+ */
+
+#ifndef IDMAP_H
+#define IDMAP_H
+
+#include "nfs41_types.h"
+
+
+/* idmap.c */
+typedef struct idmap_context nfs41_idmapper;
+
+int nfs41_idmap_create(
+ nfs41_idmapper **context_out);
+
+void nfs41_idmap_free(
+ nfs41_idmapper *context);
+
+
+int nfs41_idmap_name_to_ids(
+ nfs41_idmapper *context,
+ const char *username,
+ uid_t *uid_out,
+ gid_t *gid_out);
+
+int nfs41_idmap_uid_to_name(
+ nfs41_idmapper *context,
+ uid_t uid,
+ char *name_out,
+ size_t len);
+
+int nfs41_idmap_principal_to_ids(
+ nfs41_idmapper *context,
+ const char *principal,
+ uid_t *uid_out,
+ gid_t *gid_out);
+
+int nfs41_idmap_group_to_gid(
+ nfs41_idmapper *context,
+ const char *name,
+ gid_t *gid_out);
+
+int nfs41_idmap_gid_to_group(
+ nfs41_idmapper *context,
+ gid_t gid,
+ char *name_out,
+ size_t len);
+
+#endif /* !IDMAP_H */
diff --git a/daemon/sources b/daemon/sources
index fc590f7..d39e36a 100644
--- a/daemon/sources
+++ b/daemon/sources
@@ -5,7 +5,7 @@ SOURCES=nfs41_daemon.c daemon_debug.c nfs41_ops.c nfs41_compound.c nfs41_xdr.c \
mount.c open.c readwrite.c lock.c readdir.c getattr.c setattr.c upcall.c \
nfs41_rpc.c util.c pnfs_layout.c pnfs_device.c pnfs_debug.c pnfs_io.c \
name_cache.c namespace.c rbtree.c volume.c callback_server.c callback_xdr.c \
- service.c symlink.c
+ service.c symlink.c idmap.c
UMTYPE=console
USE_LIBCMT=1
#USE_MSVCRT=1
diff --git a/ms-nfs41-idmap.conf b/ms-nfs41-idmap.conf
new file mode 100644
index 0000000..2fe1ff3
--- /dev/null
+++ b/ms-nfs41-idmap.conf
@@ -0,0 +1,20 @@
+# ldap server information
+#ldap_hostname="localhost"
+#ldap_port="389"
+#ldap_version="3"
+#ldap_timeout="5"
+
+# ldap schema information
+#ldap_base="cn=localhost"
+
+#ldap_class_users="user"
+#ldap_class_groups="group"
+
+#ldap_attr_username="cn"
+#ldap_attr_groupname="cn"
+#ldap_attr_gssAuthName="gssAuthName"
+#ldap_attr_uidNumber="uidNumber"
+#ldap_attr_gidNumber="gidNumber"
+
+# caching configuration
+#cache_ttl="60"