xref: /reactos/base/services/nfsd/idmap.c (revision 5e2fe089)
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         StringCchCopyA(config_path, ARRAYSIZE(config_path), "C:\\ReactOS\\system32\\drivers\\etc\\ms-nfs41-idmap.conf");
385     }
386 #endif
387 
388     /* load configuration from file */
389 #ifdef __REACTOS__
390     status = config_load(config, config_path);
391 #else
392     status = config_load(config, CONFIG_FILENAME);
393 #endif
394     if (status) {
395 #ifdef __REACTOS__
396         eprintf("config_load('%s') failed with %d\n", config_path, status);
397 #else
398         eprintf("config_load('%s') failed with %d\n", CONFIG_FILENAME, status);
399 #endif
400         goto out;
401     }
402 out:
403     return status;
404 }
405 
406 
407 /* generic cache */
408 typedef struct list_entry* (*entry_alloc_fn)();
409 typedef void (*entry_free_fn)(struct list_entry*);
410 typedef void (*entry_copy_fn)(struct list_entry*, const struct list_entry*);
411 
412 struct cache_ops {
413     entry_alloc_fn entry_alloc;
414     entry_free_fn entry_free;
415     entry_copy_fn entry_copy;
416 };
417 
418 struct idmap_cache {
419     struct list_entry head;
420     const struct cache_ops *ops;
421     SRWLOCK lock;
422 };
423 
424 
425 static void cache_init(
426     struct idmap_cache *cache,
427     const struct cache_ops *ops)
428 {
429     list_init(&cache->head);
430     cache->ops = ops;
431     InitializeSRWLock(&cache->lock);
432 }
433 
434 static void cache_cleanup(
435     struct idmap_cache *cache)
436 {
437     struct list_entry *entry, *tmp;
438     list_for_each_tmp(entry, tmp, &cache->head)
439         cache->ops->entry_free(entry);
440     list_init(&cache->head);
441 }
442 
443 static int cache_insert(
444     struct idmap_cache *cache,
445     const struct idmap_lookup *lookup,
446     const struct list_entry *src)
447 {
448     struct list_entry *entry;
449     int status = NO_ERROR;
450 
451     AcquireSRWLockExclusive(&cache->lock);
452 
453     /* search for an existing match */
454     entry = list_search(&cache->head, lookup->value, lookup->compare);
455     if (entry) {
456         /* overwrite the existing entry with the new results */
457         cache->ops->entry_copy(entry, src);
458         goto out;
459     }
460 
461     /* initialize a new entry and add it to the list */
462     entry = cache->ops->entry_alloc();
463     if (entry == NULL) {
464         status = GetLastError();
465         goto out;
466     }
467     cache->ops->entry_copy(entry, src);
468     list_add_head(&cache->head, entry);
469 out:
470     ReleaseSRWLockExclusive(&cache->lock);
471     return status;
472 }
473 
474 static int cache_lookup(
475     struct idmap_cache *cache,
476     const struct idmap_lookup *lookup,
477     struct list_entry *entry_out)
478 {
479     struct list_entry *entry;
480     int status = ERROR_NOT_FOUND;
481 
482     AcquireSRWLockShared(&cache->lock);
483 
484     entry = list_search(&cache->head, lookup->value, lookup->compare);
485     if (entry) {
486         /* make a copy for use outside of the lock */
487         cache->ops->entry_copy(entry_out, entry);
488         status = NO_ERROR;
489     }
490 
491     ReleaseSRWLockShared(&cache->lock);
492     return status;
493 }
494 
495 
496 /* user cache */
497 struct idmap_user {
498     struct list_entry entry;
499     char username[VAL_LEN];
500     char principal[VAL_LEN];
501     uid_t uid;
502     gid_t gid;
503     time_t last_updated;
504 };
505 
506 static struct list_entry* user_cache_alloc()
507 {
508     struct idmap_user *user = calloc(1, sizeof(struct idmap_user));
509     return user == NULL ? NULL : &user->entry;
510 }
511 static void user_cache_free(struct list_entry *entry)
512 {
513     free(list_container(entry, struct idmap_user, entry));
514 }
515 static void user_cache_copy(
516     struct list_entry *lhs,
517     const struct list_entry *rhs)
518 {
519     struct idmap_user *dst = list_container(lhs, struct idmap_user, entry);
520     const struct idmap_user *src = list_container(rhs, const struct idmap_user, entry);
521     StringCchCopyA(dst->username, VAL_LEN, src->username);
522     StringCchCopyA(dst->principal, VAL_LEN, src->principal);
523     dst->uid = src->uid;
524     dst->gid = src->gid;
525     dst->last_updated = src->last_updated;
526 }
527 static const struct cache_ops user_cache_ops = {
528     user_cache_alloc,
529     user_cache_free,
530     user_cache_copy
531 };
532 
533 
534 /* group cache */
535 struct idmap_group {
536     struct list_entry entry;
537     char name[VAL_LEN];
538     gid_t gid;
539     time_t last_updated;
540 };
541 
542 static struct list_entry* group_cache_alloc()
543 {
544     struct idmap_group *group = calloc(1, sizeof(struct idmap_group));
545     return group == NULL ? NULL : &group->entry;
546 }
547 static void group_cache_free(struct list_entry *entry)
548 {
549     free(list_container(entry, struct idmap_group, entry));
550 }
551 static void group_cache_copy(
552     struct list_entry *lhs,
553     const struct list_entry *rhs)
554 {
555     struct idmap_group *dst = list_container(lhs, struct idmap_group, entry);
556     const struct idmap_group *src = list_container(rhs, const struct idmap_group, entry);
557     StringCchCopyA(dst->name, VAL_LEN, src->name);
558     dst->gid = src->gid;
559     dst->last_updated = src->last_updated;
560 }
561 static const struct cache_ops group_cache_ops = {
562     group_cache_alloc,
563     group_cache_free,
564     group_cache_copy
565 };
566 
567 
568 /* ldap context */
569 struct idmap_context {
570     struct idmap_config config;
571     struct idmap_cache users;
572     struct idmap_cache groups;
573     LDAP *ldap;
574 };
575 
576 
577 static int idmap_filter(
578     struct idmap_config *config,
579     const struct idmap_lookup *lookup,
580     char *filter,
581     size_t filter_len)
582 {
583     UINT_PTR i;
584     int status = NO_ERROR;
585 
586     switch (lookup->type) {
587     case TYPE_INT:
588         i = (UINT_PTR)lookup->value;
589         if (FAILED(StringCchPrintfA(filter, filter_len,
590                 "(&(objectClass=%s)(%s=%u))",
591                 config->classes[lookup->klass],
592                 config->attributes[lookup->attr], (UINT)i))) {
593             status = ERROR_BUFFER_OVERFLOW;
594             eprintf("ldap filter buffer overflow: '%s=%u'\n",
595                 config->attributes[lookup->attr], (UINT)i);
596         }
597         break;
598 
599     case TYPE_STR:
600         if (FAILED(StringCchPrintfA(filter, filter_len,
601                 "(&(objectClass=%s)(%s=%s))",
602                 config->classes[lookup->klass],
603                 config->attributes[lookup->attr], lookup->value))) {
604             status = ERROR_BUFFER_OVERFLOW;
605             eprintf("ldap filter buffer overflow: '%s=%s'\n",
606                 config->attributes[lookup->attr], lookup->value);
607         }
608         break;
609 
610     default:
611         status = ERROR_INVALID_PARAMETER;
612         break;
613     }
614     return status;
615 }
616 
617 static int idmap_query_attrs(
618     struct idmap_context *context,
619     const struct idmap_lookup *lookup,
620     const unsigned attributes,
621     const unsigned optional,
622     PCHAR *values[],
623     const int len)
624 {
625     char filter[FILTER_LEN];
626     struct idmap_config *config = &context->config;
627     LDAPMessage *res = NULL, *entry;
628     int i, status;
629 
630     /* format the ldap filter */
631     status = idmap_filter(config, lookup, filter, FILTER_LEN);
632     if (status)
633         goto out;
634 
635     /* send the ldap query */
636     status = ldap_search_st(context->ldap, config->base,
637         LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, &res);
638     if (status) {
639         eprintf("ldap search for '%s' failed with %d: %s\n",
640             filter, status, ldap_err2stringA(status));
641         status = LdapMapErrorToWin32(status);
642         goto out;
643     }
644 
645     entry = ldap_first_entry(context->ldap, res);
646     if (entry == NULL) {
647         status = LDAP_NO_RESULTS_RETURNED;
648         eprintf("ldap search for '%s' failed with %d: %s\n",
649             filter, status, ldap_err2stringA(status));
650         status = LdapMapErrorToWin32(status);
651         goto out;
652     }
653 
654     /* fetch the attributes */
655     for (i = 0; i < len; i++) {
656         if (ATTR_ISSET(attributes, i)) {
657             values[i] = ldap_get_values(context->ldap,
658                 entry, config->attributes[i]);
659 
660             /* fail if required attributes are missing */
661             if (values[i] == NULL && !ATTR_ISSET(optional, i)) {
662                 status = LDAP_NO_SUCH_ATTRIBUTE;
663                 eprintf("ldap entry for '%s' missing required "
664                     "attribute '%s', returning %d: %s\n",
665                     filter, config->attributes[i],
666                     status, ldap_err2stringA(status));
667                 status = LdapMapErrorToWin32(status);
668                 goto out;
669             }
670         }
671     }
672 out:
673     if (res) ldap_msgfree(res);
674     return status;
675 }
676 
677 static int idmap_lookup_user(
678     struct idmap_context *context,
679     const struct idmap_lookup *lookup,
680     struct idmap_user *user)
681 {
682     PCHAR* values[NUM_ATTRIBUTES] = { NULL };
683     const unsigned attributes = ATTR_FLAG(ATTR_USER_NAME)
684         | ATTR_FLAG(ATTR_PRINCIPAL)
685         | ATTR_FLAG(ATTR_UID)
686         | ATTR_FLAG(ATTR_GID);
687     /* principal is optional; we'll cache it if we have it */
688     const unsigned optional = ATTR_FLAG(ATTR_PRINCIPAL);
689     int i, status;
690 
691     /* check the user cache for an existing entry */
692     status = cache_lookup(&context->users, lookup, &user->entry);
693     if (status == NO_ERROR) {
694         /* don't return expired entries; query new attributes
695          * and overwrite the entry with cache_insert() */
696         if (time(NULL) - user->last_updated < context->config.cache_ttl)
697             goto out;
698     }
699 
700     /* send the query to the ldap server */
701     status = idmap_query_attrs(context, lookup,
702         attributes, optional, values, NUM_ATTRIBUTES);
703     if (status)
704         goto out_free_values;
705 
706     /* parse attributes */
707     if (FAILED(StringCchCopyA(user->username, VAL_LEN,
708             *values[ATTR_USER_NAME]))) {
709         eprintf("ldap attribute %s='%s' longer than %u characters\n",
710             context->config.attributes[ATTR_USER_NAME],
711             *values[ATTR_USER_NAME], VAL_LEN);
712         status = ERROR_BUFFER_OVERFLOW;
713         goto out_free_values;
714     }
715     if (FAILED(StringCchCopyA(user->principal, VAL_LEN,
716             values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : ""))) {
717         eprintf("ldap attribute %s='%s' longer than %u characters\n",
718             context->config.attributes[ATTR_PRINCIPAL],
719             values[ATTR_PRINCIPAL] ? *values[ATTR_PRINCIPAL] : "", VAL_LEN);
720         status = ERROR_BUFFER_OVERFLOW;
721         goto out_free_values;
722     }
723     if (!parse_uint(*values[ATTR_UID], &user->uid)) {
724         eprintf("failed to parse ldap attribute %s='%s'\n",
725             context->config.attributes[ATTR_UID], *values[ATTR_UID]);
726         status = ERROR_INVALID_PARAMETER;
727         goto out_free_values;
728     }
729     if (!parse_uint(*values[ATTR_GID], &user->gid)) {
730         eprintf("failed to parse ldap attribute %s='%s'\n",
731             context->config.attributes[ATTR_GID], *values[ATTR_GID]);
732         status = ERROR_INVALID_PARAMETER;
733         goto out_free_values;
734     }
735     user->last_updated = time(NULL);
736 
737     if (context->config.cache_ttl) {
738         /* insert the entry into the cache */
739         cache_insert(&context->users, lookup, &user->entry);
740     }
741 out_free_values:
742     for (i = 0; i < NUM_ATTRIBUTES; i++)
743         ldap_value_free(values[i]);
744 out:
745     return status;
746 }
747 
748 static int idmap_lookup_group(
749     struct idmap_context *context,
750     const struct idmap_lookup *lookup,
751     struct idmap_group *group)
752 {
753     PCHAR* values[NUM_ATTRIBUTES] = { NULL };
754     const unsigned attributes = ATTR_FLAG(ATTR_GROUP_NAME)
755         | ATTR_FLAG(ATTR_GID);
756     int i, status;
757 
758     /* check the group cache for an existing entry */
759     status = cache_lookup(&context->groups, lookup, &group->entry);
760     if (status == NO_ERROR) {
761         /* don't return expired entries; query new attributes
762          * and overwrite the entry with cache_insert() */
763         if (time(NULL) - group->last_updated < context->config.cache_ttl)
764             goto out;
765     }
766 
767     /* send the query to the ldap server */
768     status = idmap_query_attrs(context, lookup,
769         attributes, 0, values, NUM_ATTRIBUTES);
770     if (status)
771         goto out_free_values;
772 
773     /* parse attributes */
774     if (FAILED(StringCchCopyA(group->name, VAL_LEN,
775             *values[ATTR_GROUP_NAME]))) {
776         eprintf("ldap attribute %s='%s' longer than %u characters\n",
777             context->config.attributes[ATTR_GROUP_NAME],
778             *values[ATTR_GROUP_NAME], VAL_LEN);
779         status = ERROR_BUFFER_OVERFLOW;
780         goto out_free_values;
781     }
782     if (!parse_uint(*values[ATTR_GID], &group->gid)) {
783         eprintf("failed to parse ldap attribute %s='%s'\n",
784             context->config.attributes[ATTR_GID], *values[ATTR_GID]);
785         status = ERROR_INVALID_PARAMETER;
786         goto out_free_values;
787     }
788     group->last_updated = time(NULL);
789 
790     if (context->config.cache_ttl) {
791         /* insert the entry into the cache */
792         cache_insert(&context->groups, lookup, &group->entry);
793     }
794 out_free_values:
795     for (i = 0; i < NUM_ATTRIBUTES; i++)
796         ldap_value_free(values[i]);
797 out:
798     return status;
799 }
800 
801 
802 /* public idmap interface */
803 int nfs41_idmap_create(
804     struct idmap_context **context_out)
805 {
806     struct idmap_context *context;
807     int status = NO_ERROR;
808 
809     context = calloc(1, sizeof(struct idmap_context));
810     if (context == NULL) {
811         status = GetLastError();
812         goto out;
813     }
814 
815     /* initialize the caches */
816     cache_init(&context->users, &user_cache_ops);
817     cache_init(&context->groups, &group_cache_ops);
818 
819     /* load ldap configuration from file */
820     status = config_init(&context->config);
821     if (status) {
822         eprintf("config_init() failed with %d\n", status);
823         goto out_err_free;
824     }
825 
826     /* initialize ldap and configure options */
827     context->ldap = ldap_init(context->config.hostname, context->config.port);
828     if (context->ldap == NULL) {
829         status = LdapGetLastError();
830         eprintf("ldap_init(%s) failed with %d: %s\n",
831             context->config.hostname, status, ldap_err2stringA(status));
832         status = LdapMapErrorToWin32(status);
833         goto out_err_free;
834     }
835 
836     status = ldap_set_option(context->ldap, LDAP_OPT_PROTOCOL_VERSION,
837         (void *)&context->config.version);
838     if (status != LDAP_SUCCESS) {
839         eprintf("ldap_set_option(version=%d) failed with %d\n",
840             context->config.version, status);
841         status = LdapMapErrorToWin32(status);
842         goto out_err_free;
843     }
844 
845     if (context->config.timeout) {
846         status = ldap_set_option(context->ldap, LDAP_OPT_TIMELIMIT,
847             (void *)&context->config.timeout);
848         if (status != LDAP_SUCCESS) {
849             eprintf("ldap_set_option(timeout=%d) failed with %d\n",
850                 context->config.timeout, status);
851             status = LdapMapErrorToWin32(status);
852             goto out_err_free;
853         }
854     }
855 
856     *context_out = context;
857 out:
858     return status;
859 
860 out_err_free:
861     nfs41_idmap_free(context);
862     goto out;
863 }
864 
865 void nfs41_idmap_free(
866     struct idmap_context *context)
867 {
868     /* clean up the connection */
869     if (context->ldap)
870         ldap_unbind(context->ldap);
871 
872     cache_cleanup(&context->users);
873     cache_cleanup(&context->groups);
874     free(context);
875 }
876 
877 
878 /* username -> uid, gid */
879 static int username_cmp(const struct list_entry *list, const void *value)
880 {
881     const struct idmap_user *entry = list_container(list,
882         const struct idmap_user, entry);
883     const char *username = (const char*)value;
884     return strcmp(entry->username, username);
885 }
886 
887 int nfs41_idmap_name_to_ids(
888     struct idmap_context *context,
889     const char *username,
890     uid_t *uid_out,
891     gid_t *gid_out)
892 {
893     struct idmap_lookup lookup = { ATTR_USER_NAME,
894         CLASS_USER, TYPE_STR, username_cmp };
895     struct idmap_user user;
896     int status;
897 
898     if (context == NULL)
899         return ERROR_FILE_NOT_FOUND;
900 
901     dprintf(IDLVL, "--> nfs41_idmap_name_to_ids('%s')\n", username);
902 
903     lookup.value = username;
904 
905     /* look up the user entry */
906     status = idmap_lookup_user(context, &lookup, &user);
907     if (status) {
908         dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') "
909             "failed with %d\n", username, status);
910         goto out;
911     }
912 
913     *uid_out = user.uid;
914     *gid_out = user.gid;
915     dprintf(IDLVL, "<-- nfs41_idmap_name_to_ids('%s') "
916         "returning uid=%u, gid=%u\n", username, user.uid, user.gid);
917 out:
918     return status;
919 }
920 
921 /* uid -> username */
922 static int uid_cmp(const struct list_entry *list, const void *value)
923 {
924     const struct idmap_user *entry = list_container(list,
925         const struct idmap_user, entry);
926     const UINT_PTR uid = (const UINT_PTR)value;
927     return (UINT)uid - entry->uid;
928 }
929 
930 int nfs41_idmap_uid_to_name(
931     struct idmap_context *context,
932     uid_t uid,
933     char *name,
934     size_t len)
935 {
936     UINT_PTR uidp = uid; /* convert to pointer size to pass as void* */
937     struct idmap_lookup lookup = { ATTR_UID, CLASS_USER, TYPE_INT, uid_cmp };
938     struct idmap_user user;
939     int status;
940 
941     dprintf(IDLVL, "--> nfs41_idmap_uid_to_name(%u)\n", uid);
942 
943     lookup.value = (const void*)uidp;
944 
945     /* look up the user entry */
946     status = idmap_lookup_user(context, &lookup, &user);
947     if (status) {
948         dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) "
949             "failed with %d\n", uid, status);
950         goto out;
951     }
952 
953     if (FAILED(StringCchCopyA(name, len, user.username))) {
954         status = ERROR_BUFFER_OVERFLOW;
955         eprintf("username buffer overflow: '%s' > %u\n",
956             user.username, len);
957         goto out;
958     }
959 
960     dprintf(IDLVL, "<-- nfs41_idmap_uid_to_name(%u) "
961         "returning '%s'\n", uid, name);
962 out:
963     return status;
964 }
965 
966 /* principal -> uid, gid */
967 static int principal_cmp(const struct list_entry *list, const void *value)
968 {
969     const struct idmap_user *entry = list_container(list,
970         const struct idmap_user, entry);
971     const char *principal = (const char*)value;
972     return strcmp(entry->principal, principal);
973 }
974 
975 int nfs41_idmap_principal_to_ids(
976     struct idmap_context *context,
977     const char *principal,
978     uid_t *uid_out,
979     gid_t *gid_out)
980 {
981     struct idmap_lookup lookup = { ATTR_PRINCIPAL,
982         CLASS_USER, TYPE_STR, principal_cmp };
983     struct idmap_user user;
984     int status;
985 
986     dprintf(IDLVL, "--> nfs41_idmap_principal_to_ids('%s')\n", principal);
987 
988     lookup.value = principal;
989 
990     /* look up the user entry */
991     status = idmap_lookup_user(context, &lookup, &user);
992     if (status) {
993         dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') "
994             "failed with %d\n", principal, status);
995         goto out;
996     }
997 
998     *uid_out = user.uid;
999     *gid_out = user.gid;
1000     dprintf(IDLVL, "<-- nfs41_idmap_principal_to_ids('%s') "
1001         "returning uid=%u, gid=%u\n", principal, user.uid, user.gid);
1002 out:
1003     return status;
1004 }
1005 
1006 /* group -> gid */
1007 static int group_cmp(const struct list_entry *list, const void *value)
1008 {
1009     const struct idmap_group *entry = list_container(list,
1010         const struct idmap_group, entry);
1011     const char *group = (const char*)value;
1012     return strcmp(entry->name, group);
1013 }
1014 
1015 int nfs41_idmap_group_to_gid(
1016     struct idmap_context *context,
1017     const char *name,
1018     gid_t *gid_out)
1019 {
1020     struct idmap_lookup lookup = { ATTR_GROUP_NAME,
1021         CLASS_GROUP, TYPE_STR, group_cmp };
1022     struct idmap_group group;
1023     int status;
1024 
1025     dprintf(IDLVL, "--> nfs41_idmap_group_to_gid('%s')\n", name);
1026 
1027     lookup.value = name;
1028 
1029     /* look up the group entry */
1030     status = idmap_lookup_group(context, &lookup, &group);
1031     if (status) {
1032         dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') "
1033             "failed with %d\n", name, status);
1034         goto out;
1035     }
1036 
1037     *gid_out = group.gid;
1038     dprintf(IDLVL, "<-- nfs41_idmap_group_to_gid('%s') "
1039         "returning %u\n", name, group.gid);
1040 out:
1041     return status;
1042 }
1043 
1044 /* gid -> group */
1045 static int gid_cmp(const struct list_entry *list, const void *value)
1046 {
1047     const struct idmap_group *entry = list_container(list,
1048         const struct idmap_group, entry);
1049     const UINT_PTR gid = (const UINT_PTR)value;
1050     return (UINT)gid - entry->gid;
1051 }
1052 
1053 int nfs41_idmap_gid_to_group(
1054     struct idmap_context *context,
1055     gid_t gid,
1056     char *name,
1057     size_t len)
1058 {
1059     UINT_PTR gidp = gid; /* convert to pointer size to pass as void* */
1060     struct idmap_lookup lookup = { ATTR_GID, CLASS_GROUP, TYPE_INT, gid_cmp };
1061     struct idmap_group group;
1062     int status;
1063 
1064     dprintf(IDLVL, "--> nfs41_idmap_gid_to_group(%u)\n", gid);
1065 
1066     lookup.value = (const void*)gidp;
1067 
1068     /* look up the group entry */
1069     status = idmap_lookup_group(context, &lookup, &group);
1070     if (status) {
1071         dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) "
1072             "failed with %d\n", gid, status);
1073         goto out;
1074     }
1075 
1076     if (FAILED(StringCchCopyA(name, len, group.name))) {
1077         status = ERROR_BUFFER_OVERFLOW;
1078         eprintf("group name buffer overflow: '%s' > %u\n",
1079             group.name, len);
1080         goto out;
1081     }
1082 
1083     dprintf(IDLVL, "<-- nfs41_idmap_gid_to_group(%u) "
1084         "returning '%s'\n", gid, name);
1085 out:
1086     return status;
1087 }
1088