Cygwin: strptime: add release note
[newlib-cygwin.git] / winsup / cygwin / ldap.cc
blobed2fa3f7f4c88b11fb29d8bcef7f919db77ef990
1 /* ldap.cc: Helper functions for ldap access to Active Directory.
3 This file is part of Cygwin.
5 This software is a copyrighted work licensed under the terms of the
6 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
7 details. */
9 #include "winsup.h"
10 #include <lm.h>
11 #include <dsgetdc.h>
12 #include <iptypes.h>
13 #include <sys/param.h>
14 #include "ldap.h"
15 #include "cygerrno.h"
16 #include "security.h"
17 #include "path.h"
18 #include "fhandler.h"
19 #include "dtable.h"
20 #include "cygheap.h"
21 #include "registry.h"
22 #include "pinfo.h"
23 #include "tls_pbuf.h"
25 #define CYG_LDAP_ENUM_PAGESIZE 100 /* entries per page */
27 static PWCHAR rootdse_attr[] =
29 (PWCHAR) L"defaultNamingContext",
30 (PWCHAR) L"supportedCapabilities",
31 NULL
34 static const PCWSTR std_user_attr[] =
36 L"sAMAccountName",
37 L"objectSid",
38 L"primaryGroupID",
39 L"uidNumber",
40 L"profilePath",
41 L"cygwinUnixUid", /* TODO */
42 /* windows scheme */
43 L"displayName",
44 L"homeDrive",
45 L"homeDirectory",
46 /* cygwin scheme */
47 L"cygwinGecos",
48 L"cygwinHome",
49 L"cygwinShell",
50 /* unix scheme */
51 L"gecos",
52 L"unixHomeDirectory",
53 L"loginShell",
54 /* desc scheme */
55 L"description"
58 static PWCHAR group_attr[] =
60 (PWCHAR) L"sAMAccountName",
61 (PWCHAR) L"objectSid",
62 (PWCHAR) L"gidNumber",
63 (PWCHAR) L"cygwinUnixGid", /* TODO */
64 NULL
67 PWCHAR tdom_attr[] =
69 (PWCHAR) L"trustPosixOffset",
70 NULL
73 PWCHAR sid_attr[] =
75 (PWCHAR) L"objectSid",
76 NULL
79 PWCHAR rfc2307_uid_attr[] =
81 (PWCHAR) L"uid",
82 NULL
85 PWCHAR rfc2307_gid_attr[] =
87 (PWCHAR) L"cn",
88 NULL
91 /* ================================================================= */
92 /* Helper method of cygheap_pwdgrp class. It sets the user attribs */
93 /* from the settings in nsswitch.conf. */
94 /* ================================================================= */
96 #define user_attr (cygheap->pg.ldap_user_attr)
98 void
99 cygheap_pwdgrp::init_ldap_user_attr ()
101 ldap_user_attr = (PWCHAR *)
102 ccalloc_abort (HEAP_BUF, sizeof (std_user_attr) / sizeof (*std_user_attr)
103 + 3 * NSS_SCHEME_MAX + 1, sizeof (PWCHAR));
104 memcpy (ldap_user_attr, std_user_attr, sizeof (std_user_attr));
105 uint16_t freeattr_idx = sizeof (std_user_attr) / sizeof (*std_user_attr);
106 for (uint16_t idx = 0; idx < NSS_SCHEME_MAX; ++idx)
108 if (home_scheme[idx].method == NSS_SCHEME_FREEATTR)
109 ldap_user_attr[freeattr_idx++] = home_scheme[idx].attrib;
110 if (shell_scheme[idx].method == NSS_SCHEME_FREEATTR)
111 ldap_user_attr[freeattr_idx++] = shell_scheme[idx].attrib;
112 if (gecos_scheme[idx].method == NSS_SCHEME_FREEATTR)
113 ldap_user_attr[freeattr_idx++] = gecos_scheme[idx].attrib;
117 /* ================================================================= */
118 /* Helper methods. */
119 /* ================================================================= */
121 inline int
122 cyg_ldap::map_ldaperr_to_errno (ULONG lerr)
124 switch (lerr)
126 case LDAP_SUCCESS:
127 return NO_ERROR;
128 case LDAP_NO_RESULTS_RETURNED:
129 /* LdapMapErrorToWin32 maps LDAP_NO_RESULTS_RETURNED to ERROR_MORE_DATA,
130 which in turn is mapped to EMSGSIZE by geterrno_from_win_error. This
131 is SO wrong, especially considering that LDAP_MORE_RESULTS_TO_RETURN
132 is mapped to ERROR_MORE_DATA as well :-P */
133 return ENMFILE;
134 default:
135 break;
137 return geterrno_from_win_error (LdapMapErrorToWin32 (lerr));
140 inline int
141 cyg_ldap::wait (cygthread *thr)
143 if (!thr)
144 return EIO;
145 if (cygwait (*thr, cw_infinite, cw_sig | cw_sig_restart) != WAIT_OBJECT_0)
147 thr->terminate_thread ();
148 return EIO;
150 thr->detach ();
151 return 0;
154 /* ================================================================= */
155 /* Helper struct and functions for interruptible LDAP initalization. */
156 /* ================================================================= */
158 struct cyg_ldap_init {
159 cyg_ldap *that;
160 PCWSTR domain;
161 bool ssl;
162 ULONG ret;
165 ULONG
166 cyg_ldap::connect_ssl (PCWSTR domain)
168 ULONG ret;
170 if (!(lh = ldap_sslinitW ((PWCHAR) domain, LDAP_SSL_PORT, 1)))
172 debug_printf ("ldap_init(%W) error 0x%02x", domain, LdapGetLastError ());
173 return LdapGetLastError ();
175 if ((ret = ldap_bind_s (lh, NULL, NULL, LDAP_AUTH_NEGOTIATE)) != LDAP_SUCCESS)
176 debug_printf ("ldap_bind(%W) 0x%02x", domain, ret);
177 else if ((ret = ldap_search_sW (lh, NULL, LDAP_SCOPE_BASE,
178 (PWCHAR) L"(objectclass=*)", rootdse_attr,
179 0, &msg))
180 != LDAP_SUCCESS)
181 debug_printf ("ldap_search(%W, ROOTDSE) error 0x%02x", domain, ret);
182 return ret;
185 ULONG
186 cyg_ldap::connect_non_ssl (PCWSTR domain)
188 ULONG ret;
190 if (!(lh = ldap_initW ((PWCHAR) domain, LDAP_PORT)))
192 debug_printf ("ldap_init(%W) error 0x%02x", domain, LdapGetLastError ());
193 return LdapGetLastError ();
195 if ((ret = ldap_set_option (lh, LDAP_OPT_SIGN, LDAP_OPT_ON))
196 != LDAP_SUCCESS)
197 debug_printf ("ldap_set_option(LDAP_OPT_SIGN) error 0x%02x", ret);
198 if ((ret = ldap_set_option (lh, LDAP_OPT_ENCRYPT, LDAP_OPT_ON))
199 != LDAP_SUCCESS)
200 debug_printf ("ldap_set_option(LDAP_OPT_ENCRYPT) error 0x%02x", ret);
201 if ((ret = ldap_bind_s (lh, NULL, NULL, LDAP_AUTH_NEGOTIATE)) != LDAP_SUCCESS)
202 debug_printf ("ldap_bind(%W) 0x%02x", domain, ret);
203 else if ((ret = ldap_search_sW (lh, NULL, LDAP_SCOPE_BASE,
204 (PWCHAR) L"(objectclass=*)", rootdse_attr,
205 0, &msg))
206 != LDAP_SUCCESS)
207 debug_printf ("ldap_search(%W, ROOTDSE) error 0x%02x", domain, ret);
208 return ret;
211 static DWORD
212 ldap_init_thr (LPVOID param)
214 cyg_ldap_init *cl = (cyg_ldap_init *) param;
215 cl->ret = cl->ssl ? cl->that->connect_ssl (cl->domain)
216 : cl->that->connect_non_ssl (cl->domain);
217 return 0;
220 inline int
221 cyg_ldap::connect (PCWSTR domain)
223 /* FIXME? connect_ssl can take ages even when failing, so we're trying to
224 do everything the non-SSL (but still encrypted) way. */
225 cyg_ldap_init cl = { this, domain, false, NO_ERROR };
226 cygthread *thr = new cygthread (ldap_init_thr, &cl, "ldap_init");
227 return wait (thr) ?: map_ldaperr_to_errno (cl.ret);
230 /* ================================================================= */
231 /* Helper struct and functions for interruptible LDAP search. */
232 /* ================================================================= */
234 struct cyg_ldap_search {
235 cyg_ldap *that;
236 PWCHAR base;
237 ULONG scope;
238 PWCHAR filter;
239 PWCHAR *attrs;
240 ULONG ret;
243 ULONG
244 cyg_ldap::search_s (PWCHAR base, ULONG scope, PWCHAR filter, PWCHAR *attrs)
246 ULONG ret;
248 if ((ret = ldap_search_sW (lh, base, scope, filter, attrs, 0, &msg))
249 != LDAP_SUCCESS)
250 debug_printf ("ldap_search_sW(%W,%W) error 0x%02x", base, filter, ret);
251 return ret;
254 static DWORD
255 ldap_search_thr (LPVOID param)
257 cyg_ldap_search *cl = (cyg_ldap_search *) param;
258 cl->ret = cl->that->search_s (cl->base, cl->scope, cl->filter, cl->attrs);
259 return 0;
262 inline int
263 cyg_ldap::search (PWCHAR base, ULONG scope, PWCHAR filter, PWCHAR *attrs)
265 cyg_ldap_search cl = { this, base, scope, filter, attrs, NO_ERROR };
266 cygthread *thr = new cygthread (ldap_search_thr, &cl, "ldap_search");
267 return wait (thr) ?: map_ldaperr_to_errno (cl.ret);
270 /* ================================================================= */
271 /* Helper struct and functions for interruptible LDAP page search. */
272 /* ================================================================= */
274 struct cyg_ldap_next_page {
275 cyg_ldap *that;
276 ULONG ret;
279 ULONG
280 cyg_ldap::next_page_s ()
282 ULONG total;
283 ULONG ret;
287 ret = ldap_get_next_page_s (lh, srch_id, NULL, CYG_LDAP_ENUM_PAGESIZE,
288 &total, &msg);
290 while (ret == LDAP_SUCCESS && ldap_count_entries (lh, msg) == 0);
291 if (ret && ret != LDAP_NO_RESULTS_RETURNED)
292 debug_printf ("ldap_result() error 0x%02x", ret);
293 return ret;
296 static DWORD
297 ldap_next_page_thr (LPVOID param)
299 cyg_ldap_next_page *cl = (cyg_ldap_next_page *) param;
300 cl->ret = cl->that->next_page_s ();
301 return 0;
304 inline int
305 cyg_ldap::next_page ()
307 cyg_ldap_next_page cl = { this, NO_ERROR };
308 cygthread *thr = new cygthread (ldap_next_page_thr, &cl, "ldap_next_page");
309 return wait (thr) ?: map_ldaperr_to_errno (cl.ret);
312 /* ================================================================= */
313 /* Public methods. */
314 /* ================================================================= */
317 cyg_ldap::open (PCWSTR domain)
319 int ret = NO_ERROR;
321 /* Already open? */
322 if (lh)
323 return NO_ERROR;
325 if ((ret = connect (domain)) != NO_ERROR)
326 goto err;
327 /* Prime `ret' and fetch ROOTDSE search result. */
328 ret = EIO;
329 if (!(entry = ldap_first_entry (lh, msg)))
331 debug_printf ("No ROOTDSE entry for %W", domain);
332 goto err;
334 if (!(val = ldap_get_valuesW (lh, entry, rootdse_attr[0])))
336 debug_printf ("No %W value for %W", rootdse_attr[0], domain);
337 goto err;
339 if (!(def_context = wcsdup (val[0])))
341 debug_printf ("wcsdup(%W, %W) %d", domain, rootdse_attr[0],
342 get_errno ());
343 goto err;
345 ldap_value_freeW (val);
346 if ((val = ldap_get_valuesW (lh, entry, rootdse_attr[1])))
348 for (ULONG idx = 0; idx < ldap_count_valuesW (val); ++idx)
349 if (!wcscmp (val[idx], LDAP_CAP_ACTIVE_DIRECTORY_OID_W))
351 isAD = true;
352 break;
355 ldap_value_freeW (val);
356 val = NULL;
357 ldap_msgfree (msg);
358 msg = entry = NULL;
359 return NO_ERROR;
360 err:
361 close ();
362 return ret;
365 void
366 cyg_ldap::close ()
368 if (srch_id != NULL)
369 ldap_search_abandon_page (lh, srch_id);
370 if (lh)
371 ldap_unbind (lh);
372 if (msg)
373 ldap_msgfree (msg);
374 if (val)
375 ldap_value_freeW (val);
376 if (def_context)
377 free (def_context);
378 lh = NULL;
379 msg = entry = NULL;
380 val = NULL;
381 def_context = NULL;
382 srch_id = NULL;
383 last_fetched_sid = NO_SID;
386 PWCHAR
387 cyg_ldap::get_string_attribute (PCWSTR name)
389 if (val)
390 ldap_value_freeW (val);
391 val = ldap_get_valuesW (lh, entry, (PWCHAR) name);
392 if (val)
393 return val[0];
394 return NULL;
397 uint32_t
398 cyg_ldap::get_num_attribute (PCWSTR name)
400 PWCHAR ret = get_string_attribute (name);
401 if (ret)
402 return (uint32_t) wcstoul (ret, NULL, 10);
403 return (uint32_t) -1;
406 #define ACCOUNT_FILTER_START L"(&(|(&(objectCategory=Person)" \
407 "(objectClass=User))" \
408 "(objectClass=Group))" \
409 "(objectSid="
411 #define ACCOUNT_FILTER_END L"))"
413 bool
414 cyg_ldap::fetch_ad_account (PSID sid, bool group, PCWSTR domain)
416 WCHAR filter[sizeof (ACCOUNT_FILTER_START) + sizeof (ACCOUNT_FILTER_END)
417 + 3 * SECURITY_MAX_SID_SIZE + 1];
418 PWCHAR f, base = NULL;
419 LONG len = (LONG) RtlLengthSid (sid);
420 PBYTE s = (PBYTE) sid;
421 static WCHAR hex_wchars[] = L"0123456789abcdef";
422 tmp_pathbuf tp;
424 if (last_fetched_sid == sid)
425 return true;
427 if (open (NULL) != NO_ERROR)
428 return false;
430 if (msg)
432 ldap_msgfree (msg);
433 msg = entry = NULL;
435 if (val)
437 ldap_value_freeW (val);
438 val = NULL;
440 f = wcpcpy (filter, ACCOUNT_FILTER_START);
441 while (len-- > 0)
443 *f++ = L'\\';
444 *f++ = hex_wchars[*s >> 4];
445 *f++ = hex_wchars[*s++ & 0xf];
447 wcpcpy (f, ACCOUNT_FILTER_END);
448 if (domain)
450 /* FIXME: This is a hack. The most correct solution is probably to
451 open a connection to the DC of the trusted domain. But this always
452 takes extra time, so we're trying to avoid it. If this results in
453 problems, we know what to do. */
454 base = tp.w_get ();
455 PWCHAR b = base;
456 for (PCWSTR dotp = domain; dotp && *dotp; domain = dotp)
458 dotp = wcschr (domain, L'.');
459 if (b > base)
460 *b++ = L',';
461 b = wcpcpy (b, L"DC=");
462 b = dotp ? wcpncpy (b, domain, dotp++ - domain) : wcpcpy (b, domain);
464 debug_printf ("naming context <%W>", base);
466 else
468 /* def_context is only valid after open. */
469 base = def_context;
471 if (!user_attr)
472 cygheap->pg.init_ldap_user_attr ();
473 attr = group ? group_attr : user_attr;
474 if (search (base, LDAP_SCOPE_SUBTREE, filter, attr) != 0)
475 return false;
476 if (!(entry = ldap_first_entry (lh, msg)))
478 debug_printf ("No entry for %W in base %W", filter, base);
479 return false;
481 last_fetched_sid = sid;
482 return true;
486 cyg_ldap::enumerate_ad_accounts (PCWSTR domain, bool group)
488 int ret;
489 tmp_pathbuf tp;
490 PCWSTR filter;
492 close ();
493 if ((ret = open (domain)) != NO_ERROR)
494 return ret;
496 if (!group)
497 filter = L"(&(objectCategory=Person)"
498 "(objectClass=User)"
499 /* 512 == ADS_UF_NORMAL_ACCOUNT
500 Without checking this flag we'd enumerate undesired accounts
501 like, e.g., interdomain trusts. */
502 "(userAccountControl:" LDAP_MATCHING_RULE_BIT_AND ":=512)"
503 "(objectSid=*))";
504 else if (!domain)
505 /* From the local domain, we fetch well-known groups. */
506 filter = L"(&(objectClass=Group)"
507 "(objectSid=*))";
508 else
509 /* From foreign domains, we don't. */
510 filter = L"(&(objectClass=Group)"
511 /* 1 == BUILTIN_LOCAL_GROUP */
512 "(!(groupType:" LDAP_MATCHING_RULE_BIT_AND ":=1))"
513 "(objectSid=*))";
514 if (!user_attr)
515 cygheap->pg.init_ldap_user_attr ();
516 attr = group ? group_attr : user_attr;
517 srch_id = ldap_search_init_pageW (lh, def_context, LDAP_SCOPE_SUBTREE,
518 (PWCHAR) filter, attr, 0, NULL, NULL,
519 INFINITE, CYG_LDAP_ENUM_PAGESIZE, NULL);
520 if (srch_id == NULL)
522 debug_printf ("ldap_search_init_pageW(%W,%W) error 0x%02x",
523 def_context, filter, LdapGetLastError ());
524 return map_ldaperr_to_errno (LdapGetLastError ());
526 return NO_ERROR;
530 cyg_ldap::next_account (cygsid &sid)
532 ULONG ret;
533 PLDAP_BERVAL *bval;
535 if (entry)
537 if ((entry = ldap_next_entry (lh, entry))
538 && (bval = ldap_get_values_lenW (lh, entry, (PWCHAR) L"objectSid")))
540 last_fetched_sid = sid = (PSID) bval[0]->bv_val;
541 ldap_value_free_len (bval);
542 return NO_ERROR;
544 ldap_msgfree (msg);
545 msg = entry = NULL;
547 ret = next_page ();
548 if (ret == NO_ERROR)
550 if ((entry = ldap_first_entry (lh, msg))
551 && (bval = ldap_get_values_lenW (lh, entry, (PWCHAR) L"objectSid")))
553 last_fetched_sid = sid = (PSID) bval[0]->bv_val;
554 ldap_value_free_len (bval);
555 return NO_ERROR;
557 ret = EIO;
559 ldap_search_abandon_page (lh, srch_id);
560 srch_id = NULL;
561 return ret;
564 #define SYSTEM_CONTAINER L"CN=System,"
566 #define PSX_OFFSET_FILTER L"(&(objectClass=trustedDomain)(name=%W))"
567 #define PSX_OFFSET_FILTER_FLAT L"(&(objectClass=trustedDomain)(flatName=%W))"
569 /* Return UINT32_MAX on error to allow differing between not being able
570 to fetch a value and a real 0 offset. */
571 uint32_t
572 cyg_ldap::fetch_posix_offset_for_domain (PCWSTR domain)
574 WCHAR base[wcslen (def_context) + sizeof (SYSTEM_CONTAINER) / sizeof (WCHAR)];
575 WCHAR filter[sizeof (PSX_OFFSET_FILTER_FLAT) + wcslen (domain) + 1];
577 if (msg)
579 ldap_msgfree (msg);
580 msg = entry = NULL;
582 if (val)
584 ldap_value_freeW (val);
585 val = NULL;
587 /* As base, use system container within default naming context to restrict
588 the search to this container only. */
589 wcpcpy (wcpcpy (base, SYSTEM_CONTAINER), def_context);
590 /* If domain name has no dot, it's a Netbios name. In that case, filter
591 by flatName rather than by name. */
592 __small_swprintf (filter, wcschr (domain, L'.') ? PSX_OFFSET_FILTER
593 : PSX_OFFSET_FILTER_FLAT,
594 domain);
595 if (search (base, LDAP_SCOPE_ONELEVEL, filter, attr = tdom_attr) != 0)
596 return UINT32_MAX;
597 if (!(entry = ldap_first_entry (lh, msg)))
599 debug_printf ("No entry for %W in def_context %W", filter, def_context);
600 return UINT32_MAX;
602 return get_num_attribute (tdom_attr[0]);
605 #define UXID_FILTER_GRP L"(&(objectClass=Group)" \
606 "(gidNumber=%u))"
608 #define UXID_FILTER_USR L"(&(objectCategory=Person)" \
609 "(objectClass=User)" \
610 "(uidNumber=%u))"
612 bool
613 cyg_ldap::fetch_unix_sid_from_ad (uint32_t id, cygsid &sid, bool group)
615 WCHAR filter[MAX (sizeof (UXID_FILTER_GRP), sizeof (UXID_FILTER_USR)) + 16];
616 PLDAP_BERVAL *bval;
618 if (msg)
620 ldap_msgfree (msg);
621 msg = entry = NULL;
623 __small_swprintf (filter, group ? UXID_FILTER_GRP : UXID_FILTER_USR, id);
624 if (search (def_context, LDAP_SCOPE_SUBTREE, filter, sid_attr) != 0)
625 return false;
626 if ((entry = ldap_first_entry (lh, msg))
627 && (bval = ldap_get_values_lenW (lh, entry, (PWCHAR) L"objectSid")))
629 sid = (PSID) bval[0]->bv_val;
630 ldap_value_free_len (bval);
631 return true;
633 return false;
636 #define PSXID_FILTER_GRP L"(&(objectClass=posixGroup)" \
637 "(gidNumber=%u))"
639 #define PSXID_FILTER_USR L"(&(objectClass=posixAccount)" \
640 "(uidNumber=%u))"
642 PWCHAR
643 cyg_ldap::fetch_unix_name_from_rfc2307 (uint32_t id, bool group)
645 WCHAR filter[MAX (sizeof (PSXID_FILTER_GRP), sizeof (PSXID_FILTER_USR)) + 16];
647 if (msg)
649 ldap_msgfree (msg);
650 msg = entry = NULL;
652 if (val)
654 ldap_value_freeW (val);
655 val = NULL;
657 attr = group ? rfc2307_gid_attr : rfc2307_uid_attr;
658 __small_swprintf (filter, group ? PSXID_FILTER_GRP : PSXID_FILTER_USR, id);
659 if (search (def_context, LDAP_SCOPE_SUBTREE, filter, attr) != 0)
660 return NULL;
661 if (!(entry = ldap_first_entry (lh, msg)))
663 debug_printf ("No entry for %W in def_context %W", filter, def_context);
664 return NULL;
666 return get_string_attribute (attr[0]);
669 uid_t
670 cyg_ldap::remap_uid (uid_t uid)
672 cygsid user (NO_SID);
673 PWCHAR name;
674 struct passwd *pw;
676 if (isAD)
678 if (fetch_unix_sid_from_ad (uid, user, false)
679 && user != NO_SID
680 && (pw = internal_getpwsid (user, this)))
681 return pw->pw_uid;
683 else if ((name = fetch_unix_name_from_rfc2307 (uid, false)))
685 char *mbname = NULL;
686 sys_wcstombs_alloc (&mbname, HEAP_NOTHEAP, name);
687 if ((pw = internal_getpwnam (mbname)))
688 return pw->pw_uid;
690 return ILLEGAL_UID;
693 gid_t
694 cyg_ldap::remap_gid (gid_t gid)
696 cygsid group (NO_SID);
697 PWCHAR name;
698 struct group *gr;
700 if (isAD)
702 if (fetch_unix_sid_from_ad (gid, group, true)
703 && group != NO_SID
704 && (gr = internal_getgrsid (group, this)))
705 return gr->gr_gid;
707 else if ((name = fetch_unix_name_from_rfc2307 (gid, true)))
709 char *mbname = NULL;
710 sys_wcstombs_alloc (&mbname, HEAP_NOTHEAP, name);
711 if ((gr = internal_getgrnam (mbname)))
712 return gr->gr_gid;
714 return ILLEGAL_GID;