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"