xref: /reactos/base/services/nfsd/idmap.c (revision 1734f297)
1 /* NFSv4.1 client for Windows
2  * Copyright � 2012 The Regents of the University of Michigan
3  *
4  * Olga Kornievskaia <aglo@umich.edu>
5  * Casey Bodley <cbodley@umich.edu>
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or (at
10  * your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * without any warranty; without even the implied warranty of merchantability
14  * or fitness for a particular purpose.  See the GNU Lesser General Public
15  * License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  */
21 
22 #include <windows.h>
23 #include <strsafe.h>
24 #include <winldap.h>
25 #include <stdlib.h> /* for strtoul() */
26 #include <errno.h>
27 #include <time.h>
28 
29 #include "idmap.h"
30 #include "nfs41_const.h"
31 #include "list.h"
32 #include "daemon_debug.h"
33 
34 
35 #define IDLVL 2 /* dprintf level for idmap logging */
36 
37 #define FILTER_LEN 1024
38 #define NAME_LEN 32
39 #define VAL_LEN 257
40 
41 
42 enum ldap_class {
43     CLASS_USER,
44     CLASS_GROUP,
45 
46     NUM_CLASSES
47 };
48 
49 enum ldap_attr {
50     ATTR_USER_NAME,
51     ATTR_GROUP_NAME,
52     ATTR_PRINCIPAL,
53     ATTR_UID,
54     ATTR_GID,
55 
56     NUM_ATTRIBUTES
57 };
58 
59 #define ATTR_FLAG(attr) (1 << (attr))
60 #define ATTR_ISSET(mask, attr) (((mask) & ATTR_FLAG(attr)) != 0)
61 
62 
63 /* ldap/cache lookups */
64 struct idmap_lookup {
65     enum ldap_attr attr;
66     enum ldap_class klass;
67 #ifdef __REACTOS__
68     uint32_t type;
69 #else
70     enum config_type type;
71 #endif
72     list_compare_fn compare;
73     const void *value;
74 };
75 
76 
77 #ifndef __REACTOS__
78 /* configuration */
79 static const char CONFIG_FILENAME[] = "C:\\ReactOS\\System32\\drivers\\etc\\ms-nfs41-idmap.conf";
80 #endif
81 
82 struct idmap_config {
83     /* ldap server information */
84     char hostname[NFS41_HOSTNAME_LEN+1];
85     UINT port;
86     UINT version;
87     UINT timeout;
88 
89     /* ldap schema information */
90     char classes[NUM_CLASSES][NAME_LEN];
91     char attributes[NUM_ATTRIBUTES][NAME_LEN];
92     char base[VAL_LEN];
93 
94     /* caching configuration */
95     INT cache_ttl;
96 };
97 
98 
99 enum config_type {
100     TYPE_STR,
101     TYPE_INT
102 };
103 
104 struct config_option {
105     const char *key;
106     const char *def;
107     enum config_type type;
108     size_t offset;
109     size_t max_len;
110 };
111 
112 /* helper macros for declaring config_options */
113 #define OPT_INT(key,def,field) \
114     { key, def, TYPE_INT, FIELD_OFFSET(struct idmap_config, field), 0 }
115 #define OPT_STR(key,def,field,len) \
116     { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, field), len }
117 #define OPT_CLASS(key,def,index) \
118     { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, classes[index]), NAME_LEN }
119 #define OPT_ATTR(key,def,index) \
120     { key, def, TYPE_STR, FIELD_OFFSET(struct idmap_config, attributes[index]), NAME_LEN }
121 
122 /* table of recognized config options, including type and default value */
123 static const struct config_option g_options[] = {
124     /* server information */
125     OPT_STR("ldap_hostname", "localhost", hostname, NFS41_HOSTNAME_LEN+1),
126     OPT_INT("ldap_port", "389", port),
127     OPT_INT("ldap_version", "3", version),
128     OPT_INT("ldap_timeout", "0", timeout),
129 
130     /* schema information */
131     OPT_STR("ldap_base", "cn=localhost", base, VAL_LEN),
132     OPT_CLASS("ldap_class_users", "user", CLASS_USER),
133     OPT_CLASS("ldap_class_groups", "group", CLASS_GROUP),
134     OPT_ATTR("ldap_attr_username", "cn", ATTR_USER_NAME),
135     OPT_ATTR("ldap_attr_groupname", "cn", ATTR_GROUP_NAME),
136     OPT_ATTR("ldap_attr_gssAuthName", "gssAuthName", ATTR_PRINCIPAL),
137     OPT_ATTR("ldap_attr_uidNumber", "uidNumber", ATTR_UID),
138     OPT_ATTR("ldap_attr_gidNumber", "gidNumber", ATTR_GID),
139 
140     /* caching configuration */
141     OPT_INT("cache_ttl", "60", cache_ttl),
142 };
143 
144 
145 /* parse each line into key-value pairs
146  * accepts 'key = value' or 'key = "value"',
147  * ignores whitespace anywhere outside the ""s */
148 struct config_pair {
149     const char *key, *value;
150     size_t key_len, value_len;
151 };
152 
153 static int config_parse_pair(
154     char *line,
155     struct config_pair *pair)
156 {
157     char *pos = line;
158     int status = NO_ERROR;
159 
160     /* terminate at comment */
161     pos = strchr(line, '#');
162     if (pos) *pos = 0;
163 
164     /* skip whitespace before key */
165     pos = line;
166     while (isspace(*pos)) pos++;
167     pair->key = pos;
168 
169     pos = strchr(pos, '=');
170     if (pos == NULL) {
171         eprintf("missing '='\n");
172         status = ERROR_INVALID_PARAMETER;
173         goto out;
174     }
175 
176     /* skip whitespace after key */
177     pair->key_len = pos - pair->key;
178     while (pair->key_len && isspace(pair->key[pair->key_len-1]))
179         pair->key_len--;
180 
181     if (pair->key_len <= 0) {
182         eprintf("empty key\n");
183         status = ERROR_INVALID_PARAMETER;
184         goto out;
185     }
186 
187     /* skip whitespace after = */
188     pos++;
189     while (isspace(*pos)) pos++;
190 
191     if (*pos == 0) {
192         eprintf("end of line looking for value\n");
193         status = ERROR_INVALID_PARAMETER;
194         goto out;
195     }
196 
197     if (*pos == '\"') {
198         /* value is between the "s */
199         pair->value = pos + 1;
200         pos = strchr(pair->value, '\"');
201         if (pos == NULL) {
202             eprintf("no matching '\"'\n");
203             status = ERROR_INVALID_PARAMETER;
204             goto out;
205         }
206         pair->value_len = pos - pair->value;
207     } else {
208         pair->value = pos;
209         pair->value_len = strlen(pair->value);
210 
211         /* skip whitespace after value */
212         while (pair->value_len && isspace(pair->value[pair->value_len-1]))
213             pair->value_len--;
214     }
215 
216     /* on success, null terminate the key and value */
217     ((char*)pair->key)[pair->key_len] = 0;
218     ((char*)pair->value)[pair->value_len] = 0;
219 out:
220     return status;
221 }
222 
223 static BOOL parse_uint(
224     const char *str,
225     UINT *id_out)
226 {
227     PCHAR endp;
228     const UINT id = strtoul(str, &endp, 10);
229 
230     /* must convert the whole string */
231     if ((endp - str) < (ptrdiff_t)strlen(str))
232         return FALSE;
233 
234     /* result must fit in 32 bits */
235     if (id == ULONG_MAX && errno == ERANGE)
236         return FALSE;
237 
238     *id_out = id;
239     return TRUE;
240 }
241 
242 /* parse default values from g_options[] into idmap_config */
243 static int config_defaults(
244     struct idmap_config *config)
245 {
246     const struct config_option *option;
247     const int count = ARRAYSIZE(g_options);
248     char *dst;
249     int i, status = NO_ERROR;
250 
251     for (i = 0; i < count; i++) {
252         option = &g_options[i];
253         dst = (char*)config + option->offset;
254 
255         if (option->type == TYPE_INT) {
256             if (!parse_uint(option->def, (UINT*)dst)) {
257                 status = ERROR_INVALID_PARAMETER;
258                 eprintf("failed to parse default value of %s=\"%s\": "
259                     "expected a number\n", option->key, option->def);
260                 break;
261             }
262         } else {
263             if (FAILED(StringCchCopyA(dst, option->max_len, option->def))) {
264                 status = ERROR_BUFFER_OVERFLOW;
265                 eprintf("failed to parse default value of %s=\"%s\": "
266                     "buffer overflow > %u\n", option->key, option->def,
267                     option->max_len);
268                 break;
269             }
270         }
271     }
272     return status;
273 }
274 
275 static int config_find_option(
276     const struct config_pair *pair,
277     const struct config_option **option)
278 {
279     int i, count = ARRAYSIZE(g_options);
280     int status = ERROR_NOT_FOUND;
281 
282     /* find the config_option by key */
283     for (i = 0; i < count; i++) {
284         if (stricmp(pair->key, g_options[i].key) == 0) {
285             *option = &g_options[i];
286             status = NO_ERROR;
287             break;
288         }
289     }
290     return status;
291 }
292 
293 static int config_load(
294     struct idmap_config *config,
295     const char *filename)
296 {
297     char buffer[1024], *pos;
298     FILE *file;
299     struct config_pair pair;
300     const struct config_option *option;
301     int line = 0;
302     int status = NO_ERROR;
303 
304     /* open the file */
305     file = fopen(filename, "r");
306     if (file == NULL) {
307         eprintf("config_load() failed to open file '%s'\n", filename);
308         goto out;
309     }
310 
311     /* read each line */
312     while (fgets(buffer, sizeof(buffer), file)) {
313         line++;
314 
315         /* skip whitespace */
316         pos = buffer;
317         while (isspace(*pos)) pos++;
318 
319         /* skip comments and empty lines */
320         if (*pos == '#' || *pos == 0)
321             continue;
322 
323         /* parse line into a key=value pair */
324         status = config_parse_pair(buffer, &pair);
325         if (status) {
326             eprintf("error on line %d: %s\n", line, buffer);
327             break;
328         }
329 
330         /* find the config_option by key */
331         status = config_find_option(&pair, &option);
332         if (status) {
333             eprintf("unrecognized option '%s' on line %d: %s\n",
334                 pair.key, line, buffer);
335             status = ERROR_INVALID_PARAMETER;
336             break;
337         }
338 
339         if (option->type == TYPE_INT) {
340             if (!parse_uint(pair.value, (UINT*)((char*)config + option->offset))) {
341                 status = ERROR_INVALID_PARAMETER;
342                 eprintf("expected a number on line %d: %s=\"%s\"\n",
343                     line, pair.key, pair.value);
344                 break;
345             }
346         } else {
347             if (FAILED(StringCchCopyNA((char*)config + option->offset,
348                     option->max_len, pair.value, pair.value_len))) {
349                 status = ERROR_BUFFER_OVERFLOW;
350                 eprintf("overflow on line %d: %s=\"%s\"\n",
351                     line, pair.key, pair.value);
352                 break;
353             }
354         }
355     }
356 
357     fclose(file);
358 out:
359     return status;
360 }
361 
362 static int config_init(
363     struct idmap_config *config)
364 {
365     int status;
366 #ifdef __REACTOS__
367     char config_path[MAX_PATH];
368 #endif
369 
370     /* load default values */
371     status = config_defaults(config);
372     if (status) {
373         eprintf("config_defaults() failed with %d\n", status);
374         goto out;
375     }
376 
377 #ifdef __REACTOS__
378     if (GetSystemDirectoryA(config_path, ARRAYSIZE(config_path)))
379     {
380         StringCchCatA(config_path, ARRAYSIZE(config_path), "\\drivers\\etc\\ms-nfs41-idmap.conf");
381     }
382     else
383     {
384         status = GetLastError();
385         eprintf("GetSystemDirectoryA failed with %ld\n", GetLastError());
386         goto out;
387     }
388 #endif
389 
390     /* load configuration from file */
391 #ifdef __REACTOS__
392     status = config_load(config, config_path);
393 #else
394     status = config_load(config, CONFIG_FILENAME);
395 #endif
396     if (status) {
397 #ifdef __REACTOS__
398         eprintf("config_load('%s') failed with %d\n", config_path, status);
399 #else
400         eprintf("config_load('%s') failed with %d\n", CONFIG_FILENAME, status);
401 #endif
402         goto out;
403     }
404 out:
405     return status;
406 }
407 
408 
409 /* generic cache */
410 typedef struct list_entry* (*entry_alloc_fn)();
411 typedef void (*entry_free_fn)(struct list_entry*);
412 typedef void (*entry_copy_fn)(struct list_entry*, const struct list_entry*);
413 
414 struct cache_ops {
415     entry_alloc_fn entry_alloc;
416     entry_free_fn entry_free;
417     entry_copy_fn entry_copy;
418 };
419 
420 struct idmap_cache {
421     struct list_entry head;
422     const struct cache_ops *ops;
423     SRWLOCK lock;
424 };
425 
426 
427 static void cache_init(
428     struct idmap_cache *cache,
429     const struct cache_ops *ops)
430 {
431     list_init(&cache->head);
432     cache->ops = ops;
433     InitializeSRWLock(&cache->lock);
434 }
435 
436 static void cache_cleanup(
437     struct idmap_cache *cache)
438 {
439     struct list_entry *entry, *tmp;
440     list_for_each_tmp(entry, tmp, &cache->head)
441         cache->ops->entry_free(entry);
442     list_init(&cache->head);
443 }
444 
445 static int cache_insert(
446     struct idmap_cache *cache,
447     const struct idmap_lookup *lookup,
448     const struct list_entry *src)
449 {
450     struct list_entry *entry;
451     int status = NO_ERROR;
452 
453     AcquireSRWLockExclusive(&cache->lock);
454 
455     /* search for an existing match */
456     entry = list_search(&cache->head, lookup->value, lookup->compare);
457     if (entry) {
458         /* overwrite the existing entry with the new results */
459         cache->ops->entry_copy(entry, src);
460         goto out;
461     }
462 
463     /* initialize a new entry and add it to the list */
464     entry = cache->ops->entry_alloc();
465     if (entry == NULL) {
466         status = GetLastError();
467         goto out;
468     }
469     cache->ops->entry_copy(entry, src);
470     list_add_head(&cache->head, entry);
471 out:
472     ReleaseSRWLockExclusive(&cache->lock);
473     return status;
474 }
475 
476 static int cache_lookup(
477     struct idmap_cache *cache,
478     const struct idmap_lookup *lookup,
479     struct list_entry *entry_out)
480 {
481     struct list_entry *entry;
482     int status = ERROR_NOT_FOUND;
483 
484     AcquireSRWLockShared(&cache->lock);
485 
486     entry = list_search(&cache->head, lookup->value, lookup->compare);
487     if (entry) {
488         /* make a copy for use outside of the lock */
489         cache->ops->entry_copy(entry_out, entry);
490         status = NO_ERROR;
491     }
492 
493     ReleaseSRWLockShared(&cache->lock);
494     return status;
495 }
496 
497 
498 /* user cache */
499 struct idmap_user {
500     struct list_entry entry;
501     char username[VAL_LEN];
502     char principal[VAL_LEN];
503     uid_t uid;
504     gid_t gid;
505     time_t last_updated;
506 };
507 
508 static struct list_entry* user_cache_alloc()
509 {
510     struct idmap_user *user = calloc(1, sizeof(struct idmap_user));
511     return user == NULL ? NULL : &user->entry;
512 }
513 static void user_cache_free(struct list_entry *entry)
514 {
515     free(list_container(entry, struct idmap_user, entry));
516 }
517 static void user_cache_copy(
518     struct list_entry *lhs,
519     const struct list_entry *rhs)
520 {
521     struct idmap_user *dst = list_container(lhs, struct idmap_user, entry);
522     const struct idmap_user *src = list_container(rhs, const struct idmap_user, entry);
523     StringCchCopyA(dst->username, VAL_LEN, src->username);
524     StringCchCopyA(dst->principal, VAL_LEN, src->principal);
525     dst->uid = src->uid;
526     dst->gid = src->gid;
527     dst->last_updated = src->last_updated;
528 }
529 static const struct cache_ops user_cache_ops = {
530     user_cache_alloc,
531     user_cache_free,
532     user_cache_copy
533 };
534 
535 
536 /* group cache */
537 struct idmap_group {
538     struct list_entry entry;
539     char name[VAL_LEN];
540     gid_t gid;
541     time_t last_updated;
542 };
543 
544 static struct list_entry* group_cache_alloc()
545 {
546     struct idmap_group *group = calloc(1, sizeof(struct idmap_group));
547     return group == NULL ? NULL : &group->entry;
548 }
549 static void group_cache_free(struct list_entry *entry)
550 {
551     free(list_container(entry, struct idmap_group, entry));
552 }
553 static void group_cache_copy(
554     struct list_entry *lhs,
555     const struct list_entry *rhs)
556 {
557     struct idmap_group *dst = list_container(lhs, struct idmap_group, entry);
558     const struct idmap_group *src = list_container(rhs, const struct idmap_group, entry);
559     StringCchCopyA(dst->name, VAL_LEN, src->name);
560     dst->gid = src->gid;
561     dst->last_updated = src->last_updated;
562 }
563 static const struct cache_ops group_cache_ops = {
564     group_cache_alloc,
565     group_cache_free,
566     group_cache_copy
567 };
568 
569 
570 /* ldap context */
571 struct idmap_context {
572     struct idmap_config config;
573     struct idmap_cache users;
574     struct idmap_cache groups;
575     LDAP *ldap;
576 };
577 
578 
579 static int idmap_filter(
580     struct idmap_config *config,
581     const struct idmap_lookup *lookup,
582     char *filter,
583     size_t filter_len)
584 {
585     UINT_PTR i;
586     int status = NO_ERROR;
587 
588     switch (lookup->type) {
589     case TYPE_INT:
590         i = (UINT_PTR)lookup->value;
591         if (FAILED(StringCchPrintfA(filter, filter_len,
592                 "(&(objectClass=%s)(%s=%u))",
593                 config->classes[lookup->klass],
594                 config->attributes[lookup->attr], (UINT)i))) {
595             status = ERROR_BUFFER_OVERFLOW;
596             eprintf("ldap filter buffer overflow: '%s=%u'\n",
597                 config->attributes[lookup->attr], (UINT)i);
598         }
599         break;
600 
601     case TYPE_STR:
602         if (FAILED(StringCchPrintfA(filter, filter_len,
603                 "(&(objectClass=%s)(%s=%s))",
604                 config->classes[lookup->klass],
605                 config->attributes[lookup->attr], lookup->value))) {
606             status = ERROR_BUFFER_OVERFLOW;
607             eprintf("ldap filter buffer overflow: '%s=%s'\n",
608                 config->attributes[lookup->attr], lookup->value);
609         }
610         break;
611 
612     default:
613         status = ERROR_INVALID_PARAMETER;
614         break;
615     }
616     return status;
617 }
618 
619 static int idmap_query_attrs(
620     struct idmap_context *context,
621     const struct idmap_lookup *lookup,
622     const unsigned attributes,
623     const unsigned optional,
624     PCHAR *values[],
625     const int len)
626 {
627     char filter[FILTER_LEN];
628     struct idmap_config *config = &context->config;
629     LDAPMessage *res = NULL, *entry;
630     int i, status;
631 
632     /* format the ldap filter */
633     status = idmap_filter(config, lookup, filter, FILTER_LEN);
634     if (status)
635         goto out;
636 
637     /* send the ldap query */
638     status = ldap_search_st(context->ldap, config->base,
639         LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, &res);
640     if (status) {
641         eprintf("ldap search for '%s' failed with %d: %s\n",
642             filter, status, ldap_err2stringA(status));
643         status = LdapMapErrorToWin32(status);
644         goto out;
645     }
646 
647     entry = ldap_first_entry(context->ldap, res);
648     if (entry == NULL) {
649         status = LDAP_NO_RESULTS_RETURNED;
650         eprintf("ldap search for '%s' failed with %d: %s\n",
651             filter, status, ldap_err2stringA(status));
652         status = LdapMapErrorToWin32(status);
653         goto out;
654     }
655 
656     /* fetch the attributes */
657     for (i = 0; i < len; i++) {
658         if (ATTR_ISSET(attributes, i)) {
659             values[i] = ldap_get_values(context->ldap,
660                 entry, config->attributes[i]);
661 
662             /* fail if required attributes are missing */
663             if (values[i] == NULL && !ATTR_ISSET(optional, i)) {
664                 status = LDAP_NO_SUCH_ATTRIBUTE;
665                 eprintf("ldap entry for '%s' missing required "
666                     "attribute '%s', returning %d: %s\n",
667                     filter, config->attributes[i],
668                     status, ldap_err2stringA(status));
669                 status = LdapMapErrorToWin32(status);
670                 goto out;
671             }
672         }
673     }
674 out:
675     if (res) ldap_msgfree(res);
676     return status;
677 }
678 
679 static int idmap_lookup_user(
680     struct idmap_context *context,
681     const struct idmap_lookup *lookup,
682     struct idmap_user *user)
683 {
684     PCHAR* values[NUM_ATTRIBUTES] = { NULL };
685     const unsigned attributes = ATTR_FLAG(ATTR_USER_NAME)
686         | ATTR_FLAG(ATTR_PRINCIPAL)
687         | ATTR_FLAG(ATTR_UID)
688         | ATTR_FLAG(ATTR_GID);
689     /* principal is optional; we'll cache it if we have it */
690     const unsigned optional = ATTR_FLAG(ATTR_PRINCIPAL);
691     int i, status;
692 
693     /* check the user cache for an existing entry */
694     status = cache_lookup(&context->users, lookup, &user->entry);
695     if (status == NO_ERROR) {
696         /* don't return expired entries; query new attributes
697          * and overwrite the entry with cache_insert() */
698         if (time(NULL) - user->last_updated < context->config.cache_ttl)
699             goto out;
700     }
701 
702     /* send the query to the ldap server */
703     status = idmap_query_attrs(context, lookup,
704         attributes, optional, values, NUM_ATTRIBUTES);
705     if (status)
706         goto out_free_values;
707 
708     /* parse attributes */
709     if (FAILED(StringCchCopyA(user->username, VAL_LEN,
710             *values[ATTR_USER_NAME]))) {
711         eprintf("ldap attribute %s='%s' longer than %u characters\n",
712             context->config.attributes[ATTR_USER_NAME],
713             *values[ATTR_USER_NAME], VAL_LEN);
714         status = ERROR_BUFFER_OVERFLOW;
715         goto out_free_values;
716     }
717     if (FAILED(StringCchCopyA(user->principal, VAL_LEN,
718             values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : ""))) {
719         eprintf("ldap attribute %s='%s' longer than %u characters\n",
720             context->config.attributes[ATTR_PRINCIPAL],
721             values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : "", VAL_LEN);
722         status = ERROR_BUFFER_OVERFLOW;
723         goto out_free_values;
724     }
725     if (!parse_uint(*values[ATTR_UID], &user->uid)) {
726         eprintf("failed to parse ldap attribute %s='%s'\n",
727             context->config.attributes[ATTR_UID], *values[ATTR_UID]);
728         status = ERROR_INVALID_PARAMETER;
729         goto out_free_values;
730     }
731     if (!parse_uint(*values[ATTR_GID], &user->gid)) {
732         eprintf("failed to parse ldap attribute %s='%s'\n",
733             context->config.attributes[ATTR_GID], *values[ATTR_GID]);
734         status = ERROR_INVALID_PARAMETER;
735         goto out_free_values;
736     }
737     user->last_updated = time(NULL);
738 
739     if (context->config.cache_ttl) {
740         /* insert the entry into the cache */
741         cache_insert(&context->users, lookup, &user->entry);
742     }
743 out_free_values:
744     for (i = 0; i < NUM_ATTRIBUTES; i++)
745         ldap_value_free(values[i]);
746 out:
747     return status;
748 }
749 
750 static int idmap_lookup_group(
751     struct idmap_context *context,
752     const struct idmap_lookup *lookup,
753     struct idmap_group *group)
754 {
755     PCHAR* values[NUM_ATTRIBUTES] = { NULL };
756     const unsigned attributes = ATTR_FLAG(ATTR_GROUP_NAME)
757         | ATTR_FLAG(ATTR_GID);
758     int i, status;
759 
760     /* check the group cache for an existing entry */
761     status = cache_lookup(&context->groups, lookup, &group->entry);
762     if (status == NO_ERROR) {
763         /* don't return expired entries; query new attributes
764          * and overwrite the entry with cache_insert() */
765         if (time(NULL) - group->last_updated < context->config.cache_ttl)
766             goto out;
767     }
768 
769     /* send the query to the ldap server */
770     status = idmap_query_attrs(context, lookup,
771         attributes, 0, values, NUM_ATTRIBUTES);
772     if (status)
773         goto out_free_values;
774 
775     /* parse attributes */
776     if (FAILED(StringCchCopyA(group->name, VAL_LEN,
777             *values[ATTR_GROUP_NAME]))) {
778         eprintf("ldap attribute %s='%s' longer than %u characters\n",
779             context->config.attributes[ATTR_GROUP_NAME],
780             *values[ATTR_GROUP_NAME], VAL_LEN);
781         status = ERROR_BUFFER_OVERFLOW;
782         goto out_free_values;
783     }
784     if (!parse_uint(*values[ATTR_GID], &group->gid)) {
785         eprintf("failed to parse ldap attribute %s='%s'\n",
786             context->config.attributes[ATTR_GID], *values[ATTR_GID]);
787         status = ERROR_INVALID_PARAMETER;
788         goto out_free_values;
789     }
790     group->last_updated = time(NULL);
791 
792     if (context->config.cache_ttl) {
793         /* insert the entry into the cache */
794         cache_insert(&context->groups, lookup, &group->entry);
795     }
796 out_free_values:
797     for (i = 0; i < NUM_ATTRIBUTES; i++)
798         ldap_value_free(values[i]);
799 out:
800     return status;
801 }
802 
803 
804 /* public idmap interface */
805 int nfs41_idmap_create(
806     struct idmap_context **context_out)
807 {
808     struct idmap_context *context;
809     int status = NO_ERROR;
810 
811     context = calloc(1, sizeof(struct idmap_context));
812     if (context == NULL) {
813         status = GetLastError();
814         goto out;
815     }
816 
817     /* initialize the caches */
818     cache_init(&context->users, &user_cache_ops);
819     cache_init(&context->groups, &group_cache_ops);
820 
821     /* load ldap configuration from file */
822     status = config_init(&context->config);
823     if (status) {
824         eprintf("config_init() failed with %d\n", status);
825         goto out_err_free;
826     }
827 
828     /* initialize ldap and configure options */
829     context->ldap = ldap_init(context->config.hostname, context->config.port);
830     if (context->ldap == NULL) {
831         status = LdapGetLastError();
832         eprintf("ldap_init(%s) failed with %d: %s\n",
833             context->config.hostname, status, ldap_err2stringA(status));
834         status = LdapMapErrorToWin32(status);
835         goto out_err_free;
836     }
837 
838     status = ldap_set_option(context->ldap, LDAP_OPT_PROTOCOL_VERSION,
839         (void *)&context->config.version);
840     if (status != LDAP_SUCCESS) {
841         eprintf("ldap_set_option(version=%d) failed with %d\n",
842             context->config.version, status);
843         status = LdapMapErrorToWin32(status);
844         goto out_err_free;
845     }
846 
847     if (context->config.timeout) {
848         status = ldap_set_option(context->ldap, LDAP_OPT_TIMELIMIT,
849             (void *)&context->config.timeout);
850         if (status != LDAP_SUCCESS) {
851             eprintf("ldap_set_option(timeout=%d) failed with %d\n",
852                 context->config.timeout, status);
853             status = LdapMapErrorToWin32(status);
854             goto out_err_free;
855         }
856     }
857 
858     *context_out = context;
859 out:
860     return status;
861 
862 out_err_free:
863     nfs41_idmap_free(context);
864     goto out;
865 }
866 
867 void nfs41_idmap_free(
868     struct idmap_context *context)
869 {
870     /* clean up the connection */
871     if (context->ldap)
872         ldap_unbind(context->ldap);
873 
874     cache_cleanup(&context->users);
875     cache_cleanup(&context->groups);
876     free(context);
877 }
878 
879 
880 /* username -> uid, gid */
881 static int username_cmp(const struct list_entry *list, const void *value)
882 {
883     const struct idmap_user *entry = list_container(list,
884         const struct idmap_user, entry);
885     const char *username = (const char*)value;
886     return strcmp(entry->username, username);
887 }
888 
889 int nfs41_idmap_name_to_ids(
890     struct idmap_context *context,
891     const char *username,
892     uid_t *uid_out,
893     gid_t *gid_out)
894 {
895     struct idmap_lookup lookup = { ATTR_USER_NAME,
896         CLASS_USER, TYPE_STR, username_cmp };
897     struct idmap_user user;
898     int status;
899 
900     if (context == NULL)
901         return ERROR_FILE_NOT_FOUND;
902 
903     dprintf(IDLVL, "--> nfs41_idmap_name_to_ids('%s')\n", username);
904 
905     lookup.value = username;
906 
907     /* look up the user entry */
908     status = idmap_lookup_user(context, &lookup, &user);
909     if (status) {
910         dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') "
911             "failed with %d\n", username, status);
912         goto out;
913     }
914 
915     *uid_out = user.uid;
916     *gid_out = user.gid;
917     dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') "
918         "returning uid=%u, gid=%u\n", username, user.uid, user.gid);
919 out:
920     return status;
921 }
922 
923 /* uid -> username */
924 static int uid_cmp(const struct list_entry *list, const void *value)
925 {
926     const struct idmap_user *entry = list_container(list,
927         const struct idmap_user, entry);
928     const UINT_PTR uid = (const UINT_PTR)value;
929     return (UINT)uid - entry->uid;
930 }
931 
932 int nfs41_idmap_uid_to_name(
933     struct idmap_context *context,
934     uid_t uid,
935     char *name,
936     size_t len)
937 {
938     UINT_PTR uidp = uid; /* convert to pointer size to pass as void* */
939     struct idmap_lookup lookup = { ATTR_UID, CLASS_USER, TYPE_INT, uid_cmp };
940     struct idmap_user user;
941     int status;
942 
943     dprintf(IDLVL, "--> nfs41_idmap_uid_to_name(%u)\n", uid);
944 
945     lookup.value = (const void*)uidp;
946 
947     /* look up the user entry */
948     status = idmap_lookup_user(context, &lookup, &user);
949     if (status) {
950         dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) "
951             "failed with %d\n", uid, status);
952         goto out;
953     }
954 
955     if (FAILED(StringCchCopyA(name, len, user.username))) {
956         status = ERROR_BUFFER_OVERFLOW;
957         eprintf("username buffer overflow: '%s' > %u\n",
958             user.username, len);
959         goto out;
960     }
961 
962     dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) "
963         "returning '%s'\n", uid, name);
964 out:
965     return status;
966 }
967 
968 /* principal -> uid, gid */
969 static int principal_cmp(const struct list_entry *list, const void *value)
970 {
971     const struct idmap_user *entry = list_container(list,
972         const struct idmap_user, entry);
973     const char *principal = (const char*)value;
974     return strcmp(entry->principal, principal);
975 }
976 
977 int nfs41_idmap_principal_to_ids(
978     struct idmap_context *context,
979     const char *principal,
980     uid_t *uid_out,
981     gid_t *gid_out)
982 {
983     struct idmap_lookup lookup = { ATTR_PRINCIPAL,
984         CLASS_USER, TYPE_STR, principal_cmp };
985     struct idmap_user user;
986     int status;
987 
988     dprintf(IDLVL, "--> nfs41_idmap_principal_to_ids('%s')\n", principal);
989 
990     lookup.value = principal;
991 
992     /* look up the user entry */
993     status = idmap_lookup_user(context, &lookup, &user);
994     if (status) {
995         dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') "
996             "failed with %d\n", principal, status);
997         goto out;
998     }
999 
1000     *uid_out = user.uid;
1001     *gid_out = user.gid;
1002     dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') "
1003         "returning uid=%u, gid=%u\n", principal, user.uid, user.gid);
1004 out:
1005     return status;
1006 }
1007 
1008 /* group -> gid */
1009 static int group_cmp(const struct list_entry *list, const void *value)
1010 {
1011     const struct idmap_group *entry = list_container(list,
1012         const struct idmap_group, entry);
1013     const char *group = (const char*)value;
1014     return strcmp(entry->name, group);
1015 }
1016 
1017 int nfs41_idmap_group_to_gid(
1018     struct idmap_context *context,
1019     const char *name,
1020     gid_t *gid_out)
1021 {
1022     struct idmap_lookup lookup = { ATTR_GROUP_NAME,
1023         CLASS_GROUP, TYPE_STR, group_cmp };
1024     struct idmap_group group;
1025     int status;
1026 
1027     dprintf(IDLVL, "--> nfs41_idmap_group_to_gid('%s')\n", name);
1028 
1029     lookup.value = name;
1030 
1031     /* look up the group entry */
1032     status = idmap_lookup_group(context, &lookup, &group);
1033     if (status) {
1034         dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') "
1035             "failed with %d\n", name, status);
1036         goto out;
1037     }
1038 
1039     *gid_out = group.gid;
1040     dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') "
1041         "returning %u\n", name, group.gid);
1042 out:
1043     return status;
1044 }
1045 
1046 /* gid -> group */
1047 static int gid_cmp(const struct list_entry *list, const void *value)
1048 {
1049     const struct idmap_group *entry = list_container(list,
1050         const struct idmap_group, entry);
1051     const UINT_PTR gid = (const UINT_PTR)value;
1052     return (UINT)gid - entry->gid;
1053 }
1054 
1055 int nfs41_idmap_gid_to_group(
1056     struct idmap_context *context,
1057     gid_t gid,
1058     char *name,
1059     size_t len)
1060 {
1061     UINT_PTR gidp = gid; /* convert to pointer size to pass as void* */
1062     struct idmap_lookup lookup = { ATTR_GID, CLASS_GROUP, TYPE_INT, gid_cmp };
1063     struct idmap_group group;
1064     int status;
1065 
1066     dprintf(IDLVL, "--> nfs41_idmap_gid_to_group(%u)\n", gid);
1067 
1068     lookup.value = (const void*)gidp;
1069 
1070     /* look up the group entry */
1071     status = idmap_lookup_group(context, &lookup, &group);
1072     if (status) {
1073         dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) "
1074             "failed with %d\n", gid, status);
1075         goto out;
1076     }
1077 
1078     if (FAILED(StringCchCopyA(name, len, group.name))) {
1079         status = ERROR_BUFFER_OVERFLOW;
1080         eprintf("group name buffer overflow: '%s' > %u\n",
1081             group.name, len);
1082         goto out;
1083     }
1084 
1085     dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) "
1086         "returning '%s'\n", gid, name);
1087 out:
1088     return status;
1089 }
1090