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
13 #include <sys/param.h>
25 #define CYG_LDAP_ENUM_PAGESIZE 100 /* entries per page */
27 static PWCHAR rootdse_attr
[] =
29 (PWCHAR
) L
"defaultNamingContext",
30 (PWCHAR
) L
"supportedCapabilities",
34 static const PCWSTR std_user_attr
[] =
41 L
"cygwinUnixUid", /* TODO */
58 static PWCHAR group_attr
[] =
60 (PWCHAR
) L
"sAMAccountName",
61 (PWCHAR
) L
"objectSid",
62 (PWCHAR
) L
"gidNumber",
63 (PWCHAR
) L
"cygwinUnixGid", /* TODO */
69 (PWCHAR
) L
"trustPosixOffset",
75 (PWCHAR
) L
"objectSid",
79 PWCHAR rfc2307_uid_attr
[] =
85 PWCHAR rfc2307_gid_attr
[] =
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)
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 /* ================================================================= */
122 cyg_ldap::map_ldaperr_to_errno (ULONG lerr
)
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 */
137 return geterrno_from_win_error (LdapMapErrorToWin32 (lerr
));
141 cyg_ldap::wait (cygthread
*thr
)
145 if (cygwait (*thr
, cw_infinite
, cw_sig
| cw_sig_restart
) != WAIT_OBJECT_0
)
147 thr
->terminate_thread ();
154 /* ================================================================= */
155 /* Helper struct and functions for interruptible LDAP initalization. */
156 /* ================================================================= */
158 struct cyg_ldap_init
{
166 cyg_ldap::connect_ssl (PCWSTR domain
)
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
,
181 debug_printf ("ldap_search(%W, ROOTDSE) error 0x%02x", domain
, ret
);
186 cyg_ldap::connect_non_ssl (PCWSTR domain
)
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
))
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
))
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
,
207 debug_printf ("ldap_search(%W, ROOTDSE) error 0x%02x", domain
, ret
);
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
);
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
{
244 cyg_ldap::search_s (PWCHAR base
, ULONG scope
, PWCHAR filter
, PWCHAR
*attrs
)
248 if ((ret
= ldap_search_sW (lh
, base
, scope
, filter
, attrs
, 0, &msg
))
250 debug_printf ("ldap_search_sW(%W,%W) error 0x%02x", base
, filter
, ret
);
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
);
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
{
280 cyg_ldap::next_page_s ()
287 ret
= ldap_get_next_page_s (lh
, srch_id
, NULL
, CYG_LDAP_ENUM_PAGESIZE
,
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
);
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 ();
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
)
325 if ((ret
= connect (domain
)) != NO_ERROR
)
327 /* Prime `ret' and fetch ROOTDSE search result. */
329 if (!(entry
= ldap_first_entry (lh
, msg
)))
331 debug_printf ("No ROOTDSE entry for %W", domain
);
334 if (!(val
= ldap_get_valuesW (lh
, entry
, rootdse_attr
[0])))
336 debug_printf ("No %W value for %W", rootdse_attr
[0], domain
);
339 if (!(def_context
= wcsdup (val
[0])))
341 debug_printf ("wcsdup(%W, %W) %d", domain
, rootdse_attr
[0],
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
))
355 ldap_value_freeW (val
);
369 ldap_search_abandon_page (lh
, srch_id
);
375 ldap_value_freeW (val
);
383 last_fetched_sid
= NO_SID
;
387 cyg_ldap::get_string_attribute (PCWSTR name
)
390 ldap_value_freeW (val
);
391 val
= ldap_get_valuesW (lh
, entry
, (PWCHAR
) name
);
398 cyg_ldap::get_num_attribute (PCWSTR name
)
400 PWCHAR ret
= get_string_attribute (name
);
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))" \
411 #define ACCOUNT_FILTER_END L"))"
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";
424 if (last_fetched_sid
== sid
)
427 if (open (NULL
) != NO_ERROR
)
437 ldap_value_freeW (val
);
440 f
= wcpcpy (filter
, ACCOUNT_FILTER_START
);
444 *f
++ = hex_wchars
[*s
>> 4];
445 *f
++ = hex_wchars
[*s
++ & 0xf];
447 wcpcpy (f
, ACCOUNT_FILTER_END
);
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. */
456 for (PCWSTR dotp
= domain
; dotp
&& *dotp
; domain
= dotp
)
458 dotp
= wcschr (domain
, 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
);
468 /* def_context is only valid after open. */
472 cygheap
->pg
.init_ldap_user_attr ();
473 attr
= group
? group_attr
: user_attr
;
474 if (search (base
, LDAP_SCOPE_SUBTREE
, filter
, attr
) != 0)
476 if (!(entry
= ldap_first_entry (lh
, msg
)))
478 debug_printf ("No entry for %W in base %W", filter
, base
);
481 last_fetched_sid
= sid
;
486 cyg_ldap::enumerate_ad_accounts (PCWSTR domain
, bool group
)
493 if ((ret
= open (domain
)) != NO_ERROR
)
497 filter
= L
"(&(objectCategory=Person)"
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)"
505 /* From the local domain, we fetch well-known groups. */
506 filter
= L
"(&(objectClass=Group)"
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))"
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
);
522 debug_printf ("ldap_search_init_pageW(%W,%W) error 0x%02x",
523 def_context
, filter
, LdapGetLastError ());
524 return map_ldaperr_to_errno (LdapGetLastError ());
530 cyg_ldap::next_account (cygsid
&sid
)
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
);
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
);
559 ldap_search_abandon_page (lh
, srch_id
);
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. */
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];
584 ldap_value_freeW (val
);
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
,
595 if (search (base
, LDAP_SCOPE_ONELEVEL
, filter
, attr
= tdom_attr
) != 0)
597 if (!(entry
= ldap_first_entry (lh
, msg
)))
599 debug_printf ("No entry for %W in def_context %W", filter
, def_context
);
602 return get_num_attribute (tdom_attr
[0]);
605 #define UXID_FILTER_GRP L"(&(objectClass=Group)" \
608 #define UXID_FILTER_USR L"(&(objectCategory=Person)" \
609 "(objectClass=User)" \
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];
623 __small_swprintf (filter
, group
? UXID_FILTER_GRP
: UXID_FILTER_USR
, id
);
624 if (search (def_context
, LDAP_SCOPE_SUBTREE
, filter
, sid_attr
) != 0)
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
);
636 #define PSXID_FILTER_GRP L"(&(objectClass=posixGroup)" \
639 #define PSXID_FILTER_USR L"(&(objectClass=posixAccount)" \
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];
654 ldap_value_freeW (val
);
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)
661 if (!(entry
= ldap_first_entry (lh
, msg
)))
663 debug_printf ("No entry for %W in def_context %W", filter
, def_context
);
666 return get_string_attribute (attr
[0]);
670 cyg_ldap::remap_uid (uid_t uid
)
672 cygsid
user (NO_SID
);
678 if (fetch_unix_sid_from_ad (uid
, user
, false)
680 && (pw
= internal_getpwsid (user
, this)))
683 else if ((name
= fetch_unix_name_from_rfc2307 (uid
, false)))
686 sys_wcstombs_alloc (&mbname
, HEAP_NOTHEAP
, name
);
687 if ((pw
= internal_getpwnam (mbname
)))
694 cyg_ldap::remap_gid (gid_t gid
)
696 cygsid
group (NO_SID
);
702 if (fetch_unix_sid_from_ad (gid
, group
, true)
704 && (gr
= internal_getgrsid (group
, this)))
707 else if ((name
= fetch_unix_name_from_rfc2307 (gid
, true)))
710 sys_wcstombs_alloc (&mbname
, HEAP_NOTHEAP
, name
);
711 if ((gr
= internal_getgrnam (mbname
)))